From 787eebcf1e09404b1169105bce0f3f6fbc6e300c Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Sat, 23 Mar 2024 14:33:25 -0400 Subject: [PATCH] refactor(server): new password repo method (#8208) --- server/src/constants.ts | 2 ++ server/src/cores/user.core.ts | 3 +-- server/src/interfaces/crypto.interface.ts | 1 + server/src/repositories/crypto.repository.ts | 4 ++++ server/src/services/api-key.service.spec.ts | 4 ++-- server/src/services/api-key.service.ts | 2 +- server/src/services/auth.service.ts | 3 +-- server/src/services/user.service.ts | 5 ++--- server/test/repositories/crypto.repository.mock.ts | 1 + 9 files changed, 15 insertions(+), 10 deletions(-) diff --git a/server/src/constants.ts b/server/src/constants.ts index f6cef9059f..1289701dd8 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -3,6 +3,8 @@ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { Version } from 'src/utils/version'; +export const SALT_ROUNDS = 10; + const { version } = JSON.parse(readFileSync('./package.json', 'utf8')); export const serverVersion = Version.fromString(version); diff --git a/server/src/cores/user.core.ts b/server/src/cores/user.core.ts index 4d7da25ded..e8596db3e7 100644 --- a/server/src/cores/user.core.ts +++ b/server/src/cores/user.core.ts @@ -1,5 +1,6 @@ import { BadRequestException, ForbiddenException } from '@nestjs/common'; import sanitize from 'sanitize-filename'; +import { SALT_ROUNDS } from 'src/constants'; import { UserResponseDto } from 'src/dtos/user.dto'; import { LibraryType } from 'src/entities/library.entity'; import { UserEntity } from 'src/entities/user.entity'; @@ -7,8 +8,6 @@ import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { ILibraryRepository } from 'src/interfaces/library.interface'; import { IUserRepository } from 'src/interfaces/user.interface'; -const SALT_ROUNDS = 10; - let instance: UserCore | null; export class UserCore { diff --git a/server/src/interfaces/crypto.interface.ts b/server/src/interfaces/crypto.interface.ts index c33ee9cd79..e7ad2b045b 100644 --- a/server/src/interfaces/crypto.interface.ts +++ b/server/src/interfaces/crypto.interface.ts @@ -8,4 +8,5 @@ export interface ICryptoRepository { hashSha1(data: string | Buffer): Buffer; hashBcrypt(data: string | Buffer, saltOrRounds: string | number): Promise; compareBcrypt(data: string | Buffer, encrypted: string): boolean; + newPassword(bytes: number): string; } diff --git a/server/src/repositories/crypto.repository.ts b/server/src/repositories/crypto.repository.ts index 84b74052c9..9102715a17 100644 --- a/server/src/repositories/crypto.repository.ts +++ b/server/src/repositories/crypto.repository.ts @@ -41,4 +41,8 @@ export class CryptoRepository implements ICryptoRepository { stream.on('end', () => resolve(hash.digest())); }); } + + newPassword(bytes: number) { + return randomBytes(bytes).toString('base64').replaceAll(/\W/g, ''); + } } diff --git a/server/src/services/api-key.service.spec.ts b/server/src/services/api-key.service.spec.ts index 3c8463c8ff..47fd0f5159 100644 --- a/server/src/services/api-key.service.spec.ts +++ b/server/src/services/api-key.service.spec.ts @@ -27,7 +27,7 @@ describe(APIKeyService.name, () => { name: 'Test Key', userId: authStub.admin.user.id, }); - expect(cryptoMock.randomBytes).toHaveBeenCalled(); + expect(cryptoMock.newPassword).toHaveBeenCalled(); expect(cryptoMock.hashSha256).toHaveBeenCalled(); }); @@ -41,7 +41,7 @@ describe(APIKeyService.name, () => { name: 'API Key', userId: authStub.admin.user.id, }); - expect(cryptoMock.randomBytes).toHaveBeenCalled(); + expect(cryptoMock.newPassword).toHaveBeenCalled(); expect(cryptoMock.hashSha256).toHaveBeenCalled(); }); }); diff --git a/server/src/services/api-key.service.ts b/server/src/services/api-key.service.ts index 5de908b4da..24a57d3651 100644 --- a/server/src/services/api-key.service.ts +++ b/server/src/services/api-key.service.ts @@ -13,7 +13,7 @@ export class APIKeyService { ) {} async create(auth: AuthDto, dto: APIKeyCreateDto): Promise { - const secret = this.crypto.randomBytes(32).toString('base64').replaceAll(/\W/g, ''); + const secret = this.crypto.newPassword(32); const entity = await this.repository.create({ key: this.crypto.hashSha256(secret), name: dto.name || 'API Key', diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts index 8563d83531..19476667c1 100644 --- a/server/src/services/auth.service.ts +++ b/server/src/services/auth.service.ts @@ -146,7 +146,6 @@ export class AuthService { async adminSignUp(dto: SignUpDto): Promise { const adminUser = await this.userRepository.getAdmin(); - if (adminUser) { throw new BadRequestException('The server already has an admin'); } @@ -427,7 +426,7 @@ export class AuthService { } private async createLoginResponse(user: UserEntity, authType: AuthType, loginDetails: LoginDetails) { - const key = this.cryptoRepository.randomBytes(32).toString('base64').replaceAll(/\W/g, ''); + const key = this.cryptoRepository.newPassword(32); const token = this.cryptoRepository.hashSha256(key); await this.userTokenRepository.create({ diff --git a/server/src/services/user.service.ts b/server/src/services/user.service.ts index 6649927da4..a2bc8c7cfc 100644 --- a/server/src/services/user.service.ts +++ b/server/src/services/user.service.ts @@ -1,6 +1,5 @@ import { BadRequestException, ForbiddenException, Inject, Injectable, NotFoundException } from '@nestjs/common'; import { DateTime } from 'luxon'; -import { randomBytes } from 'node:crypto'; import { StorageCore, StorageFolder } from 'src/cores/storage.core'; import { SystemConfigCore } from 'src/cores/system-config.core'; import { UserCore } from 'src/cores/user.core'; @@ -26,7 +25,7 @@ export class UserService { constructor( @Inject(IAlbumRepository) private albumRepository: IAlbumRepository, - @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository, + @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, @Inject(IJobRepository) private jobRepository: IJobRepository, @Inject(ILibraryRepository) libraryRepository: ILibraryRepository, @Inject(IStorageRepository) private storageRepository: IStorageRepository, @@ -132,7 +131,7 @@ export class UserService { } const providedPassword = await ask(mapUser(admin)); - const password = providedPassword || randomBytes(24).toString('base64').replaceAll(/\W/g, ''); + const password = providedPassword || this.cryptoRepository.newPassword(24); await this.userCore.updateUser(admin, admin.id, { password }); diff --git a/server/test/repositories/crypto.repository.mock.ts b/server/test/repositories/crypto.repository.mock.ts index cbd90ec67c..8d13814db9 100644 --- a/server/test/repositories/crypto.repository.mock.ts +++ b/server/test/repositories/crypto.repository.mock.ts @@ -9,5 +9,6 @@ export const newCryptoRepositoryMock = (): jest.Mocked => { hashSha256: jest.fn().mockImplementation((input) => `${input} (hashed)`), hashSha1: jest.fn().mockImplementation((input) => Buffer.from(`${input.toString()} (hashed)`)), hashFile: jest.fn().mockImplementation((input) => `${input} (file-hashed)`), + newPassword: jest.fn().mockReturnValue(Buffer.from('random-bytes').toString('base64')), }; };