From 5423f1c25bec5bb28c0ed182ef88ece1f83200d1 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Thu, 9 Nov 2023 10:14:15 -0500 Subject: [PATCH] refactor(server): auth dtos (#4881) * refactor: auth dtos * chore: open api --- cli/src/api/open-api/api.ts | 41 +----- mobile/openapi/.openapi-generator/FILES | 3 - mobile/openapi/README.md | Bin 22877 -> 22817 bytes mobile/openapi/doc/AdminSignupResponseDto.md | Bin 558 -> 0 bytes mobile/openapi/doc/AuthenticationApi.md | Bin 13976 -> 13955 bytes mobile/openapi/doc/LoginResponseDto.md | Bin 744 -> 656 bytes mobile/openapi/lib/api.dart | Bin 7476 -> 7431 bytes .../openapi/lib/api/authentication_api.dart | Bin 11607 -> 11586 bytes mobile/openapi/lib/api_client.dart | Bin 22011 -> 21915 bytes .../lib/model/admin_signup_response_dto.dart | Bin 3851 -> 0 bytes .../test/admin_signup_response_dto_test.dart | Bin 982 -> 0 bytes .../openapi/test/authentication_api_test.dart | Bin 1514 -> 1507 bytes server/immich-openapi-specs.json | 41 +----- server/src/domain/auth/auth.dto.ts | 132 ++++++++++++++++++ server/src/domain/auth/auth.service.spec.ts | 2 +- server/src/domain/auth/auth.service.ts | 15 +- server/src/domain/auth/dto/auth-user.dto.ts | 12 -- .../domain/auth/dto/change-password.dto.ts | 15 -- server/src/domain/auth/dto/index.ts | 6 - .../auth/dto/login-credential.dto.spec.ts | 42 ------ .../domain/auth/dto/login-credential.dto.ts | 16 --- .../domain/auth/dto/oauth-auth-code.dto.ts | 9 -- .../src/domain/auth/dto/oauth-config.dto.ts | 7 - .../src/domain/auth/dto/sign-up.dto.spec.ts | 58 -------- server/src/domain/auth/dto/sign-up.dto.ts | 26 ---- server/src/domain/auth/index.ts | 3 +- .../response-dto/admin-signup-response.dto.ts | 19 --- .../response-dto/auth-device-response.dto.ts | 19 --- server/src/domain/auth/response-dto/index.ts | 6 - .../auth/response-dto/login-response.dto.ts | 41 ------ .../auth/response-dto/logout-response.dto.ts | 4 - .../response-dto/oauth-config-response.dto.ts | 11 -- .../validate-asset-token-response.dto.ts | 3 - .../src/immich/controllers/auth.controller.ts | 8 +- server/test/api/auth-api.ts | 7 +- server/test/e2e/auth.e2e-spec.ts | 23 ++- server/test/fixtures/auth.stub.ts | 8 -- web/src/api/open-api/api.ts | 41 +----- 38 files changed, 174 insertions(+), 444 deletions(-) delete mode 100644 mobile/openapi/doc/AdminSignupResponseDto.md delete mode 100644 mobile/openapi/lib/model/admin_signup_response_dto.dart delete mode 100644 mobile/openapi/test/admin_signup_response_dto_test.dart create mode 100644 server/src/domain/auth/auth.dto.ts delete mode 100644 server/src/domain/auth/dto/auth-user.dto.ts delete mode 100644 server/src/domain/auth/dto/change-password.dto.ts delete mode 100644 server/src/domain/auth/dto/index.ts delete mode 100644 server/src/domain/auth/dto/login-credential.dto.spec.ts delete mode 100644 server/src/domain/auth/dto/login-credential.dto.ts delete mode 100644 server/src/domain/auth/dto/oauth-auth-code.dto.ts delete mode 100644 server/src/domain/auth/dto/oauth-config.dto.ts delete mode 100644 server/src/domain/auth/dto/sign-up.dto.spec.ts delete mode 100644 server/src/domain/auth/dto/sign-up.dto.ts delete mode 100644 server/src/domain/auth/response-dto/admin-signup-response.dto.ts delete mode 100644 server/src/domain/auth/response-dto/auth-device-response.dto.ts delete mode 100644 server/src/domain/auth/response-dto/index.ts delete mode 100644 server/src/domain/auth/response-dto/login-response.dto.ts delete mode 100644 server/src/domain/auth/response-dto/logout-response.dto.ts delete mode 100644 server/src/domain/auth/response-dto/oauth-config-response.dto.ts delete mode 100644 server/src/domain/auth/response-dto/validate-asset-token-response.dto.ts diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index fb40a1ef4f..6df8262f60 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -209,43 +209,6 @@ export interface AddUsersDto { */ 'sharedUserIds': Array; } -/** - * - * @export - * @interface AdminSignupResponseDto - */ -export interface AdminSignupResponseDto { - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'createdAt': string; - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'email': string; - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'firstName': string; - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'id': string; - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'lastName': string; -} /** * * @export @@ -10509,7 +10472,7 @@ export const AuthenticationApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async signUpAdmin(signUpDto: SignUpDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async signUpAdmin(signUpDto: SignUpDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.signUpAdmin(signUpDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -10589,7 +10552,7 @@ export const AuthenticationApiFactory = function (configuration?: Configuration, * @param {*} [options] Override http request option. * @throws {RequiredError} */ - signUpAdmin(requestParameters: AuthenticationApiSignUpAdminRequest, options?: AxiosRequestConfig): AxiosPromise { + signUpAdmin(requestParameters: AuthenticationApiSignUpAdminRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.signUpAdmin(requestParameters.signUpDto, options).then((request) => request(axios, basePath)); }, /** diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 52b863a6f2..4a2870f0e2 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -13,7 +13,6 @@ doc/ActivityCreateDto.md doc/ActivityResponseDto.md doc/ActivityStatisticsResponseDto.md doc/AddUsersDto.md -doc/AdminSignupResponseDto.md doc/AlbumApi.md doc/AlbumCountResponseDto.md doc/AlbumResponseDto.md @@ -198,7 +197,6 @@ lib/model/activity_create_dto.dart lib/model/activity_response_dto.dart lib/model/activity_statistics_response_dto.dart lib/model/add_users_dto.dart -lib/model/admin_signup_response_dto.dart lib/model/album_count_response_dto.dart lib/model/album_response_dto.dart lib/model/all_job_status_response_dto.dart @@ -347,7 +345,6 @@ test/activity_create_dto_test.dart test/activity_response_dto_test.dart test/activity_statistics_response_dto_test.dart test/add_users_dto_test.dart -test/admin_signup_response_dto_test.dart test/album_api_test.dart test/album_count_response_dto_test.dart test/album_response_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 1258d37f033dfd42fd3e7e63d7efce0b002cc8e4..8d3e4a5cd70a9c540143dd35792c1101a02b0355 100644 GIT binary patch delta 14 Wcmcb+iE-g3#tmjRn~Q8-%K!i~aRzSy delta 62 zcmZ3uiSh0x#tmjR>M6OIdBK_Kd8GwGsl^5PdBv$NCHb)$Df!9z`i=<6$s4UjCl}bT KY__s_E&~7#85lhP diff --git a/mobile/openapi/doc/AdminSignupResponseDto.md b/mobile/openapi/doc/AdminSignupResponseDto.md deleted file mode 100644 index 08d3d8bfa6cb545ba116b8e0857b019e41b0a5af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 558 zcma)3O>4t2488kT7;S3@SKRfMMH|REFu%F(0 zl8_4SJXqziG)}`pHChF*M!< z;PCn$rX5PJ$}hY4KBBZf%1>3agy_n1-r z=44ItlqSl_@paw=A2Cwf&dYMSTUGUXm3l0+>(2%(sXQThbU{wLhztJZuC~wr^rp>7 Su-ZGc^ex4u;E&=9#@Gk`&#Aru diff --git a/mobile/openapi/doc/AuthenticationApi.md b/mobile/openapi/doc/AuthenticationApi.md index c56f88228fb44ff4e660d8925deccc6617820f78..9521568e9d303f2d70977240ea93829b4f030777 100644 GIT binary patch delta 26 icmbP{+nl>$l{QOgaca@#t=dXVlW%ZZZ&uK&-~a%XEeZty delta 72 zcmZq9osqj?l{U9yN^WLeaAta5X~E`o+Dc5SNCH8r#Rd6!#i=eO`C3}B8b~sm4Md|D KH#_P(u>%18)Eb5W diff --git a/mobile/openapi/doc/LoginResponseDto.md b/mobile/openapi/doc/LoginResponseDto.md index ad3592eff9dbf21de00cd26021fa92d49e27e6c7..e344ef124a52d65f95eb1d3a7c2b7deffd4c93cc 100644 GIT binary patch delta 45 zcmV+|0Mh^H1&{@h`vwXsDrRYNlV1TFlQIDvlUe~FlZ62&lehsYlkEW^lNkaYlR^Tf DRx=J% delta 131 zcmbQh`hs=BZ?5Q~)WnqhyqwC|iNBPPc$1|WUbvv&dRD2Dye(f6&Hq@LaXx6V2(T)Hf2rvtwlU#9TcyVIlo<+W7qU>9+kX<5xeT$;# zlVYmITRa?L@&Kpkc`BA1LJ(GbZIr0)z&h^=g43>06x}!P#L7O(?_4P?9lE-evLx^o zGg3jCMw8obJjarl1y@Iqj=_~UXJtgV#D3++5BCIOZ=qhjvd}rb7EtukCI;I|HaAjm z58I0&Pf}=Kwv|LdtN6Js_5jeLf8GH@R0i(2fd$ixKV-o%eVak(Icl|v=Ui*1HqZEAcxV!7pyf=CdmfS?n%}VprxPZ&!A5Xh>qDdx&fehyBE#g@Dt;@EAMn z(?c6_+yb2*hG=61I6Xu@2Q1S=AKT5)#&p5z-&_bW6dRl^A|Hd1pS7$oCf9LuQLz%f zp$Jmg(Y}B9;P_)TW@;Eh9NwV*c9$kbel>Y?gx}{{zW?>jTc7`9=}2KM!lN=UO+ z3!+D=qG)xf<7ke6kJls8s(6W0RCa(_Z4x|C>;J@H16CE_X1xYsG<;{}iffoFxomcv zSrN{{IVuckWX`ZYH!)PJ65F48eJ!*dksYqriqCOk+hMO(ISvS&(dfu<_8XCBcuXv0 zLELwOJ>&Iv8;LD^Lj)J0X8{Hk+4yLbU|3!bNWBg}tV)vOwA^7zhAz`$7!HpP+}tKp%NJ|(H=#V4Pd7Dh0vZQoif@s#{nL_Xhgpw zXwP)Fcc( zUr#RSr%^LG>|q~%3jIa4;c7O%x1e)Ur zdrguwP0|!5Pep!oGo4KzCdD*^>)X5O2(lbzc>(u%c6(FRNsf=;o z_2A+UgfVd1PisJL+SdW(N?=m8WFF;?I`@FVNm9(%ChwLiXf>JPA$%qP!kLrN4Nz>Q zM7aEiIy}5cgiw3ix#6HU`Uf}|!H;;9&opB}Ekfy0w$+411!7W>v2g>uKyY5V_Pm6b zU*yY5ONXT?W89u8k&8UbqsrGRR6KlSPo@Tx?wYL@5w(we{0%et8(S@Yk^9(CwN;<0 VeS{kwc&GSl;BF?8HonDI@(rj(GA;lB diff --git a/mobile/openapi/test/authentication_api_test.dart b/mobile/openapi/test/authentication_api_test.dart index 22eb7550ff344ab2ea7fd47904012e62839ee462..aa2f1879d55d067d4baded369f1e3c399962cdc0 100644 GIT binary patch delta 15 XcmaFG{g`{h6_&|Q7zHQ4VfhCDJF5qF delta 24 fcmaFN{fc|T6&7yCl-$g`;LP;A(t^!5ShyJhf6WPT diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 31887acebd..e87623cbd9 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -2630,14 +2630,11 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AdminSignupResponseDto" + "$ref": "#/components/schemas/UserResponseDto" } } }, "description": "" - }, - "400": { - "description": "The server already has an admin" } }, "tags": [ @@ -5812,34 +5809,6 @@ ], "type": "object" }, - "AdminSignupResponseDto": { - "properties": { - "createdAt": { - "format": "date-time", - "type": "string" - }, - "email": { - "type": "string" - }, - "firstName": { - "type": "string" - }, - "id": { - "type": "string" - }, - "lastName": { - "type": "string" - } - }, - "required": [ - "id", - "email", - "firstName", - "lastName", - "createdAt" - ], - "type": "object" - }, "AlbumCountResponseDto": { "properties": { "notShared": { @@ -7377,35 +7346,27 @@ "LoginResponseDto": { "properties": { "accessToken": { - "readOnly": true, "type": "string" }, "firstName": { - "readOnly": true, "type": "string" }, "isAdmin": { - "readOnly": true, "type": "boolean" }, "lastName": { - "readOnly": true, "type": "string" }, "profileImagePath": { - "readOnly": true, "type": "string" }, "shouldChangePassword": { - "readOnly": true, "type": "boolean" }, "userEmail": { - "readOnly": true, "type": "string" }, "userId": { - "readOnly": true, "type": "string" } }, diff --git a/server/src/domain/auth/auth.dto.ts b/server/src/domain/auth/auth.dto.ts new file mode 100644 index 0000000000..c0703911d5 --- /dev/null +++ b/server/src/domain/auth/auth.dto.ts @@ -0,0 +1,132 @@ +import { UserEntity, UserTokenEntity } from '@app/infra/entities'; +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator'; + +export class AuthUserDto { + id!: string; + email!: string; + isAdmin!: boolean; + isPublicUser?: boolean; + sharedLinkId?: string; + isAllowUpload?: boolean; + isAllowDownload?: boolean; + isShowMetadata?: boolean; + accessTokenId?: string; + externalPath?: string | null; +} + +export class LoginCredentialDto { + @IsEmail({ require_tld: false }) + @Transform(({ value }) => value?.toLowerCase()) + @IsNotEmpty() + @ApiProperty({ example: 'testuser@email.com' }) + email!: string; + + @IsString() + @IsNotEmpty() + @ApiProperty({ example: 'password' }) + password!: string; +} + +export class LoginResponseDto { + accessToken!: string; + userId!: string; + userEmail!: string; + firstName!: string; + lastName!: string; + profileImagePath!: string; + isAdmin!: boolean; + shouldChangePassword!: boolean; +} + +export function mapLoginResponse(entity: UserEntity, accessToken: string): LoginResponseDto { + return { + accessToken: accessToken, + userId: entity.id, + userEmail: entity.email, + firstName: entity.firstName, + lastName: entity.lastName, + isAdmin: entity.isAdmin, + profileImagePath: entity.profileImagePath, + shouldChangePassword: entity.shouldChangePassword, + }; +} + +export class LogoutResponseDto { + successful!: boolean; + redirectUri!: string; +} + +export class SignUpDto extends LoginCredentialDto { + @IsString() + @IsNotEmpty() + @ApiProperty({ example: 'Admin' }) + firstName!: string; + + @IsString() + @IsNotEmpty() + @ApiProperty({ example: 'Doe' }) + lastName!: string; +} + +export class ChangePasswordDto { + @IsString() + @IsNotEmpty() + @ApiProperty({ example: 'password' }) + password!: string; + + @IsString() + @IsNotEmpty() + @MinLength(8) + @ApiProperty({ example: 'password' }) + newPassword!: string; +} + +export class ValidateAccessTokenResponseDto { + authStatus!: boolean; +} + +export class AuthDeviceResponseDto { + id!: string; + createdAt!: string; + updatedAt!: string; + current!: boolean; + deviceType!: string; + deviceOS!: string; +} + +export const mapUserToken = (entity: UserTokenEntity, currentId?: string): AuthDeviceResponseDto => ({ + id: entity.id, + createdAt: entity.createdAt.toISOString(), + updatedAt: entity.updatedAt.toISOString(), + current: currentId === entity.id, + deviceOS: entity.deviceOS, + deviceType: entity.deviceType, +}); + +export class OAuthCallbackDto { + @IsNotEmpty() + @IsString() + @ApiProperty() + url!: string; +} + +export class OAuthConfigDto { + @IsNotEmpty() + @IsString() + redirectUri!: string; +} + +/** @deprecated use oauth authorize */ +export class OAuthConfigResponseDto { + enabled!: boolean; + passwordLoginEnabled!: boolean; + url?: string; + buttonText?: string; + autoLaunch?: boolean; +} + +export class OAuthAuthorizeResponseDto { + url!: string; +} diff --git a/server/src/domain/auth/auth.service.spec.ts b/server/src/domain/auth/auth.service.spec.ts index 2ef598b059..3fb5d4d328 100644 --- a/server/src/domain/auth/auth.service.spec.ts +++ b/server/src/domain/auth/auth.service.spec.ts @@ -31,8 +31,8 @@ import { IUserTokenRepository, } from '../repositories'; import { AuthType } from './auth.constant'; +import { AuthUserDto, SignUpDto } from './auth.dto'; import { AuthService } from './auth.service'; -import { AuthUserDto, SignUpDto } from './dto'; // const token = Buffer.from('my-api-key', 'utf8').toString('base64'); diff --git a/server/src/domain/auth/auth.service.ts b/server/src/domain/auth/auth.service.ts index 289f50bfb2..00aa41bfda 100644 --- a/server/src/domain/auth/auth.service.ts +++ b/server/src/domain/auth/auth.service.ts @@ -32,18 +32,21 @@ import { LOGIN_URL, MOBILE_REDIRECT, } from './auth.constant'; -import { AuthUserDto, ChangePasswordDto, LoginCredentialDto, OAuthCallbackDto, OAuthConfigDto, SignUpDto } from './dto'; import { - AdminSignupResponseDto, AuthDeviceResponseDto, + AuthUserDto, + ChangePasswordDto, + LoginCredentialDto, LoginResponseDto, LogoutResponseDto, OAuthAuthorizeResponseDto, + OAuthCallbackDto, + OAuthConfigDto, OAuthConfigResponseDto, - mapAdminSignupResponse, + SignUpDto, mapLoginResponse, mapUserToken, -} from './response-dto'; +} from './auth.dto'; export interface LoginDetails { isSecure: boolean; @@ -133,7 +136,7 @@ export class AuthService { return this.userCore.updateUser(authUser, authUser.id, { password: newPassword }); } - async adminSignUp(dto: SignUpDto): Promise { + async adminSignUp(dto: SignUpDto): Promise { const adminUser = await this.userRepository.getAdmin(); if (adminUser) { @@ -149,7 +152,7 @@ export class AuthService { storageLabel: 'admin', }); - return mapAdminSignupResponse(admin); + return mapUser(admin); } async validate(headers: IncomingHttpHeaders, params: Record): Promise { diff --git a/server/src/domain/auth/dto/auth-user.dto.ts b/server/src/domain/auth/dto/auth-user.dto.ts deleted file mode 100644 index a689096d84..0000000000 --- a/server/src/domain/auth/dto/auth-user.dto.ts +++ /dev/null @@ -1,12 +0,0 @@ -export class AuthUserDto { - id!: string; - email!: string; - isAdmin!: boolean; - isPublicUser?: boolean; - sharedLinkId?: string; - isAllowUpload?: boolean; - isAllowDownload?: boolean; - isShowMetadata?: boolean; - accessTokenId?: string; - externalPath?: string | null; -} diff --git a/server/src/domain/auth/dto/change-password.dto.ts b/server/src/domain/auth/dto/change-password.dto.ts deleted file mode 100644 index 9c5ce479ea..0000000000 --- a/server/src/domain/auth/dto/change-password.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString, MinLength } from 'class-validator'; - -export class ChangePasswordDto { - @IsString() - @IsNotEmpty() - @ApiProperty({ example: 'password' }) - password!: string; - - @IsString() - @IsNotEmpty() - @MinLength(8) - @ApiProperty({ example: 'password' }) - newPassword!: string; -} diff --git a/server/src/domain/auth/dto/index.ts b/server/src/domain/auth/dto/index.ts deleted file mode 100644 index 59a65770a8..0000000000 --- a/server/src/domain/auth/dto/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './auth-user.dto'; -export * from './change-password.dto'; -export * from './login-credential.dto'; -export * from './oauth-auth-code.dto'; -export * from './oauth-config.dto'; -export * from './sign-up.dto'; diff --git a/server/src/domain/auth/dto/login-credential.dto.spec.ts b/server/src/domain/auth/dto/login-credential.dto.spec.ts deleted file mode 100644 index 7682db4c4e..0000000000 --- a/server/src/domain/auth/dto/login-credential.dto.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { plainToInstance } from 'class-transformer'; -import { validateSync } from 'class-validator'; -import { LoginCredentialDto } from './login-credential.dto'; - -describe('LoginCredentialDto', () => { - it('should allow emails without a tld', () => { - const someEmail = 'test@test'; - - const dto = plainToInstance(LoginCredentialDto, { email: someEmail, password: 'password' }); - const errors = validateSync(dto); - expect(errors).toHaveLength(0); - expect(dto.email).toEqual(someEmail); - }); - - it('should fail without an email', () => { - const dto = plainToInstance(LoginCredentialDto, { password: 'password' }); - const errors = validateSync(dto); - expect(errors).toHaveLength(1); - expect(errors[0].property).toEqual('email'); - }); - - it('should fail with an invalid email', () => { - const dto = plainToInstance(LoginCredentialDto, { email: 'invalid.com', password: 'password' }); - const errors = validateSync(dto); - expect(errors).toHaveLength(1); - expect(errors[0].property).toEqual('email'); - }); - - it('should make the email all lowercase', () => { - const dto = plainToInstance(LoginCredentialDto, { email: 'TeSt@ImMiCh.com', password: 'password' }); - const errors = validateSync(dto); - expect(errors).toHaveLength(0); - expect(dto.email).toEqual('test@immich.com'); - }); - - it('should fail without a password', () => { - const dto = plainToInstance(LoginCredentialDto, { email: 'test@immich.com', password: '' }); - const errors = validateSync(dto); - expect(errors).toHaveLength(1); - expect(errors[0].property).toEqual('password'); - }); -}); diff --git a/server/src/domain/auth/dto/login-credential.dto.ts b/server/src/domain/auth/dto/login-credential.dto.ts deleted file mode 100644 index 12516a1083..0000000000 --- a/server/src/domain/auth/dto/login-credential.dto.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; - -export class LoginCredentialDto { - @IsEmail({ require_tld: false }) - @Transform(({ value }) => value?.toLowerCase()) - @IsNotEmpty() - @ApiProperty({ example: 'testuser@email.com' }) - email!: string; - - @IsString() - @IsNotEmpty() - @ApiProperty({ example: 'password' }) - password!: string; -} diff --git a/server/src/domain/auth/dto/oauth-auth-code.dto.ts b/server/src/domain/auth/dto/oauth-auth-code.dto.ts deleted file mode 100644 index 924db0052a..0000000000 --- a/server/src/domain/auth/dto/oauth-auth-code.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; - -export class OAuthCallbackDto { - @IsNotEmpty() - @IsString() - @ApiProperty() - url!: string; -} diff --git a/server/src/domain/auth/dto/oauth-config.dto.ts b/server/src/domain/auth/dto/oauth-config.dto.ts deleted file mode 100644 index a14fc19dc2..0000000000 --- a/server/src/domain/auth/dto/oauth-config.dto.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { IsNotEmpty, IsString } from 'class-validator'; - -export class OAuthConfigDto { - @IsNotEmpty() - @IsString() - redirectUri!: string; -} diff --git a/server/src/domain/auth/dto/sign-up.dto.spec.ts b/server/src/domain/auth/dto/sign-up.dto.spec.ts deleted file mode 100644 index de708fe2ee..0000000000 --- a/server/src/domain/auth/dto/sign-up.dto.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { plainToInstance } from 'class-transformer'; -import { validateSync } from 'class-validator'; -import { SignUpDto } from './sign-up.dto'; - -describe('SignUpDto', () => { - it('should require all fields', () => { - const dto = plainToInstance(SignUpDto, { - email: '', - password: '', - firstName: '', - lastName: '', - }); - const errors = validateSync(dto); - expect(errors).toHaveLength(4); - expect(errors[0].property).toEqual('email'); - expect(errors[1].property).toEqual('password'); - expect(errors[2].property).toEqual('firstName'); - expect(errors[3].property).toEqual('lastName'); - }); - - it('should require a valid email', () => { - const dto = plainToInstance(SignUpDto, { - email: 'immich.com', - password: 'password', - firstName: 'first name', - lastName: 'last name', - }); - const errors = validateSync(dto); - expect(errors).toHaveLength(1); - expect(errors[0].property).toEqual('email'); - }); - - it('should allow emails without a tld', () => { - const someEmail = 'test@test'; - - const dto = plainToInstance(SignUpDto, { - email: someEmail, - password: 'password', - firstName: 'first name', - lastName: 'last name', - }); - const errors = validateSync(dto); - expect(errors).toHaveLength(0); - expect(dto.email).toEqual(someEmail); - }); - - it('should make the email all lowercase', () => { - const dto = plainToInstance(SignUpDto, { - email: 'TeSt@ImMiCh.com', - password: 'password', - firstName: 'first name', - lastName: 'last name', - }); - const errors = validateSync(dto); - expect(errors).toHaveLength(0); - expect(dto.email).toEqual('test@immich.com'); - }); -}); diff --git a/server/src/domain/auth/dto/sign-up.dto.ts b/server/src/domain/auth/dto/sign-up.dto.ts deleted file mode 100644 index 66741eb7e6..0000000000 --- a/server/src/domain/auth/dto/sign-up.dto.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; - -export class SignUpDto { - @IsEmail({ require_tld: false }) - @Transform(({ value }) => value?.toLowerCase()) - @IsNotEmpty() - @ApiProperty({ example: 'testuser@email.com' }) - email!: string; - - @IsString() - @IsNotEmpty() - @ApiProperty({ example: 'password' }) - password!: string; - - @IsString() - @IsNotEmpty() - @ApiProperty({ example: 'Admin' }) - firstName!: string; - - @IsString() - @IsNotEmpty() - @ApiProperty({ example: 'Doe' }) - lastName!: string; -} diff --git a/server/src/domain/auth/index.ts b/server/src/domain/auth/index.ts index d3aa704ba1..52e0463bcb 100644 --- a/server/src/domain/auth/index.ts +++ b/server/src/domain/auth/index.ts @@ -1,4 +1,3 @@ export * from './auth.constant'; +export * from './auth.dto'; export * from './auth.service'; -export * from './dto'; -export * from './response-dto'; diff --git a/server/src/domain/auth/response-dto/admin-signup-response.dto.ts b/server/src/domain/auth/response-dto/admin-signup-response.dto.ts deleted file mode 100644 index 5c2e4413ce..0000000000 --- a/server/src/domain/auth/response-dto/admin-signup-response.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { UserEntity } from '@app/infra/entities'; - -export class AdminSignupResponseDto { - id!: string; - email!: string; - firstName!: string; - lastName!: string; - createdAt!: Date; -} - -export function mapAdminSignupResponse(entity: UserEntity): AdminSignupResponseDto { - return { - id: entity.id, - email: entity.email, - firstName: entity.firstName, - lastName: entity.lastName, - createdAt: entity.createdAt, - }; -} diff --git a/server/src/domain/auth/response-dto/auth-device-response.dto.ts b/server/src/domain/auth/response-dto/auth-device-response.dto.ts deleted file mode 100644 index 986f743c0b..0000000000 --- a/server/src/domain/auth/response-dto/auth-device-response.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { UserTokenEntity } from '@app/infra/entities'; - -export class AuthDeviceResponseDto { - id!: string; - createdAt!: string; - updatedAt!: string; - current!: boolean; - deviceType!: string; - deviceOS!: string; -} - -export const mapUserToken = (entity: UserTokenEntity, currentId?: string): AuthDeviceResponseDto => ({ - id: entity.id, - createdAt: entity.createdAt.toISOString(), - updatedAt: entity.updatedAt.toISOString(), - current: currentId === entity.id, - deviceOS: entity.deviceOS, - deviceType: entity.deviceType, -}); diff --git a/server/src/domain/auth/response-dto/index.ts b/server/src/domain/auth/response-dto/index.ts deleted file mode 100644 index 491c957fdc..0000000000 --- a/server/src/domain/auth/response-dto/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './admin-signup-response.dto'; -export * from './auth-device-response.dto'; -export * from './login-response.dto'; -export * from './logout-response.dto'; -export * from './oauth-config-response.dto'; -export * from './validate-asset-token-response.dto'; diff --git a/server/src/domain/auth/response-dto/login-response.dto.ts b/server/src/domain/auth/response-dto/login-response.dto.ts deleted file mode 100644 index 1be8049962..0000000000 --- a/server/src/domain/auth/response-dto/login-response.dto.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { UserEntity } from '@app/infra/entities'; -import { ApiResponseProperty } from '@nestjs/swagger'; - -export class LoginResponseDto { - @ApiResponseProperty() - accessToken!: string; - - @ApiResponseProperty() - userId!: string; - - @ApiResponseProperty() - userEmail!: string; - - @ApiResponseProperty() - firstName!: string; - - @ApiResponseProperty() - lastName!: string; - - @ApiResponseProperty() - profileImagePath!: string; - - @ApiResponseProperty() - isAdmin!: boolean; - - @ApiResponseProperty() - shouldChangePassword!: boolean; -} - -export function mapLoginResponse(entity: UserEntity, accessToken: string): LoginResponseDto { - return { - accessToken: accessToken, - userId: entity.id, - userEmail: entity.email, - firstName: entity.firstName, - lastName: entity.lastName, - isAdmin: entity.isAdmin, - profileImagePath: entity.profileImagePath, - shouldChangePassword: entity.shouldChangePassword, - }; -} diff --git a/server/src/domain/auth/response-dto/logout-response.dto.ts b/server/src/domain/auth/response-dto/logout-response.dto.ts deleted file mode 100644 index 16816264e6..0000000000 --- a/server/src/domain/auth/response-dto/logout-response.dto.ts +++ /dev/null @@ -1,4 +0,0 @@ -export class LogoutResponseDto { - successful!: boolean; - redirectUri!: string; -} diff --git a/server/src/domain/auth/response-dto/oauth-config-response.dto.ts b/server/src/domain/auth/response-dto/oauth-config-response.dto.ts deleted file mode 100644 index dadd66f59a..0000000000 --- a/server/src/domain/auth/response-dto/oauth-config-response.dto.ts +++ /dev/null @@ -1,11 +0,0 @@ -export class OAuthConfigResponseDto { - enabled!: boolean; - passwordLoginEnabled!: boolean; - url?: string; - buttonText?: string; - autoLaunch?: boolean; -} - -export class OAuthAuthorizeResponseDto { - url!: string; -} diff --git a/server/src/domain/auth/response-dto/validate-asset-token-response.dto.ts b/server/src/domain/auth/response-dto/validate-asset-token-response.dto.ts deleted file mode 100644 index 4fdb2971d6..0000000000 --- a/server/src/domain/auth/response-dto/validate-asset-token-response.dto.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class ValidateAccessTokenResponseDto { - authStatus!: boolean; -} diff --git a/server/src/immich/controllers/auth.controller.ts b/server/src/immich/controllers/auth.controller.ts index 83e4b51457..ae48a78ebc 100644 --- a/server/src/immich/controllers/auth.controller.ts +++ b/server/src/immich/controllers/auth.controller.ts @@ -1,5 +1,4 @@ import { - AdminSignupResponseDto, AuthDeviceResponseDto, AuthService, AuthUserDto, @@ -15,7 +14,7 @@ import { ValidateAccessTokenResponseDto, } from '@app/domain'; import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Req, Res } from '@nestjs/common'; -import { ApiBadRequestResponse, ApiTags } from '@nestjs/swagger'; +import { ApiTags } from '@nestjs/swagger'; import { Request, Response } from 'express'; import { AuthUser, Authenticated, GetLoginDetails, PublicRoute } from '../app.guard'; import { UseValidation } from '../app.utils'; @@ -42,9 +41,8 @@ export class AuthController { @PublicRoute() @Post('admin-sign-up') - @ApiBadRequestResponse({ description: 'The server already has an admin' }) - signUpAdmin(@Body() signUpCredential: SignUpDto): Promise { - return this.service.adminSignUp(signUpCredential); + signUpAdmin(@Body() dto: SignUpDto): Promise { + return this.service.adminSignUp(dto); } @Get('devices') diff --git a/server/test/api/auth-api.ts b/server/test/api/auth-api.ts index 5f17509632..3043c941f2 100644 --- a/server/test/api/auth-api.ts +++ b/server/test/api/auth-api.ts @@ -1,5 +1,5 @@ -import { AdminSignupResponseDto, AuthDeviceResponseDto, LoginCredentialDto, LoginResponseDto } from '@app/domain'; -import { adminSignupStub, loginResponseStub, loginStub, signupResponseStub } from '@test'; +import { AuthDeviceResponseDto, LoginCredentialDto, LoginResponseDto, UserResponseDto } from '@app/domain'; +import { adminSignupStub, loginResponseStub, loginStub } from '@test'; import request from 'supertest'; export const authApi = { @@ -7,9 +7,8 @@ export const authApi = { const { status, body } = await request(server).post('/auth/admin-sign-up').send(adminSignupStub); expect(status).toBe(201); - expect(body).toEqual(signupResponseStub); - return body as AdminSignupResponseDto; + return body as UserResponseDto; }, adminLogin: async (server: any) => { const { status, body } = await request(server).post('/auth/login').send(loginStub.admin); diff --git a/server/test/e2e/auth.e2e-spec.ts b/server/test/e2e/auth.e2e-spec.ts index 985645129f..191add42fa 100644 --- a/server/test/e2e/auth.e2e-spec.ts +++ b/server/test/e2e/auth.e2e-spec.ts @@ -8,7 +8,6 @@ import { errorStub, loginResponseStub, loginStub, - signupResponseStub, uuidStub, } from '@test/fixtures'; import { testApp } from '@test/test-utils'; @@ -19,6 +18,24 @@ const lastName = 'Admin'; const password = 'Password123'; const email = 'admin@immich.app'; +const adminSignupResponse = { + id: expect.any(String), + firstName: 'Immich', + lastName: 'Admin', + email: 'admin@immich.app', + storageLabel: 'admin', + externalPath: null, + profileImagePath: '', + // why? lol + shouldChangePassword: true, + isAdmin: true, + createdAt: expect.any(String), + updatedAt: expect.any(String), + deletedAt: null, + oauthId: '', + memoriesEnabled: true, +}; + describe(`${AuthController.name} (e2e)`, () => { let server: any; let accessToken: string; @@ -84,7 +101,7 @@ describe(`${AuthController.name} (e2e)`, () => { .post('/auth/admin-sign-up') .send({ ...adminSignupStub, email: 'admin@local' }); expect(status).toEqual(201); - expect(body).toEqual({ ...signupResponseStub, email: 'admin@local' }); + expect(body).toEqual({ ...adminSignupResponse, email: 'admin@local' }); }); it('should transform email to lower case', async () => { @@ -92,7 +109,7 @@ describe(`${AuthController.name} (e2e)`, () => { .post('/auth/admin-sign-up') .send({ ...adminSignupStub, email: 'aDmIn@IMMICH.app' }); expect(status).toEqual(201); - expect(body).toEqual(signupResponseStub); + expect(body).toEqual(adminSignupResponse); }); it('should not allow a second admin to sign up', async () => { diff --git a/server/test/fixtures/auth.stub.ts b/server/test/fixtures/auth.stub.ts index 6a45c16afb..68ff9b717c 100644 --- a/server/test/fixtures/auth.stub.ts +++ b/server/test/fixtures/auth.stub.ts @@ -12,14 +12,6 @@ export const userSignupStub = { memoriesEnabled: true, }; -export const signupResponseStub = { - id: expect.any(String), - email: 'admin@immich.app', - firstName: 'Immich', - lastName: 'Admin', - createdAt: expect.any(String), -}; - export const loginStub = { admin: { email: 'admin@immich.app', diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index fb40a1ef4f..6df8262f60 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -209,43 +209,6 @@ export interface AddUsersDto { */ 'sharedUserIds': Array; } -/** - * - * @export - * @interface AdminSignupResponseDto - */ -export interface AdminSignupResponseDto { - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'createdAt': string; - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'email': string; - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'firstName': string; - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'id': string; - /** - * - * @type {string} - * @memberof AdminSignupResponseDto - */ - 'lastName': string; -} /** * * @export @@ -10509,7 +10472,7 @@ export const AuthenticationApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async signUpAdmin(signUpDto: SignUpDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async signUpAdmin(signUpDto: SignUpDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.signUpAdmin(signUpDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -10589,7 +10552,7 @@ export const AuthenticationApiFactory = function (configuration?: Configuration, * @param {*} [options] Override http request option. * @throws {RequiredError} */ - signUpAdmin(requestParameters: AuthenticationApiSignUpAdminRequest, options?: AxiosRequestConfig): AxiosPromise { + signUpAdmin(requestParameters: AuthenticationApiSignUpAdminRequest, options?: AxiosRequestConfig): AxiosPromise { return localVarFp.signUpAdmin(requestParameters.signUpDto, options).then((request) => request(axios, basePath)); }, /**