فهرست منبع

refactor(server): auth dtos (#4881)

* refactor: auth dtos

* chore: open api
Jason Rasmussen 1 سال پیش
والد
کامیت
5423f1c25b
38فایلهای تغییر یافته به همراه187 افزوده شده و 657 حذف شده
  1. 2 39
      cli/src/api/open-api/api.ts
  2. 0 3
      mobile/openapi/.openapi-generator/FILES
  3. 0 1
      mobile/openapi/README.md
  4. 0 19
      mobile/openapi/doc/AdminSignupResponseDto.md
  5. 2 2
      mobile/openapi/doc/AuthenticationApi.md
  6. 8 8
      mobile/openapi/doc/LoginResponseDto.md
  7. 0 1
      mobile/openapi/lib/api.dart
  8. 2 2
      mobile/openapi/lib/api/authentication_api.dart
  9. 0 2
      mobile/openapi/lib/api_client.dart
  10. 0 130
      mobile/openapi/lib/model/admin_signup_response_dto.dart
  11. 0 47
      mobile/openapi/test/admin_signup_response_dto_test.dart
  12. 1 1
      mobile/openapi/test/authentication_api_test.dart
  13. 1 40
      server/immich-openapi-specs.json
  14. 132 0
      server/src/domain/auth/auth.dto.ts
  15. 1 1
      server/src/domain/auth/auth.service.spec.ts
  16. 9 6
      server/src/domain/auth/auth.service.ts
  17. 0 12
      server/src/domain/auth/dto/auth-user.dto.ts
  18. 0 15
      server/src/domain/auth/dto/change-password.dto.ts
  19. 0 6
      server/src/domain/auth/dto/index.ts
  20. 0 42
      server/src/domain/auth/dto/login-credential.dto.spec.ts
  21. 0 16
      server/src/domain/auth/dto/login-credential.dto.ts
  22. 0 9
      server/src/domain/auth/dto/oauth-auth-code.dto.ts
  23. 0 7
      server/src/domain/auth/dto/oauth-config.dto.ts
  24. 0 58
      server/src/domain/auth/dto/sign-up.dto.spec.ts
  25. 0 26
      server/src/domain/auth/dto/sign-up.dto.ts
  26. 1 2
      server/src/domain/auth/index.ts
  27. 0 19
      server/src/domain/auth/response-dto/admin-signup-response.dto.ts
  28. 0 19
      server/src/domain/auth/response-dto/auth-device-response.dto.ts
  29. 0 6
      server/src/domain/auth/response-dto/index.ts
  30. 0 41
      server/src/domain/auth/response-dto/login-response.dto.ts
  31. 0 4
      server/src/domain/auth/response-dto/logout-response.dto.ts
  32. 0 11
      server/src/domain/auth/response-dto/oauth-config-response.dto.ts
  33. 0 3
      server/src/domain/auth/response-dto/validate-asset-token-response.dto.ts
  34. 3 5
      server/src/immich/controllers/auth.controller.ts
  35. 3 4
      server/test/api/auth-api.ts
  36. 20 3
      server/test/e2e/auth.e2e-spec.ts
  37. 0 8
      server/test/fixtures/auth.stub.ts
  38. 2 39
      web/src/api/open-api/api.ts

+ 2 - 39
cli/src/api/open-api/api.ts

@@ -209,43 +209,6 @@ export interface AddUsersDto {
      */
     'sharedUserIds': Array<string>;
 }
-/**
- * 
- * @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<AdminSignupResponseDto>> {
+        async signUpAdmin(signUpDto: SignUpDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserResponseDto>> {
             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<AdminSignupResponseDto> {
+        signUpAdmin(requestParameters: AuthenticationApiSignUpAdminRequest, options?: AxiosRequestConfig): AxiosPromise<UserResponseDto> {
             return localVarFp.signUpAdmin(requestParameters.signUpDto, options).then((request) => request(axios, basePath));
         },
         /**

+ 0 - 3
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

+ 0 - 1
mobile/openapi/README.md

@@ -212,7 +212,6 @@ Class | Method | HTTP request | Description
  - [ActivityResponseDto](doc//ActivityResponseDto.md)
  - [ActivityStatisticsResponseDto](doc//ActivityStatisticsResponseDto.md)
  - [AddUsersDto](doc//AddUsersDto.md)
- - [AdminSignupResponseDto](doc//AdminSignupResponseDto.md)
  - [AlbumCountResponseDto](doc//AlbumCountResponseDto.md)
  - [AlbumResponseDto](doc//AlbumResponseDto.md)
  - [AllJobStatusResponseDto](doc//AllJobStatusResponseDto.md)

+ 0 - 19
mobile/openapi/doc/AdminSignupResponseDto.md

@@ -1,19 +0,0 @@
-# openapi.model.AdminSignupResponseDto
-
-## Load the model package
-```dart
-import 'package:openapi/api.dart';
-```
-
-## Properties
-Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
-**createdAt** | [**DateTime**](DateTime.md) |  | 
-**email** | **String** |  | 
-**firstName** | **String** |  | 
-**id** | **String** |  | 
-**lastName** | **String** |  | 
-
-[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
-
-

+ 2 - 2
mobile/openapi/doc/AuthenticationApi.md

@@ -322,7 +322,7 @@ void (empty response body)
 [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
 
 # **signUpAdmin**
-> AdminSignupResponseDto signUpAdmin(signUpDto)
+> UserResponseDto signUpAdmin(signUpDto)
 
 
 
@@ -349,7 +349,7 @@ Name | Type | Description  | Notes
 
 ### Return type
 
-[**AdminSignupResponseDto**](AdminSignupResponseDto.md)
+[**UserResponseDto**](UserResponseDto.md)
 
 ### Authorization
 

+ 8 - 8
mobile/openapi/doc/LoginResponseDto.md

@@ -8,14 +8,14 @@ import 'package:openapi/api.dart';
 ## Properties
 Name | Type | Description | Notes
 ------------ | ------------- | ------------- | -------------
-**accessToken** | **String** |  | [readonly] 
-**firstName** | **String** |  | [readonly] 
-**isAdmin** | **bool** |  | [readonly] 
-**lastName** | **String** |  | [readonly] 
-**profileImagePath** | **String** |  | [readonly] 
-**shouldChangePassword** | **bool** |  | [readonly] 
-**userEmail** | **String** |  | [readonly] 
-**userId** | **String** |  | [readonly] 
+**accessToken** | **String** |  | 
+**firstName** | **String** |  | 
+**isAdmin** | **bool** |  | 
+**lastName** | **String** |  | 
+**profileImagePath** | **String** |  | 
+**shouldChangePassword** | **bool** |  | 
+**userEmail** | **String** |  | 
+**userId** | **String** |  | 
 
 [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
 

+ 0 - 1
mobile/openapi/lib/api.dart

@@ -54,7 +54,6 @@ part 'model/activity_create_dto.dart';
 part 'model/activity_response_dto.dart';
 part 'model/activity_statistics_response_dto.dart';
 part 'model/add_users_dto.dart';
-part 'model/admin_signup_response_dto.dart';
 part 'model/album_count_response_dto.dart';
 part 'model/album_response_dto.dart';
 part 'model/all_job_status_response_dto.dart';

+ 2 - 2
mobile/openapi/lib/api/authentication_api.dart

@@ -300,7 +300,7 @@ class AuthenticationApi {
   /// Parameters:
   ///
   /// * [SignUpDto] signUpDto (required):
-  Future<AdminSignupResponseDto?> signUpAdmin(SignUpDto signUpDto,) async {
+  Future<UserResponseDto?> signUpAdmin(SignUpDto signUpDto,) async {
     final response = await signUpAdminWithHttpInfo(signUpDto,);
     if (response.statusCode >= HttpStatus.badRequest) {
       throw ApiException(response.statusCode, await _decodeBodyBytes(response));
@@ -309,7 +309,7 @@ class AuthenticationApi {
     // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
     // FormatException when trying to decode an empty string.
     if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
-      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AdminSignupResponseDto',) as AdminSignupResponseDto;
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserResponseDto',) as UserResponseDto;
     
     }
     return null;

+ 0 - 2
mobile/openapi/lib/api_client.dart

@@ -197,8 +197,6 @@ class ApiClient {
           return ActivityStatisticsResponseDto.fromJson(value);
         case 'AddUsersDto':
           return AddUsersDto.fromJson(value);
-        case 'AdminSignupResponseDto':
-          return AdminSignupResponseDto.fromJson(value);
         case 'AlbumCountResponseDto':
           return AlbumCountResponseDto.fromJson(value);
         case 'AlbumResponseDto':

+ 0 - 130
mobile/openapi/lib/model/admin_signup_response_dto.dart

@@ -1,130 +0,0 @@
-//
-// AUTO-GENERATED FILE, DO NOT MODIFY!
-//
-// @dart=2.12
-
-// ignore_for_file: unused_element, unused_import
-// ignore_for_file: always_put_required_named_parameters_first
-// ignore_for_file: constant_identifier_names
-// ignore_for_file: lines_longer_than_80_chars
-
-part of openapi.api;
-
-class AdminSignupResponseDto {
-  /// Returns a new [AdminSignupResponseDto] instance.
-  AdminSignupResponseDto({
-    required this.createdAt,
-    required this.email,
-    required this.firstName,
-    required this.id,
-    required this.lastName,
-  });
-
-  DateTime createdAt;
-
-  String email;
-
-  String firstName;
-
-  String id;
-
-  String lastName;
-
-  @override
-  bool operator ==(Object other) => identical(this, other) || other is AdminSignupResponseDto &&
-     other.createdAt == createdAt &&
-     other.email == email &&
-     other.firstName == firstName &&
-     other.id == id &&
-     other.lastName == lastName;
-
-  @override
-  int get hashCode =>
-    // ignore: unnecessary_parenthesis
-    (createdAt.hashCode) +
-    (email.hashCode) +
-    (firstName.hashCode) +
-    (id.hashCode) +
-    (lastName.hashCode);
-
-  @override
-  String toString() => 'AdminSignupResponseDto[createdAt=$createdAt, email=$email, firstName=$firstName, id=$id, lastName=$lastName]';
-
-  Map<String, dynamic> toJson() {
-    final json = <String, dynamic>{};
-      json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
-      json[r'email'] = this.email;
-      json[r'firstName'] = this.firstName;
-      json[r'id'] = this.id;
-      json[r'lastName'] = this.lastName;
-    return json;
-  }
-
-  /// Returns a new [AdminSignupResponseDto] instance and imports its values from
-  /// [value] if it's a [Map], null otherwise.
-  // ignore: prefer_constructors_over_static_methods
-  static AdminSignupResponseDto? fromJson(dynamic value) {
-    if (value is Map) {
-      final json = value.cast<String, dynamic>();
-
-      return AdminSignupResponseDto(
-        createdAt: mapDateTime(json, r'createdAt', '')!,
-        email: mapValueOfType<String>(json, r'email')!,
-        firstName: mapValueOfType<String>(json, r'firstName')!,
-        id: mapValueOfType<String>(json, r'id')!,
-        lastName: mapValueOfType<String>(json, r'lastName')!,
-      );
-    }
-    return null;
-  }
-
-  static List<AdminSignupResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
-    final result = <AdminSignupResponseDto>[];
-    if (json is List && json.isNotEmpty) {
-      for (final row in json) {
-        final value = AdminSignupResponseDto.fromJson(row);
-        if (value != null) {
-          result.add(value);
-        }
-      }
-    }
-    return result.toList(growable: growable);
-  }
-
-  static Map<String, AdminSignupResponseDto> mapFromJson(dynamic json) {
-    final map = <String, AdminSignupResponseDto>{};
-    if (json is Map && json.isNotEmpty) {
-      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
-      for (final entry in json.entries) {
-        final value = AdminSignupResponseDto.fromJson(entry.value);
-        if (value != null) {
-          map[entry.key] = value;
-        }
-      }
-    }
-    return map;
-  }
-
-  // maps a json object with a list of AdminSignupResponseDto-objects as value to a dart map
-  static Map<String, List<AdminSignupResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
-    final map = <String, List<AdminSignupResponseDto>>{};
-    if (json is Map && json.isNotEmpty) {
-      // ignore: parameter_assignments
-      json = json.cast<String, dynamic>();
-      for (final entry in json.entries) {
-        map[entry.key] = AdminSignupResponseDto.listFromJson(entry.value, growable: growable,);
-      }
-    }
-    return map;
-  }
-
-  /// The list of required keys that must be present in a JSON.
-  static const requiredKeys = <String>{
-    'createdAt',
-    'email',
-    'firstName',
-    'id',
-    'lastName',
-  };
-}
-

+ 0 - 47
mobile/openapi/test/admin_signup_response_dto_test.dart

@@ -1,47 +0,0 @@
-//
-// AUTO-GENERATED FILE, DO NOT MODIFY!
-//
-// @dart=2.12
-
-// ignore_for_file: unused_element, unused_import
-// ignore_for_file: always_put_required_named_parameters_first
-// ignore_for_file: constant_identifier_names
-// ignore_for_file: lines_longer_than_80_chars
-
-import 'package:openapi/api.dart';
-import 'package:test/test.dart';
-
-// tests for AdminSignupResponseDto
-void main() {
-  // final instance = AdminSignupResponseDto();
-
-  group('test AdminSignupResponseDto', () {
-    // DateTime createdAt
-    test('to test the property `createdAt`', () async {
-      // TODO
-    });
-
-    // String email
-    test('to test the property `email`', () async {
-      // TODO
-    });
-
-    // String firstName
-    test('to test the property `firstName`', () async {
-      // TODO
-    });
-
-    // String id
-    test('to test the property `id`', () async {
-      // TODO
-    });
-
-    // String lastName
-    test('to test the property `lastName`', () async {
-      // TODO
-    });
-
-
-  });
-
-}

+ 1 - 1
mobile/openapi/test/authentication_api_test.dart

@@ -47,7 +47,7 @@ void main() {
       // TODO
     });
 
-    //Future<AdminSignupResponseDto> signUpAdmin(SignUpDto signUpDto) async
+    //Future<UserResponseDto> signUpAdmin(SignUpDto signUpDto) async
     test('test signUpAdmin', () async {
       // TODO
     });

+ 1 - 40
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"
           }
         },

+ 132 - 0
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;
+}

+ 1 - 1
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');
 

+ 9 - 6
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<AdminSignupResponseDto> {
+  async adminSignUp(dto: SignUpDto): Promise<UserResponseDto> {
     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<string, string>): Promise<AuthUserDto> {

+ 0 - 12
server/src/domain/auth/dto/auth-user.dto.ts

@@ -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;
-}

+ 0 - 15
server/src/domain/auth/dto/change-password.dto.ts

@@ -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;
-}

+ 0 - 6
server/src/domain/auth/dto/index.ts

@@ -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';

+ 0 - 42
server/src/domain/auth/dto/login-credential.dto.spec.ts

@@ -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');
-  });
-});

+ 0 - 16
server/src/domain/auth/dto/login-credential.dto.ts

@@ -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;
-}

+ 0 - 9
server/src/domain/auth/dto/oauth-auth-code.dto.ts

@@ -1,9 +0,0 @@
-import { ApiProperty } from '@nestjs/swagger';
-import { IsNotEmpty, IsString } from 'class-validator';
-
-export class OAuthCallbackDto {
-  @IsNotEmpty()
-  @IsString()
-  @ApiProperty()
-  url!: string;
-}

+ 0 - 7
server/src/domain/auth/dto/oauth-config.dto.ts

@@ -1,7 +0,0 @@
-import { IsNotEmpty, IsString } from 'class-validator';
-
-export class OAuthConfigDto {
-  @IsNotEmpty()
-  @IsString()
-  redirectUri!: string;
-}

+ 0 - 58
server/src/domain/auth/dto/sign-up.dto.spec.ts

@@ -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');
-  });
-});

+ 0 - 26
server/src/domain/auth/dto/sign-up.dto.ts

@@ -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;
-}

+ 1 - 2
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';

+ 0 - 19
server/src/domain/auth/response-dto/admin-signup-response.dto.ts

@@ -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,
-  };
-}

+ 0 - 19
server/src/domain/auth/response-dto/auth-device-response.dto.ts

@@ -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,
-});

+ 0 - 6
server/src/domain/auth/response-dto/index.ts

@@ -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';

+ 0 - 41
server/src/domain/auth/response-dto/login-response.dto.ts

@@ -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,
-  };
-}

+ 0 - 4
server/src/domain/auth/response-dto/logout-response.dto.ts

@@ -1,4 +0,0 @@
-export class LogoutResponseDto {
-  successful!: boolean;
-  redirectUri!: string;
-}

+ 0 - 11
server/src/domain/auth/response-dto/oauth-config-response.dto.ts

@@ -1,11 +0,0 @@
-export class OAuthConfigResponseDto {
-  enabled!: boolean;
-  passwordLoginEnabled!: boolean;
-  url?: string;
-  buttonText?: string;
-  autoLaunch?: boolean;
-}
-
-export class OAuthAuthorizeResponseDto {
-  url!: string;
-}

+ 0 - 3
server/src/domain/auth/response-dto/validate-asset-token-response.dto.ts

@@ -1,3 +0,0 @@
-export class ValidateAccessTokenResponseDto {
-  authStatus!: boolean;
-}

+ 3 - 5
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<AdminSignupResponseDto> {
-    return this.service.adminSignUp(signUpCredential);
+  signUpAdmin(@Body() dto: SignUpDto): Promise<UserResponseDto> {
+    return this.service.adminSignUp(dto);
   }
 
   @Get('devices')

+ 3 - 4
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);

+ 20 - 3
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 () => {

+ 0 - 8
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',

+ 2 - 39
web/src/api/open-api/api.ts

@@ -209,43 +209,6 @@ export interface AddUsersDto {
      */
     'sharedUserIds': Array<string>;
 }
-/**
- * 
- * @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<AdminSignupResponseDto>> {
+        async signUpAdmin(signUpDto: SignUpDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserResponseDto>> {
             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<AdminSignupResponseDto> {
+        signUpAdmin(requestParameters: AuthenticationApiSignUpAdminRequest, options?: AxiosRequestConfig): AxiosPromise<UserResponseDto> {
             return localVarFp.signUpAdmin(requestParameters.signUpDto, options).then((request) => request(axios, basePath));
         },
         /**