From 9cfea31905edaccea702b486b3636b12e0543954 Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Fri, 10 Apr 2026 21:07:54 +0700 Subject: [PATCH] fix(auth): use custom UnauthorizedException for structured 401 error responses LocalStrategy and auth controllers were importing UnauthorizedException from @nestjs/common instead of @modules/shared. While both return 401, only the custom DomainException-based version produces the structured error format (errorCode, correlationId, timestamp) expected by the GlobalExceptionFilter's primary code path. Also adds handleRequest() override to LocalAuthGuard to ensure custom exceptions from the strategy propagate directly without Passport transforming them. Co-Authored-By: Paperclip --- .../infrastructure/strategies/local.strategy.ts | 4 ++-- .../presentation/controllers/auth.controller.ts | 2 +- .../controllers/oauth.controller.ts | 2 +- .../presentation/guards/local-auth.guard.ts | 17 +++++++++++++++-- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/apps/api/src/modules/auth/infrastructure/strategies/local.strategy.ts b/apps/api/src/modules/auth/infrastructure/strategies/local.strategy.ts index fff93e8..6f16191 100644 --- a/apps/api/src/modules/auth/infrastructure/strategies/local.strategy.ts +++ b/apps/api/src/modules/auth/infrastructure/strategies/local.strategy.ts @@ -1,7 +1,7 @@ -import { Inject, Injectable, UnauthorizedException } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Strategy } from 'passport-local'; -import { normalizeVietnamPhone } from '@modules/shared'; +import { normalizeVietnamPhone, UnauthorizedException } from '@modules/shared'; import { USER_REPOSITORY, type IUserRepository } from '../../domain/repositories/user.repository'; @Injectable() diff --git a/apps/api/src/modules/auth/presentation/controllers/auth.controller.ts b/apps/api/src/modules/auth/presentation/controllers/auth.controller.ts index 7218c2e..4079535 100644 --- a/apps/api/src/modules/auth/presentation/controllers/auth.controller.ts +++ b/apps/api/src/modules/auth/presentation/controllers/auth.controller.ts @@ -6,13 +6,13 @@ import { Post, Req, Res, - UnauthorizedException, UseGuards, } from '@nestjs/common'; import { type CommandBus, type QueryBus } from '@nestjs/cqrs'; import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiBody } from '@nestjs/swagger'; import { Throttle } from '@nestjs/throttler'; import type { Request, Response } from 'express'; +import { UnauthorizedException } from '@modules/shared'; import { LoginUserCommand } from '../../application/commands/login-user/login-user.command'; import { RefreshTokenCommand } from '../../application/commands/refresh-token/refresh-token.command'; import { RegisterUserCommand } from '../../application/commands/register-user/register-user.command'; diff --git a/apps/api/src/modules/auth/presentation/controllers/oauth.controller.ts b/apps/api/src/modules/auth/presentation/controllers/oauth.controller.ts index 6e4eddb..2b9aaa9 100644 --- a/apps/api/src/modules/auth/presentation/controllers/oauth.controller.ts +++ b/apps/api/src/modules/auth/presentation/controllers/oauth.controller.ts @@ -4,12 +4,12 @@ import { Query, Req, Res, - UnauthorizedException, UseGuards, } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; import { Throttle } from '@nestjs/throttler'; import type { Request, Response } from 'express'; +import { UnauthorizedException } from '@modules/shared'; import { type TokenPair } from '../../infrastructure/services/token.service'; import { type ZaloOAuthStrategy } from '../../infrastructure/strategies/zalo-oauth.strategy'; import { GoogleOAuthGuard } from '../guards/google-oauth.guard'; diff --git a/apps/api/src/modules/auth/presentation/guards/local-auth.guard.ts b/apps/api/src/modules/auth/presentation/guards/local-auth.guard.ts index ccf962b..1141dae 100644 --- a/apps/api/src/modules/auth/presentation/guards/local-auth.guard.ts +++ b/apps/api/src/modules/auth/presentation/guards/local-auth.guard.ts @@ -1,5 +1,18 @@ -import { Injectable } from '@nestjs/common'; +import { type ExecutionContext, Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; +import { UnauthorizedException } from '@modules/shared'; @Injectable() -export class LocalAuthGuard extends AuthGuard('local') {} +export class LocalAuthGuard extends AuthGuard('local') { + override handleRequest(err: Error | null, user: T, _info: unknown, _context: ExecutionContext): T { + // If the strategy threw a DomainException (e.g. our custom UnauthorizedException), + // re-throw it directly so GlobalExceptionFilter produces the structured error format. + if (err) { + throw err; + } + if (!user) { + throw new UnauthorizedException('Số điện thoại hoặc mật khẩu không đúng'); + } + return user; + } +}