feat(security): HttpOnly cookie sessions, token revocation, and RBAC
- Add User.role (enum Role/ADMIN) and User.tokenVersion with migration - Login now issues short-lived access token (30m default) + 7d refresh token, both embedding tokenVersion and a typ discriminator - Tokens delivered via HttpOnly SameSite cookies (ir_at, ir_rt scoped to /auth); refresh token never leaves the cookie - New endpoints: POST /auth/refresh (rotation), GET /auth/me, POST /auth/logout (bumps tokenVersion, revoking all tokens) - JWT strategy accepts bearer or cookie, rejects refresh tokens, and verifies tokenVersion + user existence on every request - Global RolesGuard: authenticated routes require ADMIN unless widened via @Roles(...) - Admin SPA: session fully cookie-based, no token in localStorage; router guard restores session via /auth/me; axios auto-refreshes once on 401; stale localStorage keys cleaned up
This commit is contained in:
+15
-3
@@ -3,6 +3,7 @@ import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||
import helmet from 'helmet';
|
||||
import * as cookieParserModule from 'cookie-parser';
|
||||
import { json } from 'express';
|
||||
import { join } from 'path';
|
||||
import { AppModule } from './app.module';
|
||||
@@ -32,13 +33,24 @@ async function bootstrap() {
|
||||
// Security headers (X-Content-Type-Options, X-Frame-Options, CSP, HSTS, ...)
|
||||
app.use(helmet());
|
||||
|
||||
// CORS: only origins listed in CORS_ORIGINS (comma-separated) are allowed.
|
||||
// Authentication uses Bearer headers, so credentialed CORS is not needed.
|
||||
// Parse auth cookies (HttpOnly access/refresh tokens). Resolve both the
|
||||
// namespace and its `default` interop shape so it works regardless of
|
||||
// the compiled module interop mode.
|
||||
const cookieParser = (
|
||||
cookieParserModule as unknown as {
|
||||
default?: typeof cookieParserModule;
|
||||
}
|
||||
).default ?? cookieParserModule;
|
||||
app.use(cookieParser());
|
||||
|
||||
// CORS: only origins listed in CORS_ORIGINS (comma-separated) are
|
||||
// allowed. Credentials are enabled because the session lives in
|
||||
// HttpOnly cookies.
|
||||
const corsOrigins = (process.env.CORS_ORIGINS ?? '')
|
||||
.split(',')
|
||||
.map((o) => o.trim())
|
||||
.filter(Boolean);
|
||||
app.enableCors(corsOrigins.length > 0 ? { origin: corsOrigins } : undefined);
|
||||
app.enableCors(corsOrigins.length > 0 ? { origin: corsOrigins, credentials: true } : undefined);
|
||||
|
||||
// Serve uploaded files. nosniff prevents browsers from sniffing a
|
||||
// non-image content type out of an uploaded file.
|
||||
|
||||
Reference in New Issue
Block a user