mirror of https://github.com/buster-so/buster.git
143 lines
4.1 KiB
TypeScript
143 lines
4.1 KiB
TypeScript
import { getUser, getUserOrganizationId } from '@buster/database';
|
|
import type { Context, Next } from 'hono';
|
|
import { bearerAuth } from 'hono/bearer-auth';
|
|
import { isOrganizationAdmin } from '../utils/admin';
|
|
import { getSupabaseClient } from './supabase';
|
|
|
|
export const requireAuth = bearerAuth({
|
|
verifyToken: async (token, c) => {
|
|
try {
|
|
const { data, error } = await getSupabaseClient().auth.getUser(token); //usually takes about 3 - 7ms
|
|
|
|
if (error) {
|
|
// Log specific auth errors to help with debugging
|
|
console.warn('Token validation failed:', {
|
|
error: error.message,
|
|
// Don't log the actual token for security
|
|
tokenPrefix: `${token.substring(0, 20)}...`,
|
|
});
|
|
return false;
|
|
}
|
|
|
|
if (!data.user) {
|
|
console.warn('No user found for valid token');
|
|
return false;
|
|
}
|
|
|
|
// Set user in context for use in route handlers
|
|
c.set('supabaseUser', data.user);
|
|
|
|
// Get the corresponding user from your database
|
|
const busterUser = await getUser({ id: data.user.id });
|
|
|
|
if (!busterUser) {
|
|
console.warn('Supabase user found but no corresponding database user:', {
|
|
supabaseUserId: data.user.id,
|
|
email: data.user.email,
|
|
});
|
|
return false;
|
|
}
|
|
|
|
c.set('busterUser', busterUser);
|
|
|
|
// Ensure user is not anonymous
|
|
const isAuthenticated = !data.user.is_anonymous;
|
|
|
|
if (!isAuthenticated) {
|
|
console.info('Anonymous user attempted to access protected resource');
|
|
}
|
|
|
|
return isAuthenticated;
|
|
} catch (error) {
|
|
console.error('Unexpected error during token validation:', error);
|
|
return false;
|
|
}
|
|
},
|
|
});
|
|
|
|
export async function requireUser(c: Context, next: Next) {
|
|
const authHeader = c.req.header('Authorization');
|
|
|
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
console.warn('Missing or invalid Authorization header');
|
|
throw new Error('User not authenticated - missing Authorization header');
|
|
}
|
|
|
|
const token = authHeader.split(' ')[1];
|
|
|
|
if (!token) {
|
|
console.warn('Empty token in Authorization header');
|
|
throw new Error('User not authenticated - empty token');
|
|
}
|
|
|
|
try {
|
|
const { data, error } = await getSupabaseClient().auth.getUser(token);
|
|
|
|
if (error) {
|
|
console.warn('Token validation failed in requireUser:', {
|
|
error: error.message,
|
|
});
|
|
throw new Error('User not authenticated - invalid token');
|
|
}
|
|
|
|
const user = data.user;
|
|
|
|
if (!user || user.is_anonymous) {
|
|
console.warn('No valid user found or user is anonymous');
|
|
throw new Error('User not authenticated - no valid user');
|
|
}
|
|
|
|
// User is valid, continue to next middleware/handler
|
|
return next();
|
|
} catch (error) {
|
|
console.error('Unexpected error in requireUser:', error);
|
|
throw new Error('User not authenticated - server error');
|
|
}
|
|
}
|
|
|
|
// Utility function to get user organization (can be called from other middleware)
|
|
const getUserOrganization = async (c: Context) => {
|
|
const user = c.get('busterUser');
|
|
const userOrganizationInfo = c.get('userOrganizationInfo');
|
|
|
|
if (userOrganizationInfo) {
|
|
return userOrganizationInfo;
|
|
}
|
|
|
|
const userOrg = await getUserOrganizationId(user.id);
|
|
|
|
if (!userOrg) {
|
|
throw new Error('User is not associated with an organization');
|
|
}
|
|
|
|
c.set('userOrganizationInfo', userOrg);
|
|
|
|
return userOrg;
|
|
};
|
|
|
|
// Middleware version that can be used in route chains
|
|
export const requireOrganization = async (c: Context, next: Next) => {
|
|
await getUserOrganization(c);
|
|
await next();
|
|
};
|
|
|
|
export const requireOrganizationAdmin = async (c: Context, next: Next) => {
|
|
const user = c.get('busterUser');
|
|
|
|
if (!user?.id) {
|
|
console.warn('This is likely an issue where requireAuth middleware was not called first');
|
|
return c.json({ message: 'User not authenticated' }, 401);
|
|
}
|
|
|
|
const userOrg = await getUserOrganization(c);
|
|
|
|
const isAdmin = isOrganizationAdmin(userOrg.role);
|
|
|
|
if (!isAdmin) {
|
|
return c.json({ message: 'User is not an organization admin' }, 403);
|
|
}
|
|
|
|
// If all checks pass, continue to the next middleware/handler
|
|
return await next();
|
|
};
|