From 2315ce06de0529688a618244b4de560d625415d2 Mon Sep 17 00:00:00 2001 From: Abubeker Yasin Date: Thu, 27 Aug 2026 14:45:16 +0300 Subject: [PATCH] refactor: ( auth ) throttle only the public sign-in endpoints --- .../src/common/throttle-sign-in.decorator.ts | 4 ++++ .../src/modules/auth/auth.controller.ts | 22 ++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) create mode 100644 apps/edr-passenger-api/src/common/throttle-sign-in.decorator.ts diff --git a/apps/edr-passenger-api/src/common/throttle-sign-in.decorator.ts b/apps/edr-passenger-api/src/common/throttle-sign-in.decorator.ts new file mode 100644 index 000000000..40c1ee106 --- /dev/null +++ b/apps/edr-passenger-api/src/common/throttle-sign-in.decorator.ts @@ -0,0 +1,4 @@ +import { applyDecorators, UseGuards } from '@nestjs/common'; +import { Throttle, ThrottlerGuard } from '@nestjs/throttler'; +export const ThrottleSignIn = (limit = 20) => + applyDecorators(UseGuards(ThrottlerGuard), Throttle({ auth: { limit, ttl: 60_000 } })); diff --git a/apps/edr-passenger-api/src/modules/auth/auth.controller.ts b/apps/edr-passenger-api/src/modules/auth/auth.controller.ts index ce076c970..76cbb8477 100644 --- a/apps/edr-passenger-api/src/modules/auth/auth.controller.ts +++ b/apps/edr-passenger-api/src/modules/auth/auth.controller.ts @@ -21,7 +21,7 @@ import { ApiBearerAuth, } from "@nestjs/swagger"; import { IsPublic } from "@tria-plc/api-common/modules/auth/decorators/public.decorator"; -import { Throttle, ThrottlerGuard } from "@nestjs/throttler"; +import { ThrottleSignIn } from "../../common/throttle-sign-in.decorator"; import { PassengerAuthService } from "./passenger-auth.service"; import { RegisterDto, @@ -37,16 +37,12 @@ import { JwtGuard } from "../../common/jwt.guard"; @ApiTags("Passenger Auth") @Controller("auth") -// Scoped to this controller rather than registered as a global APP_GUARD: the staged sign-in -// exposes an account-existence lookup, and rate limiting is the mitigation for it. Applying the -// guard app-wide would change the behaviour of every other module at the same time. -@UseGuards(ThrottlerGuard) -@Throttle({ auth: { limit: 20, ttl: 60_000 } }) export class AuthController { constructor(private passengerAuthService: PassengerAuthService) {} @Post("register") @IsPublic() + @ThrottleSignIn() @ApiOperation({ summary: "Register new passenger account (sends SMS verification code)", }) @@ -66,6 +62,7 @@ export class AuthController { @Post("register/resend-code") @IsPublic() + @ThrottleSignIn() @HttpCode(HttpStatus.OK) @ApiOperation({ summary: "Resend the registration verification code for a pending account", @@ -84,6 +81,7 @@ export class AuthController { @Post("login") @IsPublic() + @ThrottleSignIn() @HttpCode(HttpStatus.OK) @ApiOperation({ summary: "Login with email and password" }) @ApiResponse({ @@ -199,11 +197,11 @@ export class AuthController { @Post("identifier/lookup") @IsPublic() + // Tighter than the other sign-in endpoints: this is the one that answers "does this account + // exist", so it is the one worth making expensive to sweep. Still roomy enough that a + // passenger correcting a typo two or three times is unaffected. + @ThrottleSignIn(10) @HttpCode(HttpStatus.OK) - // Tighter than the rest of the controller: this is the endpoint that answers "does this - // account exist", so it is the one worth making expensive to sweep. Still roomy enough - // that a passenger correcting a typo two or three times is unaffected. - @Throttle({ auth: { limit: 10, ttl: 60_000 } }) @ApiOperation({ summary: "Step 1 of sign-in — decide what to ask the user for next", description: @@ -222,6 +220,7 @@ export class AuthController { @Post("password-setup/request") @IsPublic() + @ThrottleSignIn() @HttpCode(HttpStatus.OK) @ApiOperation({ summary: "Send the SMS code that lets an account with no password set one", @@ -237,6 +236,7 @@ export class AuthController { @Post("password-setup/complete") @IsPublic() + @ThrottleSignIn() @HttpCode(HttpStatus.OK) @ApiOperation({ summary: "Redeem the code, set the password, and sign in", @@ -256,6 +256,7 @@ export class AuthController { @Post("fayda/request-password-setup") @IsPublic() + @ThrottleSignIn() @HttpCode(HttpStatus.OK) @ApiOperation({ summary: "Send OTP to phone for Fayda-verified account password setup", @@ -277,6 +278,7 @@ export class AuthController { @Post("fayda/verify-and-login") @IsPublic() + @ThrottleSignIn() @HttpCode(HttpStatus.OK) @ApiOperation({ summary: "Verify OTP and receive session token for Fayda-verified account",