diff --git a/server/src/services/user.service.spec.ts b/server/src/services/user.service.spec.ts index d07e69df6e..007b56b212 100644 --- a/server/src/services/user.service.spec.ts +++ b/server/src/services/user.service.spec.ts @@ -274,7 +274,7 @@ describe(UserService.name, () => { }); describe('setLicense', () => { - it('should save license if valid', async () => { + it('should save client license if valid', async () => { userMock.upsertMetadata.mockResolvedValue(); const license = { licenseKey: 'IMCL-license-key', activationKey: 'activation-key' }; @@ -286,6 +286,18 @@ describe(UserService.name, () => { }); }); + it('should save server license as client if valid', async () => { + userMock.upsertMetadata.mockResolvedValue(); + + const license = { licenseKey: 'IMSV-license-key', activationKey: 'activation-key' }; + await sut.setLicense(authStub.user1, license); + + expect(userMock.upsertMetadata).toHaveBeenCalledWith(authStub.user1.user.id, { + key: UserMetadataKey.LICENSE, + value: expect.any(Object), + }); + }); + it('should not save license if invalid', async () => { userMock.upsertMetadata.mockResolvedValue(); diff --git a/server/src/services/user.service.ts b/server/src/services/user.service.ts index 641f75e9e1..03aee5c00b 100644 --- a/server/src/services/user.service.ts +++ b/server/src/services/user.service.ts @@ -1,6 +1,6 @@ import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common'; import { DateTime } from 'luxon'; -import { getClientLicensePublicKey } from 'src/config'; +import { getClientLicensePublicKey, getServerLicensePublicKey } from 'src/config'; import { SALT_ROUNDS } from 'src/constants'; import { StorageCore, StorageFolder } from 'src/cores/storage.core'; import { SystemConfigCore } from 'src/cores/system-config.core'; @@ -138,16 +138,23 @@ export class UserService { } async setLicense(auth: AuthDto, license: LicenseKeyDto): Promise { - if (!license.licenseKey.startsWith('IMCL-')) { + if (!license.licenseKey.startsWith('IMCL-') && !license.licenseKey.startsWith('IMSV-')) { throw new BadRequestException('Invalid license key'); } - const licenseValid = this.cryptoRepository.verifySha256( + + const clientLicenseValid = this.cryptoRepository.verifySha256( license.licenseKey, license.activationKey, getClientLicensePublicKey(), ); - if (!licenseValid) { + const serverLicenseValid = this.cryptoRepository.verifySha256( + license.licenseKey, + license.activationKey, + getServerLicensePublicKey(), + ); + + if (!clientLicenseValid && !serverLicenseValid) { throw new BadRequestException('Invalid license key'); } diff --git a/web/src/lib/utils/license-utils.ts b/web/src/lib/utils/license-utils.ts index 54be1559d9..077476d75c 100644 --- a/web/src/lib/utils/license-utils.ts +++ b/web/src/lib/utils/license-utils.ts @@ -3,11 +3,14 @@ import type { ImmichLicense } from '$lib/constants'; import { serverConfig } from '$lib/stores/server-config.store'; import { setServerLicense, setUserLicense, type LicenseResponseDto } from '@immich/sdk'; import { get } from 'svelte/store'; +import { loadUser } from './auth'; export const activateLicense = async (licenseKey: string, activationKey: string): Promise => { - const isServerKey = licenseKey.search('IMSV') !== -1; + // Send server key to user activation if user is not admin + const user = await loadUser(); + const isServerActivation = user?.isAdmin && licenseKey.search('IMSV') !== -1; const licenseKeyDto = { licenseKey, activationKey }; - return isServerKey ? setServerLicense({ licenseKeyDto }) : setUserLicense({ licenseKeyDto }); + return isServerActivation ? setServerLicense({ licenseKeyDto }) : setUserLicense({ licenseKeyDto }); }; export const getActivationKey = async (licenseKey: string): Promise => {