mirror of
https://github.com/immich-app/immich.git
synced 2024-12-25 10:43:13 +02:00
refactor(server): auth dtos (#4881)
* refactor: auth dtos * chore: open api
This commit is contained in:
parent
5c602bf4d4
commit
5423f1c25b
41
cli/src/api/open-api/api.ts
generated
41
cli/src/api/open-api/api.ts
generated
@ -209,43 +209,6 @@ export interface AddUsersDto {
|
|||||||
*/
|
*/
|
||||||
'sharedUserIds': Array<string>;
|
'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
|
* @export
|
||||||
@ -10509,7 +10472,7 @@ export const AuthenticationApiFp = function(configuration?: Configuration) {
|
|||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @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);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.signUpAdmin(signUpDto, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
@ -10589,7 +10552,7 @@ export const AuthenticationApiFactory = function (configuration?: Configuration,
|
|||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @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));
|
return localVarFp.signUpAdmin(requestParameters.signUpDto, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
3
mobile/openapi/.openapi-generator/FILES
generated
3
mobile/openapi/.openapi-generator/FILES
generated
@ -13,7 +13,6 @@ doc/ActivityCreateDto.md
|
|||||||
doc/ActivityResponseDto.md
|
doc/ActivityResponseDto.md
|
||||||
doc/ActivityStatisticsResponseDto.md
|
doc/ActivityStatisticsResponseDto.md
|
||||||
doc/AddUsersDto.md
|
doc/AddUsersDto.md
|
||||||
doc/AdminSignupResponseDto.md
|
|
||||||
doc/AlbumApi.md
|
doc/AlbumApi.md
|
||||||
doc/AlbumCountResponseDto.md
|
doc/AlbumCountResponseDto.md
|
||||||
doc/AlbumResponseDto.md
|
doc/AlbumResponseDto.md
|
||||||
@ -198,7 +197,6 @@ lib/model/activity_create_dto.dart
|
|||||||
lib/model/activity_response_dto.dart
|
lib/model/activity_response_dto.dart
|
||||||
lib/model/activity_statistics_response_dto.dart
|
lib/model/activity_statistics_response_dto.dart
|
||||||
lib/model/add_users_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_count_response_dto.dart
|
||||||
lib/model/album_response_dto.dart
|
lib/model/album_response_dto.dart
|
||||||
lib/model/all_job_status_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_response_dto_test.dart
|
||||||
test/activity_statistics_response_dto_test.dart
|
test/activity_statistics_response_dto_test.dart
|
||||||
test/add_users_dto_test.dart
|
test/add_users_dto_test.dart
|
||||||
test/admin_signup_response_dto_test.dart
|
|
||||||
test/album_api_test.dart
|
test/album_api_test.dart
|
||||||
test/album_count_response_dto_test.dart
|
test/album_count_response_dto_test.dart
|
||||||
test/album_response_dto_test.dart
|
test/album_response_dto_test.dart
|
||||||
|
BIN
mobile/openapi/README.md
generated
BIN
mobile/openapi/README.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/AdminSignupResponseDto.md
generated
BIN
mobile/openapi/doc/AdminSignupResponseDto.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/AuthenticationApi.md
generated
BIN
mobile/openapi/doc/AuthenticationApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/LoginResponseDto.md
generated
BIN
mobile/openapi/doc/LoginResponseDto.md
generated
Binary file not shown.
BIN
mobile/openapi/lib/api.dart
generated
BIN
mobile/openapi/lib/api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/authentication_api.dart
generated
BIN
mobile/openapi/lib/api/authentication_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api_client.dart
generated
BIN
mobile/openapi/lib/api_client.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/model/admin_signup_response_dto.dart
generated
BIN
mobile/openapi/lib/model/admin_signup_response_dto.dart
generated
Binary file not shown.
BIN
mobile/openapi/test/admin_signup_response_dto_test.dart
generated
BIN
mobile/openapi/test/admin_signup_response_dto_test.dart
generated
Binary file not shown.
BIN
mobile/openapi/test/authentication_api_test.dart
generated
BIN
mobile/openapi/test/authentication_api_test.dart
generated
Binary file not shown.
@ -2630,14 +2630,11 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/AdminSignupResponseDto"
|
"$ref": "#/components/schemas/UserResponseDto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "The server already has an admin"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tags": [
|
"tags": [
|
||||||
@ -5812,34 +5809,6 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"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": {
|
"AlbumCountResponseDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"notShared": {
|
"notShared": {
|
||||||
@ -7377,35 +7346,27 @@
|
|||||||
"LoginResponseDto": {
|
"LoginResponseDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"accessToken": {
|
"accessToken": {
|
||||||
"readOnly": true,
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"firstName": {
|
"firstName": {
|
||||||
"readOnly": true,
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"isAdmin": {
|
"isAdmin": {
|
||||||
"readOnly": true,
|
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"lastName": {
|
"lastName": {
|
||||||
"readOnly": true,
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"profileImagePath": {
|
"profileImagePath": {
|
||||||
"readOnly": true,
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"shouldChangePassword": {
|
"shouldChangePassword": {
|
||||||
"readOnly": true,
|
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"userEmail": {
|
"userEmail": {
|
||||||
"readOnly": true,
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"userId": {
|
"userId": {
|
||||||
"readOnly": true,
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
132
server/src/domain/auth/auth.dto.ts
Normal file
132
server/src/domain/auth/auth.dto.ts
Normal file
@ -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;
|
||||||
|
}
|
@ -31,8 +31,8 @@ import {
|
|||||||
IUserTokenRepository,
|
IUserTokenRepository,
|
||||||
} from '../repositories';
|
} from '../repositories';
|
||||||
import { AuthType } from './auth.constant';
|
import { AuthType } from './auth.constant';
|
||||||
|
import { AuthUserDto, SignUpDto } from './auth.dto';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { AuthUserDto, SignUpDto } from './dto';
|
|
||||||
|
|
||||||
// const token = Buffer.from('my-api-key', 'utf8').toString('base64');
|
// const token = Buffer.from('my-api-key', 'utf8').toString('base64');
|
||||||
|
|
||||||
|
@ -32,18 +32,21 @@ import {
|
|||||||
LOGIN_URL,
|
LOGIN_URL,
|
||||||
MOBILE_REDIRECT,
|
MOBILE_REDIRECT,
|
||||||
} from './auth.constant';
|
} from './auth.constant';
|
||||||
import { AuthUserDto, ChangePasswordDto, LoginCredentialDto, OAuthCallbackDto, OAuthConfigDto, SignUpDto } from './dto';
|
|
||||||
import {
|
import {
|
||||||
AdminSignupResponseDto,
|
|
||||||
AuthDeviceResponseDto,
|
AuthDeviceResponseDto,
|
||||||
|
AuthUserDto,
|
||||||
|
ChangePasswordDto,
|
||||||
|
LoginCredentialDto,
|
||||||
LoginResponseDto,
|
LoginResponseDto,
|
||||||
LogoutResponseDto,
|
LogoutResponseDto,
|
||||||
OAuthAuthorizeResponseDto,
|
OAuthAuthorizeResponseDto,
|
||||||
|
OAuthCallbackDto,
|
||||||
|
OAuthConfigDto,
|
||||||
OAuthConfigResponseDto,
|
OAuthConfigResponseDto,
|
||||||
mapAdminSignupResponse,
|
SignUpDto,
|
||||||
mapLoginResponse,
|
mapLoginResponse,
|
||||||
mapUserToken,
|
mapUserToken,
|
||||||
} from './response-dto';
|
} from './auth.dto';
|
||||||
|
|
||||||
export interface LoginDetails {
|
export interface LoginDetails {
|
||||||
isSecure: boolean;
|
isSecure: boolean;
|
||||||
@ -133,7 +136,7 @@ export class AuthService {
|
|||||||
return this.userCore.updateUser(authUser, authUser.id, { password: newPassword });
|
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();
|
const adminUser = await this.userRepository.getAdmin();
|
||||||
|
|
||||||
if (adminUser) {
|
if (adminUser) {
|
||||||
@ -149,7 +152,7 @@ export class AuthService {
|
|||||||
storageLabel: 'admin',
|
storageLabel: 'admin',
|
||||||
});
|
});
|
||||||
|
|
||||||
return mapAdminSignupResponse(admin);
|
return mapUser(admin);
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(headers: IncomingHttpHeaders, params: Record<string, string>): Promise<AuthUserDto> {
|
async validate(headers: IncomingHttpHeaders, params: Record<string, string>): Promise<AuthUserDto> {
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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';
|
|
@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
@ -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;
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
|
||||||
import { IsNotEmpty, IsString } from 'class-validator';
|
|
||||||
|
|
||||||
export class OAuthCallbackDto {
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
|
||||||
@ApiProperty()
|
|
||||||
url!: string;
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
import { IsNotEmpty, IsString } from 'class-validator';
|
|
||||||
|
|
||||||
export class OAuthConfigDto {
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
|
||||||
redirectUri!: string;
|
|
||||||
}
|
|
@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
@ -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,4 +1,3 @@
|
|||||||
export * from './auth.constant';
|
export * from './auth.constant';
|
||||||
|
export * from './auth.dto';
|
||||||
export * from './auth.service';
|
export * from './auth.service';
|
||||||
export * from './dto';
|
|
||||||
export * from './response-dto';
|
|
||||||
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
@ -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,
|
|
||||||
});
|
|
@ -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';
|
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
export class LogoutResponseDto {
|
|
||||||
successful!: boolean;
|
|
||||||
redirectUri!: string;
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
export class OAuthConfigResponseDto {
|
|
||||||
enabled!: boolean;
|
|
||||||
passwordLoginEnabled!: boolean;
|
|
||||||
url?: string;
|
|
||||||
buttonText?: string;
|
|
||||||
autoLaunch?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class OAuthAuthorizeResponseDto {
|
|
||||||
url!: string;
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
export class ValidateAccessTokenResponseDto {
|
|
||||||
authStatus!: boolean;
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
AdminSignupResponseDto,
|
|
||||||
AuthDeviceResponseDto,
|
AuthDeviceResponseDto,
|
||||||
AuthService,
|
AuthService,
|
||||||
AuthUserDto,
|
AuthUserDto,
|
||||||
@ -15,7 +14,7 @@ import {
|
|||||||
ValidateAccessTokenResponseDto,
|
ValidateAccessTokenResponseDto,
|
||||||
} from '@app/domain';
|
} from '@app/domain';
|
||||||
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Req, Res } from '@nestjs/common';
|
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 { Request, Response } from 'express';
|
||||||
import { AuthUser, Authenticated, GetLoginDetails, PublicRoute } from '../app.guard';
|
import { AuthUser, Authenticated, GetLoginDetails, PublicRoute } from '../app.guard';
|
||||||
import { UseValidation } from '../app.utils';
|
import { UseValidation } from '../app.utils';
|
||||||
@ -42,9 +41,8 @@ export class AuthController {
|
|||||||
|
|
||||||
@PublicRoute()
|
@PublicRoute()
|
||||||
@Post('admin-sign-up')
|
@Post('admin-sign-up')
|
||||||
@ApiBadRequestResponse({ description: 'The server already has an admin' })
|
signUpAdmin(@Body() dto: SignUpDto): Promise<UserResponseDto> {
|
||||||
signUpAdmin(@Body() signUpCredential: SignUpDto): Promise<AdminSignupResponseDto> {
|
return this.service.adminSignUp(dto);
|
||||||
return this.service.adminSignUp(signUpCredential);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('devices')
|
@Get('devices')
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { AdminSignupResponseDto, AuthDeviceResponseDto, LoginCredentialDto, LoginResponseDto } from '@app/domain';
|
import { AuthDeviceResponseDto, LoginCredentialDto, LoginResponseDto, UserResponseDto } from '@app/domain';
|
||||||
import { adminSignupStub, loginResponseStub, loginStub, signupResponseStub } from '@test';
|
import { adminSignupStub, loginResponseStub, loginStub } from '@test';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
|
|
||||||
export const authApi = {
|
export const authApi = {
|
||||||
@ -7,9 +7,8 @@ export const authApi = {
|
|||||||
const { status, body } = await request(server).post('/auth/admin-sign-up').send(adminSignupStub);
|
const { status, body } = await request(server).post('/auth/admin-sign-up').send(adminSignupStub);
|
||||||
|
|
||||||
expect(status).toBe(201);
|
expect(status).toBe(201);
|
||||||
expect(body).toEqual(signupResponseStub);
|
|
||||||
|
|
||||||
return body as AdminSignupResponseDto;
|
return body as UserResponseDto;
|
||||||
},
|
},
|
||||||
adminLogin: async (server: any) => {
|
adminLogin: async (server: any) => {
|
||||||
const { status, body } = await request(server).post('/auth/login').send(loginStub.admin);
|
const { status, body } = await request(server).post('/auth/login').send(loginStub.admin);
|
||||||
|
@ -8,7 +8,6 @@ import {
|
|||||||
errorStub,
|
errorStub,
|
||||||
loginResponseStub,
|
loginResponseStub,
|
||||||
loginStub,
|
loginStub,
|
||||||
signupResponseStub,
|
|
||||||
uuidStub,
|
uuidStub,
|
||||||
} from '@test/fixtures';
|
} from '@test/fixtures';
|
||||||
import { testApp } from '@test/test-utils';
|
import { testApp } from '@test/test-utils';
|
||||||
@ -19,6 +18,24 @@ const lastName = 'Admin';
|
|||||||
const password = 'Password123';
|
const password = 'Password123';
|
||||||
const email = 'admin@immich.app';
|
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)`, () => {
|
describe(`${AuthController.name} (e2e)`, () => {
|
||||||
let server: any;
|
let server: any;
|
||||||
let accessToken: string;
|
let accessToken: string;
|
||||||
@ -84,7 +101,7 @@ describe(`${AuthController.name} (e2e)`, () => {
|
|||||||
.post('/auth/admin-sign-up')
|
.post('/auth/admin-sign-up')
|
||||||
.send({ ...adminSignupStub, email: 'admin@local' });
|
.send({ ...adminSignupStub, email: 'admin@local' });
|
||||||
expect(status).toEqual(201);
|
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 () => {
|
it('should transform email to lower case', async () => {
|
||||||
@ -92,7 +109,7 @@ describe(`${AuthController.name} (e2e)`, () => {
|
|||||||
.post('/auth/admin-sign-up')
|
.post('/auth/admin-sign-up')
|
||||||
.send({ ...adminSignupStub, email: 'aDmIn@IMMICH.app' });
|
.send({ ...adminSignupStub, email: 'aDmIn@IMMICH.app' });
|
||||||
expect(status).toEqual(201);
|
expect(status).toEqual(201);
|
||||||
expect(body).toEqual(signupResponseStub);
|
expect(body).toEqual(adminSignupResponse);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow a second admin to sign up', async () => {
|
it('should not allow a second admin to sign up', async () => {
|
||||||
|
8
server/test/fixtures/auth.stub.ts
vendored
8
server/test/fixtures/auth.stub.ts
vendored
@ -12,14 +12,6 @@ export const userSignupStub = {
|
|||||||
memoriesEnabled: true,
|
memoriesEnabled: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const signupResponseStub = {
|
|
||||||
id: expect.any(String),
|
|
||||||
email: 'admin@immich.app',
|
|
||||||
firstName: 'Immich',
|
|
||||||
lastName: 'Admin',
|
|
||||||
createdAt: expect.any(String),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const loginStub = {
|
export const loginStub = {
|
||||||
admin: {
|
admin: {
|
||||||
email: 'admin@immich.app',
|
email: 'admin@immich.app',
|
||||||
|
41
web/src/api/open-api/api.ts
generated
41
web/src/api/open-api/api.ts
generated
@ -209,43 +209,6 @@ export interface AddUsersDto {
|
|||||||
*/
|
*/
|
||||||
'sharedUserIds': Array<string>;
|
'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
|
* @export
|
||||||
@ -10509,7 +10472,7 @@ export const AuthenticationApiFp = function(configuration?: Configuration) {
|
|||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @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);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.signUpAdmin(signUpDto, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
@ -10589,7 +10552,7 @@ export const AuthenticationApiFactory = function (configuration?: Configuration,
|
|||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @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));
|
return localVarFp.signUpAdmin(requestParameters.signUpDto, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user