diff --git a/apps/backend/package.json b/apps/backend/package.json index 6259803..c0101a0 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -25,12 +25,17 @@ "@nestjs/common": "^11.1.17", "@nestjs/config": "^4.0.3", "@nestjs/core": "^11.1.17", + "@nestjs/jwt": "^11.0.2", + "@nestjs/passport": "^11.0.5", "@nestjs/platform-express": "^11.1.17", "@nestjs/typeorm": "^11.0.0", + "bcrypt": "^6.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.15.1", "dotenv": "^17.3.1", "ioredis": "^5.10.1", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", "pg": "^8.20.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2", @@ -40,8 +45,10 @@ "@nestjs/cli": "^11.0.16", "@nestjs/schematics": "^11.0.9", "@nestjs/testing": "^11.1.17", + "@types/bcrypt": "^6.0.0", "@types/jest": "^30.0.0", "@types/node": "^25.5.0", + "@types/passport-jwt": "^4.0.1", "@typescript-eslint/eslint-plugin": "^8.57.2", "@typescript-eslint/parser": "^8.57.2", "eslint": "^10.1.0", diff --git a/apps/backend/src/adapters/inbound/rest/auth/auth.controller.ts b/apps/backend/src/adapters/inbound/rest/auth/auth.controller.ts new file mode 100644 index 0000000..282d5ef --- /dev/null +++ b/apps/backend/src/adapters/inbound/rest/auth/auth.controller.ts @@ -0,0 +1,36 @@ +import { Controller, Post, Body, UseGuards, Request, Get } from '@nestjs/common'; +import { AuthService } from '../../../../core/services/auth.service'; +import { RegisterDto } from './dto/register.dto'; +import { LoginDto } from './dto/login.dto'; +import { JwtAuthGuard } from './guards/jwt-auth.guard'; + +@Controller('auth') +export class AuthController { + constructor(private readonly authService: AuthService) {} + + @Post('register') + async register(@Body() dto: RegisterDto) { + return this.authService.register({ + email: dto.email, + password: dto.password, + displayName: dto.displayName, + homeName: dto.homeName, + }); + } + + @Post('login') + async login(@Body() dto: LoginDto) { + return this.authService.login(dto.email, dto.password); + } + + @Post('refresh') + async refresh(@Body('refreshToken') refreshToken: string) { + return this.authService.refreshAccessToken(refreshToken); + } + + @UseGuards(JwtAuthGuard) + @Get('me') + async me(@Request() req: { user: { id: string; email: string; homeId: string } }) { + return req.user; + } +} diff --git a/apps/backend/src/adapters/inbound/rest/auth/dto/login.dto.ts b/apps/backend/src/adapters/inbound/rest/auth/dto/login.dto.ts new file mode 100644 index 0000000..7ea3863 --- /dev/null +++ b/apps/backend/src/adapters/inbound/rest/auth/dto/login.dto.ts @@ -0,0 +1,9 @@ +import { IsEmail, IsString } from 'class-validator'; + +export class LoginDto { + @IsEmail() + email!: string; + + @IsString() + password!: string; +} diff --git a/apps/backend/src/adapters/inbound/rest/auth/dto/register.dto.ts b/apps/backend/src/adapters/inbound/rest/auth/dto/register.dto.ts new file mode 100644 index 0000000..fd81ad7 --- /dev/null +++ b/apps/backend/src/adapters/inbound/rest/auth/dto/register.dto.ts @@ -0,0 +1,21 @@ +import { IsEmail, IsString, MinLength, MaxLength } from 'class-validator'; + +export class RegisterDto { + @IsEmail() + email!: string; + + @IsString() + @MinLength(8) + @MaxLength(128) + password!: string; + + @IsString() + @MinLength(1) + @MaxLength(100) + displayName!: string; + + @IsString() + @MinLength(1) + @MaxLength(100) + homeName!: string; +} diff --git a/apps/backend/src/adapters/inbound/rest/auth/guards/jwt-auth.guard.ts b/apps/backend/src/adapters/inbound/rest/auth/guards/jwt-auth.guard.ts new file mode 100644 index 0000000..2155290 --- /dev/null +++ b/apps/backend/src/adapters/inbound/rest/auth/guards/jwt-auth.guard.ts @@ -0,0 +1,5 @@ +import { Injectable } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class JwtAuthGuard extends AuthGuard('jwt') {} diff --git a/apps/backend/src/adapters/inbound/rest/auth/strategies/jwt.strategy.ts b/apps/backend/src/adapters/inbound/rest/auth/strategies/jwt.strategy.ts new file mode 100644 index 0000000..0e55aba --- /dev/null +++ b/apps/backend/src/adapters/inbound/rest/auth/strategies/jwt.strategy.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { PassportStrategy } from '@nestjs/passport'; +import { ExtractJwt, Strategy } from 'passport-jwt'; + +export interface JwtPayload { + sub: string; + email: string; + homeId: string; + type: 'user' | 'device'; +} + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor(configService: ConfigService) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: configService.get('auth.jwtSecret', 'dev-secret-change-in-production'), + }); + } + + validate(payload: JwtPayload) { + return { + id: payload.sub, + email: payload.email, + homeId: payload.homeId, + type: payload.type, + }; + } +} diff --git a/apps/backend/src/adapters/inbound/rest/device/device.controller.ts b/apps/backend/src/adapters/inbound/rest/device/device.controller.ts new file mode 100644 index 0000000..463608d --- /dev/null +++ b/apps/backend/src/adapters/inbound/rest/device/device.controller.ts @@ -0,0 +1,27 @@ +import { Controller, Post, Get, Body, UseGuards, Request } from '@nestjs/common'; +import { AuthService } from '../../../../core/services/auth.service'; +import { DeviceService } from '../../../../core/services/device.service'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { RegisterDeviceDto } from './dto/register-device.dto'; + +@Controller('devices') +@UseGuards(JwtAuthGuard) +export class DeviceController { + constructor( + private readonly authService: AuthService, + private readonly deviceService: DeviceService, + ) {} + + @Post() + async registerDevice( + @Body() dto: RegisterDeviceDto, + @Request() req: { user: { homeId: string } }, + ) { + return this.authService.registerDevice(req.user.homeId, dto.name); + } + + @Get() + async listDevices(@Request() req: { user: { homeId: string } }) { + return this.deviceService.findByHomeId(req.user.homeId); + } +} diff --git a/apps/backend/src/adapters/inbound/rest/device/dto/register-device.dto.ts b/apps/backend/src/adapters/inbound/rest/device/dto/register-device.dto.ts new file mode 100644 index 0000000..e00ea28 --- /dev/null +++ b/apps/backend/src/adapters/inbound/rest/device/dto/register-device.dto.ts @@ -0,0 +1,8 @@ +import { IsString, MinLength, MaxLength } from 'class-validator'; + +export class RegisterDeviceDto { + @IsString() + @MinLength(1) + @MaxLength(100) + name!: string; +} diff --git a/apps/backend/src/app.module.ts b/apps/backend/src/app.module.ts index 6641227..8f4c1f7 100644 --- a/apps/backend/src/app.module.ts +++ b/apps/backend/src/app.module.ts @@ -1,23 +1,45 @@ import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { JwtModule } from '@nestjs/jwt'; +import { PassportModule } from '@nestjs/passport'; import { typeormConfig } from './config/typeorm.config'; import { redisConfig } from './config/redis.config'; import { appConfig } from './config/app.config'; +import { authConfig } from './config/auth.config'; +import { Home } from './core/domain/entities/home.entity'; +import { User } from './core/domain/entities/user.entity'; +import { Device } from './core/domain/entities/device.entity'; +import { AuthService } from './core/services/auth.service'; +import { UserService } from './core/services/user.service'; +import { HomeService } from './core/services/home.service'; +import { DeviceService } from './core/services/device.service'; +import { JwtStrategy } from './adapters/inbound/rest/auth/strategies/jwt.strategy'; +import { AuthController } from './adapters/inbound/rest/auth/auth.controller'; +import { DeviceController } from './adapters/inbound/rest/device/device.controller'; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, - load: [appConfig, redisConfig], + load: [appConfig, redisConfig, authConfig], }), TypeOrmModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: (configService: ConfigService) => typeormConfig(configService), }), + TypeOrmModule.forFeature([Home, User, Device]), + PassportModule, + JwtModule.registerAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: (configService: ConfigService) => ({ + secret: configService.get('auth.jwtSecret', 'dev-secret-change-in-production'), + }), + }), ], - controllers: [], - providers: [], + controllers: [AuthController, DeviceController], + providers: [AuthService, UserService, HomeService, DeviceService, JwtStrategy], }) export class AppModule {} diff --git a/apps/backend/src/config/auth.config.ts b/apps/backend/src/config/auth.config.ts new file mode 100644 index 0000000..e5f76ff --- /dev/null +++ b/apps/backend/src/config/auth.config.ts @@ -0,0 +1,8 @@ +import { registerAs } from '@nestjs/config'; + +export const authConfig = registerAs('auth', () => ({ + jwtSecret: process.env.JWT_SECRET || 'dev-secret-change-in-production', + jwtExpiresIn: process.env.JWT_EXPIRES_IN || '15m', + jwtRefreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '7d', + deviceTokenExpiresIn: process.env.DEVICE_TOKEN_EXPIRES_IN || '365d', +})); diff --git a/apps/backend/src/core/domain/entities/conversation-session.entity.ts b/apps/backend/src/core/domain/entities/conversation-session.entity.ts index 8741810..e17a8b5 100644 --- a/apps/backend/src/core/domain/entities/conversation-session.entity.ts +++ b/apps/backend/src/core/domain/entities/conversation-session.entity.ts @@ -64,6 +64,6 @@ export class ConversationSession { @OneToMany(() => MemoryEntry, (memory) => memory.session) memories!: MemoryEntry[]; - @CreateDateColumn({ type: 'timestamptz' }) + @CreateDateColumn({ type: 'timestamptz', name: 'created_at' }) createdAt!: Date; } diff --git a/apps/backend/src/core/domain/entities/device.entity.ts b/apps/backend/src/core/domain/entities/device.entity.ts index 7dcd31d..36fd642 100644 --- a/apps/backend/src/core/domain/entities/device.entity.ts +++ b/apps/backend/src/core/domain/entities/device.entity.ts @@ -45,9 +45,9 @@ export class Device { @Column({ type: 'timestamptz', nullable: true, name: 'last_seen_at' }) lastSeenAt!: Date | null; - @CreateDateColumn({ type: 'timestamptz' }) + @CreateDateColumn({ type: 'timestamptz', name: 'created_at' }) createdAt!: Date; - @UpdateDateColumn({ type: 'timestamptz' }) + @UpdateDateColumn({ type: 'timestamptz', name: 'updated_at' }) updatedAt!: Date; } diff --git a/apps/backend/src/core/domain/entities/home.entity.ts b/apps/backend/src/core/domain/entities/home.entity.ts index 7087c8d..315d2cb 100644 --- a/apps/backend/src/core/domain/entities/home.entity.ts +++ b/apps/backend/src/core/domain/entities/home.entity.ts @@ -23,9 +23,9 @@ export class Home { @OneToMany(() => Device, (device) => device.home) devices!: Device[]; - @CreateDateColumn({ type: 'timestamptz' }) + @CreateDateColumn({ type: 'timestamptz', name: 'created_at' }) createdAt!: Date; - @UpdateDateColumn({ type: 'timestamptz' }) + @UpdateDateColumn({ type: 'timestamptz', name: 'updated_at' }) updatedAt!: Date; } diff --git a/apps/backend/src/core/domain/entities/memory-embedding.entity.ts b/apps/backend/src/core/domain/entities/memory-embedding.entity.ts index 49f1163..1122c22 100644 --- a/apps/backend/src/core/domain/entities/memory-embedding.entity.ts +++ b/apps/backend/src/core/domain/entities/memory-embedding.entity.ts @@ -32,6 +32,6 @@ export class MemoryEmbedding { @Column({ type: 'varchar', nullable: true }) embedding!: string | null; - @CreateDateColumn({ type: 'timestamptz' }) + @CreateDateColumn({ type: 'timestamptz', name: 'created_at' }) createdAt!: Date; } diff --git a/apps/backend/src/core/domain/entities/memory-entry.entity.ts b/apps/backend/src/core/domain/entities/memory-entry.entity.ts index 56a0e95..7513a07 100644 --- a/apps/backend/src/core/domain/entities/memory-entry.entity.ts +++ b/apps/backend/src/core/domain/entities/memory-entry.entity.ts @@ -54,9 +54,9 @@ export class MemoryEntry { @Column({ type: 'boolean', default: true, name: 'is_active' }) isActive!: boolean; - @CreateDateColumn({ type: 'timestamptz' }) + @CreateDateColumn({ type: 'timestamptz', name: 'created_at' }) createdAt!: Date; - @UpdateDateColumn({ type: 'timestamptz' }) + @UpdateDateColumn({ type: 'timestamptz', name: 'updated_at' }) updatedAt!: Date; } diff --git a/apps/backend/src/core/domain/entities/message.entity.ts b/apps/backend/src/core/domain/entities/message.entity.ts index 46e7ebf..0508e51 100644 --- a/apps/backend/src/core/domain/entities/message.entity.ts +++ b/apps/backend/src/core/domain/entities/message.entity.ts @@ -45,6 +45,6 @@ export class Message { @Column({ type: 'integer', default: 0, name: 'tokens_used' }) tokensUsed!: number; - @CreateDateColumn({ type: 'timestamptz' }) + @CreateDateColumn({ type: 'timestamptz', name: 'created_at' }) createdAt!: Date; } diff --git a/apps/backend/src/core/domain/entities/timer.entity.ts b/apps/backend/src/core/domain/entities/timer.entity.ts index 90836bf..8d01507 100644 --- a/apps/backend/src/core/domain/entities/timer.entity.ts +++ b/apps/backend/src/core/domain/entities/timer.entity.ts @@ -54,6 +54,6 @@ export class Timer { @Column({ type: 'enum', enum: TimerStatus, default: TimerStatus.ACTIVE }) status!: TimerStatus; - @CreateDateColumn({ type: 'timestamptz' }) + @CreateDateColumn({ type: 'timestamptz', name: 'created_at' }) createdAt!: Date; } diff --git a/apps/backend/src/core/domain/entities/user-service-credential.entity.ts b/apps/backend/src/core/domain/entities/user-service-credential.entity.ts index d858d32..a87af79 100644 --- a/apps/backend/src/core/domain/entities/user-service-credential.entity.ts +++ b/apps/backend/src/core/domain/entities/user-service-credential.entity.ts @@ -41,9 +41,9 @@ export class UserServiceCredential { @Column({ type: 'timestamptz', nullable: true, name: 'expires_at' }) expiresAt!: Date | null; - @CreateDateColumn({ type: 'timestamptz' }) + @CreateDateColumn({ type: 'timestamptz', name: 'created_at' }) createdAt!: Date; - @UpdateDateColumn({ type: 'timestamptz' }) + @UpdateDateColumn({ type: 'timestamptz', name: 'updated_at' }) updatedAt!: Date; } diff --git a/apps/backend/src/core/domain/entities/user.entity.ts b/apps/backend/src/core/domain/entities/user.entity.ts index 6e9efcd..2c680db 100644 --- a/apps/backend/src/core/domain/entities/user.entity.ts +++ b/apps/backend/src/core/domain/entities/user.entity.ts @@ -21,13 +21,13 @@ export class User { @PrimaryGeneratedColumn('uuid') id!: string; - @Column({ type: 'uuid' }) - homeId!: string; - @ManyToOne(() => Home, (home) => home.users, { onDelete: 'CASCADE' }) @JoinColumn({ name: 'home_id' }) home!: Home; + @Column({ type: 'uuid', name: 'home_id' }) + homeId!: string; + @Column({ type: 'varchar', length: 255, unique: true }) email!: string; @@ -46,9 +46,9 @@ export class User { @OneToMany(() => UserServiceCredential, (cred) => cred.user) serviceCredentials!: UserServiceCredential[]; - @CreateDateColumn({ type: 'timestamptz' }) + @CreateDateColumn({ type: 'timestamptz', name: 'created_at' }) createdAt!: Date; - @UpdateDateColumn({ type: 'timestamptz' }) + @UpdateDateColumn({ type: 'timestamptz', name: 'updated_at' }) updatedAt!: Date; } diff --git a/apps/backend/src/core/services/auth.service.ts b/apps/backend/src/core/services/auth.service.ts new file mode 100644 index 0000000..c743b24 --- /dev/null +++ b/apps/backend/src/core/services/auth.service.ts @@ -0,0 +1,112 @@ +import { Injectable, UnauthorizedException, ConflictException } from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import * as bcrypt from 'bcrypt'; +import * as crypto from 'crypto'; +import { UserService } from './user.service'; +import { HomeService } from './home.service'; +import { DeviceService } from './device.service'; +import { UserRole } from '../domain/entities/user.entity'; +import { JwtPayload } from '../../adapters/inbound/rest/auth/strategies/jwt.strategy'; + +export interface AuthTokens { + accessToken: string; + refreshToken: string; +} + +export interface DeviceTokenResult { + deviceId: string; + token: string; +} + +@Injectable() +export class AuthService { + constructor( + private readonly userService: UserService, + private readonly homeService: HomeService, + private readonly deviceService: DeviceService, + private readonly jwtService: JwtService, + ) {} + + async register(data: { + email: string; + password: string; + displayName: string; + homeName: string; + }): Promise { + const existing = await this.userService.findByEmail(data.email); + if (existing) { + throw new ConflictException('Email already registered'); + } + + const home = await this.homeService.create(data.homeName); + + const passwordHash = await bcrypt.hash(data.password, 12); + const user = await this.userService.create({ + email: data.email, + passwordHash, + displayName: data.displayName, + homeId: home.id, + role: UserRole.OWNER, + }); + + return this.generateTokens(user.id, user.email, user.homeId); + } + + async login(email: string, password: string): Promise { + const user = await this.userService.findByEmail(email); + if (!user) { + throw new UnauthorizedException('Invalid credentials'); + } + + const valid = await bcrypt.compare(password, user.passwordHash); + if (!valid) { + throw new UnauthorizedException('Invalid credentials'); + } + + return this.generateTokens(user.id, user.email, user.homeId); + } + + async registerDevice(homeId: string, deviceName: string): Promise { + const rawToken = crypto.randomBytes(32).toString('hex'); + const tokenHash = await bcrypt.hash(rawToken, 10); + + const device = await this.deviceService.create({ + name: deviceName, + homeId, + tokenHash, + }); + + const token = this.jwtService.sign( + { sub: device.id, email: '', homeId, type: 'device' }, + { expiresIn: '365d' as const }, + ); + + return { deviceId: device.id, token }; + } + + async refreshAccessToken(refreshToken: string): Promise { + try { + const payload = this.jwtService.verify(refreshToken); + if (payload.type !== 'user') { + throw new UnauthorizedException('Invalid refresh token'); + } + return this.generateTokens(payload.sub, payload.email, payload.homeId); + } catch { + throw new UnauthorizedException('Invalid refresh token'); + } + } + + private generateTokens(userId: string, email: string, homeId: string): AuthTokens { + const payload = { sub: userId, email, homeId, type: 'user' } as Record; + + const accessToken = this.jwtService.sign(payload, { + expiresIn: '15m' as const, + }); + + const refreshToken = this.jwtService.sign(payload, { + expiresIn: '7d' as const, + }); + + return { accessToken, refreshToken }; + } +} diff --git a/apps/backend/src/core/services/device.service.ts b/apps/backend/src/core/services/device.service.ts new file mode 100644 index 0000000..05897cd --- /dev/null +++ b/apps/backend/src/core/services/device.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import * as bcrypt from 'bcrypt'; +import { Device } from '../domain/entities/device.entity'; + +@Injectable() +export class DeviceService { + constructor( + @InjectRepository(Device) + private readonly deviceRepository: Repository, + ) {} + + async findById(id: string): Promise { + return this.deviceRepository.findOne({ where: { id } }); + } + + async findByHomeId(homeId: string): Promise { + return this.deviceRepository.find({ where: { homeId } }); + } + + async create(data: { name: string; homeId: string; tokenHash: string }): Promise { + const device = this.deviceRepository.create({ + name: data.name, + homeId: data.homeId, + deviceTokenHash: data.tokenHash, + }); + return this.deviceRepository.save(device); + } + + async validateToken(deviceId: string, token: string): Promise { + const device = await this.findById(deviceId); + if (!device) return false; + return bcrypt.compare(token, device.deviceTokenHash); + } + + async updateLastSeen(id: string): Promise { + await this.deviceRepository.update(id, { lastSeenAt: new Date() }); + } +} diff --git a/apps/backend/src/core/services/home.service.ts b/apps/backend/src/core/services/home.service.ts new file mode 100644 index 0000000..0c24277 --- /dev/null +++ b/apps/backend/src/core/services/home.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Home } from '../domain/entities/home.entity'; + +@Injectable() +export class HomeService { + constructor( + @InjectRepository(Home) + private readonly homeRepository: Repository, + ) {} + + async create(name: string): Promise { + const home = this.homeRepository.create({ name }); + return this.homeRepository.save(home); + } + + async findById(id: string): Promise { + return this.homeRepository.findOne({ where: { id } }); + } +} diff --git a/apps/backend/src/core/services/user.service.ts b/apps/backend/src/core/services/user.service.ts new file mode 100644 index 0000000..f431443 --- /dev/null +++ b/apps/backend/src/core/services/user.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { User } from '../domain/entities/user.entity'; + +@Injectable() +export class UserService { + constructor( + @InjectRepository(User) + private readonly userRepository: Repository, + ) {} + + async findById(id: string): Promise { + return this.userRepository.findOne({ where: { id } }); + } + + async findByEmail(email: string): Promise { + return this.userRepository.findOne({ where: { email } }); + } + + async create(data: Partial): Promise { + const user = this.userRepository.create(data); + return this.userRepository.save(user); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0af5dfe..b7f3ab6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,12 +19,21 @@ importers: '@nestjs/core': specifier: ^11.1.17 version: 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/jwt': + specifier: ^11.0.2 + version: 11.0.2(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)) + '@nestjs/passport': + specifier: ^11.0.5 + version: 11.0.5(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0) '@nestjs/platform-express': specifier: ^11.1.17 version: 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17) '@nestjs/typeorm': specifier: ^11.0.0 version: 11.0.0(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(ioredis@5.10.1)(pg@8.20.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.8.3))) + bcrypt: + specifier: ^6.0.0 + version: 6.0.0 class-transformer: specifier: ^0.5.1 version: 0.5.1 @@ -37,6 +46,12 @@ importers: ioredis: specifier: ^5.10.1 version: 5.10.1 + passport: + specifier: ^0.7.0 + version: 0.7.0 + passport-jwt: + specifier: ^4.0.1 + version: 4.0.1 pg: specifier: ^8.20.0 version: 8.20.0 @@ -59,12 +74,18 @@ importers: '@nestjs/testing': specifier: ^11.1.17 version: 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)(@nestjs/platform-express@11.1.17) + '@types/bcrypt': + specifier: ^6.0.0 + version: 6.0.0 '@types/jest': specifier: ^30.0.0 version: 30.0.0 '@types/node': specifier: ^25.5.0 version: 25.5.0 + '@types/passport-jwt': + specifier: ^4.0.1 + version: 4.0.1 '@typescript-eslint/eslint-plugin': specifier: ^8.57.2 version: 8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0)(typescript@5.8.3))(eslint@10.1.0)(typescript@5.8.3) @@ -841,6 +862,17 @@ packages: '@nestjs/websockets': optional: true + '@nestjs/jwt@11.0.2': + resolution: {integrity: sha512-rK8aE/3/Ma45gAWfCksAXUNbOoSOUudU0Kn3rT39htPF7wsYXtKfjALKeKKJbFrIWbLjsbqfXX5bIJNvgBugGA==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + + '@nestjs/passport@11.0.5': + resolution: {integrity: sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==} + peerDependencies: + '@nestjs/common': ^10.0.0 || ^11.0.0 + passport: ^0.5.0 || ^0.6.0 || ^0.7.0 + '@nestjs/platform-express@11.1.17': resolution: {integrity: sha512-mAf4eOsSBsTOn/VbrUO1gsjW6dVh91qqXPMXun4dN8SnNjf7PTQagM9o8d6ab8ZBpNe6UdZftdrZoDetU+n4Qg==} peerDependencies: @@ -933,6 +965,15 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/bcrypt@6.0.0': + resolution: {integrity: sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -945,6 +986,15 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/express-serve-static-core@5.1.1': + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} + + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -960,9 +1010,36 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/jsonwebtoken@9.0.10': + resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node@25.5.0': resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} + '@types/passport-jwt@4.0.1': + resolution: {integrity: sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==} + + '@types/passport-strategy@0.2.38': + resolution: {integrity: sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==} + + '@types/passport@1.0.17': + resolution: {integrity: sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==} + + '@types/qs@6.15.0': + resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -1349,6 +1426,10 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + bcrypt@6.0.0: + resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==} + engines: {node: '>= 18'} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -1378,6 +1459,9 @@ packages: bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -1633,6 +1717,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -2273,6 +2360,16 @@ packages: jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} + engines: {node: '>=12', npm: '>=6'} + + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -2309,12 +2406,33 @@ packages: lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash@4.17.23: resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} @@ -2435,9 +2553,17 @@ packages: node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + node-addon-api@8.7.0: + resolution: {integrity: sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==} + engines: {node: ^18 || ^20 || >= 21} + node-emoji@1.11.0: resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -2514,6 +2640,17 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + passport-jwt@4.0.1: + resolution: {integrity: sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==} + + passport-strategy@1.0.0: + resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==} + engines: {node: '>= 0.4.0'} + + passport@0.7.0: + resolution: {integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==} + engines: {node: '>= 0.4.0'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2541,6 +2678,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pause@0.0.1: + resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} + pg-cloudflare@1.3.0: resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} @@ -3144,6 +3284,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + uuid@11.1.0: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true @@ -4085,6 +4229,17 @@ snapshots: optionalDependencies: '@nestjs/platform-express': 11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17) + '@nestjs/jwt@11.0.2(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))': + dependencies: + '@nestjs/common': 11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@types/jsonwebtoken': 9.0.10 + jsonwebtoken: 9.0.3 + + '@nestjs/passport@11.0.5(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)': + dependencies: + '@nestjs/common': 11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + passport: 0.7.0 + '@nestjs/platform-express@11.1.17(@nestjs/common@11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.17)': dependencies: '@nestjs/common': 11.1.17(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -4199,6 +4354,19 @@ snapshots: dependencies: '@babel/types': 7.29.0 + '@types/bcrypt@6.0.0': + dependencies: + '@types/node': 25.5.0 + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 25.5.0 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 25.5.0 + '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 @@ -4213,6 +4381,21 @@ snapshots: '@types/estree@1.0.8': {} + '@types/express-serve-static-core@5.1.1': + dependencies: + '@types/node': 25.5.0 + '@types/qs': 6.15.0 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@5.0.6': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.1 + '@types/serve-static': 2.2.0 + + '@types/http-errors@2.0.5': {} + '@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-report@3.0.3': @@ -4230,10 +4413,44 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/jsonwebtoken@9.0.10': + dependencies: + '@types/ms': 2.1.0 + '@types/node': 25.5.0 + + '@types/ms@2.1.0': {} + '@types/node@25.5.0': dependencies: undici-types: 7.18.2 + '@types/passport-jwt@4.0.1': + dependencies: + '@types/jsonwebtoken': 9.0.10 + '@types/passport-strategy': 0.2.38 + + '@types/passport-strategy@0.2.38': + dependencies: + '@types/express': 5.0.6 + '@types/passport': 1.0.17 + + '@types/passport@1.0.17': + dependencies: + '@types/express': 5.0.6 + + '@types/qs@6.15.0': {} + + '@types/range-parser@1.2.7': {} + + '@types/send@1.2.1': + dependencies: + '@types/node': 25.5.0 + + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 25.5.0 + '@types/stack-utils@2.0.3': {} '@types/validator@13.15.10': {} @@ -4636,6 +4853,11 @@ snapshots: baseline-browser-mapping@2.10.11: {} + bcrypt@6.0.0: + dependencies: + node-addon-api: 8.7.0 + node-gyp-build: 4.8.4 + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -4685,6 +4907,8 @@ snapshots: dependencies: node-int64: 0.4.0 + buffer-equal-constant-time@1.0.1: {} + buffer-from@1.1.2: {} buffer@5.7.1: @@ -4892,6 +5116,10 @@ snapshots: eastasianwidth@0.2.0: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + ee-first@1.1.1: {} electron-to-chromium@1.5.328: {} @@ -5778,6 +6006,30 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonwebtoken@9.0.3: + dependencies: + jws: 4.0.1 + 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.1.1 + ms: 2.1.3 + semver: 7.7.4 + + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -5807,10 +6059,24 @@ snapshots: lodash.defaults@4.2.0: {} + lodash.includes@4.3.0: {} + lodash.isarguments@3.1.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.memoize@4.1.2: {} + lodash.once@4.1.1: {} + lodash@4.17.23: {} log-symbols@4.1.0: @@ -5905,10 +6171,14 @@ snapshots: node-abort-controller@3.1.1: {} + node-addon-api@8.7.0: {} + node-emoji@1.11.0: dependencies: lodash: 4.17.23 + node-gyp-build@4.8.4: {} + node-int64@0.4.0: {} node-releases@2.0.36: {} @@ -5989,6 +6259,19 @@ snapshots: parseurl@1.3.3: {} + passport-jwt@4.0.1: + dependencies: + jsonwebtoken: 9.0.3 + passport-strategy: 1.0.0 + + passport-strategy@1.0.0: {} + + passport@0.7.0: + dependencies: + passport-strategy: 1.0.0 + pause: 0.0.1 + utils-merge: 1.0.1 + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -6009,6 +6292,8 @@ snapshots: path-type@4.0.0: {} + pause@0.0.1: {} + pg-cloudflare@1.3.0: optional: true @@ -6568,6 +6853,8 @@ snapshots: util-deprecate@1.0.2: {} + utils-merge@1.0.1: {} + uuid@11.1.0: {} v8-compile-cache-lib@3.0.1: {}