From f3fe1f55e4e16979661600653b11ce164d18d963 Mon Sep 17 00:00:00 2001 From: radmehr Date: Mon, 5 Oct 2026 17:27:37 +0330 Subject: [PATCH] fix: fix some changes --- src/i18n/en/auth.json | 3 +- src/i18n/fa/auth.json | 3 +- src/modules/auth/auth.controller.ts | 27 +++++++++ src/modules/auth/auth.module.ts | 2 + src/modules/auth/dtos/logout.dto.ts | 13 +++++ src/modules/auth/providers/auth.service.ts | 11 ++++ src/modules/auth/providers/logout.provider.ts | 56 +++++++++++++++++++ 7 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 src/modules/auth/dtos/logout.dto.ts create mode 100644 src/modules/auth/providers/logout.provider.ts diff --git a/src/i18n/en/auth.json b/src/i18n/en/auth.json index c805862..8a2614b 100644 --- a/src/i18n/en/auth.json +++ b/src/i18n/en/auth.json @@ -14,6 +14,7 @@ }, "messages": { "otpSent": "OTP sent to {phone}.", - "sendPass": "Please send password." + "sendPass": "Please send password.", + "loggedOut": "Logged out successfully." } } diff --git a/src/i18n/fa/auth.json b/src/i18n/fa/auth.json index 1a9582e..f1bb0a4 100644 --- a/src/i18n/fa/auth.json +++ b/src/i18n/fa/auth.json @@ -14,6 +14,7 @@ }, "messages": { "otpSent": "کد تایید به شماره {phone} ارسال شد.", - "sendPass": "لطفا رمز عبور را ارسال کنید." + "sendPass": "لطفا رمز عبور را ارسال کنید.", + "loggedOut": "با موفقیت خارج شدید." } } diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index e461f1c..38d1a47 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -9,6 +9,9 @@ import { ApiAppResponse } from '@/common/decorators/api-app-response.decorator'; import { NewUserLoginResponseDTO } from './dtos/responses/new-user-login.dto'; import { RequirePasswordDTO } from './dtos/responses/require-password.dto'; import { TokensResponseDTO } from './dtos/responses/tokens.dto'; +import { LogoutDTO } from './dtos/logout.dto'; +import { ActiveUser } from '@/common/decorators/active-user.decorator'; +import { User } from '@/modules/users/entities/user.entity'; @Controller('auth') export class AuthController { @@ -126,4 +129,28 @@ export class AuthController { public async refreshToken(@Body() refreshTokenDto: RefreshTokenDTO) { return await this.authService.refreshToken(refreshTokenDto); } + + @Post('logout') + @HttpCode(HttpStatus.OK) + @ApiAppResponse([ + { + status: HttpStatus.OK, + variants: [ + { + messageExample: 'با موفقیت خارج شدید.', + }, + ], + }, + { + status: HttpStatus.UNAUTHORIZED, + variants: [ + { + messageExample: 'دسترسی غیر مجاز', + }, + ], + }, + ]) + public async logout(@ActiveUser() user: User, @Body() logoutDto: LogoutDTO) { + return await this.authService.logout(user.id, logoutDto); + } } diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts index 3368189..742f92c 100644 --- a/src/modules/auth/auth.module.ts +++ b/src/modules/auth/auth.module.ts @@ -15,6 +15,7 @@ import { GlobalAuthGuard } from './guards/global-auth.guard'; import { RefreshTokensProvider } from './providers/refresh-tokens.provider'; import { RedisModule } from '@/common/modules/redis/redis.module'; import { AuthRedisProvider } from './providers/auth-redis.provider'; +import { LogoutProvider } from './providers/logout.provider'; @Module({ imports: [ @@ -43,6 +44,7 @@ import { AuthRedisProvider } from './providers/auth-redis.provider'; JwtStrategy, RefreshTokensProvider, AuthRedisProvider, + LogoutProvider, { provide: APP_GUARD, useClass: GlobalAuthGuard, diff --git a/src/modules/auth/dtos/logout.dto.ts b/src/modules/auth/dtos/logout.dto.ts new file mode 100644 index 0000000..71c3973 --- /dev/null +++ b/src/modules/auth/dtos/logout.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class LogoutDTO { + @IsNotEmpty() + @IsString() + @ApiProperty({ + type: 'string', + description: "User's Refresh Token", + required: true, + }) + refreshToken!: string; +} diff --git a/src/modules/auth/providers/auth.service.ts b/src/modules/auth/providers/auth.service.ts index 717ffaf..8586667 100644 --- a/src/modules/auth/providers/auth.service.ts +++ b/src/modules/auth/providers/auth.service.ts @@ -5,6 +5,8 @@ import { VerifyOtpDTO } from '../dtos/verify-otp.dto'; import { VerifyOTPProvider } from './verify-otp.provider'; import { RefreshTokenDTO } from '../dtos/refresh-token.dto'; import { RefreshTokensProvider } from './refresh-tokens.provider'; +import { LogoutDTO } from '../dtos/logout.dto'; +import { LogoutProvider } from './logout.provider'; @Injectable() export class AuthService { @@ -23,6 +25,11 @@ export class AuthService { * Inject RefreshToken Provider */ private readonly refreshTokensProvider: RefreshTokensProvider, + + /** + * Inject Logout Provider + */ + private readonly logoutProvider: LogoutProvider, ) {} public async logIn(loginDto: LoginDTO) { @@ -36,4 +43,8 @@ export class AuthService { public async refreshToken(refreshTokenDto: RefreshTokenDTO) { return await this.refreshTokensProvider.refreshTokens(refreshTokenDto); } + + public async logout(userId: number, logoutDto: LogoutDTO) { + return await this.logoutProvider.logout(userId, logoutDto); + } } diff --git a/src/modules/auth/providers/logout.provider.ts b/src/modules/auth/providers/logout.provider.ts new file mode 100644 index 0000000..3654857 --- /dev/null +++ b/src/modules/auth/providers/logout.provider.ts @@ -0,0 +1,56 @@ +import { Inject, Injectable, UnauthorizedException } from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import type { ConfigType } from '@nestjs/config'; +import jwtConfig from '@/config/jwt.config'; +import { RefreshTokenPayload } from '../interfaces/jwt.interface'; +import { AuthRedisProvider } from './auth-redis.provider'; +import { LogoutDTO } from '../dtos/logout.dto'; +import { AppResponse } from '@/common/responses'; +import { I18nService } from 'nestjs-i18n'; + +@Injectable() +export class LogoutProvider { + constructor( + /** + * Inject JwtService + */ + private readonly jwtService: JwtService, + + /** + * Inject jwtConfig + */ + @Inject(jwtConfig.KEY) + private readonly jwtConfiguration: ConfigType, + + /** + * Inject AuthRedis Provider + */ + private readonly authRedisProvider: AuthRedisProvider, + + /** + * Inject I18n Service + */ + private readonly i18nService: I18nService, + ) {} + + public async logout(userId: number, logoutDto: LogoutDTO) { + const payload = await this.jwtService.verifyAsync( + logoutDto.refreshToken, + { + secret: this.jwtConfiguration.refresh.secret, + issuer: this.jwtConfiguration.issuer, + audience: this.jwtConfiguration.audience, + }, + ); + + if (payload.sub !== userId || !payload.jti) { + throw new UnauthorizedException('auth.errors.unauthorized'); + } + + await this.authRedisProvider.revokeRefreshToken(payload.jti); + + const message = this.i18nService.translate('auth.messages.loggedOut'); + + return new AppResponse(null, message); + } +}