diff --git a/nestjs-auth/src/auth-typeorm/auth.controller.ts b/nestjs-auth/src/auth-typeorm/auth.controller.ts index 38d01c1..d3e3746 100644 --- a/nestjs-auth/src/auth-typeorm/auth.controller.ts +++ b/nestjs-auth/src/auth-typeorm/auth.controller.ts @@ -10,7 +10,7 @@ import { UseGuards, } from '@nestjs/common'; import { AuthService } from './auth.service'; -import { LocalAuthGuard } from './guard/local-auth.guard'; +import { AccessTokenAuthGuard, LocalAuthGuard } from './guard/local-auth.guard'; @Controller('') export class AuthController { @@ -46,8 +46,19 @@ export class AuthController { ); } + @HttpCode(200) + @UseGuards(AccessTokenAuthGuard) + @Post('logout') + async logout(@Req() req: Request) { + const accessToken = this.authService.jwtExtractor()(req); + return await this.authService.userService.onAfterLogout( + accessToken, + ); + } + + @UseGuards(AccessTokenAuthGuard) @Get('profile') - getProfile(req) { + getProfile(@Req() req: Request) { return req.user; } } diff --git a/nestjs-auth/src/auth-typeorm/auth.module.ts b/nestjs-auth/src/auth-typeorm/auth.module.ts index b9dd008..8bb1526 100644 --- a/nestjs-auth/src/auth-typeorm/auth.module.ts +++ b/nestjs-auth/src/auth-typeorm/auth.module.ts @@ -12,7 +12,7 @@ import { UserAuthServiceType, } from './types'; import { AuthController } from './auth.controller'; -import { LocalStrategy } from './strategy/local.strategy'; +import { LocalStrategy, JwtAccessTokenStrategy } from './strategy/local.strategy'; import { PassportModule } from '@nestjs/passport'; import { getOptions } from './helpers'; @@ -60,6 +60,7 @@ export class AuthModule { UserServiceClass, AuthService, LocalStrategy, + JwtAccessTokenStrategy, ], exports: [AuthService, USER_SERVICE, AUTH_CONFIG], controllers: opts.disableRouter ? [] : [AuthController], diff --git a/nestjs-auth/src/auth-typeorm/auth.service.ts b/nestjs-auth/src/auth-typeorm/auth.service.ts index 9a94b11..c53e86f 100644 --- a/nestjs-auth/src/auth-typeorm/auth.service.ts +++ b/nestjs-auth/src/auth-typeorm/auth.service.ts @@ -1,6 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { DataSource, EntityTarget, ObjectLiteral, Repository } from 'typeorm'; +import { ExtractJwt, JwtFromRequestFunction } from 'passport-jwt'; import { AUTH_CONFIG, AuthModuleConfig, @@ -41,6 +42,12 @@ export class AuthService< } } + jwtExtractor(): JwtFromRequestFunction { + return this.opts.jwt.jwtFromRequest + ? this.opts.jwt.jwtFromRequest() + : ExtractJwt.fromAuthHeaderAsBearerToken(); + } + async getLoginTokens(user: Entity): Promise { const [refreshTokenPayload, accessTokenPayload] = await Promise.all([ this.userService.createJwtRefreshTokenPayload(user), diff --git a/nestjs-auth/src/auth-typeorm/guard/local-auth.guard.ts b/nestjs-auth/src/auth-typeorm/guard/local-auth.guard.ts index 766f2f3..edd4eb3 100644 --- a/nestjs-auth/src/auth-typeorm/guard/local-auth.guard.ts +++ b/nestjs-auth/src/auth-typeorm/guard/local-auth.guard.ts @@ -3,3 +3,6 @@ import { Injectable } from '@nestjs/common'; @Injectable() export class LocalAuthGuard extends AuthGuard('local') {} + +@Injectable() +export class AccessTokenAuthGuard extends AuthGuard('jwt-access-token') {} diff --git a/nestjs-auth/src/auth-typeorm/strategy/local.strategy.ts b/nestjs-auth/src/auth-typeorm/strategy/local.strategy.ts index 03e9c84..87760dc 100644 --- a/nestjs-auth/src/auth-typeorm/strategy/local.strategy.ts +++ b/nestjs-auth/src/auth-typeorm/strategy/local.strategy.ts @@ -1,8 +1,10 @@ import { AuthService } from '../auth.service'; import { PassportStrategy } from '@nestjs/passport'; import { ObjectLiteral } from 'typeorm'; +import { Strategy as PassportJwtStrategy } from 'passport-jwt'; import { Strategy as PassportLocalStrategy } from 'passport-local'; -import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { Inject, Injectable, UnauthorizedException } from '@nestjs/common'; +import { AuthModuleConfig, AUTH_CONFIG } from '../types'; @Injectable() export class LocalStrategy< @@ -23,3 +25,27 @@ export class LocalStrategy< return user; } } + +@Injectable() +export class JwtAccessTokenStrategy< + Entity extends ObjectLiteral, + JwtPayload extends ObjectLiteral, +> extends PassportStrategy(PassportJwtStrategy, 'jwt-access-token') { + constructor( + private authService: AuthService, + @Inject(AUTH_CONFIG) public opts: AuthModuleConfig, + ) { + const secretOrKey = + opts.jwt.secret || opts.jwt.privateKey || opts.jwt.secretOrPrivateKey; + super({ + secretOrKey, + ignoreExpiration: false, + passReqToCallback: false, + jwtFromRequest: authService.jwtExtractor(), + }); + } + + async validate(payload: JwtPayload) { + return this.authService.userService.jwtValidator(payload); + } +} diff --git a/nestjs-auth/src/auth-typeorm/types.ts b/nestjs-auth/src/auth-typeorm/types.ts index 626450c..1902250 100644 --- a/nestjs-auth/src/auth-typeorm/types.ts +++ b/nestjs-auth/src/auth-typeorm/types.ts @@ -74,4 +74,6 @@ export abstract class UserAuthServiceType { accessTokenExpiresAt: any, refreshTokenExpiresAt: any, ): Promise; + abstract jwtValidator(payload: JwtPayloadSub): Promise; + abstract onAfterLogout(accessToken: string): Promise; } diff --git a/nestjs-auth/src/auth-typeorm/user-auth.service.ts b/nestjs-auth/src/auth-typeorm/user-auth.service.ts index 076e53c..7847248 100644 --- a/nestjs-auth/src/auth-typeorm/user-auth.service.ts +++ b/nestjs-auth/src/auth-typeorm/user-auth.service.ts @@ -208,4 +208,26 @@ export class BaseUserAuthService< expires_at: accessTokenExpiresAt, }; } + + async jwtValidator(payload: JwtPayloadSub) { + if (!payload.sub[this.IDField]) { + throw new Error('Invalid JWT payload'); + } + + const user = await this.userRepository.findOneBy({ + [this.IDField]: payload.sub[this.IDField], + } as FindOptionsWhere); + + if (!user) { + throw new UnauthorizedException(); + } + + // delete user[this.dbPasswordField]; + return user; + } + + async onAfterLogout(accessToken: string) { + // TODO custom your logic + return null; + } }