From f55d63fae8634f9be7d137ea7b150138f4d661c1 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Sat, 15 Jul 2023 15:50:29 -0400 Subject: [PATCH] feat(server): storage label claim (#3278) * feat: storage label claim * chore: open api --- cli/src/api/open-api/api.ts | 6 ++++++ mobile/openapi/doc/SystemConfigOAuthDto.md | 1 + .../openapi/lib/model/system_config_o_auth_dto.dart | 10 +++++++++- .../openapi/test/system_config_o_auth_dto_test.dart | 5 +++++ server/immich-openapi-specs.json | 4 ++++ server/src/domain/auth/auth.service.ts | 8 ++++++++ .../system-config/dto/system-config-oauth.dto.ts | 3 +++ .../src/domain/system-config/system-config.core.ts | 1 + .../system-config/system-config.service.spec.ts | 1 + server/src/domain/user/user.core.ts | 13 ++++++++----- server/src/infra/entities/system-config.entity.ts | 2 ++ web/src/api/open-api/api.ts | 6 ++++++ .../admin-page/settings/oauth/oauth-settings.svelte | 10 ++++++++++ 13 files changed, 64 insertions(+), 6 deletions(-) diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 3819b021f4..b0bf9a8f2f 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -2596,6 +2596,12 @@ export interface SystemConfigOAuthDto { * @memberof SystemConfigOAuthDto */ 'scope': string; + /** + * + * @type {string} + * @memberof SystemConfigOAuthDto + */ + 'storageLabelClaim': string; /** * * @type {string} diff --git a/mobile/openapi/doc/SystemConfigOAuthDto.md b/mobile/openapi/doc/SystemConfigOAuthDto.md index 745b13b79a..a79b0729f9 100644 --- a/mobile/openapi/doc/SystemConfigOAuthDto.md +++ b/mobile/openapi/doc/SystemConfigOAuthDto.md @@ -13,6 +13,7 @@ Name | Type | Description | Notes **clientId** | **String** | | **clientSecret** | **String** | | **scope** | **String** | | +**storageLabelClaim** | **String** | | **buttonText** | **String** | | **autoRegister** | **bool** | | **autoLaunch** | **bool** | | diff --git a/mobile/openapi/lib/model/system_config_o_auth_dto.dart b/mobile/openapi/lib/model/system_config_o_auth_dto.dart index 364b99fd2f..edfa240eeb 100644 --- a/mobile/openapi/lib/model/system_config_o_auth_dto.dart +++ b/mobile/openapi/lib/model/system_config_o_auth_dto.dart @@ -18,6 +18,7 @@ class SystemConfigOAuthDto { required this.clientId, required this.clientSecret, required this.scope, + required this.storageLabelClaim, required this.buttonText, required this.autoRegister, required this.autoLaunch, @@ -35,6 +36,8 @@ class SystemConfigOAuthDto { String scope; + String storageLabelClaim; + String buttonText; bool autoRegister; @@ -52,6 +55,7 @@ class SystemConfigOAuthDto { other.clientId == clientId && other.clientSecret == clientSecret && other.scope == scope && + other.storageLabelClaim == storageLabelClaim && other.buttonText == buttonText && other.autoRegister == autoRegister && other.autoLaunch == autoLaunch && @@ -66,6 +70,7 @@ class SystemConfigOAuthDto { (clientId.hashCode) + (clientSecret.hashCode) + (scope.hashCode) + + (storageLabelClaim.hashCode) + (buttonText.hashCode) + (autoRegister.hashCode) + (autoLaunch.hashCode) + @@ -73,7 +78,7 @@ class SystemConfigOAuthDto { (mobileRedirectUri.hashCode); @override - String toString() => 'SystemConfigOAuthDto[enabled=$enabled, issuerUrl=$issuerUrl, clientId=$clientId, clientSecret=$clientSecret, scope=$scope, buttonText=$buttonText, autoRegister=$autoRegister, autoLaunch=$autoLaunch, mobileOverrideEnabled=$mobileOverrideEnabled, mobileRedirectUri=$mobileRedirectUri]'; + String toString() => 'SystemConfigOAuthDto[enabled=$enabled, issuerUrl=$issuerUrl, clientId=$clientId, clientSecret=$clientSecret, scope=$scope, storageLabelClaim=$storageLabelClaim, buttonText=$buttonText, autoRegister=$autoRegister, autoLaunch=$autoLaunch, mobileOverrideEnabled=$mobileOverrideEnabled, mobileRedirectUri=$mobileRedirectUri]'; Map toJson() { final json = {}; @@ -82,6 +87,7 @@ class SystemConfigOAuthDto { json[r'clientId'] = this.clientId; json[r'clientSecret'] = this.clientSecret; json[r'scope'] = this.scope; + json[r'storageLabelClaim'] = this.storageLabelClaim; json[r'buttonText'] = this.buttonText; json[r'autoRegister'] = this.autoRegister; json[r'autoLaunch'] = this.autoLaunch; @@ -103,6 +109,7 @@ class SystemConfigOAuthDto { clientId: mapValueOfType(json, r'clientId')!, clientSecret: mapValueOfType(json, r'clientSecret')!, scope: mapValueOfType(json, r'scope')!, + storageLabelClaim: mapValueOfType(json, r'storageLabelClaim')!, buttonText: mapValueOfType(json, r'buttonText')!, autoRegister: mapValueOfType(json, r'autoRegister')!, autoLaunch: mapValueOfType(json, r'autoLaunch')!, @@ -160,6 +167,7 @@ class SystemConfigOAuthDto { 'clientId', 'clientSecret', 'scope', + 'storageLabelClaim', 'buttonText', 'autoRegister', 'autoLaunch', diff --git a/mobile/openapi/test/system_config_o_auth_dto_test.dart b/mobile/openapi/test/system_config_o_auth_dto_test.dart index ca5fadad4a..88c8d2aa85 100644 --- a/mobile/openapi/test/system_config_o_auth_dto_test.dart +++ b/mobile/openapi/test/system_config_o_auth_dto_test.dart @@ -41,6 +41,11 @@ void main() { // TODO }); + // String storageLabelClaim + test('to test the property `storageLabelClaim`', () async { + // TODO + }); + // String buttonText test('to test the property `buttonText`', () async { // TODO diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 2dc23b1a04..c2c006744c 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -6503,6 +6503,9 @@ "scope": { "type": "string" }, + "storageLabelClaim": { + "type": "string" + }, "buttonText": { "type": "string" }, @@ -6525,6 +6528,7 @@ "clientId", "clientSecret", "scope", + "storageLabelClaim", "buttonText", "autoRegister", "autoLaunch", diff --git a/server/src/domain/auth/auth.service.ts b/server/src/domain/auth/auth.service.ts index 200ca0a743..a0c0967260 100644 --- a/server/src/domain/auth/auth.service.ts +++ b/server/src/domain/auth/auth.service.ts @@ -240,11 +240,19 @@ export class AuthService { } this.logger.log(`Registering new user: ${profile.email}/${profile.sub}`); + this.logger.verbose(`OAuth Profile: ${JSON.stringify(profile)}`); + + let storageLabel: string | null = profile[config.oauth.storageLabelClaim as keyof OAuthProfile] as string; + if (typeof storageLabel !== 'string') { + storageLabel = null; + } + user = await this.userCore.createUser({ firstName: profile.given_name || '', lastName: profile.family_name || '', email: profile.email, oauthId: profile.sub, + storageLabel, }); } diff --git a/server/src/domain/system-config/dto/system-config-oauth.dto.ts b/server/src/domain/system-config/dto/system-config-oauth.dto.ts index 6cc4590744..e13048761c 100644 --- a/server/src/domain/system-config/dto/system-config-oauth.dto.ts +++ b/server/src/domain/system-config/dto/system-config-oauth.dto.ts @@ -25,6 +25,9 @@ export class SystemConfigOAuthDto { @IsString() scope!: string; + @IsString() + storageLabelClaim!: string; + @IsString() buttonText!: string; diff --git a/server/src/domain/system-config/system-config.core.ts b/server/src/domain/system-config/system-config.core.ts index 771d5d0486..3051b82b3e 100644 --- a/server/src/domain/system-config/system-config.core.ts +++ b/server/src/domain/system-config/system-config.core.ts @@ -48,6 +48,7 @@ export const defaults = Object.freeze({ mobileOverrideEnabled: false, mobileRedirectUri: '', scope: 'openid email profile', + storageLabelClaim: 'preferred_username', buttonText: 'Login with OAuth', autoRegister: true, autoLaunch: false, diff --git a/server/src/domain/system-config/system-config.service.spec.ts b/server/src/domain/system-config/system-config.service.spec.ts index 8ac2efea6e..a3296df922 100644 --- a/server/src/domain/system-config/system-config.service.spec.ts +++ b/server/src/domain/system-config/system-config.service.spec.ts @@ -53,6 +53,7 @@ const updatedConfig = Object.freeze({ mobileOverrideEnabled: false, mobileRedirectUri: '', scope: 'openid email profile', + storageLabelClaim: 'preferred_username', }, passwordLogin: { enabled: true, diff --git a/server/src/domain/user/user.core.ts b/server/src/domain/user/user.core.ts index 1b3e872256..e7c0f7f2b1 100644 --- a/server/src/domain/user/user.core.ts +++ b/server/src/domain/user/user.core.ts @@ -8,9 +8,9 @@ import { } from '@nestjs/common'; import { constants, createReadStream, ReadStream } from 'fs'; import fs from 'fs/promises'; +import sanitize from 'sanitize-filename'; import { AuthUserDto } from '../auth'; import { ICryptoRepository } from '../crypto'; -import { CreateAdminDto, CreateUserDto, CreateUserOAuthDto } from './dto/create-user.dto'; import { IUserRepository, UserListFilter } from './user.repository'; const SALT_ROUNDS = 10; @@ -67,13 +67,13 @@ export class UserCore { } } - async createUser(createUserDto: CreateUserDto | CreateAdminDto | CreateUserOAuthDto): Promise { - const user = await this.userRepository.getByEmail(createUserDto.email); + async createUser(dto: Partial & { email: string }): Promise { + const user = await this.userRepository.getByEmail(dto.email); if (user) { throw new BadRequestException('User exists'); } - if (!(createUserDto as CreateAdminDto).isAdmin) { + if (!dto.isAdmin) { const localAdmin = await this.userRepository.getAdmin(); if (!localAdmin) { throw new BadRequestException('The first registered account must the administrator.'); @@ -81,10 +81,13 @@ export class UserCore { } try { - const payload: Partial = { ...createUserDto }; + const payload: Partial = { ...dto }; if (payload.password) { payload.password = await this.cryptoRepository.hashBcrypt(payload.password, SALT_ROUNDS); } + if (payload.storageLabel) { + payload.storageLabel = sanitize(payload.storageLabel); + } return this.userRepository.create(payload); } catch (e) { Logger.error(e, 'Create new user'); diff --git a/server/src/infra/entities/system-config.entity.ts b/server/src/infra/entities/system-config.entity.ts index 8046546132..af13b47da8 100644 --- a/server/src/infra/entities/system-config.entity.ts +++ b/server/src/infra/entities/system-config.entity.ts @@ -40,6 +40,7 @@ export enum SystemConfigKey { OAUTH_CLIENT_ID = 'oauth.clientId', OAUTH_CLIENT_SECRET = 'oauth.clientSecret', OAUTH_SCOPE = 'oauth.scope', + OAUTH_STORAGE_LABEL_CLAIM = 'oauth.storageLabelClaim', OAUTH_AUTO_LAUNCH = 'oauth.autoLaunch', OAUTH_BUTTON_TEXT = 'oauth.buttonText', OAUTH_AUTO_REGISTER = 'oauth.autoRegister', @@ -89,6 +90,7 @@ export interface SystemConfig { clientId: string; clientSecret: string; scope: string; + storageLabelClaim: string; buttonText: string; autoRegister: boolean; autoLaunch: boolean; diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 93ba50c605..66c717c814 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -2596,6 +2596,12 @@ export interface SystemConfigOAuthDto { * @memberof SystemConfigOAuthDto */ 'scope': string; + /** + * + * @type {string} + * @memberof SystemConfigOAuthDto + */ + 'storageLabelClaim': string; /** * * @type {string} diff --git a/web/src/lib/components/admin-page/settings/oauth/oauth-settings.svelte b/web/src/lib/components/admin-page/settings/oauth/oauth-settings.svelte index ffee598f89..3079c5f17e 100644 --- a/web/src/lib/components/admin-page/settings/oauth/oauth-settings.svelte +++ b/web/src/lib/components/admin-page/settings/oauth/oauth-settings.svelte @@ -155,6 +155,16 @@ isEdited={!(oauthConfig.scope == savedConfig.scope)} /> + +