From 0778086e29f2877b1596b5914c7312464bb88900 Mon Sep 17 00:00:00 2001 From: IsthisLee Date: Mon, 14 Aug 2023 04:37:35 +0900 Subject: [PATCH] =?UTF-8?q?feat(auth):=20AuthGuard=EC=97=90=20=EC=A0=84?= =?UTF-8?q?=EB=9E=B5=ED=8C=A8=ED=84=B4=20=EC=A0=81=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=97=AC=20AT,=20RT=20authorization=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 + src/apis/auth/auth.controller.ts | 20 ++-- src/apis/auth/auth.module.ts | 9 +- src/apis/auth/auth.service.ts | 56 ++--------- .../security/guards/access-token.guard.ts | 97 ------------------- .../strategies/access-token.strategy.ts | 32 ++++++ .../strategies/refresh-token.strategy.ts | 48 +++++++++ .../decorators/auth-guard-type.decorator.ts | 5 - src/common/decorators/jwt-user.decorator.ts | 6 ++ src/common/decorators/user.decorator.ts | 8 -- src/common/swagger-setting.ts | 2 +- src/common/types/auth.type.ts | 9 +- yarn.lock | 70 ++++++++++++- 13 files changed, 184 insertions(+), 182 deletions(-) delete mode 100644 src/apis/auth/security/guards/access-token.guard.ts create mode 100644 src/apis/auth/security/strategies/access-token.strategy.ts create mode 100644 src/apis/auth/security/strategies/refresh-token.strategy.ts delete mode 100644 src/common/decorators/auth-guard-type.decorator.ts create mode 100644 src/common/decorators/jwt-user.decorator.ts delete mode 100644 src/common/decorators/user.decorator.ts diff --git a/package.json b/package.json index 78a7aed..483e27a 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@nestjs/config": "^3.0.0", "@nestjs/core": "^10.0.0", "@nestjs/jwt": "^10.1.0", + "@nestjs/passport": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.1.7", "@nestjs/throttler": "^4.2.1", @@ -45,6 +46,8 @@ "express-basic-auth": "^1.2.1", "google-auth-library": "^9.0.0", "openai": "^4.0.0-beta.9", + "passport": "^0.6.0", + "passport-jwt": "^4.0.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "xml-js": "^1.6.11" @@ -57,6 +60,7 @@ "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", + "@types/passport-jwt": "^3.0.9", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", diff --git a/src/apis/auth/auth.controller.ts b/src/apis/auth/auth.controller.ts index a243826..a5ac5e5 100644 --- a/src/apis/auth/auth.controller.ts +++ b/src/apis/auth/auth.controller.ts @@ -2,11 +2,11 @@ import { Controller, Param, ParseEnumPipe, Body, Res, Req, Post, UseGuards } fro import { ApiTags, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger'; import { AuthService } from './auth.service'; import { Provider } from '@prisma/client'; -import { Request, Response } from 'express'; +import { Response } from 'express'; import { LoginDto } from './dtos/login.dto'; -import { AuthGuard } from './security/guards/access-token.guard'; -import { SetAuthGuardType } from 'src/common/decorators/auth-guard-type.decorator'; -import { AuthGuardType } from 'src/common/types'; +import { AuthGuard } from '@nestjs/passport'; +import { JwtUserPayload } from 'src/common/decorators/jwt-user.decorator'; +import { JwtPayloadInfo } from 'src/common/types'; @Controller('auth') @ApiTags('Auth') @@ -55,17 +55,15 @@ export class AuthController { } @Post('/silent-refresh') - @SetAuthGuardType(AuthGuardType.REFRESH) - @UseGuards(AuthGuard) + @UseGuards(AuthGuard('jwt-refresh')) @ApiOperation({ summary: '토큰 리프레시 API', description: ` - AT 만료 시 쿠키에 담겨오는 RT를 활용하여 AT를 refresh합니다.(보안을 위해 RT도 함께 refresh 됩니다.)`, + AT 만료 시 쿠키에 담겨오는 RT를 활용하여 AT를 refresh합니다.(추가 보안 작업 및 RT refresh도 함께 진행될 예정)`, }) - async refreshToken(@Req() req: Request, @Res({ passthrough: true }) res: Response) { - const userRefreshToken = req.cookies['refreshToken']; - const { accessToken, refreshToken } = await this.authService.refreshToken(res, userRefreshToken); - this.authService.setRefreshToken(res, refreshToken); + async refreshToken(@JwtUserPayload() jwtUser: JwtPayloadInfo, @Res({ passthrough: true }) res: Response) { + const { accessToken } = await this.authService.generateTokens(jwtUser); + // this.authService.setRefreshToken(res, refreshToken); // TODO: 추가 보안 작업 및 RT refresh도 함께 진행될 예정 return { accessToken }; } diff --git a/src/apis/auth/auth.module.ts b/src/apis/auth/auth.module.ts index 6dd5ac2..8898dab 100644 --- a/src/apis/auth/auth.module.ts +++ b/src/apis/auth/auth.module.ts @@ -1,10 +1,11 @@ -import { Global, Module } from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; import { UsersModule } from '../users/users.module'; import { JwtModule } from '@nestjs/jwt'; +import { AccessTokenStrategy } from './security/strategies/access-token.strategy'; +import { RefreshTokenStrategy } from './security/strategies/refresh-token.strategy'; -@Global() @Module({ imports: [ UsersModule, @@ -13,7 +14,7 @@ import { JwtModule } from '@nestjs/jwt'; }), ], controllers: [AuthController], - providers: [AuthService], - exports: [AuthService, UsersModule], + providers: [AuthService, AccessTokenStrategy, RefreshTokenStrategy], + exports: [AuthService], }) export class AuthModule {} diff --git a/src/apis/auth/auth.service.ts b/src/apis/auth/auth.service.ts index f5ed014..c7f266a 100644 --- a/src/apis/auth/auth.service.ts +++ b/src/apis/auth/auth.service.ts @@ -1,6 +1,6 @@ import { Injectable, Logger, UnauthorizedException } from '@nestjs/common'; import { UsersService } from '../users/users.service'; -import { UserPayloadInfo, CreateUserInfo } from 'src/common/types'; +import { JwtPayloadInfo, CreateUserInfo } from 'src/common/types'; import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; import { Provider, User } from '@prisma/client'; @@ -39,45 +39,13 @@ export class AuthService { // 토큰 발급 const userPayloadInfo = this.getUserPayloadInfo(user); - const { accessToken, refreshToken } = await this.getTokens(userPayloadInfo); + const { accessToken, refreshToken } = await this.generateTokens(userPayloadInfo); return { accessToken, refreshToken }; } - async refreshToken(res: Response, userRefreshToken: string) { - let userId: number; - - if (!userRefreshToken) { - throw new UnauthorizedException('Refresh Token이 없습니다.'); - } - - try { - const payload = await this.verifyToken(userRefreshToken, { - isRefreshToken: true, - }); - userId = payload.userId; - } catch (err) { - res.cookie('refreshToken', '', { - maxAge: 0, - }); - throw new UnauthorizedException(err.message); - } - - const user = await this.usersService.findOneById(userId); - if (!user) { - throw new UnauthorizedException('존재하지 않는 유저입니다.'); - } - - const { accessToken, refreshToken } = await this.getTokens(this.getUserPayloadInfo(user)); - - return { - accessToken, - refreshToken, - }; - } - verifyToken(token: string, { isRefreshToken }: { isRefreshToken: boolean } = { isRefreshToken: false }) { - const payload: UserPayloadInfo | { userId: number } = this.jwtService.verify(token, { + const payload: JwtPayloadInfo = this.jwtService.verify(token, { secret: this.configService.get(`${isRefreshToken ? 'REFRESH' : 'ACCESS'}_SECRET_KEY`), }); @@ -110,28 +78,22 @@ export class AuthService { return account; } - getUserPayloadInfo(user: User): UserPayloadInfo { + getUserPayloadInfo(user: User): JwtPayloadInfo { return { userId: user.id, - email: user.email, - name: user.name, - profileImage: user.profileImage, }; } - private async getTokens(user: UserPayloadInfo): Promise<{ accessToken: string; refreshToken: string }> { + async generateTokens(user: JwtPayloadInfo): Promise<{ accessToken: string; refreshToken: string }> { const [accessToken, refreshToken] = await Promise.all([ this.jwtService.signAsync(user, { secret: this.configService.get('ACCESS_SECRET_KEY'), expiresIn: this.configService.get('ACCESS_TOKEN_EXPIRES_IN'), }), - this.jwtService.signAsync( - { userId: user.userId }, - { - secret: this.configService.get('REFRESH_SECRET_KEY'), - expiresIn: this.configService.get('REFRESH_TOKEN_EXPIRES_IN'), - }, - ), + this.jwtService.signAsync(user, { + secret: this.configService.get('REFRESH_SECRET_KEY'), + expiresIn: this.configService.get('REFRESH_TOKEN_EXPIRES_IN'), + }), ]); return { diff --git a/src/apis/auth/security/guards/access-token.guard.ts b/src/apis/auth/security/guards/access-token.guard.ts deleted file mode 100644 index 51e6154..0000000 --- a/src/apis/auth/security/guards/access-token.guard.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { - CanActivate, - ExecutionContext, - Injectable, - InternalServerErrorException, - NotFoundException, - UnauthorizedException, -} from '@nestjs/common'; -import { JwtService } from '@nestjs/jwt'; -import { Request } from 'express'; -import { Reflector } from '@nestjs/core'; -import { UsersService } from '../../../users/users.service'; -import { ConfigService } from '@nestjs/config'; -import { AuthGuardType, UserPayloadInfo, RefreshToktenPayloadInfo } from '../../../../common/types'; -import { AUTH_GUARD_TYPE_KEY } from '../../../../common/decorators/auth-guard-type.decorator'; -import { AuthService } from '../../auth.service'; - -@Injectable() -export class AuthGuard implements CanActivate { - constructor( - private jwtService: JwtService, - private readonly reflector: Reflector, - private readonly usersService: UsersService, - private readonly authService: AuthService, - private readonly configService: ConfigService, - ) {} - - async canActivate(context: ExecutionContext): Promise { - const authGuardType: AuthGuardType = this.reflector.getAllAndOverride(AUTH_GUARD_TYPE_KEY, [ - context.getHandler(), - context.getClass(), - ]); - console.log('🚀 ~ file: access-token.guard.ts:31 ~ AuthGuard ~ canActivate ~ authGuardType:', authGuardType); - - switch (authGuardType) { - case AuthGuardType.ACCESS: - return await this.validateAccessToken(context); - - case AuthGuardType.REFRESH: - return await this.validateRefreshToken(context); - - default: - throw new InternalServerErrorException('존재하지 않는 AuthGuardType 입니다.'); - } - } - - private async validateAccessToken(context: ExecutionContext) { - const request = context.switchToHttp().getRequest(); - const token = this.extractTokenFromHeader(request); - - if (!token) { - throw new UnauthorizedException('토큰이 없습니다.'); - } - - try { - const payload: UserPayloadInfo = await this.jwtService.verifyAsync(token, { - secret: this.configService.get('ACCESS_SECRET_KEY'), - }); - - request['user'] = payload; - } catch (error) { - throw new UnauthorizedException(error.message); - } - return true; - } - - private async validateRefreshToken(context: ExecutionContext) { - const request = context.switchToHttp().getRequest(); - const token = request.cookies['refreshToken']; - - if (!token) { - throw new UnauthorizedException('토큰이 없습니다.'); - } - - try { - const payload: RefreshToktenPayloadInfo = await this.jwtService.verifyAsync(token, { - secret: this.configService.get('REFRESH_SECRET_KEY'), - }); - const { userId } = payload; - const user = await this.usersService.findOneById(userId); - if (!user) { - throw new NotFoundException('토큰값에 해당하는 유저가 존재하지 않습니다.'); - } - const userPayloadInfo = this.authService.getUserPayloadInfo(user); - - request['user'] = userPayloadInfo; - } catch (error) { - throw new UnauthorizedException(error.message); - } - return true; - } - - private extractTokenFromHeader(request: Request): string | undefined { - const [type, token] = request.headers.authorization?.split(' ') ?? []; - return type === 'Bearer' ? token : undefined; - } -} diff --git a/src/apis/auth/security/strategies/access-token.strategy.ts b/src/apis/auth/security/strategies/access-token.strategy.ts new file mode 100644 index 0000000..8249985 --- /dev/null +++ b/src/apis/auth/security/strategies/access-token.strategy.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { Request } from 'express'; +import { JwtPayloadInfo } from '../../../../common/types'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class AccessTokenStrategy extends PassportStrategy(Strategy, 'jwt-access') { + constructor(private readonly configService: ConfigService) { + // token 유효 확인 + super({ + secretOrKey: configService.get('ACCESS_SECRET_KEY'), + jwtFromRequest: ExtractJwt.fromExtractors([ + (request: Request) => { + const headerToken = this.extractTokenFromHeader(request); + return headerToken; + }, + ]), + passReqToCallback: true, + }); + } + + validate(req: Request, payload: JwtPayloadInfo): JwtPayloadInfo { + return payload; // req.user에 저장됨. + } + + private extractTokenFromHeader(request: Request): string | undefined { + const [type, token] = request.headers.authorization?.split(' ') ?? []; + return type === 'Bearer' ? token : undefined; + } +} diff --git a/src/apis/auth/security/strategies/refresh-token.strategy.ts b/src/apis/auth/security/strategies/refresh-token.strategy.ts new file mode 100644 index 0000000..a586c39 --- /dev/null +++ b/src/apis/auth/security/strategies/refresh-token.strategy.ts @@ -0,0 +1,48 @@ +import { Injectable } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { Request } from 'express'; +import { JwtPayloadInfo } from '../../../../common/types'; +import { ConfigService } from '@nestjs/config'; +import { NotFoundException } from '@nestjs/common'; +import { UsersService } from '../../../users/users.service'; +import { AuthService } from '../../auth.service'; + +@Injectable() +export class RefreshTokenStrategy extends PassportStrategy(Strategy, 'jwt-refresh') { + constructor( + private readonly configService: ConfigService, + private readonly usersService: UsersService, + private readonly authService: AuthService, + ) { + // token 유효 확인 + super({ + secretOrKey: configService.get('REFRESH_SECRET_KEY'), + jwtFromRequest: ExtractJwt.fromExtractors([ + (request: Request) => { + const cookieToken = request.cookies['refreshToken']; + return cookieToken; + }, + ]), + passReqToCallback: true, + }); + } + + async validate(req: Request, payload: JwtPayloadInfo): Promise { + const { userId } = payload; + const user = await this.usersService.findOneById(userId); + if (!user) { + throw new NotFoundException('토큰값에 해당하는 유저가 존재하지 않습니다.'); + } + + // TODO: 1. RT의 jti 검증 + // -> RT 토큰의 jti를 Cache에 저장해두고 검증 + // -> RT 토큰의 jti가 Cache에 존재하는 jti와 일치하지 않으면 검증 실패 + + // TODO: 2. AT 리프래시 할 때 RT도 함께 리프래시하고, 새로운 RT의 jti를 Cache에 저장 + // -> 최초 로그인 시에도 RT의 jti를 Cache에 저장해야 함. + // -> (TODO2 로직은 AuthController의 silentRefresh API에서 구현) + + return payload; // req.user에 저장됨. + } +} diff --git a/src/common/decorators/auth-guard-type.decorator.ts b/src/common/decorators/auth-guard-type.decorator.ts deleted file mode 100644 index 69c8003..0000000 --- a/src/common/decorators/auth-guard-type.decorator.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; -import { AuthGuardType } from '../types'; - -export const AUTH_GUARD_TYPE_KEY = 'AUTH_GUARD_TYPE'; -export const SetAuthGuardType = (authType: AuthGuardType) => SetMetadata(AUTH_GUARD_TYPE_KEY, authType); diff --git a/src/common/decorators/jwt-user.decorator.ts b/src/common/decorators/jwt-user.decorator.ts new file mode 100644 index 0000000..49d660e --- /dev/null +++ b/src/common/decorators/jwt-user.decorator.ts @@ -0,0 +1,6 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +export const JwtUserPayload = createParamDecorator((data, ctx: ExecutionContext): ParameterDecorator => { + const request = ctx.switchToHttp().getRequest(); + return request.user; +}); diff --git a/src/common/decorators/user.decorator.ts b/src/common/decorators/user.decorator.ts deleted file mode 100644 index 9daf616..0000000 --- a/src/common/decorators/user.decorator.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createParamDecorator, ExecutionContext } from '@nestjs/common'; - -export const TokenUser = createParamDecorator((data, ctx: ExecutionContext): ParameterDecorator => { - const request = ctx.switchToHttp().getRequest(); // 클라이언트에서 보낸 request의 정보 - - // 이전에 AuthGuard 클래스에서 할당했던 request.userId 객체의 정보를 return - return request.user; -}); diff --git a/src/common/swagger-setting.ts b/src/common/swagger-setting.ts index bd7165a..5408ca5 100644 --- a/src/common/swagger-setting.ts +++ b/src/common/swagger-setting.ts @@ -19,7 +19,7 @@ export const setupSwagger = (app: INestApplication): void => { name: 'JWT', in: 'header', }, - 'accessToken', + 'access-token', ) .build(); diff --git a/src/common/types/auth.type.ts b/src/common/types/auth.type.ts index 4ba808a..e0024ec 100644 --- a/src/common/types/auth.type.ts +++ b/src/common/types/auth.type.ts @@ -1,14 +1,9 @@ import { Request } from 'express'; -export interface UserPayloadInfo { +export interface JwtPayloadInfo { userId: number; - name: string; - email: string; - profileImage: string; } -export type RefreshToktenPayloadInfo = Pick; - export interface CreateUserInfo { social_id: string; email: string; @@ -17,7 +12,7 @@ export interface CreateUserInfo { } export interface RequestWithUser extends Request { - user: UserPayloadInfo; + user: JwtPayloadInfo; } export enum AuthGuardType { diff --git a/yarn.lock b/yarn.lock index bd07674..8e66fee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -773,6 +773,11 @@ resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-2.0.2.tgz#c8a090a8d22145b85ed977414c158534210f2e4f" integrity sha512-V0izw6tWs6fTp9+KiiPUbGHWALy563Frn8X6Bm87ANLRuE46iuBMD5acKBDP5lKL/75QFvrzSJT7HkCbB0jTpg== +"@nestjs/passport@^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@nestjs/passport/-/passport-10.0.0.tgz#92d36d4b8796b373da3f4d1a055db03cb246b127" + integrity sha512-IlKKc6M7JOe+4dBbW6gZsXBSD05ZYgwfGf3GJhgCmUGYVqffpDdALQSS6JftnExrE+12rACoEmHkzYwKAGVK0Q== + "@nestjs/platform-express@^10.0.0": version "10.1.3" resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-10.1.3.tgz#d1f644e86f2bc45c7529b9eed7669613f4392e99" @@ -1051,7 +1056,7 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== -"@types/jsonwebtoken@9.0.2": +"@types/jsonwebtoken@*", "@types/jsonwebtoken@9.0.2": version "9.0.2" resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#9eeb56c76dd555039be2a3972218de5bd3b8d83e" integrity sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q== @@ -1091,6 +1096,30 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/passport-jwt@^3.0.9": + version "3.0.9" + resolved "https://registry.yarnpkg.com/@types/passport-jwt/-/passport-jwt-3.0.9.tgz#6c74c71e133206c697344feaf6e6044e01fe2d1d" + integrity sha512-5XJt+79emfgpuBvBQusUPylFIVtW1QVAAkTRwCbRJAmxUjmLtIqUU6V1ovpnHPu6Qut3mR5Juc+s7kd06roNTg== + dependencies: + "@types/express" "*" + "@types/jsonwebtoken" "*" + "@types/passport-strategy" "*" + +"@types/passport-strategy@*": + version "0.2.35" + resolved "https://registry.yarnpkg.com/@types/passport-strategy/-/passport-strategy-0.2.35.tgz#e52f5212279ea73f02d9b06af67efe9cefce2d0c" + integrity sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g== + dependencies: + "@types/express" "*" + "@types/passport" "*" + +"@types/passport@*": + version "1.0.12" + resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.12.tgz#7dc8ab96a5e895ec13688d9e3a96920a7f42e73e" + integrity sha512-QFdJ2TiAEoXfEQSNDISJR1Tm51I78CymqcBa8imbjo6dNNu+l2huDxxbDEIoFIwOSKMkOfHEikyDuZ38WwWsmw== + dependencies: + "@types/express" "*" + "@types/qs@*": version "6.9.7" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" @@ -3666,6 +3695,16 @@ jsonwebtoken@9.0.0: ms "^2.1.1" semver "^7.3.8" +jsonwebtoken@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz#81d8c901c112c24e497a55daf6b2be1225b40145" + integrity sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg== + dependencies: + jws "^3.2.2" + lodash "^4.17.21" + ms "^2.1.1" + semver "^7.3.8" + jwa@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" @@ -4171,6 +4210,28 @@ parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +passport-jwt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-4.0.1.tgz#c443795eff322c38d173faa0a3c481479646ec3d" + integrity sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ== + dependencies: + jsonwebtoken "^9.0.0" + passport-strategy "^1.0.0" + +passport-strategy@1.x.x, passport-strategy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" + integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA== + +passport@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/passport/-/passport-0.6.0.tgz#e869579fab465b5c0b291e841e6cc95c005fac9d" + integrity sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug== + dependencies: + passport-strategy "1.x.x" + pause "0.0.1" + utils-merge "^1.0.1" + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -4214,6 +4275,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pause@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" + integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg== + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -5039,7 +5105,7 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -utils-merge@1.0.1: +utils-merge@1.0.1, utils-merge@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==