feat: logout

This commit is contained in:
Abdalhamid Alhamad
2024-12-19 16:50:39 +03:00
parent 93f5d83825
commit 8112fb81a2
6 changed files with 41 additions and 8 deletions

View File

@ -49,6 +49,7 @@ import { TaskModule } from './task/task.module';
inject: [ConfigService], inject: [ConfigService],
}), }),
I18nModule.forRoot(buildI18nOptions()), I18nModule.forRoot(buildI18nOptions()),
CacheModule, CacheModule,
// App modules // App modules

View File

@ -1,5 +1,6 @@
import { Body, Controller, Headers, HttpCode, HttpStatus, Post, UseGuards } from '@nestjs/common'; import { Body, Controller, Headers, HttpCode, HttpStatus, Post, Req, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { Request } from 'express';
import { DEVICE_ID_HEADER } from '~/common/constants'; import { DEVICE_ID_HEADER } from '~/common/constants';
import { AuthenticatedUser, Public } from '~/common/decorators'; import { AuthenticatedUser, Public } from '~/common/decorators';
import { AccessTokenGuard } from '~/common/guards'; import { AccessTokenGuard } from '~/common/guards';
@ -98,4 +99,11 @@ export class AuthController {
const [res, user] = await this.authService.login(loginDto, deviceId); const [res, user] = await this.authService.login(loginDto, deviceId);
return ResponseFactory.data(new LoginResponseDto(res, user)); return ResponseFactory.data(new LoginResponseDto(res, user));
} }
@Post('logout')
@HttpCode(HttpStatus.NO_CONTENT)
@UseGuards(AccessTokenGuard)
async logout(@Req() request: Request) {
await this.authService.logout(request);
}
} }

View File

@ -2,6 +2,8 @@ import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
import { Request } from 'express';
import { CacheService } from '~/common/modules/cache/services';
import { OtpScope, OtpType } from '~/common/modules/otp/enums'; import { OtpScope, OtpType } from '~/common/modules/otp/enums';
import { OtpService } from '~/common/modules/otp/services'; import { OtpService } from '~/common/modules/otp/services';
import { JuniorTokenService } from '~/junior/services'; import { JuniorTokenService } from '~/junior/services';
@ -35,6 +37,7 @@ export class AuthService {
private readonly userService: UserService, private readonly userService: UserService,
private readonly deviceService: DeviceService, private readonly deviceService: DeviceService,
private readonly juniorTokenService: JuniorTokenService, private readonly juniorTokenService: JuniorTokenService,
private readonly cacheService: CacheService,
) {} ) {}
async sendRegisterOtp({ phoneNumber, countryCode }: CreateUnverifiedUserRequestDto) { async sendRegisterOtp({ phoneNumber, countryCode }: CreateUnverifiedUserRequestDto) {
const user = await this.userService.findOrCreateUser({ phoneNumber, countryCode }); const user = await this.userService.findOrCreateUser({ phoneNumber, countryCode });
@ -213,6 +216,12 @@ export class AuthService {
} }
} }
logout(req: Request) {
const accessToken = req.headers.authorization?.split(' ')[1] as string;
const expiryInTtl = this.jwtService.decode(accessToken).exp - Date.now() / ONE_THOUSAND;
return this.cacheService.set(accessToken, 'LOGOUT', expiryInTtl);
}
private async loginWithPassword(loginDto: LoginRequestDto, user: User): Promise<ILoginResponse> { private async loginWithPassword(loginDto: LoginRequestDto, user: User): Promise<ILoginResponse> {
const isPasswordValid = bcrypt.compareSync(loginDto.password, user.password); const isPasswordValid = bcrypt.compareSync(loginDto.password, user.password);

View File

@ -1,15 +1,15 @@
import { ExecutionContext, Injectable } from '@nestjs/common'; import { ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core'; import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport'; import { AuthGuard } from '@nestjs/passport';
import { Observable } from 'rxjs';
import { IS_PUBLIC_KEY } from '../decorators'; import { IS_PUBLIC_KEY } from '../decorators';
import { CacheService } from '../modules/cache/services';
@Injectable() @Injectable()
export class AccessTokenGuard extends AuthGuard('access-token') { export class AccessTokenGuard extends AuthGuard('access-token') {
constructor(protected reflector: Reflector) { constructor(protected reflector: Reflector, private readonly cacheService: CacheService) {
super(); super();
} }
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> { async canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [ const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(), context.getHandler(),
context.getClass(), context.getClass(),
@ -18,6 +18,21 @@ export class AccessTokenGuard extends AuthGuard('access-token') {
if (isPublic) { if (isPublic) {
return true; return true;
} }
return super.canActivate(context);
const isValid = await super.canActivate(context);
if (!isValid) {
return false;
}
const token = context.switchToHttp().getRequest().headers['authorization']?.split(' ')[1];
const isRevoked = await this.cacheService.get(token);
if (isRevoked) {
return false;
}
return !isRevoked;
} }
} }

View File

@ -1,4 +1,4 @@
import { Module } from '@nestjs/common'; import { Global, Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { buildKeyvOptions } from '~/core/module-options'; import { buildKeyvOptions } from '~/core/module-options';
import { CacheService } from './services'; import { CacheService } from './services';
@ -14,4 +14,5 @@ import { CacheService } from './services';
], ],
exports: ['CACHE_INSTANCE', CacheService], exports: ['CACHE_INSTANCE', CacheService],
}) })
@Global()
export class CacheModule {} export class CacheModule {}

View File

@ -1,6 +1,5 @@
import { Global, Module } from '@nestjs/common'; import { Global, Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { CacheModule } from '~/common/modules/cache/cache.module';
import { DocumentController } from './controllers'; import { DocumentController } from './controllers';
import { Document } from './entities'; import { Document } from './entities';
import { DocumentRepository } from './repositories'; import { DocumentRepository } from './repositories';
@ -8,7 +7,7 @@ import { DocumentService, OciService } from './services';
@Global() @Global()
@Module({ @Module({
imports: [TypeOrmModule.forFeature([Document]), CacheModule], imports: [TypeOrmModule.forFeature([Document])],
controllers: [DocumentController], controllers: [DocumentController],
providers: [DocumentService, OciService, DocumentRepository], providers: [DocumentService, OciService, DocumentRepository],
exports: [DocumentService, OciService], exports: [DocumentService, OciService],