1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-03-01 15:11:21 +01:00

feat(server): storage label claim (#3278)

* feat: storage label claim

* chore: open api
This commit is contained in:
Jason Rasmussen 2023-07-15 15:50:29 -04:00 committed by GitHub
parent ed594c1987
commit f55d63fae8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 64 additions and 6 deletions

View file

@ -2596,6 +2596,12 @@ export interface SystemConfigOAuthDto {
* @memberof SystemConfigOAuthDto * @memberof SystemConfigOAuthDto
*/ */
'scope': string; 'scope': string;
/**
*
* @type {string}
* @memberof SystemConfigOAuthDto
*/
'storageLabelClaim': string;
/** /**
* *
* @type {string} * @type {string}

View file

@ -13,6 +13,7 @@ Name | Type | Description | Notes
**clientId** | **String** | | **clientId** | **String** | |
**clientSecret** | **String** | | **clientSecret** | **String** | |
**scope** | **String** | | **scope** | **String** | |
**storageLabelClaim** | **String** | |
**buttonText** | **String** | | **buttonText** | **String** | |
**autoRegister** | **bool** | | **autoRegister** | **bool** | |
**autoLaunch** | **bool** | | **autoLaunch** | **bool** | |

View file

@ -18,6 +18,7 @@ class SystemConfigOAuthDto {
required this.clientId, required this.clientId,
required this.clientSecret, required this.clientSecret,
required this.scope, required this.scope,
required this.storageLabelClaim,
required this.buttonText, required this.buttonText,
required this.autoRegister, required this.autoRegister,
required this.autoLaunch, required this.autoLaunch,
@ -35,6 +36,8 @@ class SystemConfigOAuthDto {
String scope; String scope;
String storageLabelClaim;
String buttonText; String buttonText;
bool autoRegister; bool autoRegister;
@ -52,6 +55,7 @@ class SystemConfigOAuthDto {
other.clientId == clientId && other.clientId == clientId &&
other.clientSecret == clientSecret && other.clientSecret == clientSecret &&
other.scope == scope && other.scope == scope &&
other.storageLabelClaim == storageLabelClaim &&
other.buttonText == buttonText && other.buttonText == buttonText &&
other.autoRegister == autoRegister && other.autoRegister == autoRegister &&
other.autoLaunch == autoLaunch && other.autoLaunch == autoLaunch &&
@ -66,6 +70,7 @@ class SystemConfigOAuthDto {
(clientId.hashCode) + (clientId.hashCode) +
(clientSecret.hashCode) + (clientSecret.hashCode) +
(scope.hashCode) + (scope.hashCode) +
(storageLabelClaim.hashCode) +
(buttonText.hashCode) + (buttonText.hashCode) +
(autoRegister.hashCode) + (autoRegister.hashCode) +
(autoLaunch.hashCode) + (autoLaunch.hashCode) +
@ -73,7 +78,7 @@ class SystemConfigOAuthDto {
(mobileRedirectUri.hashCode); (mobileRedirectUri.hashCode);
@override @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<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@ -82,6 +87,7 @@ class SystemConfigOAuthDto {
json[r'clientId'] = this.clientId; json[r'clientId'] = this.clientId;
json[r'clientSecret'] = this.clientSecret; json[r'clientSecret'] = this.clientSecret;
json[r'scope'] = this.scope; json[r'scope'] = this.scope;
json[r'storageLabelClaim'] = this.storageLabelClaim;
json[r'buttonText'] = this.buttonText; json[r'buttonText'] = this.buttonText;
json[r'autoRegister'] = this.autoRegister; json[r'autoRegister'] = this.autoRegister;
json[r'autoLaunch'] = this.autoLaunch; json[r'autoLaunch'] = this.autoLaunch;
@ -103,6 +109,7 @@ class SystemConfigOAuthDto {
clientId: mapValueOfType<String>(json, r'clientId')!, clientId: mapValueOfType<String>(json, r'clientId')!,
clientSecret: mapValueOfType<String>(json, r'clientSecret')!, clientSecret: mapValueOfType<String>(json, r'clientSecret')!,
scope: mapValueOfType<String>(json, r'scope')!, scope: mapValueOfType<String>(json, r'scope')!,
storageLabelClaim: mapValueOfType<String>(json, r'storageLabelClaim')!,
buttonText: mapValueOfType<String>(json, r'buttonText')!, buttonText: mapValueOfType<String>(json, r'buttonText')!,
autoRegister: mapValueOfType<bool>(json, r'autoRegister')!, autoRegister: mapValueOfType<bool>(json, r'autoRegister')!,
autoLaunch: mapValueOfType<bool>(json, r'autoLaunch')!, autoLaunch: mapValueOfType<bool>(json, r'autoLaunch')!,
@ -160,6 +167,7 @@ class SystemConfigOAuthDto {
'clientId', 'clientId',
'clientSecret', 'clientSecret',
'scope', 'scope',
'storageLabelClaim',
'buttonText', 'buttonText',
'autoRegister', 'autoRegister',
'autoLaunch', 'autoLaunch',

View file

@ -41,6 +41,11 @@ void main() {
// TODO // TODO
}); });
// String storageLabelClaim
test('to test the property `storageLabelClaim`', () async {
// TODO
});
// String buttonText // String buttonText
test('to test the property `buttonText`', () async { test('to test the property `buttonText`', () async {
// TODO // TODO

View file

@ -6503,6 +6503,9 @@
"scope": { "scope": {
"type": "string" "type": "string"
}, },
"storageLabelClaim": {
"type": "string"
},
"buttonText": { "buttonText": {
"type": "string" "type": "string"
}, },
@ -6525,6 +6528,7 @@
"clientId", "clientId",
"clientSecret", "clientSecret",
"scope", "scope",
"storageLabelClaim",
"buttonText", "buttonText",
"autoRegister", "autoRegister",
"autoLaunch", "autoLaunch",

View file

@ -240,11 +240,19 @@ export class AuthService {
} }
this.logger.log(`Registering new user: ${profile.email}/${profile.sub}`); 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({ user = await this.userCore.createUser({
firstName: profile.given_name || '', firstName: profile.given_name || '',
lastName: profile.family_name || '', lastName: profile.family_name || '',
email: profile.email, email: profile.email,
oauthId: profile.sub, oauthId: profile.sub,
storageLabel,
}); });
} }

View file

@ -25,6 +25,9 @@ export class SystemConfigOAuthDto {
@IsString() @IsString()
scope!: string; scope!: string;
@IsString()
storageLabelClaim!: string;
@IsString() @IsString()
buttonText!: string; buttonText!: string;

View file

@ -48,6 +48,7 @@ export const defaults = Object.freeze<SystemConfig>({
mobileOverrideEnabled: false, mobileOverrideEnabled: false,
mobileRedirectUri: '', mobileRedirectUri: '',
scope: 'openid email profile', scope: 'openid email profile',
storageLabelClaim: 'preferred_username',
buttonText: 'Login with OAuth', buttonText: 'Login with OAuth',
autoRegister: true, autoRegister: true,
autoLaunch: false, autoLaunch: false,

View file

@ -53,6 +53,7 @@ const updatedConfig = Object.freeze<SystemConfig>({
mobileOverrideEnabled: false, mobileOverrideEnabled: false,
mobileRedirectUri: '', mobileRedirectUri: '',
scope: 'openid email profile', scope: 'openid email profile',
storageLabelClaim: 'preferred_username',
}, },
passwordLogin: { passwordLogin: {
enabled: true, enabled: true,

View file

@ -8,9 +8,9 @@ import {
} from '@nestjs/common'; } from '@nestjs/common';
import { constants, createReadStream, ReadStream } from 'fs'; import { constants, createReadStream, ReadStream } from 'fs';
import fs from 'fs/promises'; import fs from 'fs/promises';
import sanitize from 'sanitize-filename';
import { AuthUserDto } from '../auth'; import { AuthUserDto } from '../auth';
import { ICryptoRepository } from '../crypto'; import { ICryptoRepository } from '../crypto';
import { CreateAdminDto, CreateUserDto, CreateUserOAuthDto } from './dto/create-user.dto';
import { IUserRepository, UserListFilter } from './user.repository'; import { IUserRepository, UserListFilter } from './user.repository';
const SALT_ROUNDS = 10; const SALT_ROUNDS = 10;
@ -67,13 +67,13 @@ export class UserCore {
} }
} }
async createUser(createUserDto: CreateUserDto | CreateAdminDto | CreateUserOAuthDto): Promise<UserEntity> { async createUser(dto: Partial<UserEntity> & { email: string }): Promise<UserEntity> {
const user = await this.userRepository.getByEmail(createUserDto.email); const user = await this.userRepository.getByEmail(dto.email);
if (user) { if (user) {
throw new BadRequestException('User exists'); throw new BadRequestException('User exists');
} }
if (!(createUserDto as CreateAdminDto).isAdmin) { if (!dto.isAdmin) {
const localAdmin = await this.userRepository.getAdmin(); const localAdmin = await this.userRepository.getAdmin();
if (!localAdmin) { if (!localAdmin) {
throw new BadRequestException('The first registered account must the administrator.'); throw new BadRequestException('The first registered account must the administrator.');
@ -81,10 +81,13 @@ export class UserCore {
} }
try { try {
const payload: Partial<UserEntity> = { ...createUserDto }; const payload: Partial<UserEntity> = { ...dto };
if (payload.password) { if (payload.password) {
payload.password = await this.cryptoRepository.hashBcrypt(payload.password, SALT_ROUNDS); payload.password = await this.cryptoRepository.hashBcrypt(payload.password, SALT_ROUNDS);
} }
if (payload.storageLabel) {
payload.storageLabel = sanitize(payload.storageLabel);
}
return this.userRepository.create(payload); return this.userRepository.create(payload);
} catch (e) { } catch (e) {
Logger.error(e, 'Create new user'); Logger.error(e, 'Create new user');

View file

@ -40,6 +40,7 @@ export enum SystemConfigKey {
OAUTH_CLIENT_ID = 'oauth.clientId', OAUTH_CLIENT_ID = 'oauth.clientId',
OAUTH_CLIENT_SECRET = 'oauth.clientSecret', OAUTH_CLIENT_SECRET = 'oauth.clientSecret',
OAUTH_SCOPE = 'oauth.scope', OAUTH_SCOPE = 'oauth.scope',
OAUTH_STORAGE_LABEL_CLAIM = 'oauth.storageLabelClaim',
OAUTH_AUTO_LAUNCH = 'oauth.autoLaunch', OAUTH_AUTO_LAUNCH = 'oauth.autoLaunch',
OAUTH_BUTTON_TEXT = 'oauth.buttonText', OAUTH_BUTTON_TEXT = 'oauth.buttonText',
OAUTH_AUTO_REGISTER = 'oauth.autoRegister', OAUTH_AUTO_REGISTER = 'oauth.autoRegister',
@ -89,6 +90,7 @@ export interface SystemConfig {
clientId: string; clientId: string;
clientSecret: string; clientSecret: string;
scope: string; scope: string;
storageLabelClaim: string;
buttonText: string; buttonText: string;
autoRegister: boolean; autoRegister: boolean;
autoLaunch: boolean; autoLaunch: boolean;

View file

@ -2596,6 +2596,12 @@ export interface SystemConfigOAuthDto {
* @memberof SystemConfigOAuthDto * @memberof SystemConfigOAuthDto
*/ */
'scope': string; 'scope': string;
/**
*
* @type {string}
* @memberof SystemConfigOAuthDto
*/
'storageLabelClaim': string;
/** /**
* *
* @type {string} * @type {string}

View file

@ -155,6 +155,16 @@
isEdited={!(oauthConfig.scope == savedConfig.scope)} isEdited={!(oauthConfig.scope == savedConfig.scope)}
/> />
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="STORAGE LABEL CLAIM"
desc="Automatically set the user's storage label to the value of this claim."
bind:value={oauthConfig.storageLabelClaim}
required={true}
disabled={!oauthConfig.storageLabelClaim}
isEdited={!(oauthConfig.storageLabelClaim == savedConfig.storageLabelClaim)}
/>
<SettingInputField <SettingInputField
inputType={SettingInputFieldType.TEXT} inputType={SettingInputFieldType.TEXT}
label="BUTTON TEXT" label="BUTTON TEXT"