harden(api): adminGuard rejects banned admins; fix mixed-case create + real last-admin guard coverage
This commit is contained in:
@@ -4,23 +4,26 @@ import { auth } from '../auth';
|
||||
export interface SessionUser {
|
||||
id: string;
|
||||
role: string;
|
||||
banned: boolean;
|
||||
}
|
||||
|
||||
export async function getSessionUser(c: Context): Promise<SessionUser | null> {
|
||||
const session = await auth.api.getSession({ headers: c.req.raw.headers });
|
||||
if (!session) return null;
|
||||
const role = (session.user as { role?: string | null }).role ?? 'worker';
|
||||
return { id: session.user.id, role };
|
||||
const u = session.user as { role?: string | null; banned?: boolean | null };
|
||||
return { id: session.user.id, role: u.role ?? 'worker', banned: Boolean(u.banned) };
|
||||
}
|
||||
|
||||
export function isAdmin(u: SessionUser | null): boolean {
|
||||
return u?.role === 'admin';
|
||||
}
|
||||
|
||||
// Reusable gate for the whole /api/admin/* surface: 401 if unauthenticated, 403 if not an admin.
|
||||
// Reusable gate for the whole /api/admin/* surface: 401 if unauthenticated, 403 if not an
|
||||
// active admin. A banned admin is rejected even with a still-valid session (defense-in-depth
|
||||
// beyond deactivate revoking their sessions).
|
||||
export const adminGuard: MiddlewareHandler = async (c, next) => {
|
||||
const u = await getSessionUser(c);
|
||||
if (!u) return c.json({ error: 'Unauthorized' }, 401);
|
||||
if (!isAdmin(u)) return c.json({ error: 'Forbidden' }, 403);
|
||||
if (u.banned || !isAdmin(u)) return c.json({ error: 'Forbidden' }, 403);
|
||||
await next();
|
||||
};
|
||||
|
||||
@@ -57,7 +57,9 @@ adminUsersRoutes.post('/api/admin/users', async (c) => {
|
||||
return c.json({ error: 'E-mailadres bestaat al.' }, 409);
|
||||
}
|
||||
|
||||
const [row] = await db.select().from(user).where(eq(user.email, d.email));
|
||||
// better-auth lowercases the email before storing, so re-select on the normalized form
|
||||
// (zod's .email() does not lowercase) — otherwise a mixed-case email would miss and 500.
|
||||
const [row] = await db.select().from(user).where(eq(user.email, d.email.toLowerCase()));
|
||||
return c.json(toListItem(row));
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user