diff --git a/server/src/app.module.ts b/server/src/app.module.ts index 247958b686..ae267c82b4 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -9,8 +9,9 @@ import { commandsAndQuestions } from 'src/commands'; import { IWorker } from 'src/constants'; import { controllers } from 'src/controllers'; import { StorageCore } from 'src/cores/storage.core'; -import { ImmichWorker } from 'src/enum'; +import { ImmichWorker, SystemMetadataKey } from 'src/enum'; import { MaintenanceAuthGuard } from 'src/maintenance/maintenance-auth.guard'; +import { MaintenanceEphemeralStateRepository } from 'src/maintenance/maintenance-ephemeral-state.repository'; import { MaintenanceWebsocketRepository } from 'src/maintenance/maintenance-websocket.repository'; import { MaintenanceWorkerController } from 'src/maintenance/maintenance-worker.controller'; import { MaintenanceWorkerService } from 'src/maintenance/maintenance-worker.service'; @@ -34,6 +35,7 @@ import { services } from 'src/services'; import { AuthService } from 'src/services/auth.service'; import { CliService } from 'src/services/cli.service'; import { QueueService } from 'src/services/queue.service'; +import { MaintenanceModeState } from 'src/types'; import { getKyselyConfig } from 'src/utils/database'; const common = [...repositories, ...services, GlobalExceptionFilter]; @@ -113,6 +115,7 @@ export class ApiModule extends BaseModule {} SystemMetadataRepository, AppRepository, MaintenanceWebsocketRepository, + MaintenanceEphemeralStateRepository, MaintenanceWorkerService, ...commonMiddleware, { provide: APP_GUARD, useClass: MaintenanceAuthGuard }, @@ -123,13 +126,20 @@ export class MaintenanceModule { constructor( @Inject(IWorker) private worker: ImmichWorker, logger: LoggingRepository, + private systemMetadataRepository: SystemMetadataRepository, private maintenanceWorkerService: MaintenanceWorkerService, private maintenanceWebsocketRepository: MaintenanceWebsocketRepository, + private maintenanceEphemeralStateRepository: MaintenanceEphemeralStateRepository, ) { logger.setAppName(this.worker); } async onModuleInit() { + const state = (await this.systemMetadataRepository.get( + SystemMetadataKey.MaintenanceMode, + )) as MaintenanceModeState & { isMaintenanceMode: true }; + + this.maintenanceEphemeralStateRepository.setSecret(state.secret); StorageCore.setMediaLocation(this.maintenanceWorkerService.detectMediaLocation()); this.maintenanceWebsocketRepository.setAuthFn(async (client) => diff --git a/server/src/maintenance/maintenance-ephemeral-state.repository.ts b/server/src/maintenance/maintenance-ephemeral-state.repository.ts new file mode 100644 index 0000000000..c19ccca17e --- /dev/null +++ b/server/src/maintenance/maintenance-ephemeral-state.repository.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class MaintenanceEphemeralStateRepository { + #secret: string = null!; + + setSecret(secret: string) { + this.#secret = secret; + } + + getSecret() { + return this.#secret; + } +} diff --git a/server/src/maintenance/maintenance-worker.service.spec.ts b/server/src/maintenance/maintenance-worker.service.spec.ts index 8a68025678..877b160c56 100644 --- a/server/src/maintenance/maintenance-worker.service.spec.ts +++ b/server/src/maintenance/maintenance-worker.service.spec.ts @@ -1,6 +1,7 @@ import { UnauthorizedException } from '@nestjs/common'; import { SignJWT } from 'jose'; import { MaintenanceAction, SystemMetadataKey } from 'src/enum'; +import { MaintenanceEphemeralStateRepository } from 'src/maintenance/maintenance-ephemeral-state.repository'; import { MaintenanceWebsocketRepository } from 'src/maintenance/maintenance-websocket.repository'; import { MaintenanceWorkerService } from 'src/maintenance/maintenance-worker.service'; import { automock, getMocks, ServiceMocks } from 'test/utils'; @@ -8,17 +9,28 @@ import { automock, getMocks, ServiceMocks } from 'test/utils'; describe(MaintenanceWorkerService.name, () => { let sut: MaintenanceWorkerService; let mocks: ServiceMocks; - let maintenanceWorkerRepositoryMock: MaintenanceWebsocketRepository; + let maintenanceWebsocketRepositoryMock: MaintenanceWebsocketRepository; + let maintenanceEphemeralStateRepositoryMock: MaintenanceEphemeralStateRepository; beforeEach(() => { mocks = getMocks(); - maintenanceWorkerRepositoryMock = automock(MaintenanceWebsocketRepository, { args: [mocks.logger], strict: false }); + maintenanceWebsocketRepositoryMock = automock(MaintenanceWebsocketRepository, { + args: [mocks.logger], + strict: false, + }); + + maintenanceEphemeralStateRepositoryMock = automock(MaintenanceEphemeralStateRepository, { + args: [mocks.logger], + strict: false, + }); + sut = new MaintenanceWorkerService( mocks.logger as never, mocks.app, mocks.config, mocks.systemMetadata as never, - maintenanceWorkerRepositoryMock, + maintenanceWebsocketRepositoryMock, + maintenanceEphemeralStateRepositoryMock, mocks.storage as never, mocks.process, mocks.database as never, @@ -144,11 +156,11 @@ describe(MaintenanceWorkerService.name, () => { isMaintenanceMode: false, }); - expect(maintenanceWorkerRepositoryMock.clientBroadcast).toHaveBeenCalledWith('AppRestartV1', { + expect(maintenanceWebsocketRepositoryMock.clientBroadcast).toHaveBeenCalledWith('AppRestartV1', { isMaintenanceMode: false, }); - expect(maintenanceWorkerRepositoryMock.serverSend).toHaveBeenCalledWith('AppRestart', { + expect(maintenanceWebsocketRepositoryMock.serverSend).toHaveBeenCalledWith('AppRestart', { isMaintenanceMode: false, }); }); diff --git a/server/src/maintenance/maintenance-worker.service.ts b/server/src/maintenance/maintenance-worker.service.ts index a251d8b953..b63e7e5ec4 100644 --- a/server/src/maintenance/maintenance-worker.service.ts +++ b/server/src/maintenance/maintenance-worker.service.ts @@ -5,7 +5,9 @@ import { jwtVerify } from 'jose'; import { readFileSync } from 'node:fs'; import { IncomingHttpHeaders } from 'node:http'; import { MaintenanceAuthDto } from 'src/dtos/maintenance.dto'; +import { ServerConfigDto } from 'src/dtos/server.dto'; import { ImmichCookie, SystemMetadataKey } from 'src/enum'; +import { MaintenanceEphemeralStateRepository } from 'src/maintenance/maintenance-ephemeral-state.repository'; import { MaintenanceWebsocketRepository } from 'src/maintenance/maintenance-websocket.repository'; import { AppRepository } from 'src/repositories/app.repository'; import { ConfigRepository } from 'src/repositories/config.repository'; @@ -34,6 +36,7 @@ export class MaintenanceWorkerService { private configRepository: ConfigRepository, private systemMetadataRepository: SystemMetadataRepository, private maintenanceWorkerRepository: MaintenanceWebsocketRepository, + private maintenanceEphemeralStateRepository: MaintenanceEphemeralStateRepository, private storageRepository: StorageRepository, private processRepository: ProcessRepository, private databaseRepository: DatabaseRepository, @@ -63,21 +66,9 @@ export class MaintenanceWorkerService { * {@link _ServerService.getSystemConfig} */ async getSystemConfig() { - const config = await this.getConfig({ withCache: false }); - return { - loginPageMessage: config.server.loginPageMessage, - trashDays: config.trash.days, - userDeleteDelay: config.user.deleteDelay, - oauthButtonText: config.oauth.buttonText, - isInitialized: true, - isOnboarded: true, - externalDomain: config.server.externalDomain, - publicUsers: config.server.publicUsers, - mapDarkStyleUrl: config.map.darkStyle, - mapLightStyleUrl: config.map.lightStyle, maintenanceMode: true, - }; + } as ServerConfigDto; } /** @@ -139,14 +130,6 @@ export class MaintenanceWorkerService { return '/usr/src/app/upload'; } - private async secret(): Promise { - const state = (await this.systemMetadataRepository.get(SystemMetadataKey.MaintenanceMode)) as { - secret: string; - }; - - return state.secret; - } - async logSecret(): Promise { const { server } = await this.getConfig({ withCache: true }); @@ -156,7 +139,7 @@ export class MaintenanceWorkerService { { username: 'immich-admin', }, - await this.secret(), + this.maintenanceEphemeralStateRepository.getSecret(), ); this.logger.log(`\n\n🚧 Immich is in maintenance mode, you can log in using the following URL:\n${url}\n`); @@ -172,7 +155,7 @@ export class MaintenanceWorkerService { throw new UnauthorizedException('Missing JWT Token'); } - const secret = await this.secret(); + const secret = this.maintenanceEphemeralStateRepository.getSecret(); try { const result = await jwtVerify(jwt, new TextEncoder().encode(secret));