From 60b1701d01cdb1a69519eec58a8348d089e933a4 Mon Sep 17 00:00:00 2001 From: Francesco Spilla Date: Wed, 12 Feb 2025 16:26:04 +0100 Subject: [PATCH] chore(repo) init AppAuth module --- apps/ebitemp-api/.tomg-config | 22 ++ apps/ebitemp-api/.tomg-naming-strategy.js | 39 ++ apps/ebitemp-api/src/app/app.controller.ts | 2 + apps/ebitemp-api/src/app/app.module.ts | 2 + .../src/app/modules/auth/auth.config.ts | 31 ++ .../src/app/modules/auth/auth.controller.ts | 36 ++ .../src/app/modules/auth/auth.dto.ts | 16 + .../src/app/modules/auth/auth.module.ts | 37 ++ .../src/app/modules/auth/auth.service.ts | 50 +++ .../auth/authenticated-user.decorator.ts | 8 + .../auth/constants/request-with-user.ts | 6 + .../auth/constants/token-payload.interface.ts | 5 + .../jwt/jwt-access-token-auth.strategy.ts | 24 ++ .../strategies/jwt/jwt-access-token.module.ts | 30 ++ .../auth/strategies/jwt/jwt-auth.guard.ts | 36 ++ .../jwt/jwt-refresh-token-auth.strategy.ts | 30 ++ .../jwt/jwt-refresh-token.module.ts | 30 ++ .../auth/strategies/local/local-auth.guard.ts | 5 + .../strategies/local/local-auth.strategy.ts | 22 ++ .../modules/auth/users/users-auth.service.ts | 42 +++ .../app/modules/auth/users/users.module.ts | 13 + .../app/modules/database/database.module.ts | 3 +- .../database/entities/accounts.entity.ts | 41 +++ .../app/modules/database/entities/index.ts | 5 +- .../database/entities/profili.entity.ts | 28 ++ .../modules/database/entities/ruoli.entity.ts | 22 ++ .../database/entities/tipi_jobs.entity.ts | 1 + .../utils/typeorm-module-options-factory.ts | 3 +- .../app/modules/health/health.controller.ts | 2 + apps/ebitemp-api/src/main.ts | 18 +- apps/ebitemp-api/webpack.config.js | 9 +- package.json | 8 + pnpm-lock.yaml | 343 +++++++++++++++++- 33 files changed, 955 insertions(+), 14 deletions(-) create mode 100644 apps/ebitemp-api/.tomg-config create mode 100644 apps/ebitemp-api/.tomg-naming-strategy.js create mode 100644 apps/ebitemp-api/src/app/modules/auth/auth.config.ts create mode 100644 apps/ebitemp-api/src/app/modules/auth/auth.controller.ts create mode 100644 apps/ebitemp-api/src/app/modules/auth/auth.dto.ts create mode 100644 apps/ebitemp-api/src/app/modules/auth/auth.module.ts create mode 100644 apps/ebitemp-api/src/app/modules/auth/auth.service.ts create mode 100644 apps/ebitemp-api/src/app/modules/auth/authenticated-user.decorator.ts create mode 100644 apps/ebitemp-api/src/app/modules/auth/constants/request-with-user.ts create mode 100644 apps/ebitemp-api/src/app/modules/auth/constants/token-payload.interface.ts create mode 100644 apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-access-token-auth.strategy.ts create mode 100644 apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-access-token.module.ts create mode 100644 apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-auth.guard.ts create mode 100644 apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-refresh-token-auth.strategy.ts create mode 100644 apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-refresh-token.module.ts create mode 100644 apps/ebitemp-api/src/app/modules/auth/strategies/local/local-auth.guard.ts create mode 100644 apps/ebitemp-api/src/app/modules/auth/strategies/local/local-auth.strategy.ts create mode 100644 apps/ebitemp-api/src/app/modules/auth/users/users-auth.service.ts create mode 100644 apps/ebitemp-api/src/app/modules/auth/users/users.module.ts create mode 100644 apps/ebitemp-api/src/app/modules/database/entities/accounts.entity.ts create mode 100644 apps/ebitemp-api/src/app/modules/database/entities/profili.entity.ts create mode 100644 apps/ebitemp-api/src/app/modules/database/entities/ruoli.entity.ts diff --git a/apps/ebitemp-api/.tomg-config b/apps/ebitemp-api/.tomg-config new file mode 100644 index 0000000..93dca45 --- /dev/null +++ b/apps/ebitemp-api/.tomg-config @@ -0,0 +1,22 @@ +[ + { + "pluralizeNames": true, + "noConfigs": true, + "convertCaseFile": "none", + "convertCaseEntity": "pascal", + "convertCaseProperty": "camel", + "convertEol": "LF", + "propertyVisibility": "none", + "lazy": false, + "activeRecord": false, + "generateConstructor": true, + "customNamingStrategyPath": ".tomg-naming-strategy.js", + "relationIds": false, + "strictMode": "none", + "skipSchema": true, + "indexFile": false, + "exportType": "named", + "skipNonPrimaryKeyIndexes": true, + "removeColumnsInRelation": false + } +] \ No newline at end of file diff --git a/apps/ebitemp-api/.tomg-naming-strategy.js b/apps/ebitemp-api/.tomg-naming-strategy.js new file mode 100644 index 0000000..20cb3dc --- /dev/null +++ b/apps/ebitemp-api/.tomg-naming-strategy.js @@ -0,0 +1,39 @@ +/** + * @typedef {import('typeorm-model-generator').Column} Column + * @typedef {import('typeorm-model-generator').Entity} Entity + */ + +/** + * Customizes the entity name. + * @param {string} oldEntityName - The default entity name. + * @param {Entity} entity - The entity. + * @returns {string} The new entity name. + */ +function entityName(oldEntityName, entity) { + return oldEntityName + 'Entity'; +} + +/** + * Customizes the column name. + * @param {string} oldColumnName - The default column name. + * @param {Column} column - The column. + * @returns {string} The new column name. + */ +function columnName(oldColumnName, column) { + return oldColumnName; +} + +/** + * Customizes the file name. + * @param {string} oldFileName - The default file name. + * @returns {string} The new file name. + */ +function fileName(oldFileName) { + return oldFileName.replace('Entity', '.entity'); +} + +module.exports = { + entityName, + columnName, + fileName, +}; diff --git a/apps/ebitemp-api/src/app/app.controller.ts b/apps/ebitemp-api/src/app/app.controller.ts index 8064b06..bcfa889 100644 --- a/apps/ebitemp-api/src/app/app.controller.ts +++ b/apps/ebitemp-api/src/app/app.controller.ts @@ -1,6 +1,8 @@ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; +import { Public } from './modules/auth/strategies/jwt/jwt-auth.guard'; +@Public() @Controller() export class AppController { constructor(private readonly appService: AppService) {} diff --git a/apps/ebitemp-api/src/app/app.module.ts b/apps/ebitemp-api/src/app/app.module.ts index 374fc4f..a7d324e 100644 --- a/apps/ebitemp-api/src/app/app.module.ts +++ b/apps/ebitemp-api/src/app/app.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; +import { AppAuthModule } from './modules/auth/auth.module'; import { AppConfigModule } from './modules/config/config.module'; import { AppDatabaseModule } from './modules/database/database.module'; import { EnumifyModule } from './modules/enumify/enumify.module'; @@ -20,6 +21,7 @@ import { AppValidationModule } from './modules/validation/validation.module'; AppFileTransactionsModule, AppScheduleModule, AppValidationModule, + AppAuthModule, EnumifyModule, ], controllers: [AppController], diff --git a/apps/ebitemp-api/src/app/modules/auth/auth.config.ts b/apps/ebitemp-api/src/app/modules/auth/auth.config.ts new file mode 100644 index 0000000..6160b2b --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/auth/auth.config.ts @@ -0,0 +1,31 @@ +import { registerAs } from '@nestjs/config'; +import { z } from 'zod'; +import coerceRecordTypes from '../config/utils/coerce-record-types'; + +export const authSchema = z.object({ + accessToken: z.object({ + secret: z.string(), + expTimeInSecs: z.number().finite().nonnegative(), + }), + refreshToken: z.object({ + secret: z.string(), + expTimeInSecs: z.number().finite().nonnegative(), + }), +}); +export type AuthConfig = z.infer; + +export const authConfig = registerAs('auth', () => { + const env = coerceRecordTypes(process.env); + + const config: AuthConfig = authSchema.strict().parse({ + accessToken: { + secret: env['JWT_ACCESS_TOKEN_SECRET'], + expTimeInSecs: env['JWT_ACCESS_TOKEN_EXPIRATION_TIME_IN_SECONDS'], + }, + refreshToken: { + secret: env['JWT_REFRESH_TOKEN_SECRET'], + expTimeInSecs: env['JWT_REFRESH_TOKEN_EXPIRATION_TIME_IN_SECONDS'], + } + }); + return config; +}); diff --git a/apps/ebitemp-api/src/app/modules/auth/auth.controller.ts b/apps/ebitemp-api/src/app/modules/auth/auth.controller.ts new file mode 100644 index 0000000..b40558a --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/auth/auth.controller.ts @@ -0,0 +1,36 @@ +import { Body, Controller, HttpCode, HttpStatus, Post, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth } from '@nestjs/swagger'; +import { AccountsEntity } from '../database/entities'; +import { LoginDto, LoginResDto } from './auth.dto'; +import { AuthService } from './auth.service'; +import { AuthenticatedUser } from './authenticated-user.decorator'; +import { Public } from './strategies/jwt/jwt-auth.guard'; +import { LocalAuthGuard } from './strategies/local/local-auth.guard'; + +@ApiBearerAuth() +@Controller('auth') +export class AuthController { + constructor(private readonly authService: AuthService) {} + + @HttpCode(HttpStatus.OK) + @UseGuards(LocalAuthGuard) + @Public() + @Post('login') + async logIn(@AuthenticatedUser() user: AccountsEntity, @Body() body: LoginDto): Promise { + const { accessToken, refreshToken } = await this.authService.signJwts(user); + return { + accessToken: accessToken, + refreshToken: refreshToken, + }; + } + + @HttpCode(HttpStatus.OK) + @Post('refresh') + async refresh(@AuthenticatedUser() user: AccountsEntity): Promise { + const { accessToken, refreshToken } = await this.authService.signJwts(user); + return { + accessToken: accessToken, + refreshToken: refreshToken, + }; + } +} diff --git a/apps/ebitemp-api/src/app/modules/auth/auth.dto.ts b/apps/ebitemp-api/src/app/modules/auth/auth.dto.ts new file mode 100644 index 0000000..0eb7b8b --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/auth/auth.dto.ts @@ -0,0 +1,16 @@ +import { createZodDto } from '@anatine/zod-nestjs'; +import { z } from 'zod'; + +export const loginSchema = z.object({ + username: z.string(), + password: z.string().nonempty(), +}); +export type Login = z.infer; +export class LoginDto extends createZodDto(loginSchema) {} + +export const loginResSchema = z.object({ + accessToken: z.string().jwt(), + refreshToken: z.string().jwt(), +}) +export type LoginRes = z.infer; +export class LoginResDto extends createZodDto(loginResSchema) {} diff --git a/apps/ebitemp-api/src/app/modules/auth/auth.module.ts b/apps/ebitemp-api/src/app/modules/auth/auth.module.ts new file mode 100644 index 0000000..3ae1354 --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/auth/auth.module.ts @@ -0,0 +1,37 @@ +import { Module } from '@nestjs/common'; +import { APP_GUARD, Reflector } from '@nestjs/core'; +import { PassportModule } from '@nestjs/passport'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { AccountsEntity } from '../database/entities'; +import { AuthController } from './auth.controller'; +import { AuthService } from './auth.service'; +import { JwtAccessTokenAuthStrategy } from './strategies/jwt/jwt-access-token-auth.strategy'; +import { JwtAccessTokenModule } from './strategies/jwt/jwt-access-token.module'; +import { JwtAuthGuard } from './strategies/jwt/jwt-auth.guard'; +import { JwtRefreshTokenAuthStrategy } from './strategies/jwt/jwt-refresh-token-auth.strategy'; +import { JwtRefreshTokenModule } from './strategies/jwt/jwt-refresh-token.module'; +import { LocalAuthStrategy } from './strategies/local/local-auth.strategy'; +import { UsersAuthModule } from './users/users.module'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([AccountsEntity]), + JwtAccessTokenModule, + JwtRefreshTokenModule, + PassportModule, + UsersAuthModule, + ], + controllers: [AuthController], + providers: [ + { + provide: APP_GUARD, + useFactory: (reflector) => new JwtAuthGuard(reflector), + inject: [Reflector], + }, + AuthService, + LocalAuthStrategy, + JwtAccessTokenAuthStrategy, + JwtRefreshTokenAuthStrategy, + ], +}) +export class AppAuthModule {} diff --git a/apps/ebitemp-api/src/app/modules/auth/auth.service.ts b/apps/ebitemp-api/src/app/modules/auth/auth.service.ts new file mode 100644 index 0000000..9c4bcf5 --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/auth/auth.service.ts @@ -0,0 +1,50 @@ +import { Inject, Injectable, Logger, UnauthorizedException } from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import * as bcrypt from 'bcrypt'; +import { isEmpty } from 'lodash'; +import { AccountsEntity } from '../database/entities'; +import { ACCESS_TOKEN_JWT_SERVICE } from './strategies/jwt/jwt-access-token.module'; +import { REFRESH_TOKEN_JWT_SERVICE } from './strategies/jwt/jwt-refresh-token.module'; +import { UsersAuthService } from './users/users-auth.service'; + +@Injectable() +export class AuthService { + private readonly logger = new Logger(AuthService.name); + + constructor( + private readonly usersAuthService: UsersAuthService, + @Inject(ACCESS_TOKEN_JWT_SERVICE) private readonly accessTokenJwtService: JwtService, + @Inject(REFRESH_TOKEN_JWT_SERVICE) private readonly refreshTokenJwtService: JwtService + ) {} + + public async signJwts(user: AccountsEntity) { + const payload = { username: user.username, sub: user.id }; + + const accessToken = await this.accessTokenJwtService.signAsync(payload); + + const refreshToken = await this.refreshTokenJwtService.signAsync(payload); + await this.usersAuthService.setCurrentRefreshTokenHash(user.id, refreshToken); + + return { accessToken, refreshToken }; + } + + public async getAuthenticatedUser(username: string, password: string): Promise { + try { + const account = await this.usersAuthService.getUserByUsername(username); + if (!account) throw new UnauthorizedException(`Username not found`); + await this.verifyPassword(password, account.password); + return account; + } catch (error) { + this.logger.error(error); + throw new UnauthorizedException(`Unknown error`); + } + } + + private async verifyPassword(plainTextPassword: string, hashedPassword: string | null) { + const isPasswordMatching = + hashedPassword && !isEmpty(hashedPassword) ? await bcrypt.compare(plainTextPassword, hashedPassword) : null; + if (!isPasswordMatching) { + throw new UnauthorizedException(`Wrong credentials`); + } + } +} diff --git a/apps/ebitemp-api/src/app/modules/auth/authenticated-user.decorator.ts b/apps/ebitemp-api/src/app/modules/auth/authenticated-user.decorator.ts new file mode 100644 index 0000000..89a950d --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/auth/authenticated-user.decorator.ts @@ -0,0 +1,8 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; +import { RequestWithUser } from './constants/request-with-user'; + +export const AuthenticatedUser = createParamDecorator((data: unknown, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + + return request.user; +}); diff --git a/apps/ebitemp-api/src/app/modules/auth/constants/request-with-user.ts b/apps/ebitemp-api/src/app/modules/auth/constants/request-with-user.ts new file mode 100644 index 0000000..a299ca0 --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/auth/constants/request-with-user.ts @@ -0,0 +1,6 @@ +import { FastifyRequest } from "fastify"; +import { AccountsEntity } from "../../database/entities"; + +export interface RequestWithUser extends FastifyRequest { + user: AccountsEntity; +} diff --git a/apps/ebitemp-api/src/app/modules/auth/constants/token-payload.interface.ts b/apps/ebitemp-api/src/app/modules/auth/constants/token-payload.interface.ts new file mode 100644 index 0000000..e12527d --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/auth/constants/token-payload.interface.ts @@ -0,0 +1,5 @@ +export interface TokenPayload { + userId: number; + iat?: number; + exp?: number; +} diff --git a/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-access-token-auth.strategy.ts b/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-access-token-auth.strategy.ts new file mode 100644 index 0000000..8032285 --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-access-token-auth.strategy.ts @@ -0,0 +1,24 @@ +import { Inject, Injectable, UnauthorizedException } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { AuthConfig, authConfig } from '../../auth.config'; +import { RequestWithUser } from '../../constants/request-with-user'; +import { TokenPayload } from '../../constants/token-payload.interface'; +import { UsersAuthService } from '../../users/users-auth.service'; + +@Injectable() +export class JwtAccessTokenAuthStrategy extends PassportStrategy(Strategy, 'jwt-access-token') { + constructor(@Inject(authConfig.KEY) authConfig: AuthConfig, private readonly userAuthService: UsersAuthService) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKey: authConfig.accessToken.secret, + passReqToCallback: true, + }); + } + + async validate(request: RequestWithUser, payload: TokenPayload) { + const account = await this.userAuthService.getUserById(payload.userId); + if (!account) throw new UnauthorizedException('Access Token Guard'); + return account; + } +} diff --git a/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-access-token.module.ts b/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-access-token.module.ts new file mode 100644 index 0000000..a96f1f4 --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-access-token.module.ts @@ -0,0 +1,30 @@ +import { Module } from '@nestjs/common'; +import { JwtService, JwtModule as NestJwtModule } from '@nestjs/jwt'; +import { authConfig, AuthConfig } from '../../auth.config'; +import { ConfigModule } from '@nestjs/config'; + +export const ACCESS_TOKEN_JWT_SERVICE = Symbol('ACCESS_TOKEN_JWT_SERVICE'); +const accessTokenJwtProvider = { + provide: ACCESS_TOKEN_JWT_SERVICE, + useFactory: (jwtService: JwtService) => jwtService, + inject: [JwtService], +}; + +@Module({ + imports: [ + ConfigModule.forFeature(authConfig), + NestJwtModule.registerAsync({ + imports: [...authConfig.asProvider().imports], + useFactory: async (authConfig: AuthConfig) => ({ + secret: authConfig.accessToken.secret, + signOptions: { + expiresIn: `${authConfig.accessToken.expTimeInSecs}s`, + }, + }), + inject: [authConfig.KEY], + }), + ], + providers: [accessTokenJwtProvider], + exports: [ConfigModule, accessTokenJwtProvider], +}) +export class JwtAccessTokenModule {} diff --git a/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-auth.guard.ts b/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-auth.guard.ts new file mode 100644 index 0000000..0c0484b --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-auth.guard.ts @@ -0,0 +1,36 @@ +import { applyDecorators, ExecutionContext, SetMetadata } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { AuthGuard } from '@nestjs/passport'; +import { OpenAPIObject } from '@nestjs/swagger'; + +export const IS_PUBLIC_KEY = 'isPublic'; +export const Public = () => + applyDecorators(SetMetadata(IS_PUBLIC_KEY, true), SetMetadata('swagger/apiSecurity', ['public'])); + +export const patchPublicDecoratorSupport = (document: OpenAPIObject) => { + Object.values(document.paths).forEach((path: any) => { + Object.values(path).forEach((method: any) => { + if (Array.isArray(method.security) && method.security.includes('public')) { + method.security = []; + } + }); + }); + return document; +}; + +export class JwtAuthGuard extends AuthGuard(['jwt-access-token', 'jwt-refresh-token']) { + constructor(private reflector: Reflector) { + super(); + } + + canActivate(context: ExecutionContext) { + const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]); + if (isPublic) { + return true; + } + return super.canActivate(context); + } +} diff --git a/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-refresh-token-auth.strategy.ts b/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-refresh-token-auth.strategy.ts new file mode 100644 index 0000000..7d41f63 --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-refresh-token-auth.strategy.ts @@ -0,0 +1,30 @@ +import { Inject, Injectable, UnauthorizedException } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { FastifyRequest } from 'fastify'; +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { authConfig, AuthConfig } from '../../auth.config'; +import { RequestWithUser } from '../../constants/request-with-user'; +import { TokenPayload } from '../../constants/token-payload.interface'; +import { UsersAuthService } from '../../users/users-auth.service'; + +@Injectable() +export class JwtRefreshTokenAuthStrategy extends PassportStrategy(Strategy, 'jwt-refresh-token') { + constructor(@Inject(authConfig.KEY) authConfig: AuthConfig, private readonly usersAuthService: UsersAuthService) { + super({ + jwtFromRequest: ExtractJwt.fromExtractors([ + (request: FastifyRequest) => { + return request?.headers?.Bearer as string; + }, + ]), + secretOrKey: authConfig.refreshToken.secret, + passReqToCallback: true, + }); + } + + async validate(request: RequestWithUser, payload: TokenPayload) { + const refreshToken = request.headers?.Refresh as string; + const account = this.usersAuthService.getUserByIdAndRefreshTokenPair(payload.userId, refreshToken); + if (!account) throw new UnauthorizedException('Refresh Token Guard'); + return account; + } +} diff --git a/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-refresh-token.module.ts b/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-refresh-token.module.ts new file mode 100644 index 0000000..6424afe --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-refresh-token.module.ts @@ -0,0 +1,30 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { JwtService, JwtModule as NestJwtModule } from '@nestjs/jwt'; +import { authConfig, AuthConfig } from '../../auth.config'; + +export const REFRESH_TOKEN_JWT_SERVICE = Symbol('REFRESH_TOKEN_JWT_SERVICE'); +const refreshTokenJwtProvider = { + provide: REFRESH_TOKEN_JWT_SERVICE, + useFactory: (jwtService: JwtService) => jwtService, + inject: [JwtService], +}; + +@Module({ + imports: [ + ConfigModule.forFeature(authConfig), + NestJwtModule.registerAsync({ + imports: [...authConfig.asProvider().imports], + useFactory: async (authConfig: AuthConfig) => ({ + secret: authConfig.refreshToken.secret, + signOptions: { + expiresIn: `${authConfig.refreshToken.expTimeInSecs}s`, + }, + }), + inject: [authConfig.KEY], + }), + ], + providers: [refreshTokenJwtProvider], + exports: [ConfigModule, refreshTokenJwtProvider], +}) +export class JwtRefreshTokenModule {} diff --git a/apps/ebitemp-api/src/app/modules/auth/strategies/local/local-auth.guard.ts b/apps/ebitemp-api/src/app/modules/auth/strategies/local/local-auth.guard.ts new file mode 100644 index 0000000..ccf962b --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/auth/strategies/local/local-auth.guard.ts @@ -0,0 +1,5 @@ +import { Injectable } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class LocalAuthGuard extends AuthGuard('local') {} diff --git a/apps/ebitemp-api/src/app/modules/auth/strategies/local/local-auth.strategy.ts b/apps/ebitemp-api/src/app/modules/auth/strategies/local/local-auth.strategy.ts new file mode 100644 index 0000000..05cfb5a --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/auth/strategies/local/local-auth.strategy.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { Strategy } from 'passport-local'; +import { AuthService } from '../../auth.service'; +import { RequestWithUser } from '../../constants/request-with-user'; +import { AccountsEntity } from '../../../database/entities'; + +@Injectable() +export class LocalAuthStrategy extends PassportStrategy(Strategy, 'local') { + constructor(private readonly authService: AuthService) { + super({ + usernameField: 'username', + passwordField: 'password', + passReqToCallback: true, + }); + } + + async validate(request: RequestWithUser, username: string, password: string): Promise { + const account = await this.authService.getAuthenticatedUser(username, password); + return account; + } +} diff --git a/apps/ebitemp-api/src/app/modules/auth/users/users-auth.service.ts b/apps/ebitemp-api/src/app/modules/auth/users/users-auth.service.ts new file mode 100644 index 0000000..a367e98 --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/auth/users/users-auth.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import bcrypt from 'bcrypt'; +import { Repository } from 'typeorm'; +import { AccountsEntity } from '../../database/entities'; + +@Injectable() +export class UsersAuthService { + constructor( + @InjectRepository(AccountsEntity) + private readonly accountsRepository: Repository + ) {} + + async getUserById(accountId: number) { + return await this.accountsRepository.findOne({ + relations: { profili: { ruolo: true } }, + where: { id: accountId }, + }); + } + + async getUserByUsername(username: string) { + return await this.accountsRepository.findOne({ + relations: { profili: { ruolo: true } }, + where: { username: username }, + }); + } + + async getUserByIdAndRefreshTokenPair(accountId: number, refreshToken: string) { + const accountById = await this.getUserById(accountId); + if (!accountById?.ultimoHashRefreshToken) return null; + + const isRefreshTokenMatching = await bcrypt.compare(refreshToken, accountById.ultimoHashRefreshToken); + return isRefreshTokenMatching ? accountById : null; + } + + async setCurrentRefreshTokenHash(accountId: number, refreshToken: string | null) { + const hash = refreshToken ? await bcrypt.hash(refreshToken, 10) : null; + await this.accountsRepository.update(accountId, { + ultimoHashRefreshToken: hash, + }); + } +} diff --git a/apps/ebitemp-api/src/app/modules/auth/users/users.module.ts b/apps/ebitemp-api/src/app/modules/auth/users/users.module.ts new file mode 100644 index 0000000..6c7ddb8 --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/auth/users/users.module.ts @@ -0,0 +1,13 @@ + +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { UsersAuthService } from './users-auth.service'; +import { AccountsEntity } from '../../database/entities'; + +@Module({ + imports: [TypeOrmModule.forFeature([AccountsEntity])], + controllers: [], + providers: [UsersAuthService], + exports: [UsersAuthService], +}) +export class UsersAuthModule {} diff --git a/apps/ebitemp-api/src/app/modules/database/database.module.ts b/apps/ebitemp-api/src/app/modules/database/database.module.ts index 6f811a6..5c2f919 100644 --- a/apps/ebitemp-api/src/app/modules/database/database.module.ts +++ b/apps/ebitemp-api/src/app/modules/database/database.module.ts @@ -6,6 +6,7 @@ import { DatabaseConfig, databaseConfig } from './database.config'; import { APP_DATASOURCES } from './database.constants'; import { typeormTransactionalDataSourceFactory } from './utils/typeorm-data-source-factory'; import { typeormModuleOptionsFactory } from './utils/typeorm-module-options-factory'; +import { typeormEntitiesFromImport } from './utils/typeorm-import-entities'; const dataSources: DataSource[] = []; @@ -14,7 +15,7 @@ const typeormModules = [ imports: databaseConfig.asProvider().imports, dataSourceFactory: typeormTransactionalDataSourceFactory(), useFactory: async (dbConfig: DatabaseConfig) => { - const config = await typeormModuleOptionsFactory(dbConfig); + const config = await typeormModuleOptionsFactory(dbConfig, await typeormEntitiesFromImport(await import('./entities'))); return config; }, inject: [databaseConfig.KEY], diff --git a/apps/ebitemp-api/src/app/modules/database/entities/accounts.entity.ts b/apps/ebitemp-api/src/app/modules/database/entities/accounts.entity.ts new file mode 100644 index 0000000..9ae2237 --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/database/entities/accounts.entity.ts @@ -0,0 +1,41 @@ +import { Column, Entity, Index, OneToMany } from 'typeorm'; +import { ProfiliEntity } from './profili.entity'; + +@Index('pk_accounts', ['id'], { unique: true }) +@Entity('accounts') +export class AccountsEntity { + @Column('int', { primary: true, name: 'id' }) + id: number; + + @Column('varchar', { name: 'username', unique: true, length: 255 }) + username: string; + + @Column('varchar', { name: 'password', nullable: true, length: 255 }) + password: string | null; + + @Column('varchar', { name: 'nome', length: 255 }) + nome: string; + + @Column('date', { name: 'data_creazione' }) + dataCreazione: Date; + + @Column('date', { name: 'data_scadenza', nullable: true }) + dataScadenza: Date | null; + + @Column('bit', { name: 'flag_primo_accesso', default: () => '(1)' }) + flagPrimoAccesso: boolean; + + @Column('varchar', { + name: 'ultimo_hash_refresh_token', + nullable: true, + length: 1024, + }) + ultimoHashRefreshToken: string | null; + + @OneToMany(() => ProfiliEntity, (profiliEntity) => profiliEntity.account) + profili: ProfiliEntity[]; + + constructor(init?: Partial) { + Object.assign(this, init); + } +} diff --git a/apps/ebitemp-api/src/app/modules/database/entities/index.ts b/apps/ebitemp-api/src/app/modules/database/entities/index.ts index 4b4c18a..11fabeb 100644 --- a/apps/ebitemp-api/src/app/modules/database/entities/index.ts +++ b/apps/ebitemp-api/src/app/modules/database/entities/index.ts @@ -1 +1,4 @@ -export { TipiJobsEntity } from './tipi_jobs.entity'; +export * from './accounts.entity'; +export * from './profili.entity'; +export * from './ruoli.entity'; +export * from './tipi_jobs.entity'; diff --git a/apps/ebitemp-api/src/app/modules/database/entities/profili.entity.ts b/apps/ebitemp-api/src/app/modules/database/entities/profili.entity.ts new file mode 100644 index 0000000..0f6d4b1 --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/database/entities/profili.entity.ts @@ -0,0 +1,28 @@ +import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm'; +import { AccountsEntity } from './accounts.entity'; +import { RuoliEntity } from './ruoli.entity'; + +@Index('pk_profili', ['id'], { unique: true }) +@Entity('profili') +export class ProfiliEntity { + @Column('int', { primary: true, name: 'id' }) + id: number; + + @Column('int', { name: 'id_account', unique: true }) + idAccount: number; + + @Column('int', { name: 'id_ruolo', unique: true }) + idRuolo: number; + + @ManyToOne(() => AccountsEntity, (accountsEntity) => accountsEntity.profili) + @JoinColumn([{ name: 'id_account', referencedColumnName: 'id' }]) + account: AccountsEntity; + + @ManyToOne(() => RuoliEntity, (ruoliEntity) => ruoliEntity.profili) + @JoinColumn([{ name: 'id_ruolo', referencedColumnName: 'id' }]) + ruolo: RuoliEntity; + + constructor(init?: Partial) { + Object.assign(this, init); + } +} diff --git a/apps/ebitemp-api/src/app/modules/database/entities/ruoli.entity.ts b/apps/ebitemp-api/src/app/modules/database/entities/ruoli.entity.ts new file mode 100644 index 0000000..ea163b7 --- /dev/null +++ b/apps/ebitemp-api/src/app/modules/database/entities/ruoli.entity.ts @@ -0,0 +1,22 @@ +import { Column, Entity, Index, OneToMany } from 'typeorm'; +import { ProfiliEntity } from './profili.entity'; + +@Index('pk_ruoli', ['id'], { unique: true }) +@Entity('ruoli') +export class RuoliEntity { + @Column('int', { primary: true, name: 'id' }) + id: number; + + @Column('varchar', { name: 'nome', unique: true, length: 255 }) + nome: string; + + @Column('varchar', { name: 'descrizione', length: 255 }) + descrizione: string; + + @OneToMany(() => ProfiliEntity, (profiliEntity) => profiliEntity.ruolo) + profili: ProfiliEntity[]; + + constructor(init?: Partial) { + Object.assign(this, init); + } +} diff --git a/apps/ebitemp-api/src/app/modules/database/entities/tipi_jobs.entity.ts b/apps/ebitemp-api/src/app/modules/database/entities/tipi_jobs.entity.ts index 8524d93..7feb1ff 100644 --- a/apps/ebitemp-api/src/app/modules/database/entities/tipi_jobs.entity.ts +++ b/apps/ebitemp-api/src/app/modules/database/entities/tipi_jobs.entity.ts @@ -15,6 +15,7 @@ export class InviaMailSeErrori { cc: string | null; } +@Index('pk_tipi_jobs', ['id'], { unique: true }) @Entity('tipi_jobs') export class TipiJobsEntity extends Enumify { static _ = TipiJobsEntity.closeEnum(); diff --git a/apps/ebitemp-api/src/app/modules/database/utils/typeorm-module-options-factory.ts b/apps/ebitemp-api/src/app/modules/database/utils/typeorm-module-options-factory.ts index 62606a1..4aa5397 100644 --- a/apps/ebitemp-api/src/app/modules/database/utils/typeorm-module-options-factory.ts +++ b/apps/ebitemp-api/src/app/modules/database/utils/typeorm-module-options-factory.ts @@ -4,6 +4,7 @@ import { SnakeNamingStrategy } from 'typeorm-naming-strategies'; export const typeormModuleOptionsFactory = async ( databaseConfig: DatabaseConfig, + entities: any[], name?: string ): Promise => { return { @@ -14,7 +15,7 @@ export const typeormModuleOptionsFactory = async ( username: databaseConfig.username, password: databaseConfig.password, database: databaseConfig.database, - autoLoadEntities: true, + entities: entities, synchronize: false, logging: process.env['NODE_ENV'] !== 'production', namingStrategy: new SnakeNamingStrategy(), diff --git a/apps/ebitemp-api/src/app/modules/health/health.controller.ts b/apps/ebitemp-api/src/app/modules/health/health.controller.ts index 732b77b..bae8f53 100644 --- a/apps/ebitemp-api/src/app/modules/health/health.controller.ts +++ b/apps/ebitemp-api/src/app/modules/health/health.controller.ts @@ -6,7 +6,9 @@ import { MemoryHealthIndicator, TypeOrmHealthIndicator, } from '@nestjs/terminus'; +import { Public } from '../auth/strategies/jwt/jwt-auth.guard'; +@Public() @Controller('health') export class HealthController { constructor( diff --git a/apps/ebitemp-api/src/main.ts b/apps/ebitemp-api/src/main.ts index 8535f98..e328f68 100644 --- a/apps/ebitemp-api/src/main.ts +++ b/apps/ebitemp-api/src/main.ts @@ -3,11 +3,13 @@ import { NestFactory } from '@nestjs/core'; import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify'; import { Logger as PinoLogger } from 'nestjs-pino'; +import { DocumentBuilder, SwaggerDocumentOptions, SwaggerModule } from '@nestjs/swagger'; +import { flow } from 'lodash'; import { AppModule } from './app/app.module'; +import { patchPublicDecoratorSupport } from './app/modules/auth/strategies/jwt/jwt-auth.guard'; import { LocalConfig, localConfig } from './app/modules/config/local.config'; import { patchTypeOrm } from './app/modules/database/utils/typeorm-patch'; import { patchNestjsSwagger } from './app/modules/validation/utils/nestjs-swagger-patches'; -import { DocumentBuilder, SwaggerDocumentOptions, SwaggerModule } from '@nestjs/swagger'; async function bootstrap() { await patchTypeOrm(); @@ -23,7 +25,9 @@ async function bootstrap() { const app = await NestFactory.create(AppModule, new FastifyAdapter(appOpts)); const globalPrefix = 'v1'; app.setGlobalPrefix(globalPrefix, { - exclude: [{ path: 'health', method: RequestMethod.GET }], + exclude: [ + { path: '', method: RequestMethod.ALL }, + { path: 'health', method: RequestMethod.ALL }], }); app.enableShutdownHooks(); @@ -32,15 +36,17 @@ async function bootstrap() { app.useLogger(app.get(PinoLogger)); - const swaggerConfig = new DocumentBuilder() - .addBearerAuth() - .build(); + const swaggerConfig = new DocumentBuilder().addBearerAuth().build(); const swaggerOptions = { operationIdFactory: (controllerKey: string, methodKey: string) => { return `${controllerKey}_${methodKey}`; }, } satisfies SwaggerDocumentOptions; - const document = SwaggerModule.createDocument(app, swaggerConfig, swaggerOptions); + const document = flow( + () => SwaggerModule.createDocument(app, swaggerConfig, swaggerOptions), + patchPublicDecoratorSupport + )(); + SwaggerModule.setup(`${globalPrefix}/docs`, app, document, { swaggerOptions: { operationsSorter: 'alpha', diff --git a/apps/ebitemp-api/webpack.config.js b/apps/ebitemp-api/webpack.config.js index 670604a..bd8c278 100644 --- a/apps/ebitemp-api/webpack.config.js +++ b/apps/ebitemp-api/webpack.config.js @@ -6,9 +6,9 @@ module.exports = { output: { path: join(__dirname, '../../dist/apps/ebitemp-api'), devtoolModuleFilenameTemplate(info) { - const rel = relative(workspaceRoot, info.absoluteResourcePath); - return `webpack:///./${rel}`; - }, + const rel = relative(workspaceRoot, info.absoluteResourcePath); + return `webpack:///./${rel}`; + }, }, plugins: [ new NxAppWebpackPlugin({ @@ -20,6 +20,9 @@ module.exports = { optimization: false, outputHashing: 'none', generatePackageJson: true, + transformers: [ + { name: '@nestjs/swagger/plugin', options: { introspectComments: true } } + ], }), ], }; diff --git a/package.json b/package.json index 8be0939..d30ff76 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,8 @@ "@nestjs/common": "^11.0.8", "@nestjs/config": "^4.0.0", "@nestjs/core": "^11.0.8", + "@nestjs/jwt": "^11.0.0", + "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.0.8", "@nestjs/platform-fastify": "^11.0.8", "@nestjs/schedule": "^5.0.1", @@ -28,14 +30,18 @@ "@nestjs/typeorm": "^11.0.0", "@nx/devkit": "20.4.2", "axios": "^1.7.9", + "bcrypt": "^5.1.1", "cacheable": "^1.8.8", "connection-string": "^4.4.0", "cron": "^3.5.0", "dayjs": "^1.11.13", + "fastify": "^5.2.1", "flatted": "^3.3.2", "lodash": "^4.17.21", "nestjs-pino": "^4.3.0", "openapi3-ts": "^4.4.0", + "passport-jwt": "^4.0.1", + "passport-local": "^1.0.0", "patch-package": "^8.0.0", "pino-http": "^10.4.0", "pino-pretty": "^13.0.0", @@ -65,9 +71,11 @@ "@swc/cli": "~0.3.12", "@swc/core": "~1.10.15", "@swc/helpers": "~0.5.11", + "@types/bcrypt": "^5.0.2", "@types/jest": "^29.5.14", "@types/lodash": "^4.17.15", "@types/node": "~18.16.9", + "@types/passport-jwt": "^4.0.1", "eslint": "^9.20.0", "eslint-config-prettier": "^9.0.0", "execa": "5.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0cbdea8..112554b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,12 @@ dependencies: '@nestjs/core': specifier: ^11.0.8 version: 11.0.8(@nestjs/common@11.0.8)(@nestjs/platform-express@11.0.8)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/jwt': + specifier: ^11.0.0 + version: 11.0.0(@nestjs/common@11.0.8) + '@nestjs/passport': + specifier: ^11.0.5 + version: 11.0.5(@nestjs/common@11.0.8)(passport@0.7.0) '@nestjs/platform-express': specifier: ^11.0.8 version: 11.0.8(@nestjs/common@11.0.8)(@nestjs/core@11.0.8) @@ -59,6 +65,9 @@ dependencies: axios: specifier: ^1.7.9 version: 1.7.9 + bcrypt: + specifier: ^5.1.1 + version: 5.1.1 cacheable: specifier: ^1.8.8 version: 1.8.8 @@ -71,6 +80,9 @@ dependencies: dayjs: specifier: ^1.11.13 version: 1.11.13 + fastify: + specifier: ^5.2.1 + version: 5.2.1 flatted: specifier: ^3.3.2 version: 3.3.2 @@ -83,6 +95,12 @@ dependencies: openapi3-ts: specifier: ^4.4.0 version: 4.4.0 + passport-jwt: + specifier: ^4.0.1 + version: 4.0.1 + passport-local: + specifier: ^1.0.0 + version: 1.0.0 patch-package: specifier: ^8.0.0 version: 8.0.0 @@ -166,6 +184,9 @@ devDependencies: '@swc/helpers': specifier: ~0.5.11 version: 0.5.15 + '@types/bcrypt': + specifier: ^5.0.2 + version: 5.0.2 '@types/jest': specifier: ^29.5.14 version: 29.5.14 @@ -175,6 +196,9 @@ devDependencies: '@types/node': specifier: ~18.16.9 version: 18.16.20 + '@types/passport-jwt': + specifier: ^4.0.1 + version: 4.0.1 eslint: specifier: ^9.20.0 version: 9.20.0 @@ -2328,6 +2352,24 @@ packages: engines: {node: '>=8'} dev: false + /@mapbox/node-pre-gyp@1.0.11: + resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} + hasBin: true + dependencies: + detect-libc: 2.0.3 + https-proxy-agent: 5.0.1 + make-dir: 3.1.0 + node-fetch: 2.7.0 + nopt: 5.0.0 + npmlog: 5.0.1 + rimraf: 3.0.2 + semver: 7.7.1 + tar: 6.2.1 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + /@microsoft/tsdoc@0.15.0: resolution: {integrity: sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==} dev: false @@ -2583,6 +2625,16 @@ packages: tslib: 2.8.1 uid: 2.0.2 + /@nestjs/jwt@11.0.0(@nestjs/common@11.0.8): + resolution: {integrity: sha512-v7YRsW3Xi8HNTsO+jeHSEEqelX37TVWgwt+BcxtkG/OfXJEOs6GZdbdza200d6KqId1pJQZ6UPj1F0M6E+mxaA==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + dependencies: + '@nestjs/common': 11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@types/jsonwebtoken': 9.0.7 + jsonwebtoken: 9.0.2 + dev: false + /@nestjs/mapped-types@2.1.0(@nestjs/common@11.0.8)(reflect-metadata@0.2.2): resolution: {integrity: sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==} peerDependencies: @@ -2600,6 +2652,16 @@ packages: reflect-metadata: 0.2.2 dev: false + /@nestjs/passport@11.0.5(@nestjs/common@11.0.8)(passport@0.7.0): + resolution: {integrity: sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + passport: ^0.5.0 || ^0.6.0 || ^0.7.0 + dependencies: + '@nestjs/common': 11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1) + passport: 0.7.0 + dev: false + /@nestjs/platform-express@11.0.8(@nestjs/common@11.0.8)(@nestjs/core@11.0.8): resolution: {integrity: sha512-Ru7seOYYglKNGQFzNALE5ilLqkdtX/ge6AJDKLMt+WI7iElZ7lXjT40fE3+HVUiZODunmeKQ7jVxcQyZwLafVA==} peerDependencies: @@ -3813,6 +3875,12 @@ packages: '@babel/types': 7.26.7 dev: true + /@types/bcrypt@5.0.2: + resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==} + dependencies: + '@types/node': 18.16.20 + dev: true + /@types/body-parser@1.19.5: resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} dependencies: @@ -3946,6 +4014,19 @@ packages: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} dev: true + /@types/jsonwebtoken@9.0.7: + resolution: {integrity: sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==} + dependencies: + '@types/node': 18.16.20 + dev: false + + /@types/jsonwebtoken@9.0.8: + resolution: {integrity: sha512-7fx54m60nLFUVYlxAB1xpe9CBWX2vSrk50Y6ogRJ1v5xxtba7qXTg5BgYDN5dq+yuQQ9HaVlHJyAAt1/mxryFg==} + dependencies: + '@types/ms': 2.1.0 + '@types/node': 18.16.20 + dev: true + /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: @@ -3964,6 +4045,10 @@ packages: resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} dev: true + /@types/ms@2.1.0: + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + dev: true + /@types/node-forge@1.3.11: resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} dependencies: @@ -3977,6 +4062,26 @@ packages: resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} dev: true + /@types/passport-jwt@4.0.1: + resolution: {integrity: sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==} + dependencies: + '@types/jsonwebtoken': 9.0.8 + '@types/passport-strategy': 0.2.38 + dev: true + + /@types/passport-strategy@0.2.38: + resolution: {integrity: sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==} + dependencies: + '@types/express': 4.17.21 + '@types/passport': 1.0.17 + dev: true + + /@types/passport@1.0.17: + resolution: {integrity: sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==} + dependencies: + '@types/express': 4.17.21 + dev: true + /@types/qs@6.9.18: resolution: {integrity: sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==} dev: true @@ -4335,6 +4440,10 @@ packages: dependencies: argparse: 2.0.1 + /abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + dev: false + /abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -4384,6 +4493,15 @@ packages: engines: {node: '>= 10.0.0'} dev: true + /agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + dependencies: + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + dev: false + /agent-base@7.1.3: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} @@ -4526,10 +4644,23 @@ packages: /append-field@1.0.0: resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + /aproba@2.0.0: + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + dev: false + /arch@2.2.0: resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} dev: true + /are-we-there-yet@2.0.0: + resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + dev: false + /arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -4793,6 +4924,18 @@ packages: resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} dev: true + /bcrypt@5.1.1: + resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==} + engines: {node: '>= 10.0.0'} + requiresBuild: true + dependencies: + '@mapbox/node-pre-gyp': 1.0.11 + node-addon-api: 5.1.0 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + /big.js@5.2.2: resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} dev: true @@ -5131,6 +5274,11 @@ packages: readdirp: 4.1.1 dev: true + /chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + dev: false + /chrome-trace-event@1.0.4: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} @@ -5242,6 +5390,11 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + dev: false + /colord@2.9.3: resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} dev: true @@ -5353,6 +5506,10 @@ packages: resolution: {integrity: sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==} engines: {node: ^14.18.0 || >=16.10.0} + /console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + dev: false + /constant-case@3.0.4: resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} dependencies: @@ -5796,6 +5953,10 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + /delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + dev: false + /depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} @@ -5822,6 +5983,11 @@ packages: dev: true optional: true + /detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + dev: false + /detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -6736,6 +6902,13 @@ packages: jsonfile: 6.1.0 universalify: 2.0.1 + /fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 + dev: false + /fs-monkey@1.0.6: resolution: {integrity: sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==} dev: true @@ -6754,6 +6927,22 @@ packages: /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + /gauge@3.0.2: + resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + object-assign: 4.1.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + dev: false + /generic-pool@3.9.0: resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} engines: {node: '>= 4'} @@ -6960,6 +7149,10 @@ packages: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} + /has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + dev: false + /hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -7118,6 +7311,16 @@ packages: resolve-alpn: 1.2.1 dev: true + /https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + dependencies: + agent-base: 6.0.2 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + dev: false + /https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -8306,6 +8509,13 @@ packages: dev: true optional: true + /make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + dependencies: + semver: 6.3.1 + dev: false + /make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} @@ -8480,10 +8690,30 @@ packages: /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + /minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + dependencies: + yallist: 4.0.0 + dev: false + + /minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + dev: false + /minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + /minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + dev: false + /mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -8494,7 +8724,6 @@ packages: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} hasBin: true - dev: true /mkdirp@2.1.6: resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} @@ -8627,12 +8856,28 @@ packages: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} dev: true + /node-addon-api@5.1.0: + resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} + dev: false + /node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} requiresBuild: true dev: true optional: true + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: false + /node-forge@1.3.1: resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} engines: {node: '>= 6.13.0'} @@ -8649,6 +8894,14 @@ packages: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} dev: true + /nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true + dependencies: + abbrev: 1.1.1 + dev: false + /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -8687,6 +8940,16 @@ packages: dependencies: path-key: 3.1.1 + /npmlog@5.0.1: + resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} + deprecated: This package is no longer supported. + dependencies: + are-we-there-yet: 2.0.0 + console-control-strings: 1.1.0 + gauge: 3.0.2 + set-blocking: 2.0.0 + dev: false + /nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} dependencies: @@ -9031,6 +9294,34 @@ packages: tslib: 2.8.1 dev: true + /passport-jwt@4.0.1: + resolution: {integrity: sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==} + dependencies: + jsonwebtoken: 9.0.2 + passport-strategy: 1.0.0 + dev: false + + /passport-local@1.0.0: + resolution: {integrity: sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==} + engines: {node: '>= 0.4.0'} + dependencies: + passport-strategy: 1.0.0 + dev: false + + /passport-strategy@1.0.0: + resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==} + engines: {node: '>= 0.4.0'} + dev: false + + /passport@0.7.0: + resolution: {integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==} + engines: {node: '>= 0.4.0'} + dependencies: + passport-strategy: 1.0.0 + pause: 0.0.1 + utils-merge: 1.0.1 + dev: false + /patch-package@8.0.0: resolution: {integrity: sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==} engines: {node: '>=14', npm: '>5'} @@ -9115,6 +9406,10 @@ packages: engines: {node: '>=8'} dev: true + /pause@0.0.1: + resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} + dev: false + /peek-readable@5.4.2: resolution: {integrity: sha512-peBp3qZyuS6cNIJ2akRNG1uo1WJ1d0wTxg/fxMdZ0BqCVhx242bSFHM9eNqflfJVS9SsgkzgT/1UgnsurBOTMg==} engines: {node: '>=14.16'} @@ -9994,6 +10289,14 @@ packages: glob: 7.2.3 dev: false + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + dependencies: + glob: 7.2.3 + dev: false + /router@2.0.0: resolution: {integrity: sha512-dIM5zVoG8xhC6rnSN8uoAgFARwTE7BQs8YwHEvK0VCmfxQXMaOuA1uiR1IPwsW7JyK5iTt7Od/TC9StasS2NPQ==} engines: {node: '>= 0.10'} @@ -10158,7 +10461,6 @@ packages: /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - dev: true /semver@7.7.1: resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} @@ -10261,6 +10563,10 @@ packages: transitivePeerDependencies: - supports-color + /set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + dev: false + /set-cookie-parser@2.7.1: resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} dev: false @@ -10722,6 +11028,18 @@ packages: inherits: 2.0.4 readable-stream: 3.6.2 + /tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + dev: false + /tarn@3.0.2: resolution: {integrity: sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==} engines: {node: '>=8.0.0'} @@ -10867,6 +11185,10 @@ packages: ieee754: 1.2.1 dev: true + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: false + /tree-dump@1.0.2(tslib@2.8.1): resolution: {integrity: sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==} engines: {node: '>=10.0'} @@ -11464,6 +11786,10 @@ packages: dependencies: defaults: 1.0.4 + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: false + /webpack-cli@5.1.4(webpack@5.97.1): resolution: {integrity: sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==} engines: {node: '>=14.15.0'} @@ -11658,6 +11984,13 @@ packages: iconv-lite: 0.6.3 dev: true + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: false + /which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true @@ -11672,6 +12005,12 @@ packages: dependencies: isexe: 2.0.0 + /wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + dependencies: + string-width: 4.2.3 + dev: false + /widest-line@3.1.0: resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} engines: {node: '>=8'}