Compare commits
3 Commits
2b861c220a
...
07e84f910b
| Author | SHA1 | Date | |
|---|---|---|---|
| 07e84f910b | |||
| 59f57ded54 | |||
| cce2d83101 |
@ -30,8 +30,10 @@
|
|||||||
"@nestjs/swagger": "^11.2.5",
|
"@nestjs/swagger": "^11.2.5",
|
||||||
"@teacinema/common": "^1.0.0",
|
"@teacinema/common": "^1.0.0",
|
||||||
"@teacinema/contracts": "^1.0.0",
|
"@teacinema/contracts": "^1.0.0",
|
||||||
|
"@teacinema/passport": "^1.0.0",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.3",
|
"class-validator": "^0.14.3",
|
||||||
|
"cookie-parser": "^1.4.7",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1"
|
||||||
},
|
},
|
||||||
@ -42,6 +44,7 @@
|
|||||||
"@nestjs/schematics": "^11.0.0",
|
"@nestjs/schematics": "^11.0.0",
|
||||||
"@nestjs/testing": "^11.0.1",
|
"@nestjs/testing": "^11.0.1",
|
||||||
"@teacinema/core": "1.0.7",
|
"@teacinema/core": "1.0.7",
|
||||||
|
"@types/cookie-parser": "^1.4.10",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/jest": "^30.0.0",
|
"@types/jest": "^30.0.0",
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
|
|||||||
@ -1,13 +1,24 @@
|
|||||||
import { Module } from '@nestjs/common'
|
import { Module } from '@nestjs/common'
|
||||||
import { ConfigModule } from '@nestjs/config'
|
import { ConfigModule, ConfigService } from '@nestjs/config'
|
||||||
|
import { PassportModule } from '@teacinema/passport'
|
||||||
|
|
||||||
|
import { AccountModule } from '../modules/account/account.module'
|
||||||
import { AuthModule } from '../modules/auth/auth.module'
|
import { AuthModule } from '../modules/auth/auth.module'
|
||||||
|
|
||||||
import { AppController } from './app.controller'
|
import { AppController } from './app.controller'
|
||||||
import { AppService } from './app.service'
|
import { AppService } from './app.service'
|
||||||
|
import { getPassportConfig } from './config'
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule.forRoot({ isGlobal: true }), AuthModule],
|
imports: [
|
||||||
|
ConfigModule.forRoot({ isGlobal: true }),
|
||||||
|
AuthModule,
|
||||||
|
AccountModule,
|
||||||
|
PassportModule.registerAsync({
|
||||||
|
useFactory: getPassportConfig,
|
||||||
|
inject: [ConfigService]
|
||||||
|
})
|
||||||
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService]
|
providers: [AppService]
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
export * from './cors.config'
|
export * from './cors.config'
|
||||||
export * from './swagger.config'
|
export * from './swagger.config'
|
||||||
export * from './validation-pipe.config'
|
export * from './validation-pipe.config'
|
||||||
|
export * from './passport.config'
|
||||||
|
|||||||
10
src/core/config/passport.config.ts
Normal file
10
src/core/config/passport.config.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { ConfigService } from '@nestjs/config'
|
||||||
|
import { PassportOptions } from '@teacinema/passport'
|
||||||
|
|
||||||
|
export function getPassportConfig(
|
||||||
|
configService: ConfigService
|
||||||
|
): PassportOptions {
|
||||||
|
return {
|
||||||
|
secretKey: configService.getOrThrow<string>('PASSPORT_SECRET_KEY')
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/global.d.ts
vendored
Normal file
7
src/global.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
declare namespace Express {
|
||||||
|
export interface Request {
|
||||||
|
user?: {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ import { Logger } from '@nestjs/common'
|
|||||||
import { ConfigService } from '@nestjs/config'
|
import { ConfigService } from '@nestjs/config'
|
||||||
import { NestFactory } from '@nestjs/core'
|
import { NestFactory } from '@nestjs/core'
|
||||||
import { SwaggerModule } from '@nestjs/swagger'
|
import { SwaggerModule } from '@nestjs/swagger'
|
||||||
|
import cookieParser from 'cookie-parser'
|
||||||
|
|
||||||
import { AppModule } from './core/app.module'
|
import { AppModule } from './core/app.module'
|
||||||
import { getCorsConfig, swaggerConfig, validationPipe } from './core/config'
|
import { getCorsConfig, swaggerConfig, validationPipe } from './core/config'
|
||||||
@ -13,6 +14,8 @@ async function bootstrap() {
|
|||||||
const logger = new Logger()
|
const logger = new Logger()
|
||||||
const config = app.get(ConfigService)
|
const config = app.get(ConfigService)
|
||||||
|
|
||||||
|
app.use(cookieParser(config.getOrThrow<string>('COOKIES_SECRET')))
|
||||||
|
|
||||||
app.useGlobalPipes(validationPipe)
|
app.useGlobalPipes(validationPipe)
|
||||||
app.useGlobalFilters(new GrpcExceptionFilter())
|
app.useGlobalFilters(new GrpcExceptionFilter())
|
||||||
|
|
||||||
|
|||||||
22
src/modules/account/account.grpc.ts
Normal file
22
src/modules/account/account.grpc.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Inject, Injectable, OnModuleInit } from '@nestjs/common'
|
||||||
|
import type { ClientGrpc } from '@nestjs/microservices'
|
||||||
|
import {
|
||||||
|
AccountServiceClient,
|
||||||
|
GetAccountRequest
|
||||||
|
} from '@teacinema/contracts/gen/account'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AccountClientGrpc implements OnModuleInit {
|
||||||
|
private accountService: AccountServiceClient
|
||||||
|
|
||||||
|
constructor(@Inject('ACCOUNT_PACKAGE') private readonly client: ClientGrpc) {}
|
||||||
|
|
||||||
|
onModuleInit() {
|
||||||
|
this.accountService =
|
||||||
|
this.client.getService<AccountServiceClient>('AccountService')
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAccount(request: GetAccountRequest) {
|
||||||
|
return this.accountService.getAccount(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/modules/account/account.module.ts
Normal file
28
src/modules/account/account.module.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { Module } from '@nestjs/common'
|
||||||
|
import { ConfigService } from '@nestjs/config'
|
||||||
|
import { ClientsModule, Transport } from '@nestjs/microservices'
|
||||||
|
import { PROTO_PATHS } from '@teacinema/contracts'
|
||||||
|
|
||||||
|
import { AccountClientGrpc } from './account.grpc'
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ClientsModule.registerAsync([
|
||||||
|
{
|
||||||
|
name: 'ACCOUNT_PACKAGE',
|
||||||
|
useFactory: (configService: ConfigService) => ({
|
||||||
|
transport: Transport.GRPC,
|
||||||
|
options: {
|
||||||
|
package: 'account.v1',
|
||||||
|
protoPath: PROTO_PATHS.ACCOUNT,
|
||||||
|
url: configService.getOrThrow<string>('AUTH_GRPC_URL')
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
inject: [ConfigService]
|
||||||
|
}
|
||||||
|
])
|
||||||
|
],
|
||||||
|
providers: [AccountClientGrpc],
|
||||||
|
exports: [AccountClientGrpc]
|
||||||
|
})
|
||||||
|
export class AccountModule {}
|
||||||
@ -1,12 +1,26 @@
|
|||||||
import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common'
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
HttpCode,
|
||||||
|
HttpStatus,
|
||||||
|
Post,
|
||||||
|
Req,
|
||||||
|
Res
|
||||||
|
} from '@nestjs/common'
|
||||||
|
import { ConfigService } from '@nestjs/config'
|
||||||
import { ApiOperation } from '@nestjs/swagger'
|
import { ApiOperation } from '@nestjs/swagger'
|
||||||
|
import type { Request, Response } from 'express'
|
||||||
|
import { lastValueFrom } from 'rxjs'
|
||||||
|
|
||||||
import { AuthClientGrpc } from './auth.grpc'
|
import { AuthClientGrpc } from './auth.grpc'
|
||||||
import { SendOtpRequestDto, VerifyOtpRequestDto } from './dto'
|
import { SendOtpRequestDto, VerifyOtpRequestDto } from './dto'
|
||||||
|
|
||||||
@Controller('auth')
|
@Controller('auth')
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
constructor(private readonly client: AuthClientGrpc) {}
|
constructor(
|
||||||
|
private readonly client: AuthClientGrpc,
|
||||||
|
private readonly config: ConfigService
|
||||||
|
) {}
|
||||||
|
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: 'Send OTP',
|
summary: 'Send OTP',
|
||||||
@ -24,7 +38,60 @@ export class AuthController {
|
|||||||
})
|
})
|
||||||
@Post('otp/verify')
|
@Post('otp/verify')
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
public verifyOtp(@Body() dto: VerifyOtpRequestDto) {
|
public async verifyOtp(
|
||||||
return this.client.verifyOtp(dto)
|
@Body() dto: VerifyOtpRequestDto,
|
||||||
|
@Res({ passthrough: true }) res: Response
|
||||||
|
) {
|
||||||
|
const { accessToken, refreshToken } = await lastValueFrom(
|
||||||
|
this.client.verifyOtp(dto)
|
||||||
|
)
|
||||||
|
|
||||||
|
res.cookie('refreshToken', refreshToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: process.env.NODE_ENV === 'production',
|
||||||
|
domain: this.config.getOrThrow('COOKIES_DOMAIN'),
|
||||||
|
sameSite: 'lax',
|
||||||
|
maxAge: 30 * 24 * 60 * 60 * 1000
|
||||||
|
})
|
||||||
|
|
||||||
|
return { accessToken }
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Refresh',
|
||||||
|
description: 'Refresh access token'
|
||||||
|
})
|
||||||
|
@Post('refresh')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
public async refresh(
|
||||||
|
@Req() req: Request,
|
||||||
|
@Res({ passthrough: true }) res: Response
|
||||||
|
) {
|
||||||
|
const refreshToken = req.cookies?.refreshToken as string
|
||||||
|
|
||||||
|
const { accessToken, refreshToken: newRefreshToken } = await lastValueFrom(
|
||||||
|
this.client.refresh({ refreshToken })
|
||||||
|
)
|
||||||
|
|
||||||
|
res.cookie('refreshToken', newRefreshToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: process.env.NODE_ENV === 'production',
|
||||||
|
domain: this.config.getOrThrow('COOKIES_DOMAIN'),
|
||||||
|
sameSite: 'lax',
|
||||||
|
maxAge: 30 * 24 * 60 * 60 * 1000
|
||||||
|
})
|
||||||
|
|
||||||
|
return { accessToken }
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Logout',
|
||||||
|
description: 'Logout user'
|
||||||
|
})
|
||||||
|
@Post('logout')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
public logout(@Res({ passthrough: true }) res: Response) {
|
||||||
|
res.clearCookie('refreshToken')
|
||||||
|
return { ok: true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { Inject, Injectable, OnModuleInit } from '@nestjs/common'
|
|||||||
import type { ClientGrpc } from '@nestjs/microservices'
|
import type { ClientGrpc } from '@nestjs/microservices'
|
||||||
import {
|
import {
|
||||||
AuthServiceClient,
|
AuthServiceClient,
|
||||||
|
RefreshRequest,
|
||||||
SendOtpRequest,
|
SendOtpRequest,
|
||||||
VerifyOtpRequest
|
VerifyOtpRequest
|
||||||
} from '@teacinema/contracts/gen/auth'
|
} from '@teacinema/contracts/gen/auth'
|
||||||
@ -23,4 +24,8 @@ export class AuthClientGrpc implements OnModuleInit {
|
|||||||
public verifyOtp(request: VerifyOtpRequest) {
|
public verifyOtp(request: VerifyOtpRequest) {
|
||||||
return this.authService.verifyOtp(request)
|
return this.authService.verifyOtp(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public refresh(request: RefreshRequest) {
|
||||||
|
return this.authService.refresh(request)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import { ConfigService } from '@nestjs/config'
|
|||||||
import { ClientsModule, Transport } from '@nestjs/microservices'
|
import { ClientsModule, Transport } from '@nestjs/microservices'
|
||||||
import { PROTO_PATHS } from '@teacinema/contracts'
|
import { PROTO_PATHS } from '@teacinema/contracts'
|
||||||
|
|
||||||
|
import { AccountModule } from '../account/account.module'
|
||||||
|
|
||||||
import { AuthController } from './auth.controller'
|
import { AuthController } from './auth.controller'
|
||||||
import { AuthClientGrpc } from './auth.grpc'
|
import { AuthClientGrpc } from './auth.grpc'
|
||||||
|
|
||||||
@ -21,7 +23,8 @@ import { AuthClientGrpc } from './auth.grpc'
|
|||||||
}),
|
}),
|
||||||
inject: [ConfigService]
|
inject: [ConfigService]
|
||||||
}
|
}
|
||||||
])
|
]),
|
||||||
|
AccountModule
|
||||||
],
|
],
|
||||||
controllers: [AuthController],
|
controllers: [AuthController],
|
||||||
providers: [AuthClientGrpc]
|
providers: [AuthClientGrpc]
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { IdentifierValidator } from '../../../../shared/validators'
|
|||||||
export class SendOtpRequestDto {
|
export class SendOtpRequestDto {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'User phone or email',
|
description: 'User phone or email',
|
||||||
example: '+791234567890'
|
example: '+79123456789'
|
||||||
})
|
})
|
||||||
@Validate(IdentifierValidator)
|
@Validate(IdentifierValidator)
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { IdentifierValidator } from '../../../../shared/validators'
|
|||||||
export class VerifyOtpRequestDto {
|
export class VerifyOtpRequestDto {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'User phone or email',
|
description: 'User phone or email',
|
||||||
example: '+791234567890'
|
example: '+79123456789'
|
||||||
})
|
})
|
||||||
@Validate(IdentifierValidator)
|
@Validate(IdentifierValidator)
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
7
src/shared/decorators/current-user.decorator.ts
Normal file
7
src/shared/decorators/current-user.decorator.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { createParamDecorator } from '@nestjs/common'
|
||||||
|
import type { Request } from 'express'
|
||||||
|
|
||||||
|
export const CurrentUser = createParamDecorator((_, ctx) => {
|
||||||
|
const req = ctx.switchToHttp().getRequest<Request>()
|
||||||
|
return req.user?.id ?? null
|
||||||
|
})
|
||||||
3
src/shared/decorators/index.ts
Normal file
3
src/shared/decorators/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './protected.decorator'
|
||||||
|
export * from './current-user.decorator'
|
||||||
|
export * from './roles.decorator'
|
||||||
14
src/shared/decorators/protected.decorator.ts
Normal file
14
src/shared/decorators/protected.decorator.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { applyDecorators, UseGuards } from '@nestjs/common'
|
||||||
|
import { Role } from '@teacinema/contracts/gen/account'
|
||||||
|
|
||||||
|
import { AuthGuard, RolesGuard } from '../guards'
|
||||||
|
|
||||||
|
import { Roles } from './roles.decorator'
|
||||||
|
|
||||||
|
export const Protected = (...roles: Role[]) => {
|
||||||
|
if (roles.length === 0) {
|
||||||
|
return applyDecorators(UseGuards(AuthGuard))
|
||||||
|
}
|
||||||
|
|
||||||
|
return applyDecorators(Roles(...roles), UseGuards(AuthGuard, RolesGuard))
|
||||||
|
}
|
||||||
6
src/shared/decorators/roles.decorator.ts
Normal file
6
src/shared/decorators/roles.decorator.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { SetMetadata } from '@nestjs/common'
|
||||||
|
import { Role } from '@teacinema/contracts/gen/account'
|
||||||
|
|
||||||
|
export const ROLES_KEY = 'required_roles'
|
||||||
|
|
||||||
|
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles)
|
||||||
38
src/shared/guards/auth.guard.ts
Normal file
38
src/shared/guards/auth.guard.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import {
|
||||||
|
CanActivate,
|
||||||
|
ExecutionContext,
|
||||||
|
Injectable,
|
||||||
|
UnauthorizedException
|
||||||
|
} from '@nestjs/common'
|
||||||
|
import { PassportService } from '@teacinema/passport'
|
||||||
|
import { Request } from 'express'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthGuard implements CanActivate {
|
||||||
|
public constructor(private readonly passportService: PassportService) {}
|
||||||
|
public canActivate(context: ExecutionContext): boolean {
|
||||||
|
const request = context.switchToHttp().getRequest<Request>()
|
||||||
|
const token = this.extractToken(request)
|
||||||
|
const result = this.passportService.verify(token)
|
||||||
|
|
||||||
|
if (!result.valid) {
|
||||||
|
throw new UnauthorizedException(result.reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
request.user = {
|
||||||
|
id: result.userId
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractToken(request: Request) {
|
||||||
|
const [type, token] = request.headers.authorization?.split(' ') ?? []
|
||||||
|
|
||||||
|
if (!token || type !== 'Bearer') {
|
||||||
|
throw new UnauthorizedException('Authorization header is missing')
|
||||||
|
}
|
||||||
|
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/shared/guards/index.ts
Normal file
2
src/shared/guards/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './auth.guard'
|
||||||
|
export * from './roles.guard'
|
||||||
54
src/shared/guards/roles.guard.ts
Normal file
54
src/shared/guards/roles.guard.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import {
|
||||||
|
CanActivate,
|
||||||
|
ExecutionContext,
|
||||||
|
ForbiddenException,
|
||||||
|
Injectable,
|
||||||
|
NotFoundException
|
||||||
|
} from '@nestjs/common'
|
||||||
|
import { Reflector } from '@nestjs/core'
|
||||||
|
import { Role } from '@teacinema/contracts/gen/account'
|
||||||
|
import { Request } from 'express'
|
||||||
|
import { lastValueFrom } from 'rxjs'
|
||||||
|
|
||||||
|
import { AccountClientGrpc } from '../../modules/account/account.grpc'
|
||||||
|
import { ROLES_KEY } from '../decorators'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RolesGuard implements CanActivate {
|
||||||
|
public constructor(
|
||||||
|
private readonly reflector: Reflector,
|
||||||
|
private readonly accountClient: AccountClientGrpc
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async canActivate(context: ExecutionContext) {
|
||||||
|
const required = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
|
||||||
|
context.getHandler(),
|
||||||
|
context.getClass()
|
||||||
|
])
|
||||||
|
|
||||||
|
if (!required || required.length === 0) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = context.switchToHttp().getRequest<Request>()
|
||||||
|
const user = request.user
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ForbiddenException('User context missing')
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = await lastValueFrom(
|
||||||
|
this.accountClient.getAccount({ id: user.id })
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
throw new NotFoundException('Account not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!required.includes(account.role)) {
|
||||||
|
throw new ForbiddenException('Forbidden')
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
59
yarn.lock
59
yarn.lock
@ -1011,6 +1011,17 @@
|
|||||||
load-esm "1.0.3"
|
load-esm "1.0.3"
|
||||||
tslib "2.8.1"
|
tslib "2.8.1"
|
||||||
|
|
||||||
|
"@nestjs/common@^11.1.12":
|
||||||
|
version "11.1.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-11.1.13.tgz#3d4bcc2a593236546bc62f7801b0d4450b26a9ac"
|
||||||
|
integrity sha512-ieqWtipT+VlyDWLz5Rvz0f3E5rXcVAnaAi+D53DEHLjc1kmFxCgZ62qVfTX2vwkywwqNkTNXvBgGR72hYqV//Q==
|
||||||
|
dependencies:
|
||||||
|
uid "2.0.2"
|
||||||
|
file-type "21.3.0"
|
||||||
|
iterare "1.2.1"
|
||||||
|
load-esm "1.0.3"
|
||||||
|
tslib "2.8.1"
|
||||||
|
|
||||||
"@nestjs/config@^4.0.2":
|
"@nestjs/config@^4.0.2":
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@nestjs/config/-/config-4.0.2.tgz#a2777a1fd2d0d594bab3953f50fbca95c14cce52"
|
resolved "https://registry.yarnpkg.com/@nestjs/config/-/config-4.0.2.tgz#a2777a1fd2d0d594bab3953f50fbca95c14cce52"
|
||||||
@ -1032,6 +1043,18 @@
|
|||||||
path-to-regexp "8.3.0"
|
path-to-regexp "8.3.0"
|
||||||
tslib "2.8.1"
|
tslib "2.8.1"
|
||||||
|
|
||||||
|
"@nestjs/core@^11.1.12":
|
||||||
|
version "11.1.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-11.1.13.tgz#4444e124d255759d545a631f63e9d84fe1a1e3e5"
|
||||||
|
integrity sha512-Tq9EIKiC30EBL8hLK93tNqaToy0hzbuVGYt29V8NhkVJUsDzlmiVf6c3hSPtzx2krIUVbTgQ2KFeaxr72rEyzQ==
|
||||||
|
dependencies:
|
||||||
|
uid "2.0.2"
|
||||||
|
"@nuxt/opencollective" "0.4.1"
|
||||||
|
fast-safe-stringify "2.1.1"
|
||||||
|
iterare "1.2.1"
|
||||||
|
path-to-regexp "8.3.0"
|
||||||
|
tslib "2.8.1"
|
||||||
|
|
||||||
"@nestjs/mapped-types@2.1.0":
|
"@nestjs/mapped-types@2.1.0":
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz#b9b536b7c3571567aa1d0223db8baa1a51505a19"
|
resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz#b9b536b7c3571567aa1d0223db8baa1a51505a19"
|
||||||
@ -1198,9 +1221,9 @@
|
|||||||
integrity sha512-L006mySvhRpUlRHf3ONbXA0RG42gbQ9QAnmQuZx4zejHEsWB9702xe18MSYcBilv+lmYkQpiX/g3p8P7suLfqQ==
|
integrity sha512-L006mySvhRpUlRHf3ONbXA0RG42gbQ9QAnmQuZx4zejHEsWB9702xe18MSYcBilv+lmYkQpiX/g3p8P7suLfqQ==
|
||||||
|
|
||||||
"@teacinema/contracts@^1.0.0":
|
"@teacinema/contracts@^1.0.0":
|
||||||
version "1.0.4"
|
version "1.0.6"
|
||||||
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcontracts/-/1.0.4/contracts-1.0.4.tgz#178dc6392aae52c4dc2b60bb1470a0a2885a8715"
|
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fcontracts/-/1.0.6/contracts-1.0.6.tgz#7aa97d4c3081b791908c90d2a12d771b5fb79bd5"
|
||||||
integrity sha512-3jHP4NAoH9munaN2Iegvg0X6KN/SR1rwZU7KpbuBtlcTJqsLljTaHwG+XKjEL84Y9b+sF73jGGbg/w2Y8IBQmw==
|
integrity sha512-2IU2S9hWtKeMpn4zRS9pJAaSryIODrRjS1q9QezQOXF6554vtQlMVr0fLVTGhNmXlbRfuje0f6HggApqPDFMaA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@nestjs/microservices" "^11.1.12"
|
"@nestjs/microservices" "^11.1.12"
|
||||||
protoc "33.4.0"
|
protoc "33.4.0"
|
||||||
@ -1214,6 +1237,16 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@trivago/prettier-plugin-sort-imports" "^5.2.2"
|
"@trivago/prettier-plugin-sort-imports" "^5.2.2"
|
||||||
|
|
||||||
|
"@teacinema/passport@^1.0.0":
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://git.ksv741.keenetic.pro/api/packages/teacinema/npm/%40teacinema%2Fpassport/-/1.0.1/passport-1.0.1.tgz#83a8a605534b23929d3b1090d28023626fd5cc9f"
|
||||||
|
integrity sha512-hgFHXtWgXOemAf9InBLuhHbtF8wS01HKmot0nSaUqGlQCtu74ArMMCG8mup1SR50UGz5DEpHPEL8G5CzVnTTSQ==
|
||||||
|
dependencies:
|
||||||
|
"@nestjs/common" "^11.1.12"
|
||||||
|
"@nestjs/core" "^11.1.12"
|
||||||
|
reflect-metadata "^0.2.2"
|
||||||
|
rxjs "^7.8.2"
|
||||||
|
|
||||||
"@tokenizer/inflate@^0.4.1":
|
"@tokenizer/inflate@^0.4.1":
|
||||||
version "0.4.1"
|
version "0.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/@tokenizer/inflate/-/inflate-0.4.1.tgz#fa6cdb8366151b3cc8426bf9755c1ea03a2fba08"
|
resolved "https://registry.yarnpkg.com/@tokenizer/inflate/-/inflate-0.4.1.tgz#fa6cdb8366151b3cc8426bf9755c1ea03a2fba08"
|
||||||
@ -1314,6 +1347,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/cookie-parser@^1.4.10":
|
||||||
|
version "1.4.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/cookie-parser/-/cookie-parser-1.4.10.tgz#a045272a383a30597a01955d4f9c790018f214e4"
|
||||||
|
integrity sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==
|
||||||
|
|
||||||
"@types/cookiejar@^2.1.5":
|
"@types/cookiejar@^2.1.5":
|
||||||
version "2.1.5"
|
version "2.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78"
|
resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.5.tgz#14a3e83fa641beb169a2dd8422d91c3c345a9a78"
|
||||||
@ -2375,12 +2413,25 @@ convert-source-map@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
||||||
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
||||||
|
|
||||||
|
cookie-parser@^1.4.7:
|
||||||
|
version "1.4.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.7.tgz#e2125635dfd766888ffe90d60c286404fa0e7b26"
|
||||||
|
integrity sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==
|
||||||
|
dependencies:
|
||||||
|
cookie "0.7.2"
|
||||||
|
cookie-signature "1.0.6"
|
||||||
|
|
||||||
|
cookie-signature@1.0.6:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||||
|
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
|
||||||
|
|
||||||
cookie-signature@^1.2.1, cookie-signature@^1.2.2:
|
cookie-signature@^1.2.1, cookie-signature@^1.2.2:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793"
|
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793"
|
||||||
integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==
|
integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==
|
||||||
|
|
||||||
cookie@^0.7.1:
|
cookie@0.7.2, cookie@^0.7.1:
|
||||||
version "0.7.2"
|
version "0.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
|
||||||
integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
|
integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user