feat(api): password login, sliding renewal, revocable sessions
- POST /v1/auth/password|refresh|logout per contract operationIds; long-lived single token (32B hex, DB stores sha256 only), sliding renewal without rotation, logout sets sessions.revokedAt. - SessionGuard: Bearer -> session -> user eligibility (disabled/locked/ pending revoke access); all failures map to 401 AUTH_TOKEN_EXPIRED. - Anti-enumeration: unknown account / wrong password / status-blocked share one 401 AUTH_INVALID_CREDENTIALS; unknown-account path runs a dummy argon2 verify to equalize timing. - Login rate limiting (P1): in-memory account+IP sliding window, 5 failures / 15 min -> 429; success clears. Contract amendment (registered): new ErrorCodes.AUTH_RATE_LIMITED added to errors.ts + openapi Error enum + 429 response on /auth/password; generated types regenerated and committed. P1a single-instance limiter; P5 needs a Redis-backed implementation. - Global ValidationPipe (APP_PIPE) enforces class-validator DTOs -> 400 VALIDATION_FAILED via the existing exception filter. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,22 @@
|
||||
// services/api/src/app-cn.module.ts
|
||||
// 根模块:Prisma(@Global,封装 @photo/db-cn)、日志(nestjs-pino / pino-http)、
|
||||
// request-id 中间件、全局异常过滤器、/v1/health。
|
||||
// 功能模块(auth/albums/uploads/photos/public)自 Task 12+ 起逐个加入。
|
||||
// 过滤器经 APP_FILTER、中间件经 NestModule 在模块层注册——e2e 直接 import 本模块
|
||||
// 即可获得与生产一致的管线(全局前缀除外,由入口设置)。
|
||||
import { type MiddlewareConsumer, Module, type NestModule } from '@nestjs/common';
|
||||
import { APP_FILTER } from '@nestjs/core';
|
||||
// request-id 中间件、全局异常过滤器、全局校验管道(Task 13 起,DTO 经
|
||||
// class-validator 声明、ValidationPipe 强制,失败由过滤器映射 400 VALIDATION_FAILED)、
|
||||
// /v1/health、/v1/auth/*(Task 13)。
|
||||
// 功能模块(albums/uploads/photos/public)自 Task 15+ 起逐个加入。
|
||||
// 过滤器/管道经 APP_FILTER/APP_PIPE、中间件经 NestModule 在模块层注册——e2e 直接
|
||||
// import 本模块即可获得与生产一致的管线(全局前缀除外,由入口设置)。
|
||||
import {
|
||||
type MiddlewareConsumer,
|
||||
Module,
|
||||
type NestModule,
|
||||
ValidationPipe,
|
||||
} from '@nestjs/common';
|
||||
import { APP_FILTER, APP_PIPE } from '@nestjs/core';
|
||||
import { LoggerModule } from 'nestjs-pino';
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
import { AllExceptionsFilter } from './all-exceptions.filter';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
import { HealthController } from './health.controller';
|
||||
import { PrismaModule } from './prisma/prisma.module';
|
||||
import { RequestIdMiddleware, resolveRequestId } from './request-id.middleware';
|
||||
@@ -19,6 +27,7 @@ import type { ServerRequestWithId } from './shared/request-with-id';
|
||||
imports: [
|
||||
// @Global:功能模块(Task 12+)直接注入 PrismaService,无需 re-import。
|
||||
PrismaModule,
|
||||
AuthModule,
|
||||
LoggerModule.forRoot({
|
||||
pinoHttp: {
|
||||
// 与 RequestIdMiddleware 共用同一解析函数:优先透传 x-request-id,
|
||||
@@ -43,7 +52,16 @@ import type { ServerRequestWithId } from './shared/request-with-id';
|
||||
}),
|
||||
],
|
||||
controllers: [HealthController],
|
||||
providers: [{ provide: APP_FILTER, useClass: AllExceptionsFilter }],
|
||||
providers: [
|
||||
{ provide: APP_FILTER, useClass: AllExceptionsFilter },
|
||||
// 全局校验管道(Task 13):whitelist 剥离 DTO 未声明的属性(契约未禁未知
|
||||
// 字段,剥离而非拒绝);校验失败抛 BadRequestException → 过滤器映射
|
||||
// 400 VALIDATION_FAILED。经 APP_PIPE 在模块层注册,e2e 复用同一管线。
|
||||
{
|
||||
provide: APP_PIPE,
|
||||
useFactory: () => new ValidationPipe({ whitelist: true }),
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AppCnModule implements NestModule {
|
||||
configure(consumer: MiddlewareConsumer): void {
|
||||
|
||||
Reference in New Issue
Block a user