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 <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-10 21:07:54 +07:00
parent a003df9a8a
commit 9cfea31905
4 changed files with 19 additions and 6 deletions

View File

@@ -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()

View File

@@ -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';

View File

@@ -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';

View File

@@ -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<T>(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;
}
}