Files
pwa/backend/src/modules/auth/auth.routes.ts
T
ar.badieiandCursor c6243caa35 Add read-only Noya admin panel with assignments and UX polish.
Ship admin auth, user overview, assessments, chats, assignment filters/export, security hardening, and a separate desktop admin shell with clearer assessment answers.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-21 08:00:24 +03:30

107 lines
3.0 KiB
TypeScript

import { Hono } from 'hono';
import { jsonValidator } from '../../shared/validation/index.js';
import { rateLimiter } from 'hono-rate-limiter';
import { env } from '../../config/index.js';
import { getClientIp } from '../../lib/clientIp.js';
import { created, success } from '../../lib/response.js';
import { requireAuth } from '../../middleware/auth.js';
import { loginSchema, registerSchema } from './auth.schemas.js';
import {
createGuest,
login,
logout,
register,
toPublicUser,
} from './auth.service.js';
import { prisma } from '../../lib/prisma.js';
import { AUDIT_ACTIONS } from '../admin/auditActions.js';
import { recordAuditBestEffort } from '../admin/audit.service.js';
import { UserRole } from '@prisma/client';
const authAbuseLimiter = rateLimiter({
windowMs: env.RATE_LIMIT_WINDOW_MS,
limit: env.AUTH_RATE_LIMIT_MAX,
standardHeaders: 'draft-6',
keyGenerator: (c) => `auth:${getClientIp(c)}`,
handler: (c) =>
c.json(
{
success: false,
error: {
code: 'RATE_LIMIT_EXCEEDED',
message: 'Too many requests',
},
meta: {
requestId: c.get('requestId') ?? 'unknown',
timestamp: new Date().toISOString(),
},
},
429,
),
});
export function createAuthRoutes() {
const routes = new Hono();
routes.post('/register', authAbuseLimiter, jsonValidator(registerSchema), async (c) => {
const { email, password } = c.req.valid('json');
const result = await register(email, password);
return created(c, result);
});
routes.post('/login', authAbuseLimiter, jsonValidator(loginSchema), async (c) => {
const { email, password } = c.req.valid('json');
const result = await login(email, password);
if (result.user.role === UserRole.admin) {
const user = await prisma.user.findUnique({
where: { externalId: result.user.id },
select: { id: true },
});
await recordAuditBestEffort({
actorUserId: user?.id ?? null,
action: AUDIT_ACTIONS.ADMIN_LOGIN,
metadata: { source: 'password_login' },
ipAddress: getClientIp(c),
userAgent: c.req.header('user-agent') ?? null,
});
}
return success(c, result);
});
routes.post('/guest', authAbuseLimiter, async (c) => {
const result = await createGuest();
return created(c, result);
});
routes.get('/me', requireAuth, async (c) => {
const authUser = c.get('authUser');
const user = await prisma.user.findUnique({
where: { id: authUser.id },
include: { profile: true, identities: true },
});
if (!user) {
return success(c, {
id: authUser.externalId,
email: authUser.email,
isGuest: authUser.isGuest,
profileComplete: false,
role: authUser.role,
});
}
return success(c, toPublicUser(user));
});
routes.post('/logout', requireAuth, async (c) => {
const authUser = c.get('authUser');
await logout(authUser.sessionId);
return success(c, { loggedOut: true });
});
return routes;
}