import { Body, Controller, Get, HttpCode, Patch, Post, Req, Res, UseGuards, } from "@nestjs/common"; import { Request, Response } from "express"; import { LocalAuthGuard } from "./local-auth.guard"; import { AuthenticatedGuard } from "./authenticated.guard"; import { LoginDto } from "./login.dto"; import { UpdatePreferencesDto } from "./update-preferences.dto"; import { abilitiesFor, Role } from "./abilities"; import { UsersService } from "../users/users.service"; /** Attach the resolved ability map so the web can gate its UI off one payload. */ function withAbilities(user: unknown) { const u = user as { role?: Role } | undefined; if (!u?.role) return u; return { ...u, abilities: abilitiesFor(u.role) }; } @Controller("auth") export class AuthController { constructor(private readonly users: UsersService) {} // LoginDto is only used for request-shape documentation/validation here — // the actual credential check happens inside LocalStrategy via Passport, // which populates req.user before this handler runs. @UseGuards(LocalAuthGuard) @Post("login") @HttpCode(200) login(@Req() req: Request, @Res({ passthrough: true }) _res: Response, _body?: LoginDto) { return withAbilities(req.user); } @UseGuards(AuthenticatedGuard) @Get("me") me(@Req() req: Request) { return withAbilities(req.user); } /** * Update the caller's own UI preferences. Deliberately not on /users/:id — * that controller is ADMIN-only, and this has to work for every role. The * target is always the session's own user id, never a body parameter. */ @UseGuards(AuthenticatedGuard) @Patch("preferences") async updatePreferences(@Req() req: Request, @Body() dto: UpdatePreferencesDto) { const id = (req.user as { id: string }).id; const user = await this.users.updatePreferences(id, dto.uiScale); return withAbilities(user); } @Post("logout") @HttpCode(200) logout(@Req() req: Request) { return new Promise((resolve, reject) => { req.logout((err) => { if (err) { reject(err); return; } resolve({ success: true }); }); }); } }