From b3fc882ba4872e7fc143398cdd616f76bf9933b8 Mon Sep 17 00:00:00 2001 From: Son Daehyeon Date: Wed, 17 Jul 2024 23:33:28 +0900 Subject: [PATCH] feat: Features/Auth (#6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Controller DTO 생성 * feat: Controller DTO 생성 * chore: NodeMailer 모듈 추가 * feat: RedisRepository CRUD 함수 추가 * feat: 인증코드 발급/검증 로직 * feat: 회원가입/로그인 구현 * fix: response가 void인 경우 content가 누락되던 현상 수정 * fix: MemberRepository 조회 시 password가 불러와지던 오류 수정 * feat: UserGuard 및 UserMiddleware 구현 * fix: 코드 인증시 redis 삭제 lazy * fix: 코드 인증시 가입 확인 * refactor: 리팩토링 * refactor: 리팩토링 * docs: Auth Swagger 문서화 * refactor: validate 오류 문자열 한글화 * refactor: import 최적화 * docs: Swagger 문서에서 인증 토큰 및 uid를 UUIDv4로 표기 * chore: 재사용을 위해 ValidationPipe Factory 생성 * fix: DTO Validation시 하나의 주요 규칙만 적용 * fix: 비밀번호 정규식 수정 (특수문자 선택적 포함) * test: Auth의 Request DTO 검증 테스트 추가 * chore: 테스트 폴더 분리 * test: Auth의 Request DTO 검증 테스트 * test: Auth Service 테스트 * test: Auth 통합 테스트 * docs: config 변화 README.md 업데이트 * test: test * test: rollback * chore: 누락된 yarn.lock 추가 * chore: 폴더 이동 (from dto/spec to spec) * chore: 폴더 이동 (from dto/spec to spec) --- README.md | 20 + config/config.template.yaml | 11 + package.json | 8 + src/app/app.module.ts | 6 +- src/domain/auth/auth.controller.spec.ts | 18 - src/domain/auth/auth.controller.ts | 132 +++- src/domain/auth/auth.guard.ts | 35 + src/domain/auth/auth.middleware.ts | 29 + src/domain/auth/auth.module.ts | 6 + src/domain/auth/auth.service.spec.ts | 18 - src/domain/auth/auth.service.ts | 102 ++- src/domain/auth/dto/request/LoginRequest.ts | 19 + .../auth/dto/request/RegisterRequest.ts | 39 ++ .../auth/dto/request/SendCodeRequest.ts | 12 + .../auth/dto/request/VerifyCodeRequest.ts | 20 + src/domain/auth/dto/response/LoginResponse.ts | 9 + .../auth/dto/response/MyInfoResponse.ts | 59 ++ .../auth/dto/response/VerifyCodeResponse.ts | 9 + .../AlreadyRegisteredByEmailException.ts | 7 + .../AlreadyRegisteredByStudentIdException.ts | 7 + .../exception/InvalidVerifyCodeException.ts | 7 + .../exception/InvalidVerifyTokenException.ts | 7 + .../auth/exception/MemberNotFoundException.ts | 7 + .../auth/exception/WrongPasswordException.ts | 7 + src/domain/auth/spec/auth.integrated.spec.ts | 153 +++++ .../auth/spec/auth.request-validate.spec.ts | 497 ++++++++++++++ src/domain/auth/spec/auth.service.spec.ts | 300 +++++++++ src/domain/member/constant/Role.ts | 7 + src/domain/member/member.module.ts | 8 +- src/domain/member/member.repository.ts | 39 ++ src/domain/member/member.schema.ts | 46 ++ src/main.ts | 5 +- src/utils/mail/NodeMail.ts | 30 + src/utils/mail/mail.module.ts | 8 + src/utils/redis/RedisRepository.ts | 12 + .../ApiCustomErrorResponse.decorator.ts | 51 ++ .../swagger/ApiCustomResponse.decorator.ts | 47 ++ src/utils/validator/Validator.ts | 28 + yarn.lock | 613 +++++++++++++----- 39 files changed, 2241 insertions(+), 197 deletions(-) delete mode 100644 src/domain/auth/auth.controller.spec.ts create mode 100644 src/domain/auth/auth.guard.ts create mode 100644 src/domain/auth/auth.middleware.ts delete mode 100644 src/domain/auth/auth.service.spec.ts create mode 100644 src/domain/auth/dto/request/LoginRequest.ts create mode 100644 src/domain/auth/dto/request/RegisterRequest.ts create mode 100644 src/domain/auth/dto/request/SendCodeRequest.ts create mode 100644 src/domain/auth/dto/request/VerifyCodeRequest.ts create mode 100644 src/domain/auth/dto/response/LoginResponse.ts create mode 100644 src/domain/auth/dto/response/MyInfoResponse.ts create mode 100644 src/domain/auth/dto/response/VerifyCodeResponse.ts create mode 100644 src/domain/auth/exception/AlreadyRegisteredByEmailException.ts create mode 100644 src/domain/auth/exception/AlreadyRegisteredByStudentIdException.ts create mode 100644 src/domain/auth/exception/InvalidVerifyCodeException.ts create mode 100644 src/domain/auth/exception/InvalidVerifyTokenException.ts create mode 100644 src/domain/auth/exception/MemberNotFoundException.ts create mode 100644 src/domain/auth/exception/WrongPasswordException.ts create mode 100644 src/domain/auth/spec/auth.integrated.spec.ts create mode 100644 src/domain/auth/spec/auth.request-validate.spec.ts create mode 100644 src/domain/auth/spec/auth.service.spec.ts create mode 100644 src/domain/member/constant/Role.ts create mode 100644 src/domain/member/member.repository.ts create mode 100644 src/domain/member/member.schema.ts create mode 100644 src/utils/mail/NodeMail.ts create mode 100644 src/utils/mail/mail.module.ts create mode 100644 src/utils/swagger/ApiCustomErrorResponse.decorator.ts create mode 100644 src/utils/swagger/ApiCustomResponse.decorator.ts create mode 100644 src/utils/validator/Validator.ts diff --git a/README.md b/README.md index 89da896..972df06 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,18 @@ mongo: authSource: ${MONGO_AUTH_SOURCE:=''} database: ${MONGO_DATABASE:=test} +smtp: + host: ${SMTP_HOST:=smtp.gmail.com} + port: ${SMTP_PORT:=465} + secure: ${SMTP_SECURE:=true} + username: ${SMTP_USERNAME:=''} + password: ${SMTP_PASSWORD:=''} + +jwt: + secret: ${JWT_SECRET:=secret} + expiresIn: ${JWT_EXPIRES_IN:=14d} + + ``` ### 2. 의존성 설치 @@ -53,6 +65,10 @@ docker run --name wink-backend \ -e MONGO_HOST=(MONGO_HOST) -e MONGO_PORT=(MONGO_PORT) \ -e MONGO_USERNAME=(MONGO_USERNAME) -e MONGO_PASSWORD=(MONGO_PASSWORD) \ -e MONGO_AUTH_SOURCE=(MONGO_AUTH_SOURCE) -e MONGO_DATABASE=(MONGO_DATABASE) \ + -e SMTP_HOST=(SMTP_HOST) -e SMTP_PORT=(SMTP_PORT) \ + -e SMTP_USER=(SMTP_USER) -e SMTP_PASS=(SMTP_PASS) \ + -e SMTP_SECURE=(SMTP_SECURE) -e \ + -e JWT_SECRET=(JWT_SECRET) -e JWT_EXPIRES_IN=(JWT_EXPIRES_IN) \ -p 8080:8080 \ -v /path/to/logs:/app/logs \ -d wink-backend @@ -66,6 +82,10 @@ docker run --name wink-backend \ -e REDIS_HOST=redis -e REDIS_PORT=6379 \ -e MONGO_HOST=mongo -e MONGO_PORT=27017 \ -e MONGO_DATABASE=wink \ + -e SMTP_HOST=(SMTP_HOST) -e SMTP_PORT=(SMTP_PORT) \ + -e SMTP_USER=(SMTP_USER) -e SMTP_PASS=(SMTP_PASS) \ + -e SMTP_SECURE=(SMTP_SECURE) -e \ + -e JWT_SECRET=(JWT_SECRET) -e JWT_EXPIRES_IN=(JWT_EXPIRES_IN) \ -p 8080:8080 \ -v /path/to/logs:/app/logs \ -d wink-backend diff --git a/config/config.template.yaml b/config/config.template.yaml index 9329cfb..6a38256 100644 --- a/config/config.template.yaml +++ b/config/config.template.yaml @@ -9,3 +9,14 @@ mongo: password: ${MONGO_PASSWORD:=''} authSource: ${MONGO_AUTH_SOURCE:=''} database: ${MONGO_DATABASE:=test} + +smtp: + host: ${SMTP_HOST:=smtp.gmail.com} + port: ${SMTP_PORT:=465} + secure: ${SMTP_SECURE:=true} + username: ${SMTP_USERNAME:=''} + password: ${SMTP_PASSWORD:=''} + +jwt: + secret: ${JWT_SECRET:=secret} + expiresIn: ${JWT_EXPIRES_IN:=14d} diff --git a/package.json b/package.json index 19cda6c..e3874c2 100644 --- a/package.json +++ b/package.json @@ -20,15 +20,19 @@ "@nestjs/mongoose": "^10.0.10", "@nestjs/platform-express": "^10.3.10", "@nestjs/swagger": "^7.4.0", + "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "ioredis": "^5.4.1", "js-yaml": "^4.1.0", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.5.1", "mongoose-autopopulate": "^1.1.0", "nest-winston": "^1.9.7", + "nodemailer": "^6.9.14", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", + "uuid": "^10.0.0", "winston": "^3.13.1", "winston-daily-rotate-file": "^5.0.0" }, @@ -42,7 +46,11 @@ "@types/jest": "^29.5.12", "@types/js-yaml": "^4.0.9", "@types/node": "^20.14.10", + "@types/nodemailer": "^6.4.15", "@types/supertest": "^6.0.2", + "@types/uuid": "^10.0.0", + "@types/bcrypt": "^5.0.2", + "@types/jsonwebtoken": "^9.0.6", "@typescript-eslint/eslint-plugin": "^7.16.0", "@typescript-eslint/parser": "^7.16.0", "eslint": "^8.42.0", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index ffa7570..1545798 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -7,8 +7,10 @@ import { MemberModule } from '../domain/member/member.module'; import { ActivityModule } from '../domain/activity/activity.module'; import { RequestLoggingMiddleware } from '../utils/logger/RequestLoggingMiddleware'; -import configuration from '../utils/config/configuration'; +import { AuthMiddleware } from '../domain/auth/auth.middleware'; + import { MongooseConfigService } from '../utils/mongo/MongooseConfigService'; +import configuration from '../utils/config/configuration'; @Module({ imports: [ @@ -23,10 +25,10 @@ import { MongooseConfigService } from '../utils/mongo/MongooseConfigService'; ActivityModule, ], controllers: [], - providers: [RequestLoggingMiddleware], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer.apply(RequestLoggingMiddleware).forRoutes('*'); + consumer.apply(AuthMiddleware).forRoutes('*'); } } diff --git a/src/domain/auth/auth.controller.spec.ts b/src/domain/auth/auth.controller.spec.ts deleted file mode 100644 index 27a31e6..0000000 --- a/src/domain/auth/auth.controller.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AuthController } from './auth.controller'; - -describe('AuthController', () => { - let controller: AuthController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [AuthController], - }).compile(); - - controller = module.get(AuthController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/domain/auth/auth.controller.ts b/src/domain/auth/auth.controller.ts index 268eeb2..86514c8 100644 --- a/src/domain/auth/auth.controller.ts +++ b/src/domain/auth/auth.controller.ts @@ -1,4 +1,132 @@ -import { Controller } from '@nestjs/common'; +import { Body, Controller, Get, HttpCode, Post, Put, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiProperty, ApiTags } from '@nestjs/swagger'; + +import { AuthService } from './auth.service'; +import { AuthGuard } from './auth.guard'; +import { ReqMember } from './auth.middleware'; + +import { Member } from '../member/member.schema'; + +import { LoginRequest } from './dto/request/LoginRequest'; +import { LoginResponse } from './dto/response/LoginResponse'; +import { RegisterRequest } from './dto/request/RegisterRequest'; +import { SendCodeRequest } from './dto/request/SendCodeRequest'; +import { VerifyCodeRequest } from './dto/request/VerifyCodeRequest'; +import { VerifyCodeResponse } from './dto/response/VerifyCodeResponse'; +import { MyInfoResponse } from './dto/response/MyInfoResponse'; + +import { ApiCustomResponse } from '../../utils/swagger/ApiCustomResponse.decorator'; +import { ApiCustomErrorResponseDecorator } from '../../utils/swagger/ApiCustomErrorResponse.decorator'; + +import { MemberNotFoundException } from './exception/MemberNotFoundException'; +import { WrongPasswordException } from './exception/WrongPasswordException'; +import { InvalidVerifyTokenException } from './exception/InvalidVerifyTokenException'; +import { AlreadyRegisteredByEmailException } from './exception/AlreadyRegisteredByEmailException'; +import { AlreadyRegisteredByStudentIdException } from './exception/AlreadyRegisteredByStudentIdException'; +import { InvalidVerifyCodeException } from './exception/InvalidVerifyCodeException'; @Controller('auth') -export class AuthController {} +@ApiTags('Auth') +export class AuthController { + constructor(private readonly service: AuthService) {} + + @Post() + @HttpCode(200) + @ApiOperation({ summary: '로그인' }) + @ApiProperty({ type: LoginRequest }) + @ApiCustomResponse({ type: LoginResponse, status: 200 }) + @ApiCustomErrorResponseDecorator([ + { + description: '회원을 찾을 수 없음', + error: MemberNotFoundException, + }, + { + description: '비밀번호가 틀림', + error: WrongPasswordException, + }, + ]) + async login(@Body() request: LoginRequest): Promise { + const { email, password } = request; + + const token = await this.service.login(email, password); + + return { token }; + } + + @Put() + @HttpCode(201) + @ApiOperation({ summary: '회원가입' }) + @ApiProperty({ type: RegisterRequest }) + @ApiCustomResponse({ status: 201 }) + @ApiCustomErrorResponseDecorator([ + { + description: '이메일 인증 토큰이 잘못됨', + error: InvalidVerifyTokenException, + }, + { + description: '이미 가입된 이메일', + error: AlreadyRegisteredByEmailException, + }, + { + description: '이미 가입된 학번', + error: AlreadyRegisteredByStudentIdException, + }, + ]) + async register(@Body() request: RegisterRequest): Promise { + const { name, studentId, password, verifyToken } = request; + + await this.service.register(name, studentId, password, verifyToken); + } + + @Get('/code') + @HttpCode(201) + @ApiOperation({ summary: '인증코드 전송' }) + @ApiProperty({ type: SendCodeRequest }) + @ApiCustomResponse({ status: 201 }) + @ApiCustomErrorResponseDecorator([ + { + description: '이미 가입된 이메일', + error: AlreadyRegisteredByEmailException, + }, + ]) + async sendCode(@Body() request: SendCodeRequest): Promise { + const { email } = request; + + await this.service.sendCode(email); + } + + @Post('/code') + @HttpCode(200) + @ApiOperation({ summary: '인증 토큰 발급' }) + @ApiProperty({ type: VerifyCodeRequest }) + @ApiCustomResponse({ type: VerifyCodeResponse, status: 200 }) + @ApiCustomErrorResponseDecorator([ + { + description: '잘못된 인증 코드', + error: InvalidVerifyCodeException, + }, + ]) + async verifyCode(@Body() request: VerifyCodeRequest): Promise { + const { email, code } = request; + + const verifyToken = await this.service.verifyCode(email, code); + + return { verifyToken }; + } + + @Get('/me') + @HttpCode(200) + @UseGuards(AuthGuard) + @ApiOperation({ summary: '인증 토큰으로 정보 조회' }) + @ApiBearerAuth() + @ApiCustomResponse({ type: MyInfoResponse, status: 200 }) + async getMyInfo(@ReqMember() member: Member): Promise { + const memberDoc = member['_doc']; + const memberId = member['_id']; + + delete memberDoc['_id']; + delete memberDoc['__v']; + + return { userId: memberId, ...memberDoc }; + } +} diff --git a/src/domain/auth/auth.guard.ts b/src/domain/auth/auth.guard.ts new file mode 100644 index 0000000..7e9d773 --- /dev/null +++ b/src/domain/auth/auth.guard.ts @@ -0,0 +1,35 @@ +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +import * as jwt from 'jsonwebtoken'; + +import { MemberRepository } from '../member/member.repository'; + +@Injectable() +export class AuthGuard implements CanActivate { + private readonly jwtSecret: string; + + constructor( + private configService: ConfigService, + private repository: MemberRepository, + ) { + this.jwtSecret = this.configService.get('jwt.secret'); + } + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const authorization = request.headers['authorization']; + + if (authorization && authorization.startsWith('Bearer ')) { + const token = authorization.slice(7); + + if (jwt.verify(token, this.jwtSecret)) { + const id = jwt.decode(authorization.slice(7))['id']; + + return await this.repository.existsById(id); + } + } + + return false; + } +} diff --git a/src/domain/auth/auth.middleware.ts b/src/domain/auth/auth.middleware.ts new file mode 100644 index 0000000..a6dd6f5 --- /dev/null +++ b/src/domain/auth/auth.middleware.ts @@ -0,0 +1,29 @@ +import { createParamDecorator, ExecutionContext, Injectable, NestMiddleware } from '@nestjs/common'; +import { NextFunction } from 'express'; + +import { MemberRepository } from '../member/member.repository'; + +import * as jwt from 'jsonwebtoken'; + +@Injectable() +export class AuthMiddleware implements NestMiddleware { + constructor(private repository: MemberRepository) {} + + async use(req: Request, res: Response, next: NextFunction) { + const authorization = req.headers['authorization']; + + if (authorization) { + const id = jwt.decode(authorization.slice(7))['id']; + + req['member'] = await this.repository.findById(id); + } + + next(); + } +} + +export const ReqMember = createParamDecorator((data: any, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + + return request.member; +}); diff --git a/src/domain/auth/auth.module.ts b/src/domain/auth/auth.module.ts index a7d9fbc..b3cc9ee 100644 --- a/src/domain/auth/auth.module.ts +++ b/src/domain/auth/auth.module.ts @@ -1,8 +1,14 @@ import { Module } from '@nestjs/common'; + import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; +import { MemberModule } from '../member/member.module'; +import { MailModule } from '../../utils/mail/mail.module'; +import { RedisModule } from '../../utils/redis/redis.module'; + @Module({ + imports: [MemberModule, MailModule, RedisModule], controllers: [AuthController], providers: [AuthService], }) diff --git a/src/domain/auth/auth.service.spec.ts b/src/domain/auth/auth.service.spec.ts deleted file mode 100644 index 800ab66..0000000 --- a/src/domain/auth/auth.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AuthService } from './auth.service'; - -describe('AuthService', () => { - let service: AuthService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [AuthService], - }).compile(); - - service = module.get(AuthService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/domain/auth/auth.service.ts b/src/domain/auth/auth.service.ts index a41c649..3c8412c 100644 --- a/src/domain/auth/auth.service.ts +++ b/src/domain/auth/auth.service.ts @@ -1,4 +1,104 @@ import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +import { v4 as uuid } from 'uuid'; +import * as bcrypt from 'bcrypt'; +import * as jwt from 'jsonwebtoken'; + +import { MemberRepository } from '../member/member.repository'; +import { NodeMail } from '../../utils/mail/NodeMail'; +import { RedisRepository } from '../../utils/redis/RedisRepository'; + +import { InvalidVerifyCodeException } from './exception/InvalidVerifyCodeException'; +import { InvalidVerifyTokenException } from './exception/InvalidVerifyTokenException'; +import { AlreadyRegisteredByEmailException } from './exception/AlreadyRegisteredByEmailException'; +import { AlreadyRegisteredByStudentIdException } from './exception/AlreadyRegisteredByStudentIdException'; +import { MemberNotFoundException } from './exception/MemberNotFoundException'; +import { WrongPasswordException } from './exception/WrongPasswordException'; @Injectable() -export class AuthService {} +export class AuthService { + private readonly jwtSecret: string; + private readonly jwtExpiresIn: string; + + constructor( + private readonly configService: ConfigService, + private readonly repository: MemberRepository, + private readonly nodeMail: NodeMail, + private readonly redis: RedisRepository, + ) { + this.jwtSecret = this.configService.get('jwt.secret'); + this.jwtExpiresIn = this.configService.get('jwt.expiresIn'); + } + + async login(email: string, password: string): Promise { + if (!(await this.repository.existsByEmail(email))) { + throw new MemberNotFoundException(); + } + + const member = await this.repository.raw().findOne({ email }).select('+password').exec(); + + if (!(await bcrypt.compare(password, member.password))) { + throw new WrongPasswordException(); + } + + return jwt.sign({ id: member['_id'] }, this.jwtSecret, { expiresIn: this.jwtExpiresIn }); + } + + async register( + name: string, + studentId: number, + password: string, + verifyToken: string, + ): Promise { + if (!(await this.redis.exists(verifyToken))) { + throw new InvalidVerifyTokenException(); + } + + const email = await this.redis.get(verifyToken); + + if (await this.repository.existsByEmail(email)) { + throw new AlreadyRegisteredByEmailException(); + } + + if (await this.repository.existsByStudentId(studentId)) { + throw new AlreadyRegisteredByStudentIdException(); + } + + const salt = await bcrypt.genSalt(10); + const hash = await bcrypt.hash(password, salt); + + await this.repository.save({ name, studentId, email, password: hash }); + + await this.redis.delete(verifyToken); + } + + async sendCode(email: string): Promise { + if (await this.repository.existsByEmail(email)) { + throw new AlreadyRegisteredByEmailException(); + } + + const code = Math.floor(Math.random() * 1_000_000) + .toString() + .padStart(6, '0'); + + await this.redis.setex(email, code, 60 * 10); + + await this.nodeMail.sendMail(email, '[WINK] 회원가입 인증코드', `인증코드: ${code}`); + } + + async verifyCode(email: string, code: string): Promise { + const storedCode = await this.redis.get(email); + + if (storedCode !== code) { + throw new InvalidVerifyCodeException(); + } + + await this.redis.delete(email); + + const verifyToken = uuid(); + await this.redis.setex(verifyToken, email, 60 * 60); + + return verifyToken; + } +} diff --git a/src/domain/auth/dto/request/LoginRequest.ts b/src/domain/auth/dto/request/LoginRequest.ts new file mode 100644 index 0000000..1d3511f --- /dev/null +++ b/src/domain/auth/dto/request/LoginRequest.ts @@ -0,0 +1,19 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { IsEmail, IsNotEmpty } from 'class-validator'; + +export class LoginRequest { + @IsEmail({}, { message: '이메일 형식이 올바르지 않습니다.' }) + @ApiProperty({ + description: '이메일', + example: 'test@gmail.com', + }) + email: string; + + @IsNotEmpty({ message: '비밀번호는 필수 입력 값입니다.' }) + @ApiProperty({ + description: '비밀번호', + example: 'p4sSw0rd!', + }) + password: string; +} diff --git a/src/domain/auth/dto/request/RegisterRequest.ts b/src/domain/auth/dto/request/RegisterRequest.ts new file mode 100644 index 0000000..ed1764d --- /dev/null +++ b/src/domain/auth/dto/request/RegisterRequest.ts @@ -0,0 +1,39 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { IsNumber, Length, Matches, Max, Min } from 'class-validator'; + +export class RegisterRequest { + @Length(2, 10, { message: '이름은 2글자 이상 10글자 이하로 입력해주세요.' }) + @ApiProperty({ + description: '이름', + example: '홍길동', + }) + name: string; + + @IsNumber({}, { message: '올바른 학번이 아닙니다.' }) + @Min(2000_0001, { message: '올바른 학번이 아닙니다.' }) + @Max(2100_9999, { message: '올바른 학번이 아닙니다.' }) + @ApiProperty({ + description: '학번', + example: 20240001, + }) + studentId: number; + + @Matches(/^(?=.*[a-zA-Z])(?=.*\d)[A-Za-z\d\W]{8,24}$/, { + message: '비밀번호는 8글자 이상 24글자 이하의 영어 대소문자와 숫자로 입력해주세요.', + }) + @ApiProperty({ + description: '비밀번호', + example: 'p4sSw0rd!', + }) + password: string; + + @Matches(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, { + message: '올바른 인증 토큰이 아닙니다.', + }) + @ApiProperty({ + description: '인증 토큰', + example: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + }) + verifyToken: string; +} diff --git a/src/domain/auth/dto/request/SendCodeRequest.ts b/src/domain/auth/dto/request/SendCodeRequest.ts new file mode 100644 index 0000000..23d6b6c --- /dev/null +++ b/src/domain/auth/dto/request/SendCodeRequest.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { IsEmail } from 'class-validator'; + +export class SendCodeRequest { + @IsEmail({}, { message: '이메일 형식이 올바르지 않습니다.' }) + @ApiProperty({ + description: '이메일', + example: 'honggildong@kookmin.ac.kr', + }) + email: string; +} diff --git a/src/domain/auth/dto/request/VerifyCodeRequest.ts b/src/domain/auth/dto/request/VerifyCodeRequest.ts new file mode 100644 index 0000000..ace1a1c --- /dev/null +++ b/src/domain/auth/dto/request/VerifyCodeRequest.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { IsEmail, IsNumberString, Length } from 'class-validator'; + +export class VerifyCodeRequest { + @IsEmail({}, { message: '이메일 형식이 올바르지 않습니다.' }) + @ApiProperty({ + description: '이메일', + example: 'honggildong@kookmin.ac.kr', + }) + email: string; + + @IsNumberString({ no_symbols: true }, { message: '올바른 인증코드가 아닙니다.' }) + @Length(6, 6, { message: '인증코드는 6자리 숫자로 입력해주세요.' }) + @ApiProperty({ + description: '인증코드', + example: '123456', + }) + code: string; +} diff --git a/src/domain/auth/dto/response/LoginResponse.ts b/src/domain/auth/dto/response/LoginResponse.ts new file mode 100644 index 0000000..af83b86 --- /dev/null +++ b/src/domain/auth/dto/response/LoginResponse.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class LoginResponse { + @ApiProperty({ + description: 'JWT 토큰', + example: 'A.B.C', + }) + token: string; +} diff --git a/src/domain/auth/dto/response/MyInfoResponse.ts b/src/domain/auth/dto/response/MyInfoResponse.ts new file mode 100644 index 0000000..e839184 --- /dev/null +++ b/src/domain/auth/dto/response/MyInfoResponse.ts @@ -0,0 +1,59 @@ +import { Role } from '../../../member/constant/Role'; +import { MyInfoLinks } from '../../../member/member.schema'; +import { ApiProperty } from '@nestjs/swagger'; + +export class MyInfoResponse { + @ApiProperty({ + description: '유저 아이디', + example: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + }) + userId: string; + + @ApiProperty({ + description: '이름', + example: '홍길동', + }) + name: string; + + @ApiProperty({ + description: '학번', + example: 20240001, + }) + studentId: number; + + @ApiProperty({ + description: '유저 아이콘 URL', + example: 'https://example.com/avatar.png', + }) + avatar?: string; + + @ApiProperty({ + description: '자기소개', + example: '안녕하세요', + }) + description?: string; + + @ApiProperty({ + description: '링크', + type: Object, + example: { + github: 'https://github.com/hongildong', + instagram: 'https://www.instagram.com/hongildong/', + blog: 'https://hongildong.tistory.com/', + }, + }) + link: MyInfoLinks; + + @ApiProperty({ + description: '역할', + enum: Role, + example: Role.MEMBER, + }) + role: Role; + + @ApiProperty({ + description: '회비 납부 여부', + example: true, + }) + fee: boolean; +} diff --git a/src/domain/auth/dto/response/VerifyCodeResponse.ts b/src/domain/auth/dto/response/VerifyCodeResponse.ts new file mode 100644 index 0000000..6a525af --- /dev/null +++ b/src/domain/auth/dto/response/VerifyCodeResponse.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class VerifyCodeResponse { + @ApiProperty({ + description: '인증 토큰', + example: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + }) + verifyToken: string; +} diff --git a/src/domain/auth/exception/AlreadyRegisteredByEmailException.ts b/src/domain/auth/exception/AlreadyRegisteredByEmailException.ts new file mode 100644 index 0000000..f3c62f4 --- /dev/null +++ b/src/domain/auth/exception/AlreadyRegisteredByEmailException.ts @@ -0,0 +1,7 @@ +import { HttpException } from '@nestjs/common'; + +export class AlreadyRegisteredByEmailException extends HttpException { + constructor() { + super('이미 가입된 이메일입니다.', 409); + } +} diff --git a/src/domain/auth/exception/AlreadyRegisteredByStudentIdException.ts b/src/domain/auth/exception/AlreadyRegisteredByStudentIdException.ts new file mode 100644 index 0000000..37d3dbd --- /dev/null +++ b/src/domain/auth/exception/AlreadyRegisteredByStudentIdException.ts @@ -0,0 +1,7 @@ +import { HttpException } from '@nestjs/common'; + +export class AlreadyRegisteredByStudentIdException extends HttpException { + constructor() { + super('이미 가입된 학번입니다.', 409); + } +} diff --git a/src/domain/auth/exception/InvalidVerifyCodeException.ts b/src/domain/auth/exception/InvalidVerifyCodeException.ts new file mode 100644 index 0000000..ccfa30a --- /dev/null +++ b/src/domain/auth/exception/InvalidVerifyCodeException.ts @@ -0,0 +1,7 @@ +import { HttpException } from '@nestjs/common'; + +export class InvalidVerifyCodeException extends HttpException { + constructor() { + super('인증코드가 일치하지 않습니다.', 400); + } +} diff --git a/src/domain/auth/exception/InvalidVerifyTokenException.ts b/src/domain/auth/exception/InvalidVerifyTokenException.ts new file mode 100644 index 0000000..37554c5 --- /dev/null +++ b/src/domain/auth/exception/InvalidVerifyTokenException.ts @@ -0,0 +1,7 @@ +import { HttpException } from '@nestjs/common'; + +export class InvalidVerifyTokenException extends HttpException { + constructor() { + super('올바르지 않은 인증 토큰입니다.', 400); + } +} diff --git a/src/domain/auth/exception/MemberNotFoundException.ts b/src/domain/auth/exception/MemberNotFoundException.ts new file mode 100644 index 0000000..f4d6d75 --- /dev/null +++ b/src/domain/auth/exception/MemberNotFoundException.ts @@ -0,0 +1,7 @@ +import { HttpException } from '@nestjs/common'; + +export class MemberNotFoundException extends HttpException { + constructor() { + super('가입되지 않은 회원입니다.', 404); + } +} diff --git a/src/domain/auth/exception/WrongPasswordException.ts b/src/domain/auth/exception/WrongPasswordException.ts new file mode 100644 index 0000000..4c7d788 --- /dev/null +++ b/src/domain/auth/exception/WrongPasswordException.ts @@ -0,0 +1,7 @@ +import { HttpException } from '@nestjs/common'; + +export class WrongPasswordException extends HttpException { + constructor() { + super('잘못된 비밀번호입니다.', 400); + } +} diff --git a/src/domain/auth/spec/auth.integrated.spec.ts b/src/domain/auth/spec/auth.integrated.spec.ts new file mode 100644 index 0000000..035feff --- /dev/null +++ b/src/domain/auth/spec/auth.integrated.spec.ts @@ -0,0 +1,153 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ConfigService } from '@nestjs/config'; + +import * as jwt from 'jsonwebtoken'; + +import { AuthController } from '../auth.controller'; +import { AuthService } from '../auth.service'; + +import { MemberRepository } from '../../member/member.repository'; +import { NodeMail } from '../../../utils/mail/NodeMail'; +import { RedisRepository } from '../../../utils/redis/RedisRepository'; + +import { Member } from '../../member/member.schema'; + +import { SendCodeRequest } from '../dto/request/SendCodeRequest'; +import { VerifyCodeRequest } from '../dto/request/VerifyCodeRequest'; +import { RegisterRequest } from '../dto/request/RegisterRequest'; + +describe('Auth 통합 테스트', () => { + let controller: AuthController; + + let memoryMemberRepository: Member[] = []; + let memoryRedisRepository: Record = {}; + + beforeAll(async () => { + const mockConfigService = { + get: (key: string) => { + switch (key) { + case 'jwt.secret': + return 'test'; + case 'jwt.expiresIn': + return '1h'; + } + }, + }; + + const mockMemberRepository = { + existsByEmail: (email: string) => + memoryMemberRepository.find((member) => member.email === email), + existsByStudentId: (studentId: number) => + memoryMemberRepository.find((member) => member.studentId === studentId), + save: (member: Member) => memoryMemberRepository.push(member), + raw: () => ({ + findOne: (condition: Record) => ({ + select: () => ({ + exec: () => memoryMemberRepository.find((member) => member.email === condition.email), + }), + }), + }), + }; + + const mockNodeMail = { + sendMail: jest.fn(), + }; + + const mockRedisRepository = { + setex: (key: string, value: string) => (memoryRedisRepository[key] = value), + exists: (key: string) => memoryRedisRepository[key] !== undefined, + get: (key: string) => memoryRedisRepository[key], + delete: (key: string) => delete memoryRedisRepository[key], + }; + + const module: TestingModule = await Test.createTestingModule({ + controllers: [AuthController], + providers: [ + AuthService, + { + provide: ConfigService, + useValue: mockConfigService, + }, + { + provide: MemberRepository, + useValue: mockMemberRepository, + }, + { + provide: NodeMail, + useValue: mockNodeMail, + }, + { + provide: RedisRepository, + useValue: mockRedisRepository, + }, + ], + }).compile(); + + controller = module.get(AuthController); + }); + + afterAll(() => { + memoryMemberRepository = []; + memoryRedisRepository = {}; + }); + + describe('통합 테스트', () => { + const name = '홍길동'; + const studentId = 2024_0001; + const email = 'honggildong@gmail.com'; + const password = 'p4sSw0rd!'; + + let verifyToken: string; + let token: string; + + it('인증코드 전송', async () => { + // Given + const request: SendCodeRequest = { + email, + }; + + // When + await controller.sendCode(request); + + // Then + expect(memoryRedisRepository[email]).toBeDefined(); + }); + + it('인증 토큰 발급', async () => { + // Given + const request: VerifyCodeRequest = { email, code: memoryRedisRepository[email] }; + + // When + const response = await controller.verifyCode(request); + verifyToken = response.verifyToken; + + // Then + expect(memoryRedisRepository[email]).toBeUndefined(); + expect(memoryRedisRepository[verifyToken]).toBe(email); + }); + + it('회원가입', async () => { + // Given + const request: RegisterRequest = { name, studentId, password, verifyToken }; + + // When + await controller.register(request); + + // Then + expect(memoryRedisRepository[email]).toBeUndefined(); + }); + + it('로그인', async () => { + // Given + const request = { email, password }; + + // When + const response = await controller.login(request); + token = response.token; + + // Then + const member = memoryMemberRepository.find((member) => member.email === email); + expect(jwt.decode(token)['id']).toBe(member['_id']); + }); + }); +}); diff --git a/src/domain/auth/spec/auth.request-validate.spec.ts b/src/domain/auth/spec/auth.request-validate.spec.ts new file mode 100644 index 0000000..f92e7d9 --- /dev/null +++ b/src/domain/auth/spec/auth.request-validate.spec.ts @@ -0,0 +1,497 @@ +import { Validator } from '../../../utils/validator/Validator'; + +import { LoginRequest } from '../dto/request/LoginRequest'; +import { RegisterRequest } from '../dto/request/RegisterRequest'; +import { SendCodeRequest } from '../dto/request/SendCodeRequest'; +import { VerifyCodeRequest } from '../dto/request/VerifyCodeRequest'; + +describe('Auth Request DTO 검증 테스트', () => { + let validator: Validator; + + beforeAll(async () => { + validator = new Validator(); + }); + + describe('로그인', () => { + describe('이메일', () => { + it('이메일이 주어지지 않았을 때', async () => { + // Given + const body: LoginRequest = { + email: null, + password: 'p4sSw0rd!', + }; + + // When + const result = validator.validateBody(body, LoginRequest); + + // Then + await expect(result).rejects.toThrow('이메일 형식이 올바르지 않습니다.'); + }); + + it('이메일 형식이 올바르지 않을 때', async () => { + // Given + const body: LoginRequest = { + email: 'invalidEmail', + password: 'p4sSw0rd!', + }; + + // When + const result = validator.validateBody(body, LoginRequest); + + // Then + await expect(result).rejects.toThrow('이메일 형식이 올바르지 않습니다.'); + }); + }); + + describe('비밀번호', () => { + it('비밀번호가 주어지지 않았을 때', async () => { + // Given + const body: LoginRequest = { + email: 'test@gmail.com', + password: null, + }; + + // When + const result = validator.validateBody(body, LoginRequest); + + // Then + await expect(result).rejects.toThrow('비밀번호는 필수 입력 값입니다.'); + }); + + it('비밀번호가 빈 문자열일 때', async () => { + // Given + const body: LoginRequest = { + email: 'honggildong@gmail.com', + password: '', + }; + + // When + const result = validator.validateBody(body, LoginRequest); + + // Then + await expect(result).rejects.toThrow('비밀번호는 필수 입력 값입니다.'); + }); + }); + + it('모든 입력이 유효할 때', async () => { + // Given + const body: LoginRequest = { + email: 'honggildong@gmail.com', + password: 'p4sSw0rd!', + }; + + // When + const result = validator.validateBody(body, LoginRequest); + + // Then + await expect(result).resolves.toBeDefined(); + }); + }); + + describe('회원가입', () => { + describe('이름', () => { + it('이름이 주어지지 않았을 때', async () => { + // Given + const body: RegisterRequest = { + name: null, + studentId: 2024_0001, + password: 'p4sSw0rd!', + verifyToken: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + }; + + // When + const result = validator.validateBody(body, RegisterRequest); + + // Then + await expect(result).rejects.toThrow('이름은 2글자 이상 10글자 이하로 입력해주세요.'); + }); + + it('이름이 짧을 때', async () => { + // Given + const body: RegisterRequest = { + name: '홍', + studentId: 2024_0001, + password: 'p4sSw0rd!', + verifyToken: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + }; + + // When + const result = validator.validateBody(body, RegisterRequest); + + // Then + await expect(result).rejects.toThrow('이름은 2글자 이상 10글자 이하로 입력해주세요.'); + }); + + it('이름이 길 때', async () => { + // Given + const body: RegisterRequest = { + name: '홍길동홍길동홍길동홍길동', + studentId: 2024_0001, + password: 'p4sSw0rd!', + verifyToken: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + }; + + // When + const result = validator.validateBody(body, RegisterRequest); + + // Then + await expect(result).rejects.toThrow('이름은 2글자 이상 10글자 이하로 입력해주세요.'); + }); + }); + + describe('학번', () => { + it('학번이 주어지지 않았을 때', async () => { + // Given + const body: RegisterRequest = { + name: '홍길동', + studentId: null, + password: 'p4sSw0rd!', + verifyToken: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + }; + + // When + const result = validator.validateBody(body, RegisterRequest); + + // Then + await expect(result).rejects.toThrow('올바른 학번이 아닙니다.'); + }); + + it('학번이 짧을 때', async () => { + // Given + const body: RegisterRequest = { + name: '홍길동', + studentId: 2024_001, + password: 'p4sSw0rd!', + verifyToken: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + }; + + // When + const result = validator.validateBody(body, RegisterRequest); + + // Then + await expect(result).rejects.toThrow('올바른 학번이 아닙니다.'); + }); + + it('학번이 길 때', async () => { + // Given + const body: RegisterRequest = { + name: '홍길동', + studentId: 2024_0000_1, + password: 'p4sSw0rd!', + verifyToken: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + }; + + // When + const result = validator.validateBody(body, RegisterRequest); + + // Then + await expect(result).rejects.toThrow('올바른 학번이 아닙니다.'); + }); + }); + + describe('비밀번호', () => { + it('비밀번호가 주어지지 않았을 때', async () => { + // Given + const body: RegisterRequest = { + name: '홍길동', + studentId: 2024_0001, + password: null, + verifyToken: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + }; + + // When + const result = validator.validateBody(body, RegisterRequest); + + // Then + await expect(result).rejects.toThrow( + '비밀번호는 8글자 이상 24글자 이하의 영어 대소문자와 숫자로 입력해주세요.', + ); + }); + + it('비밀번호가 빈 문자열일 때', async () => { + // Given + const body: RegisterRequest = { + name: '홍길동', + studentId: 2024_0001, + password: '', + verifyToken: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + }; + + // When + const result = validator.validateBody(body, RegisterRequest); + + // Then + await expect(result).rejects.toThrow( + '비밀번호는 8글자 이상 24글자 이하의 영어 대소문자와 숫자로 입력해주세요.', + ); + }); + + it('비밀번호가 짧을 때', async () => { + // Given + const body: RegisterRequest = { + name: '홍길동', + studentId: 2024_0001, + password: 'aaaa', + verifyToken: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + }; + + // When + const result = validator.validateBody(body, RegisterRequest); + + // Then + await expect(result).rejects.toThrow( + '비밀번호는 8글자 이상 24글자 이하의 영어 대소문자와 숫자로 입력해주세요.', + ); + }); + + it('비밀번호가 길 때', async () => { + // Given + const body: RegisterRequest = { + name: '홍길동', + studentId: 2024_0001, + password: 'aaaaaaaaaaaaaaaaaaaaaaaaz', + verifyToken: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + }; + + // When + const result = validator.validateBody(body, RegisterRequest); + + // Then + await expect(result).rejects.toThrow( + '비밀번호는 8글자 이상 24글자 이하의 영어 대소문자와 숫자로 입력해주세요.', + ); + }); + + it('비밀번호가 영어만 있을 때', async () => { + // Given + const body: RegisterRequest = { + name: '홍길동', + studentId: 2024_0001, + password: 'aaaaaaaa', + verifyToken: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + }; + + // When + const result = validator.validateBody(body, RegisterRequest); + + // Then + await expect(result).rejects.toThrow( + '비밀번호는 8글자 이상 24글자 이하의 영어 대소문자와 숫자로 입력해주세요.', + ); + }); + }); + + describe('인증 토큰', () => { + it('인증 토큰이 주어지지 않았을 때', async () => { + // Given + const body: RegisterRequest = { + name: '홍길동', + studentId: 2024_0001, + password: 'p4sSw0rd!', + verifyToken: null, + }; + + // When + const result = validator.validateBody(body, RegisterRequest); + + // Then + await expect(result).rejects.toThrow('올바른 인증 토큰이 아닙니다.'); + }); + + it('인증 토큰이 빈 문자열일 때', async () => { + // Given + const body: RegisterRequest = { + name: '홍길동', + studentId: 2024_0001, + password: 'p4sSw0rd!', + verifyToken: '', + }; + + // When + const result = validator.validateBody(body, RegisterRequest); + + // Then + await expect(result).rejects.toThrow('올바른 인증 토큰이 아닙니다.'); + }); + + it('인증 토큰이 UUID v4 형태가 아닐 때', async () => { + // Given + const body: RegisterRequest = { + name: '홍길동', + studentId: 2024_0001, + password: 'p4sSw0rd!', + verifyToken: 'aaaa-bbbb-cccc-dddd-eeee', + }; + + // When + const result = validator.validateBody(body, RegisterRequest); + + // Then + await expect(result).rejects.toThrow('올바른 인증 토큰이 아닙니다.'); + }); + }); + + it('모든 입력이 유효할 때', async () => { + // Given + const body: RegisterRequest = { + name: '홍길동', + studentId: 2024_0001, + password: 'p4sSw0rd!', + verifyToken: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + }; + + // When + const result = validator.validateBody(body, RegisterRequest); + + // Then + await expect(result).resolves.toBeDefined(); + }); + }); + + describe('인증코드 전송', () => { + describe('이메일', () => { + it('이메일이 주어지지 않았을 때', async () => { + // Given + const body: SendCodeRequest = { + email: null, + }; + + // When + const result = validator.validateBody(body, SendCodeRequest); + + // Then + await expect(result).rejects.toThrow('이메일 형식이 올바르지 않습니다.'); + }); + + it('이메일 형식이 올바르지 않을 때', async () => { + // Given + const body: SendCodeRequest = { + email: 'invalidEmail', + }; + + // When + const result = validator.validateBody(body, SendCodeRequest); + + // Then + await expect(result).rejects.toThrow('이메일 형식이 올바르지 않습니다.'); + }); + }); + + it('모든 입력이 유효할 때', async () => { + // Given + const body: SendCodeRequest = { + email: 'honggildong@gmail.com', + }; + + // When + const result = validator.validateBody(body, SendCodeRequest); + + // Then + await expect(result).resolves.toBeDefined(); + }); + }); + + describe('인증 토큰 발급', () => { + describe('이메일', () => { + it('이메일이 주어지지 않았을 때', async () => { + // Given + const body: VerifyCodeRequest = { + email: null, + code: '123456', + }; + + // When + const result = validator.validateBody(body, VerifyCodeRequest); + + // Then + await expect(result).rejects.toThrow('이메일 형식이 올바르지 않습니다.'); + }); + + it('이메일 형식이 올바르지 않을 때', async () => { + // Given + const body: VerifyCodeRequest = { + email: 'invalidEmail', + code: '123456', + }; + + // When + const result = validator.validateBody(body, VerifyCodeRequest); + + // Then + await expect(result).rejects.toThrow('이메일 형식이 올바르지 않습니다.'); + }); + }); + + describe('인증코드', () => { + it('인증코드가 주어지지 않았을 때', async () => { + // Given + const body: VerifyCodeRequest = { + email: 'honggildong@gmail.com', + code: null, + }; + + // When + const result = validator.validateBody(body, VerifyCodeRequest); + + // Then + await expect(result).rejects.toThrow(); + }); + + it('인증코드에 문자가 들어갔을 때', async () => { + // Given + const body: VerifyCodeRequest = { + email: 'honggildong@gmail.com', + code: 'abcdef', + }; + + // When + const result = validator.validateBody(body, VerifyCodeRequest); + + // Then + await expect(result).rejects.toThrow('올바른 인증코드가 아닙니다.'); + }); + + it('인증코드가 짧을 때', async () => { + // Given + const body: VerifyCodeRequest = { + email: 'honggildong@gmail.com', + code: '12345', + }; + + // When + const result = validator.validateBody(body, VerifyCodeRequest); + + // Then + await expect(result).rejects.toThrow('인증코드는 6자리 숫자로 입력해주세요.'); + }); + + it('인증코드가 길 때', async () => { + // Given + const body: VerifyCodeRequest = { + email: 'honggildong@gmail.com', + code: '1234567', + }; + + // When + const result = validator.validateBody(body, VerifyCodeRequest); + + // Then + await expect(result).rejects.toThrow('인증코드는 6자리 숫자로 입력해주세요.'); + }); + }); + + it('모든 입력이 유효할 때', async () => { + // Given + const body: VerifyCodeRequest = { + email: 'honggildong@gmail.com', + code: '123456', + }; + + // When + const result = validator.validateBody(body, VerifyCodeRequest); + + // Then + await expect(result).resolves.toBeDefined(); + }); + }); +}); diff --git a/src/domain/auth/spec/auth.service.spec.ts b/src/domain/auth/spec/auth.service.spec.ts new file mode 100644 index 0000000..c526334 --- /dev/null +++ b/src/domain/auth/spec/auth.service.spec.ts @@ -0,0 +1,300 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ConfigService } from '@nestjs/config'; + +import * as bcrypt from 'bcrypt'; + +import { AuthService } from '../auth.service'; + +import { MemberRepository } from '../../member/member.repository'; +import { NodeMail } from '../../../utils/mail/NodeMail'; +import { RedisRepository } from '../../../utils/redis/RedisRepository'; + +import { Member } from '../../member/member.schema'; + +import { MemberNotFoundException } from '../exception/MemberNotFoundException'; +import { WrongPasswordException } from '../exception/WrongPasswordException'; +import { AlreadyRegisteredByEmailException } from '../exception/AlreadyRegisteredByEmailException'; +import { AlreadyRegisteredByStudentIdException } from '../exception/AlreadyRegisteredByStudentIdException'; +import { InvalidVerifyTokenException } from '../exception/InvalidVerifyTokenException'; +import { InvalidVerifyCodeException } from '../exception/InvalidVerifyCodeException'; + +describe('Auth Service 테스트', () => { + let service: AuthService; + + let memoryMemberRepository: Member[] = []; + let memoryRedisRepository: Record = {}; + + beforeAll(async () => { + const mockConfigService = { + get: (key: string) => { + switch (key) { + case 'jwt.secret': + return 'test'; + case 'jwt.expiresIn': + return '1h'; + } + }, + }; + + const mockMemberRepository = { + existsByEmail: (email: string) => + memoryMemberRepository.find((member) => member.email === email), + existsByStudentId: (studentId: number) => + memoryMemberRepository.find((member) => member.studentId === studentId), + save: (member: Member) => memoryMemberRepository.push(member), + raw: () => ({ + findOne: (condition: Record) => ({ + select: () => ({ + exec: () => memoryMemberRepository.find((member) => member.email === condition.email), + }), + }), + }), + }; + + const mockNodeMail = { + sendMail: jest.fn(), + }; + + const mockRedisRepository = { + setex: (key: string, value: string) => (memoryRedisRepository[key] = value), + exists: (key: string) => memoryRedisRepository[key] !== undefined, + get: (key: string) => memoryRedisRepository[key], + delete: (key: string) => delete memoryRedisRepository[key], + }; + + const module: TestingModule = await Test.createTestingModule({ + providers: [ + AuthService, + { + provide: ConfigService, + useValue: mockConfigService, + }, + { + provide: MemberRepository, + useValue: mockMemberRepository, + }, + { + provide: NodeMail, + useValue: mockNodeMail, + }, + { + provide: RedisRepository, + useValue: mockRedisRepository, + }, + ], + }).compile(); + + service = module.get(AuthService); + }); + + afterEach(() => { + memoryMemberRepository = []; + memoryRedisRepository = {}; + }); + + describe('로그인', () => { + it('존재하지 않는 유저일 때', async () => { + // Given + + // When + const result = service.login('not-exists@gmail.com', 'p4sSw0rd!'); + + // Then + await expect(result).rejects.toThrow(MemberNotFoundException); + }); + + it('잘못된 비밀번호일 때', async () => { + // Given + const email = 'honggildong@gmail.com'; + const password = 'p4sSw0rd!'; + + const salt = await bcrypt.genSalt(10); + const hash = await bcrypt.hash(password, salt); + + memoryMemberRepository.push({ + email, + password: hash, + fee: false, + link: undefined, + name: '', + role: undefined, + studentId: 0, + }); + + // When + const result = service.login(email, `${password}!`); + + // Then + await expect(result).rejects.toThrow(WrongPasswordException); + }); + + it('올바른 정보가 주어졌을 때', async () => { + // Given + const email = 'honggildong@gmail.com'; + const password = 'p4sSw0rd!'; + + const salt = await bcrypt.genSalt(10); + const hash = await bcrypt.hash(password, salt); + + memoryMemberRepository.push({ + email, + password: hash, + fee: false, + link: undefined, + name: '', + role: undefined, + studentId: 0, + }); + + // When + const result = service.login(email, password); + + // Then + await expect(result).resolves.toBeDefined(); + }); + }); + + describe('회원가입', () => { + it('인증 토큰이 존재하지 않을 때', async () => { + // Given + + // When + const result = service.register('', 0, '', 'empty-token'); + + // Then + await expect(result).rejects.toThrow(InvalidVerifyTokenException); + }); + + it('이미 가입된 이메일일 때', async () => { + // Given + const email = 'honggildong@gmail.com'; + const password = 'p4sSw0rd!'; + const verifyToken = 'verify-token'; + + memoryMemberRepository.push({ + email, + password, + fee: false, + link: undefined, + name: '', + role: undefined, + studentId: 0, + }); + + memoryRedisRepository[verifyToken] = email; + + // When + const result = service.register('', 0, password, verifyToken); + + // Then + await expect(result).rejects.toThrow(AlreadyRegisteredByEmailException); + }); + + it('이미 가입된 학번일 때', async () => { + // Given + const email = 'honggildong@gmail.com'; + const password = 'p4sSw0rd!'; + const verifyToken = 'verify-token'; + + memoryMemberRepository.push({ + email: `other_${email}`, + password: '', + fee: false, + link: undefined, + name: '', + role: undefined, + studentId: 20240001, + }); + + memoryRedisRepository[verifyToken] = email; + + // When + const result = service.register('', 20240001, password, verifyToken); + + // Then + await expect(result).rejects.toThrow(AlreadyRegisteredByStudentIdException); + }); + + it('올바른 정보가 주어졌을 때', async () => { + // Given + const email = 'honggildong@gmail.com'; + const password = 'p4sSw0rd!'; + const verifyToken = 'verify-token'; + + memoryRedisRepository[verifyToken] = email; + + // When + const result = service.register('', 20240001, password, verifyToken); + + // Then + await expect(result).resolves.toBeUndefined(); + expect(memoryMemberRepository).toHaveLength(1); + expect(memoryMemberRepository[0].email).toBe(email); + }); + }); + + describe('인증코드 전송', () => { + it('이미 가입된 이메일일 때', async () => { + // Given + const email = 'honggildong@gmail.com'; + + memoryMemberRepository.push({ + email: email, + password: '', + fee: false, + link: undefined, + name: '', + role: undefined, + studentId: 20240001, + }); + + // When + const result = service.sendCode(email); + + // Then + await expect(result).rejects.toThrow(AlreadyRegisteredByEmailException); + }); + + it('올바른 정보가 주어졌을 때', async () => { + // Given + const email = 'honggildong@gmail.com'; + + // When + const result = service.sendCode(email); + + // Then + await expect(result).resolves.toBeUndefined(); + expect(memoryRedisRepository[email]).toBeDefined(); + }); + }); + + describe('인증 토큰 발급', () => { + it('인증코드가 일치하지 않을 때', async () => { + // Given + const email = 'honggildong@gmail.com'; + const code = '123456'; + + memoryRedisRepository[email] = '654321'; + + // When + const result = service.verifyCode(email, code); + + // Then + await expect(result).rejects.toThrow(InvalidVerifyCodeException); + }); + + it('올바른 정보가 주어졌을 때', async () => { + // Given + const email = 'honggildong@gmail.com'; + const code = '123456'; + + memoryRedisRepository[email] = code; + + // When + const result = service.verifyCode(email, code); + + // Then + await expect(result).resolves.toBeDefined(); + }); + }); +}); diff --git a/src/domain/member/constant/Role.ts b/src/domain/member/constant/Role.ts new file mode 100644 index 0000000..f9ea30c --- /dev/null +++ b/src/domain/member/constant/Role.ts @@ -0,0 +1,7 @@ +export enum Role { + PRESIDENT = 'PRESIDENT', + VICE_PRESIDENT = 'VICE_PRESIDENT', + EXECUTIVE = 'EXECUTIVE', + MEMBER = 'MEMBER', + WAITING = 'WAITING', +} diff --git a/src/domain/member/member.module.ts b/src/domain/member/member.module.ts index 5ef274d..6b07d47 100644 --- a/src/domain/member/member.module.ts +++ b/src/domain/member/member.module.ts @@ -1,9 +1,15 @@ import { Module } from '@nestjs/common'; import { MemberController } from './member.controller'; import { MemberService } from './member.service'; +import { MongoModel } from '../../utils/mongo/MongoModel'; +import { Member, MemberSchema } from './member.schema'; +import { MongooseModule } from '@nestjs/mongoose'; +import { MemberRepository } from './member.repository'; @Module({ + imports: [MongooseModule.forFeatureAsync([MongoModel.generate(Member.name, MemberSchema)])], controllers: [MemberController], - providers: [MemberService], + providers: [MemberService, MemberRepository], + exports: [MemberRepository], }) export class MemberModule {} diff --git a/src/domain/member/member.repository.ts b/src/domain/member/member.repository.ts new file mode 100644 index 0000000..37dfc08 --- /dev/null +++ b/src/domain/member/member.repository.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; + +import { Member } from './member.schema'; + +import { Model, ObjectId } from 'mongoose'; + +@Injectable() +export class MemberRepository { + constructor(@InjectModel(Member.name) private memberModel: Model) {} + + async save(Member: Partial): Promise { + return await this.memberModel.create(Member); + } + + async findById(id: string | ObjectId): Promise { + return await this.memberModel.findById(id).exec(); + } + + async findByEmail(email: string): Promise { + return await this.memberModel.findOne({ email }).exec(); + } + + async existsById(id: string | ObjectId): Promise { + return !!(await this.memberModel.exists({ _id: id }).exec()); + } + + async existsByEmail(email: string): Promise { + return !!(await this.memberModel.exists({ email }).exec()); + } + + async existsByStudentId(studentId: number): Promise { + return !!(await this.memberModel.exists({ studentId }).exec()); + } + + raw() { + return this.memberModel; + } +} diff --git a/src/domain/member/member.schema.ts b/src/domain/member/member.schema.ts new file mode 100644 index 0000000..7018935 --- /dev/null +++ b/src/domain/member/member.schema.ts @@ -0,0 +1,46 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; + +import { HydratedDocument } from 'mongoose'; +import { Role } from './constant/Role'; + +export type MemberDocument = HydratedDocument; + +export type MyInfoLinks = Record<'github' | 'instagram' | 'blog', string>; + +const DEFAULT_LINKS: MyInfoLinks = { + github: null, + instagram: null, + blog: null, +}; + +@Schema() +export class Member { + @Prop({ required: true }) + name: string; + + @Prop({ required: true, unique: true, index: true }) + studentId: number; + + @Prop({ required: true, unique: true, index: true }) + email: string; + + @Prop({ default: null }) + avatar?: string; + + @Prop({ default: null }) + description?: string; + + @Prop({ required: true, select: false }) + password: string; + + @Prop({ type: Object, default: DEFAULT_LINKS }) + link: MyInfoLinks; + + @Prop({ default: Role.WAITING }) + role: Role; + + @Prop({ default: false }) + fee: boolean; +} + +export const MemberSchema = SchemaFactory.createForClass(Member); diff --git a/src/main.ts b/src/main.ts index 5153878..8db2a9d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,9 +1,9 @@ import { NestFactory } from '@nestjs/core'; -import { ValidationPipe } from '@nestjs/common'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { AppModule } from './app/app.module'; +import { Validator } from './utils/validator/Validator'; import { WinstonLogger } from './utils/logger/WinstonLogger'; import { ResponseInterceptor } from './utils/interceptor/ResponseInterceptor'; import { @@ -16,7 +16,7 @@ async function bootstrap() { logger: WinstonLogger, }); - app.useGlobalPipes(new ValidationPipe()); + app.useGlobalPipes(Validator.getValidationPipe()); app.useGlobalInterceptors(new ResponseInterceptor()); app.useGlobalFilters(new HttpExceptionResponseFilter()); app.useGlobalFilters(new NotFoundExceptionResponseFilter()); @@ -30,6 +30,7 @@ async function bootstrap() { .setTitle('Wink Official') .setDescription('윙크 공식 홈페이지 API 명세서') .setVersion('1.0.0') + .addSecurity('bearer', { type: 'http', scheme: 'bearer' }) .build(), ), { diff --git a/src/utils/mail/NodeMail.ts b/src/utils/mail/NodeMail.ts new file mode 100644 index 0000000..9928258 --- /dev/null +++ b/src/utils/mail/NodeMail.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import * as nodemailer from 'nodemailer'; +import { Transporter } from 'nodemailer'; + +@Injectable() +export class NodeMail { + private readonly transporter: Transporter; + + constructor(private readonly configService: ConfigService) { + this.transporter = nodemailer.createTransport({ + host: configService.getOrThrow('smtp.host'), + port: configService.getOrThrow('smtp.port'), + secure: configService.getOrThrow('smtp.secure'), + auth: { + user: configService.getOrThrow('smtp.username'), + pass: configService.getOrThrow('smtp.password'), + }, + }); + } + + async sendMail(to: string, subject: string, html: string): Promise { + await this.transporter.sendMail({ + from: this.configService.getOrThrow('smtp.username'), + to, + subject, + html, + }); + } +} diff --git a/src/utils/mail/mail.module.ts b/src/utils/mail/mail.module.ts new file mode 100644 index 0000000..3c0ee60 --- /dev/null +++ b/src/utils/mail/mail.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { NodeMail } from './NodeMail'; + +@Module({ + providers: [NodeMail], + exports: [NodeMail], +}) +export class MailModule {} diff --git a/src/utils/redis/RedisRepository.ts b/src/utils/redis/RedisRepository.ts index 702330b..037285f 100644 --- a/src/utils/redis/RedisRepository.ts +++ b/src/utils/redis/RedisRepository.ts @@ -20,4 +20,16 @@ export class RedisRepository { async set(key: string, value: string) { return this.redisClient.set(key, value); } + + async setex(key: string, value: string, seconds: number) { + return this.redisClient.setex(key, seconds, value); + } + + async delete(key: string) { + return this.redisClient.del(key); + } + + async exists(key: string) { + return this.redisClient.exists(key); + } } diff --git a/src/utils/swagger/ApiCustomErrorResponse.decorator.ts b/src/utils/swagger/ApiCustomErrorResponse.decorator.ts new file mode 100644 index 0000000..1061178 --- /dev/null +++ b/src/utils/swagger/ApiCustomErrorResponse.decorator.ts @@ -0,0 +1,51 @@ +import { applyDecorators, HttpException, Type } from '@nestjs/common'; +import { ApiResponse, getSchemaPath } from '@nestjs/swagger'; + +import { ApiCustomResponseDto } from './ApiCustomResponse.decorator'; + +interface ApiCustomErrorResponseOptions { + description: string; + error: Type; +} + +export const ApiCustomErrorResponseDecorator = (options: ApiCustomErrorResponseOptions[]) => { + const errors = new Map(); + + options.forEach((option) => { + const error = new option.error(); + errors.has(error.getStatus()); + + if (!errors.has(error.getStatus())) { + errors.set(error.getStatus(), []); + } + + errors.get(error.getStatus()).push([option.description, error]); + }); + + return applyDecorators( + ...Array.from(errors.entries()).map(([status, errors]) => + ApiResponse({ + status, + content: { + 'application/json': { + schema: { + $ref: getSchemaPath(ApiCustomResponseDto), + }, + examples: Object.fromEntries( + errors.map(([description, error], index) => [ + `error${index}`, + { + value: { + error: true, + data: error.getResponse(), + }, + summary: description, + }, + ]), + ), + }, + }, + }), + ), + ); +}; diff --git a/src/utils/swagger/ApiCustomResponse.decorator.ts b/src/utils/swagger/ApiCustomResponse.decorator.ts new file mode 100644 index 0000000..99cadb7 --- /dev/null +++ b/src/utils/swagger/ApiCustomResponse.decorator.ts @@ -0,0 +1,47 @@ +import { applyDecorators, HttpStatus, Type } from '@nestjs/common'; +import { ApiExtraModels, ApiProperty, ApiResponse, getSchemaPath } from '@nestjs/swagger'; + +export class ApiCustomResponseDto { + @ApiProperty({ type: Boolean, description: '오류 여부', default: false }) + error: boolean; + + @ApiProperty({ + type: 'object', + description: '응답 데이터', + }) + data: any; +} + +class EmptyResponse {} + +interface ApiCustomResponseOptions { + type?: Type; + status: HttpStatus; +} +export const ApiCustomResponse = (options: ApiCustomResponseOptions) => { + options.type ??= EmptyResponse; + + return applyDecorators( + ApiExtraModels(options.type, ApiCustomResponseDto), + ApiResponse({ + status: options.status, + description: '성공', + content: { + 'application/json': { + schema: { + allOf: [ + { + $ref: getSchemaPath(ApiCustomResponseDto), + }, + { + properties: { + data: { $ref: getSchemaPath(options.type) }, + }, + }, + ], + }, + }, + }, + }), + ); +}; diff --git a/src/utils/validator/Validator.ts b/src/utils/validator/Validator.ts new file mode 100644 index 0000000..305f047 --- /dev/null +++ b/src/utils/validator/Validator.ts @@ -0,0 +1,28 @@ +import { ArgumentMetadata, HttpException, ValidationPipe } from '@nestjs/common'; + +export class Validator { + private readonly validator: ValidationPipe; + + constructor() { + this.validator = Validator.getValidationPipe(); + } + + static getValidationPipe(): ValidationPipe { + return new ValidationPipe({ + whitelist: true, + exceptionFactory: (errors) => { + return new HttpException(Object.entries(errors[0].constraints)[0][1], 400); + }, + }); + } + + async validateBody(body: T, metatype: new () => T): Promise { + const metadata: ArgumentMetadata = { + type: 'body', + metatype, + data: '', + }; + + return await this.validator.transform(body, metadata); + } +} diff --git a/yarn.lock b/yarn.lock index 0c21fd0..bc18945 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,50 +53,50 @@ "@babel/highlight" "^7.24.7" picocolors "^1.0.0" -"@babel/compat-data@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.7.tgz#d23bbea508c3883ba8251fb4164982c36ea577ed" - integrity sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw== +"@babel/compat-data@^7.24.8": + version "7.24.9" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.9.tgz#53eee4e68f1c1d0282aa0eb05ddb02d033fc43a0" + integrity sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng== "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.7.tgz#b676450141e0b52a3d43bc91da86aa608f950ac4" - integrity sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g== + version "7.24.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.9.tgz#dc07c9d307162c97fa9484ea997ade65841c7c82" + integrity sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.24.7" - "@babel/helper-compilation-targets" "^7.24.7" - "@babel/helper-module-transforms" "^7.24.7" - "@babel/helpers" "^7.24.7" - "@babel/parser" "^7.24.7" + "@babel/generator" "^7.24.9" + "@babel/helper-compilation-targets" "^7.24.8" + "@babel/helper-module-transforms" "^7.24.9" + "@babel/helpers" "^7.24.8" + "@babel/parser" "^7.24.8" "@babel/template" "^7.24.7" - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" + "@babel/traverse" "^7.24.8" + "@babel/types" "^7.24.9" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.24.7", "@babel/generator@^7.7.2": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.7.tgz#1654d01de20ad66b4b4d99c135471bc654c55e6d" - integrity sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA== +"@babel/generator@^7.24.8", "@babel/generator@^7.24.9", "@babel/generator@^7.7.2": + version "7.24.10" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.10.tgz#a4ab681ec2a78bbb9ba22a3941195e28a81d8e76" + integrity sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg== dependencies: - "@babel/types" "^7.24.7" + "@babel/types" "^7.24.9" "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" -"@babel/helper-compilation-targets@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz#4eb6c4a80d6ffeac25ab8cd9a21b5dfa48d503a9" - integrity sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg== +"@babel/helper-compilation-targets@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz#b607c3161cd9d1744977d4f97139572fe778c271" + integrity sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw== dependencies: - "@babel/compat-data" "^7.24.7" - "@babel/helper-validator-option" "^7.24.7" - browserslist "^4.22.2" + "@babel/compat-data" "^7.24.8" + "@babel/helper-validator-option" "^7.24.8" + browserslist "^4.23.1" lru-cache "^5.1.1" semver "^6.3.1" @@ -130,10 +130,10 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" -"@babel/helper-module-transforms@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz#31b6c9a2930679498db65b685b1698bfd6c7daf8" - integrity sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ== +"@babel/helper-module-transforms@^7.24.9": + version "7.24.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.9.tgz#e13d26306b89eea569180868e652e7f514de9d29" + integrity sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw== dependencies: "@babel/helper-environment-visitor" "^7.24.7" "@babel/helper-module-imports" "^7.24.7" @@ -142,9 +142,9 @@ "@babel/helper-validator-identifier" "^7.24.7" "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.8.0": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz#98c84fe6fe3d0d3ae7bfc3a5e166a46844feb2a0" - integrity sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg== + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" + integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== "@babel/helper-simple-access@^7.24.7": version "7.24.7" @@ -161,28 +161,28 @@ dependencies: "@babel/types" "^7.24.7" -"@babel/helper-string-parser@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz#4d2d0f14820ede3b9807ea5fc36dfc8cd7da07f2" - integrity sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg== +"@babel/helper-string-parser@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" + integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== "@babel/helper-validator-identifier@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== -"@babel/helper-validator-option@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz#24c3bb77c7a425d1742eec8fb433b5a1b38e62f6" - integrity sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw== +"@babel/helper-validator-option@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" + integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== -"@babel/helpers@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.7.tgz#aa2ccda29f62185acb5d42fb4a3a1b1082107416" - integrity sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg== +"@babel/helpers@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.8.tgz#2820d64d5d6686cca8789dd15b074cd862795873" + integrity sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ== dependencies: "@babel/template" "^7.24.7" - "@babel/types" "^7.24.7" + "@babel/types" "^7.24.8" "@babel/highlight@^7.24.7": version "7.24.7" @@ -194,10 +194,10 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.7.tgz#9a5226f92f0c5c8ead550b750f5608e766c8ce85" - integrity sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.7", "@babel/parser@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.8.tgz#58a4dbbcad7eb1d48930524a3fd93d93e9084c6f" + integrity sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -306,28 +306,28 @@ "@babel/parser" "^7.24.7" "@babel/types" "^7.24.7" -"@babel/traverse@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.7.tgz#de2b900163fa741721ba382163fe46a936c40cf5" - integrity sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA== +"@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.8.tgz#6c14ed5232b7549df3371d820fbd9abfcd7dfab7" + integrity sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ== dependencies: "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.24.7" + "@babel/generator" "^7.24.8" "@babel/helper-environment-visitor" "^7.24.7" "@babel/helper-function-name" "^7.24.7" "@babel/helper-hoist-variables" "^7.24.7" "@babel/helper-split-export-declaration" "^7.24.7" - "@babel/parser" "^7.24.7" - "@babel/types" "^7.24.7" + "@babel/parser" "^7.24.8" + "@babel/types" "^7.24.8" debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.7", "@babel/types@^7.3.3": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.7.tgz#6027fe12bc1aa724cd32ab113fb7f1988f1f66f2" - integrity sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.24.9", "@babel/types@^7.3.3": + version "7.24.9" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.9.tgz#228ce953d7b0d16646e755acf204f4cf3d08cc73" + integrity sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ== dependencies: - "@babel/helper-string-parser" "^7.24.7" + "@babel/helper-string-parser" "^7.24.8" "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" @@ -854,6 +854,21 @@ resolved "https://registry.yarnpkg.com/@lukeed/csprng/-/csprng-1.1.0.tgz#1e3e4bd05c1cc7a0b2ddbd8a03f39f6e4b5e6cfe" integrity sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA== +"@mapbox/node-pre-gyp@^1.0.11": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa" + integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + "@microsoft/tsdoc@^0.15.0": version "0.15.0" resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.15.0.tgz#f29a55df17cb6e87cfbabce33ff6a14a9f85076d" @@ -1084,6 +1099,13 @@ dependencies: "@babel/types" "^7.20.7" +"@types/bcrypt@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@types/bcrypt/-/bcrypt-5.0.2.tgz#22fddc11945ea4fbc3655b3e8b8847cc9f811477" + integrity sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ== + dependencies: + "@types/node" "*" + "@types/body-parser@*": version "1.19.5" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" @@ -1201,6 +1223,13 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/jsonwebtoken@^9.0.6": + version "9.0.6" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz#d1af3544d99ad992fb6681bbe60676e06b032bd3" + integrity sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw== + dependencies: + "@types/node" "*" + "@types/methods@^1.1.4": version "1.1.4" resolved "https://registry.yarnpkg.com/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547" @@ -1212,12 +1241,19 @@ integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== "@types/node@*", "@types/node@^20.14.10": - version "20.14.10" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.10.tgz#a1a218290f1b6428682e3af044785e5874db469a" - integrity sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ== + version "20.14.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.11.tgz#09b300423343460455043ddd4d0ded6ac579b74b" + integrity sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA== dependencies: undici-types "~5.26.4" +"@types/nodemailer@^6.4.15": + version "6.4.15" + resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.15.tgz#494be695e11c438f7f5df738fb4ab740312a6ed2" + integrity sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ== + dependencies: + "@types/node" "*" + "@types/qs@*": version "6.9.15" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce" @@ -1272,6 +1308,11 @@ resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.5.tgz#74fef9ffbaa198eb8b588be029f38b00299caa2c" integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw== +"@types/uuid@^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-10.0.0.tgz#e9c07fe50da0f53dc24970cca94d619ff03f6f6d" + integrity sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ== + "@types/validator@^13.11.8": version "13.12.0" resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.12.0.tgz#1fe4c3ae9de5cf5193ce64717c99ef2fa7d8756f" @@ -1302,61 +1343,61 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.0.tgz#b3563927341eca15124a18c6f94215f779f5c02a" - integrity sha512-py1miT6iQpJcs1BiJjm54AMzeuMPBSPuKPlnT8HlfudbcS5rYeX5jajpLf3mrdRh9dA/Ec2FVUY0ifeVNDIhZw== + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.1.tgz#f5f5da52db674b1f2cdb9d5f3644e5b2ec750465" + integrity sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A== dependencies: "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "7.16.0" - "@typescript-eslint/type-utils" "7.16.0" - "@typescript-eslint/utils" "7.16.0" - "@typescript-eslint/visitor-keys" "7.16.0" + "@typescript-eslint/scope-manager" "7.16.1" + "@typescript-eslint/type-utils" "7.16.1" + "@typescript-eslint/utils" "7.16.1" + "@typescript-eslint/visitor-keys" "7.16.1" graphemer "^1.4.0" ignore "^5.3.1" natural-compare "^1.4.0" ts-api-utils "^1.3.0" "@typescript-eslint/parser@^7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.16.0.tgz#53fae8112f8c912024aea7b499cf7374487af6d8" - integrity sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw== - dependencies: - "@typescript-eslint/scope-manager" "7.16.0" - "@typescript-eslint/types" "7.16.0" - "@typescript-eslint/typescript-estree" "7.16.0" - "@typescript-eslint/visitor-keys" "7.16.0" + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.16.1.tgz#84c581cf86c8b2becd48d33ddc41a6303d57b274" + integrity sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA== + dependencies: + "@typescript-eslint/scope-manager" "7.16.1" + "@typescript-eslint/types" "7.16.1" + "@typescript-eslint/typescript-estree" "7.16.1" + "@typescript-eslint/visitor-keys" "7.16.1" debug "^4.3.4" -"@typescript-eslint/scope-manager@7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.16.0.tgz#eb0757af5720c9c53c8010d7a0355ae27e17b7e5" - integrity sha512-8gVv3kW6n01Q6TrI1cmTZ9YMFi3ucDT7i7aI5lEikk2ebk1AEjrwX8MDTdaX5D7fPXMBLvnsaa0IFTAu+jcfOw== +"@typescript-eslint/scope-manager@7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.16.1.tgz#2b43041caabf8ddd74512b8b550b9fc53ca3afa1" + integrity sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw== dependencies: - "@typescript-eslint/types" "7.16.0" - "@typescript-eslint/visitor-keys" "7.16.0" + "@typescript-eslint/types" "7.16.1" + "@typescript-eslint/visitor-keys" "7.16.1" -"@typescript-eslint/type-utils@7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.16.0.tgz#ec52b1932b8fb44a15a3e20208e0bd49d0b6bd00" - integrity sha512-j0fuUswUjDHfqV/UdW6mLtOQQseORqfdmoBNDFOqs9rvNVR2e+cmu6zJu/Ku4SDuqiJko6YnhwcL8x45r8Oqxg== +"@typescript-eslint/type-utils@7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.16.1.tgz#4d7ae4f3d9e3c8cbdabae91609b1a431de6aa6ca" + integrity sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA== dependencies: - "@typescript-eslint/typescript-estree" "7.16.0" - "@typescript-eslint/utils" "7.16.0" + "@typescript-eslint/typescript-estree" "7.16.1" + "@typescript-eslint/utils" "7.16.1" debug "^4.3.4" ts-api-utils "^1.3.0" -"@typescript-eslint/types@7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.16.0.tgz#60a19d7e7a6b1caa2c06fac860829d162a036ed2" - integrity sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw== +"@typescript-eslint/types@7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.16.1.tgz#bbab066276d18e398bc64067b23f1ce84dfc6d8c" + integrity sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ== -"@typescript-eslint/typescript-estree@7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.0.tgz#98ac779d526fab2a781e5619c9250f3e33867c09" - integrity sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw== +"@typescript-eslint/typescript-estree@7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.1.tgz#9b145ba4fd1dde1986697e1ce57dc501a1736dd3" + integrity sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ== dependencies: - "@typescript-eslint/types" "7.16.0" - "@typescript-eslint/visitor-keys" "7.16.0" + "@typescript-eslint/types" "7.16.1" + "@typescript-eslint/visitor-keys" "7.16.1" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" @@ -1364,22 +1405,22 @@ semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/utils@7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.16.0.tgz#b38dc0ce1778e8182e227c98d91d3418449aa17f" - integrity sha512-PqP4kP3hb4r7Jav+NiRCntlVzhxBNWq6ZQ+zQwII1y/G/1gdIPeYDCKr2+dH6049yJQsWZiHU6RlwvIFBXXGNA== +"@typescript-eslint/utils@7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.16.1.tgz#df42dc8ca5a4603016fd102db0346cdab415cdb7" + integrity sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "7.16.0" - "@typescript-eslint/types" "7.16.0" - "@typescript-eslint/typescript-estree" "7.16.0" + "@typescript-eslint/scope-manager" "7.16.1" + "@typescript-eslint/types" "7.16.1" + "@typescript-eslint/typescript-estree" "7.16.1" -"@typescript-eslint/visitor-keys@7.16.0": - version "7.16.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.0.tgz#a1d99fa7a3787962d6e0efd436575ef840e23b06" - integrity sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg== +"@typescript-eslint/visitor-keys@7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.1.tgz#4287bcf44c34df811ff3bb4d269be6cfc7d8c74b" + integrity sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg== dependencies: - "@typescript-eslint/types" "7.16.0" + "@typescript-eslint/types" "7.16.1" eslint-visitor-keys "^3.4.3" "@ungap/structured-clone@^1.2.0": @@ -1526,6 +1567,11 @@ JSONStream@^1.3.5: jsonparse "^1.2.0" through ">=2.2.7 <3" +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -1556,6 +1602,13 @@ acorn@^8.11.0, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + ajv-formats@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" @@ -1589,14 +1642,14 @@ ajv@^6.12.4, ajv@^6.12.5: uri-js "^4.2.2" ajv@^8.0.0, ajv@^8.11.0: - version "8.16.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.16.0.tgz#22e2a92b94f005f7e0f9c9d39652ef0b8f6f0cb4" - integrity sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw== + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== dependencies: fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" - uri-js "^4.4.1" ansi-colors@4.1.3: version "4.1.3" @@ -1662,6 +1715,19 @@ append-field@^1.0.0: resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" integrity sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw== +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -1784,6 +1850,14 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +bcrypt@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.1.1.tgz#0f732c6dcb4e12e5b70a25e326a72965879ba6e2" + integrity sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.11" + node-addon-api "^5.0.0" + binary-extensions@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" @@ -1838,7 +1912,7 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -browserslist@^4.21.10, browserslist@^4.22.2: +browserslist@^4.21.10, browserslist@^4.23.1: version "4.23.2" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.2.tgz#244fe803641f1c19c28c48c4b6ec9736eb3d32ed" integrity sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA== @@ -1867,6 +1941,11 @@ bson@^6.7.0: resolved "https://registry.yarnpkg.com/bson/-/bson-6.8.0.tgz#5063c41ba2437c2b8ff851b50d9e36cb7aaa7525" integrity sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ== +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -1919,9 +1998,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001640: - version "1.0.30001641" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001641.tgz#3572862cd18befae3f637f2a1101cc033c6782ac" - integrity sha512-Phv5thgl67bHYo1TtMY/MurjkHhV4EDaCosezRXgZ8jzA/Ub+wjxAvbGvjoFENStinwi5kCyOYV3mi5tOGykwA== + version "1.0.30001642" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz#6aa6610eb24067c246d30c57f055a9d0a7f8d05f" + integrity sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA== chalk@4.1.2, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" @@ -1970,6 +2049,11 @@ chokidar@3.6.0, chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + chrome-trace-event@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" @@ -2106,6 +2190,11 @@ color-string@^1.6.0: color-name "^1.0.0" simple-swizzle "^0.2.2" +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + color@^3.1.3: version "3.2.1" resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" @@ -2193,6 +2282,11 @@ consola@^2.15.0: resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550" integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw== +console-control-strings@^1.0.0, console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + content-disposition@0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" @@ -2328,7 +2422,7 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4.x, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.4: +debug@4, debug@4.x, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.4: version "4.3.5" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== @@ -2371,6 +2465,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + denque@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" @@ -2386,6 +2485,11 @@ destroy@1.2.0: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== +detect-libc@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -2445,6 +2549,13 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -2458,9 +2569,9 @@ ejs@^3.0.0: jake "^10.8.5" electron-to-chromium@^1.4.820: - version "1.4.825" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.825.tgz#2d9c3d1eb81a67ecea0c06bcf70740b00ba52718" - integrity sha512-OCcF+LwdgFGcsYPYC5keEEFC2XT0gBhrYbeGzHCx7i9qRFbzO/AqTmc/C/1xNhJj+JA7rzlN7mpBuStshh96Cg== + version "1.4.829" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.829.tgz#3034a865b5eac9064c9db8b38ba99b60a446bb73" + integrity sha512-5qp1N2POAfW0u1qGAxXEtz6P7bO1m6gpZr5hdf5ve6lxpLM7MpiM4jIPz7xcrNlClQMafbyUDDWjlIQZ1Mw0Rw== emittery@^0.13.1: version "0.13.1" @@ -2560,12 +2671,12 @@ eslint-config-prettier@^9.1.0: integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw== eslint-plugin-prettier@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz#17cfade9e732cef32b5f5be53bd4e07afd8e67e1" - integrity sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw== + version "5.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz#d1c8f972d8f60e414c25465c163d16f209411f95" + integrity sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw== dependencies: prettier-linter-helpers "^1.0.0" - synckit "^0.8.6" + synckit "^0.9.1" eslint-scope@5.1.1: version "5.1.1" @@ -2818,6 +2929,11 @@ fast-safe-stringify@2.1.1, fast-safe-stringify@^2.1.1: resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== +fast-uri@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.1.tgz#cddd2eecfc83a71c1be2cc2ef2061331be8a7134" + integrity sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw== + fastq@^1.6.0: version "1.17.1" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" @@ -2992,6 +3108,13 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs-monkey@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.6.tgz#8ead082953e88d992cf3ff844faa907b26756da2" @@ -3012,6 +3135,21 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -3185,6 +3323,11 @@ has-symbols@^1.0.3: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + hasown@^2.0.0, hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" @@ -3213,6 +3356,14 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" @@ -3989,6 +4140,39 @@ jsonparse@^1.2.0: resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== +jsonwebtoken@^9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + kareem@2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.6.3.tgz#23168ec8ffb6c1abfd31b7169a6fb1dd285992ac" @@ -4103,16 +4287,41 @@ lodash.defaults@^4.2.0: resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + lodash.isarguments@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + lodash.kebabcase@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" @@ -4133,6 +4342,11 @@ lodash.mergewith@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55" integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ== +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + lodash.snakecase@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" @@ -4208,6 +4422,13 @@ magic-string@0.30.8: dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" +make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + make-dir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" @@ -4335,11 +4556,31 @@ minimist@^1.2.6, minimist@^1.2.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +minipass@^3.0.0: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + "minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + mkdirp@^0.5.4: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" @@ -4347,6 +4588,11 @@ mkdirp@^0.5.4: dependencies: minimist "^1.2.6" +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + moment@^2.29.1: version "2.30.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" @@ -4464,6 +4710,11 @@ node-abort-controller@^3.0.1: resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== +node-addon-api@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" + integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== + node-emoji@1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" @@ -4471,7 +4722,7 @@ node-emoji@1.11.0: dependencies: lodash "^4.17.21" -node-fetch@^2.6.1: +node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -4484,9 +4735,21 @@ node-int64@^0.4.0: integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== node-releases@^2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" - integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + version "2.0.17" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.17.tgz#d74bc4fec38d839eec5db2a3c9c963d4f33cb366" + integrity sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA== + +nodemailer@^6.9.14: + version "6.9.14" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.14.tgz#845fda981f9fd5ac264f4446af908a7c78027f75" + integrity sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA== + +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" @@ -4507,6 +4770,16 @@ npm-run-path@^5.1.0: dependencies: path-key "^4.0.0" +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + object-assign@^4, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -4766,9 +5039,9 @@ prettier-linter-helpers@^1.0.0: fast-diff "^1.1.2" prettier@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.2.tgz#03ff86dc7c835f2d2559ee76876a3914cec4a90a" - integrity sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA== + version "3.3.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== pretty-format@^29.0.0, pretty-format@^29.7.0: version "29.7.0" @@ -4869,7 +5142,7 @@ readable-stream@^2.2.2: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.4.0, readable-stream@^3.6.2: +readable-stream@^3.4.0, readable-stream@^3.6.0, readable-stream@^3.6.2: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -5005,7 +5278,7 @@ rxjs@7.8.1, rxjs@^7.5.5, rxjs@^7.8.1: dependencies: tslib "^2.1.0" -safe-buffer@5.2.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -5034,15 +5307,15 @@ schema-utils@^3.1.1, schema-utils@^3.2.0: ajv "^6.12.5" ajv-keywords "^3.5.2" -semver@^6.3.0, semver@^6.3.1: +semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== semver@^7.3.4, semver@^7.3.5, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0: - version "7.6.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" - integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== send@0.18.0: version "0.18.0" @@ -5080,6 +5353,11 @@ serve-static@1.15.0: parseurl "~1.3.3" send "0.18.0" +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + set-function-length@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" @@ -5124,7 +5402,7 @@ sift@17.1.3: resolved "https://registry.yarnpkg.com/sift/-/sift-17.1.3.tgz#9d2000d4d41586880b0079b5183d839c7a142bf7" integrity sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ== -signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -5259,7 +5537,7 @@ string-length@^4.0.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -5405,10 +5683,10 @@ symbol-observable@4.0.0: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205" integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ== -synckit@^0.8.6: - version "0.8.8" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.8.tgz#fe7fe446518e3d3d49f5e429f443cf08b6edfcd7" - integrity sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ== +synckit@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.9.1.tgz#febbfbb6649979450131f64735aa3f6c14575c88" + integrity sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A== dependencies: "@pkgr/core" "^0.1.0" tslib "^2.6.2" @@ -5418,6 +5696,18 @@ tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== +tar@^6.1.11: + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + terser-webpack-plugin@^5.3.10: version "5.3.10" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" @@ -5430,9 +5720,9 @@ terser-webpack-plugin@^5.3.10: terser "^5.26.0" terser@^5.26.0: - version "5.31.2" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.2.tgz#b5ca188107b706084dca82f988089fa6102eba11" - integrity sha512-LGyRZVFm/QElZHy/CPr/O4eNZOZIzsrQ92y4v9UJe/pFJjypje2yI3C2FmPtvUEnhadlSbmG2nXtdcjHOjCfxw== + version "5.31.3" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.3.tgz#b24b7beb46062f4653f049eea4f0cd165d0f0c38" + integrity sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -5672,7 +5962,7 @@ update-browserslist-db@^1.1.0: escalade "^3.1.2" picocolors "^1.0.1" -uri-js@^4.2.2, uri-js@^4.4.1: +uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== @@ -5689,6 +5979,11 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== +uuid@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294" + integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -5808,6 +6103,13 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + winston-daily-rotate-file@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz#8cd94800025490e47c00ec892b655a5821f4266d" @@ -5922,6 +6224,11 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yaml@~2.4.2: version "2.4.5" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.5.tgz#60630b206dd6d84df97003d33fc1ddf6296cca5e"