refactor(api): extract adminGuard + add user-management contracts
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import type { Context } from 'hono';
|
import type { Context, MiddlewareHandler } from 'hono';
|
||||||
import { auth } from '../auth';
|
import { auth } from '../auth';
|
||||||
|
|
||||||
export interface SessionUser {
|
export interface SessionUser {
|
||||||
@@ -16,3 +16,11 @@ export async function getSessionUser(c: Context): Promise<SessionUser | null> {
|
|||||||
export function isAdmin(u: SessionUser | null): boolean {
|
export function isAdmin(u: SessionUser | null): boolean {
|
||||||
return u?.role === 'admin';
|
return u?.role === 'admin';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reusable gate for the whole /api/admin/* surface: 401 if unauthenticated, 403 if not an admin.
|
||||||
|
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);
|
||||||
|
await next();
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { and, asc, desc, eq, gte, lte, type SQL } from 'drizzle-orm';
|
|||||||
import { AdminUpdateSessionInput, CreateManualSessionInput, InsoleType } from '@solelog/shared';
|
import { AdminUpdateSessionInput, CreateManualSessionInput, InsoleType } from '@solelog/shared';
|
||||||
import { db } from '../db/client';
|
import { db } from '../db/client';
|
||||||
import { activities, user, workSessions } from '../db/schema';
|
import { activities, user, workSessions } from '../db/schema';
|
||||||
import { getSessionUser, isAdmin } from '../lib/require-user';
|
import { adminGuard } from '../lib/require-user';
|
||||||
import { toWorkSession } from '../lib/work-session';
|
import { toWorkSession } from '../lib/work-session';
|
||||||
import { buildSessionsCsv } from '../lib/csv';
|
import { buildSessionsCsv } from '../lib/csv';
|
||||||
|
|
||||||
@@ -59,12 +59,7 @@ function buildSessionFilters(q: ReportQuery): SQL[] {
|
|||||||
export const adminRoutes = new Hono();
|
export const adminRoutes = new Hono();
|
||||||
|
|
||||||
// Gate the whole /api/admin/* surface to admins.
|
// Gate the whole /api/admin/* surface to admins.
|
||||||
adminRoutes.use('/api/admin/*', async (c, next) => {
|
adminRoutes.use('/api/admin/*', adminGuard);
|
||||||
const sessionUser = await getSessionUser(c);
|
|
||||||
if (!sessionUser) return c.json({ error: 'Unauthorized' }, 401);
|
|
||||||
if (!isAdmin(sessionUser)) return c.json({ error: 'Forbidden' }, 403);
|
|
||||||
await next();
|
|
||||||
});
|
|
||||||
|
|
||||||
const baseSelect = {
|
const baseSelect = {
|
||||||
session: workSessions,
|
session: workSessions,
|
||||||
|
|||||||
@@ -78,15 +78,33 @@ export const StartSessionInput = z.object({
|
|||||||
});
|
});
|
||||||
export type StartSessionInput = z.infer<typeof StartSessionInput>;
|
export type StartSessionInput = z.infer<typeof StartSessionInput>;
|
||||||
|
|
||||||
|
export const UserStatus = z.enum(['active', 'inactive']);
|
||||||
|
export type UserStatus = z.infer<typeof UserStatus>;
|
||||||
|
|
||||||
export const AdminUser = z.object({
|
export const AdminUser = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
role: Role,
|
role: Role,
|
||||||
|
status: UserStatus,
|
||||||
created_at: z.string(),
|
created_at: z.string(),
|
||||||
});
|
});
|
||||||
export type AdminUser = z.infer<typeof AdminUser>;
|
export type AdminUser = z.infer<typeof AdminUser>;
|
||||||
|
|
||||||
|
export const CreateUserInput = z.object({
|
||||||
|
email: z.string().email(),
|
||||||
|
name: z.string().trim().min(1),
|
||||||
|
password: z.string().min(8),
|
||||||
|
role: Role,
|
||||||
|
});
|
||||||
|
export type CreateUserInput = z.infer<typeof CreateUserInput>;
|
||||||
|
|
||||||
|
export const SetRoleInput = z.object({ role: Role });
|
||||||
|
export type SetRoleInput = z.infer<typeof SetRoleInput>;
|
||||||
|
|
||||||
|
export const SetPasswordInput = z.object({ password: z.string().min(8) });
|
||||||
|
export type SetPasswordInput = z.infer<typeof SetPasswordInput>;
|
||||||
|
|
||||||
export const CreateManualSessionInput = z.object({
|
export const CreateManualSessionInput = z.object({
|
||||||
user_id: z.string(),
|
user_id: z.string(),
|
||||||
activity_id: z.number().int(),
|
activity_id: z.number().int(),
|
||||||
|
|||||||
Reference in New Issue
Block a user