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:
parent
ed594c1987
commit
f55d63fae8
13 changed files with 64 additions and 6 deletions
6
cli/src/api/open-api/api.ts
generated
6
cli/src/api/open-api/api.ts
generated
|
@ -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}
|
||||||
|
|
1
mobile/openapi/doc/SystemConfigOAuthDto.md
generated
1
mobile/openapi/doc/SystemConfigOAuthDto.md
generated
|
@ -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** | |
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,9 @@ export class SystemConfigOAuthDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
scope!: string;
|
scope!: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
storageLabelClaim!: string;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
buttonText!: string;
|
buttonText!: string;
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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;
|
||||||
|
|
6
web/src/api/open-api/api.ts
generated
6
web/src/api/open-api/api.ts
generated
|
@ -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}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Add table
Reference in a new issue