mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:39:37 -05:00 
			
		
		
		
	refactor(server): new password repo method (#8208)
This commit is contained in:
		
							parent
							
								
									604b8ff17c
								
							
						
					
					
						commit
						787eebcf1e
					
				@ -3,6 +3,8 @@ import { readFileSync } from 'node:fs';
 | 
				
			|||||||
import { join } from 'node:path';
 | 
					import { join } from 'node:path';
 | 
				
			||||||
import { Version } from 'src/utils/version';
 | 
					import { Version } from 'src/utils/version';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const SALT_ROUNDS = 10;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { version } = JSON.parse(readFileSync('./package.json', 'utf8'));
 | 
					const { version } = JSON.parse(readFileSync('./package.json', 'utf8'));
 | 
				
			||||||
export const serverVersion = Version.fromString(version);
 | 
					export const serverVersion = Version.fromString(version);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
import { BadRequestException, ForbiddenException } from '@nestjs/common';
 | 
					import { BadRequestException, ForbiddenException } from '@nestjs/common';
 | 
				
			||||||
import sanitize from 'sanitize-filename';
 | 
					import sanitize from 'sanitize-filename';
 | 
				
			||||||
 | 
					import { SALT_ROUNDS } from 'src/constants';
 | 
				
			||||||
import { UserResponseDto } from 'src/dtos/user.dto';
 | 
					import { UserResponseDto } from 'src/dtos/user.dto';
 | 
				
			||||||
import { LibraryType } from 'src/entities/library.entity';
 | 
					import { LibraryType } from 'src/entities/library.entity';
 | 
				
			||||||
import { UserEntity } from 'src/entities/user.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 { ILibraryRepository } from 'src/interfaces/library.interface';
 | 
				
			||||||
import { IUserRepository } from 'src/interfaces/user.interface';
 | 
					import { IUserRepository } from 'src/interfaces/user.interface';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const SALT_ROUNDS = 10;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
let instance: UserCore | null;
 | 
					let instance: UserCore | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class UserCore {
 | 
					export class UserCore {
 | 
				
			||||||
 | 
				
			|||||||
@ -8,4 +8,5 @@ export interface ICryptoRepository {
 | 
				
			|||||||
  hashSha1(data: string | Buffer): Buffer;
 | 
					  hashSha1(data: string | Buffer): Buffer;
 | 
				
			||||||
  hashBcrypt(data: string | Buffer, saltOrRounds: string | number): Promise<string>;
 | 
					  hashBcrypt(data: string | Buffer, saltOrRounds: string | number): Promise<string>;
 | 
				
			||||||
  compareBcrypt(data: string | Buffer, encrypted: string): boolean;
 | 
					  compareBcrypt(data: string | Buffer, encrypted: string): boolean;
 | 
				
			||||||
 | 
					  newPassword(bytes: number): string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -41,4 +41,8 @@ export class CryptoRepository implements ICryptoRepository {
 | 
				
			|||||||
      stream.on('end', () => resolve(hash.digest()));
 | 
					      stream.on('end', () => resolve(hash.digest()));
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  newPassword(bytes: number) {
 | 
				
			||||||
 | 
					    return randomBytes(bytes).toString('base64').replaceAll(/\W/g, '');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -27,7 +27,7 @@ describe(APIKeyService.name, () => {
 | 
				
			|||||||
        name: 'Test Key',
 | 
					        name: 'Test Key',
 | 
				
			||||||
        userId: authStub.admin.user.id,
 | 
					        userId: authStub.admin.user.id,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      expect(cryptoMock.randomBytes).toHaveBeenCalled();
 | 
					      expect(cryptoMock.newPassword).toHaveBeenCalled();
 | 
				
			||||||
      expect(cryptoMock.hashSha256).toHaveBeenCalled();
 | 
					      expect(cryptoMock.hashSha256).toHaveBeenCalled();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -41,7 +41,7 @@ describe(APIKeyService.name, () => {
 | 
				
			|||||||
        name: 'API Key',
 | 
					        name: 'API Key',
 | 
				
			||||||
        userId: authStub.admin.user.id,
 | 
					        userId: authStub.admin.user.id,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      expect(cryptoMock.randomBytes).toHaveBeenCalled();
 | 
					      expect(cryptoMock.newPassword).toHaveBeenCalled();
 | 
				
			||||||
      expect(cryptoMock.hashSha256).toHaveBeenCalled();
 | 
					      expect(cryptoMock.hashSha256).toHaveBeenCalled();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@ export class APIKeyService {
 | 
				
			|||||||
  ) {}
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async create(auth: AuthDto, dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
 | 
					  async create(auth: AuthDto, dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
 | 
				
			||||||
    const secret = this.crypto.randomBytes(32).toString('base64').replaceAll(/\W/g, '');
 | 
					    const secret = this.crypto.newPassword(32);
 | 
				
			||||||
    const entity = await this.repository.create({
 | 
					    const entity = await this.repository.create({
 | 
				
			||||||
      key: this.crypto.hashSha256(secret),
 | 
					      key: this.crypto.hashSha256(secret),
 | 
				
			||||||
      name: dto.name || 'API Key',
 | 
					      name: dto.name || 'API Key',
 | 
				
			||||||
 | 
				
			|||||||
@ -146,7 +146,6 @@ export class AuthService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  async adminSignUp(dto: SignUpDto): Promise<UserResponseDto> {
 | 
					  async adminSignUp(dto: SignUpDto): Promise<UserResponseDto> {
 | 
				
			||||||
    const adminUser = await this.userRepository.getAdmin();
 | 
					    const adminUser = await this.userRepository.getAdmin();
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (adminUser) {
 | 
					    if (adminUser) {
 | 
				
			||||||
      throw new BadRequestException('The server already has an admin');
 | 
					      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) {
 | 
					  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);
 | 
					    const token = this.cryptoRepository.hashSha256(key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await this.userTokenRepository.create({
 | 
					    await this.userTokenRepository.create({
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,5 @@
 | 
				
			|||||||
import { BadRequestException, ForbiddenException, Inject, Injectable, NotFoundException } from '@nestjs/common';
 | 
					import { BadRequestException, ForbiddenException, Inject, Injectable, NotFoundException } from '@nestjs/common';
 | 
				
			||||||
import { DateTime } from 'luxon';
 | 
					import { DateTime } from 'luxon';
 | 
				
			||||||
import { randomBytes } from 'node:crypto';
 | 
					 | 
				
			||||||
import { StorageCore, StorageFolder } from 'src/cores/storage.core';
 | 
					import { StorageCore, StorageFolder } from 'src/cores/storage.core';
 | 
				
			||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
 | 
					import { SystemConfigCore } from 'src/cores/system-config.core';
 | 
				
			||||||
import { UserCore } from 'src/cores/user.core';
 | 
					import { UserCore } from 'src/cores/user.core';
 | 
				
			||||||
@ -26,7 +25,7 @@ export class UserService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    @Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
 | 
					    @Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
 | 
				
			||||||
    @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
 | 
					    @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
 | 
				
			||||||
    @Inject(IJobRepository) private jobRepository: IJobRepository,
 | 
					    @Inject(IJobRepository) private jobRepository: IJobRepository,
 | 
				
			||||||
    @Inject(ILibraryRepository) libraryRepository: ILibraryRepository,
 | 
					    @Inject(ILibraryRepository) libraryRepository: ILibraryRepository,
 | 
				
			||||||
    @Inject(IStorageRepository) private storageRepository: IStorageRepository,
 | 
					    @Inject(IStorageRepository) private storageRepository: IStorageRepository,
 | 
				
			||||||
@ -132,7 +131,7 @@ export class UserService {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const providedPassword = await ask(mapUser(admin));
 | 
					    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 });
 | 
					    await this.userCore.updateUser(admin, admin.id, { password });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -9,5 +9,6 @@ export const newCryptoRepositoryMock = (): jest.Mocked<ICryptoRepository> => {
 | 
				
			|||||||
    hashSha256: jest.fn().mockImplementation((input) => `${input} (hashed)`),
 | 
					    hashSha256: jest.fn().mockImplementation((input) => `${input} (hashed)`),
 | 
				
			||||||
    hashSha1: jest.fn().mockImplementation((input) => Buffer.from(`${input.toString()} (hashed)`)),
 | 
					    hashSha1: jest.fn().mockImplementation((input) => Buffer.from(`${input.toString()} (hashed)`)),
 | 
				
			||||||
    hashFile: jest.fn().mockImplementation((input) => `${input} (file-hashed)`),
 | 
					    hashFile: jest.fn().mockImplementation((input) => `${input} (file-hashed)`),
 | 
				
			||||||
 | 
					    newPassword: jest.fn().mockReturnValue(Buffer.from('random-bytes').toString('base64')),
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user