diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index a596cc0d8..7b27be8b2 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -19,6 +19,7 @@ doc/AssetFileUploadResponseDto.md doc/AssetResponseDto.md doc/AssetTypeEnum.md doc/AuthenticationApi.md +doc/ChangePasswordDto.md doc/CheckDuplicateAssetDto.md doc/CheckDuplicateAssetResponseDto.md doc/CheckExistingAssetsDto.md @@ -114,6 +115,7 @@ lib/model/asset_count_by_user_id_response_dto.dart lib/model/asset_file_upload_response_dto.dart lib/model/asset_response_dto.dart lib/model/asset_type_enum.dart +lib/model/change_password_dto.dart lib/model/check_duplicate_asset_dto.dart lib/model/check_duplicate_asset_response_dto.dart lib/model/check_existing_assets_dto.dart @@ -186,6 +188,7 @@ test/asset_file_upload_response_dto_test.dart test/asset_response_dto_test.dart test/asset_type_enum_test.dart test/authentication_api_test.dart +test/change_password_dto_test.dart test/check_duplicate_asset_dto_test.dart test/check_duplicate_asset_response_dto_test.dart test/check_existing_assets_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index e29aec936..49b024420 100644 Binary files a/mobile/openapi/README.md and b/mobile/openapi/README.md differ diff --git a/mobile/openapi/doc/AuthenticationApi.md b/mobile/openapi/doc/AuthenticationApi.md index 01f106f73..ffcece086 100644 Binary files a/mobile/openapi/doc/AuthenticationApi.md and b/mobile/openapi/doc/AuthenticationApi.md differ diff --git a/mobile/openapi/doc/ChangePasswordDto.md b/mobile/openapi/doc/ChangePasswordDto.md new file mode 100644 index 000000000..a257395ba Binary files /dev/null and b/mobile/openapi/doc/ChangePasswordDto.md differ diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index e227bb752..83ca7a388 100644 Binary files a/mobile/openapi/lib/api.dart and b/mobile/openapi/lib/api.dart differ diff --git a/mobile/openapi/lib/api/authentication_api.dart b/mobile/openapi/lib/api/authentication_api.dart index 8f90376a4..39888550d 100644 Binary files a/mobile/openapi/lib/api/authentication_api.dart and b/mobile/openapi/lib/api/authentication_api.dart differ diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 0fd170593..13f5a3e02 100644 Binary files a/mobile/openapi/lib/api_client.dart and b/mobile/openapi/lib/api_client.dart differ diff --git a/mobile/openapi/lib/model/change_password_dto.dart b/mobile/openapi/lib/model/change_password_dto.dart new file mode 100644 index 000000000..dc8bb31e2 Binary files /dev/null and b/mobile/openapi/lib/model/change_password_dto.dart differ diff --git a/mobile/openapi/test/authentication_api_test.dart b/mobile/openapi/test/authentication_api_test.dart index 97fe5b87a..f855d3239 100644 Binary files a/mobile/openapi/test/authentication_api_test.dart and b/mobile/openapi/test/authentication_api_test.dart differ diff --git a/mobile/openapi/test/change_password_dto_test.dart b/mobile/openapi/test/change_password_dto_test.dart new file mode 100644 index 000000000..5095250fc Binary files /dev/null and b/mobile/openapi/test/change_password_dto_test.dart differ diff --git a/server/apps/immich/src/api-v1/auth/auth.controller.ts b/server/apps/immich/src/api-v1/auth/auth.controller.ts index a2dbce89e..53b6b02b9 100644 --- a/server/apps/immich/src/api-v1/auth/auth.controller.ts +++ b/server/apps/immich/src/api-v1/auth/auth.controller.ts @@ -5,7 +5,9 @@ import { AuthType, IMMICH_AUTH_TYPE_COOKIE } from '../../constants/jwt.constant' import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator'; import { Authenticated } from '../../decorators/authenticated.decorator'; import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service'; +import { UserResponseDto } from '../user/response-dto/user-response.dto'; import { AuthService } from './auth.service'; +import { ChangePasswordDto } from './dto/change-password.dto'; import { LoginCredentialDto } from './dto/login-credential.dto'; import { SignUpDto } from './dto/sign-up.dto'; import { AdminSignupResponseDto } from './response-dto/admin-signup-response.dto'; @@ -45,6 +47,13 @@ export class AuthController { return new ValidateAccessTokenResponseDto(true); } + @Authenticated() + @ApiBearerAuth() + @Post('change-password') + async changePassword(@GetAuthUser() authUser: AuthUserDto, @Body() dto: ChangePasswordDto): Promise { + return this.authService.changePassword(authUser, dto); + } + @Post('/logout') async logout(@Req() req: Request, @Res({ passthrough: true }) response: Response): Promise { const authType: AuthType = req.cookies[IMMICH_AUTH_TYPE_COOKIE]; diff --git a/server/apps/immich/src/api-v1/auth/auth.service.ts b/server/apps/immich/src/api-v1/auth/auth.service.ts index cfcaf5893..090b8c86a 100644 --- a/server/apps/immich/src/api-v1/auth/auth.service.ts +++ b/server/apps/immich/src/api-v1/auth/auth.service.ts @@ -1,9 +1,18 @@ -import { BadRequestException, Inject, Injectable, InternalServerErrorException, Logger } from '@nestjs/common'; +import { + BadRequestException, + Inject, + Injectable, + InternalServerErrorException, + Logger, + UnauthorizedException, +} from '@nestjs/common'; import * as bcrypt from 'bcrypt'; import { UserEntity } from '../../../../../libs/database/src/entities/user.entity'; import { AuthType } from '../../constants/jwt.constant'; +import { AuthUserDto } from '../../decorators/auth-user.decorator'; import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service'; import { IUserRepository, USER_REPOSITORY } from '../user/user-repository'; +import { ChangePasswordDto } from './dto/change-password.dto'; import { LoginCredentialDto } from './dto/login-credential.dto'; import { SignUpDto } from './dto/sign-up.dto'; import { AdminSignupResponseDto, mapAdminSignupResponse } from './response-dto/admin-signup-response.dto'; @@ -48,6 +57,23 @@ export class AuthService { return { successful: true, redirectUri: '/auth/login' }; } + public async changePassword(authUser: AuthUserDto, dto: ChangePasswordDto) { + const { password, newPassword } = dto; + const user = await this.userRepository.getByEmail(authUser.email, true); + if (!user) { + throw new UnauthorizedException(); + } + + const valid = await this.validatePassword(password, user); + if (!valid) { + throw new BadRequestException('Wrong password'); + } + + user.password = newPassword; + + return this.userRepository.update(user.id, user); + } + public async adminSignUp(dto: SignUpDto): Promise { const adminUser = await this.userRepository.getAdmin(); diff --git a/server/apps/immich/src/api-v1/auth/dto/change-password.dto.ts b/server/apps/immich/src/api-v1/auth/dto/change-password.dto.ts new file mode 100644 index 000000000..9c5ce479e --- /dev/null +++ b/server/apps/immich/src/api-v1/auth/dto/change-password.dto.ts @@ -0,0 +1,15 @@ +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/apps/immich/src/api-v1/user/user-repository.ts b/server/apps/immich/src/api-v1/user/user-repository.ts index e4dd2e646..2c1160790 100644 --- a/server/apps/immich/src/api-v1/user/user-repository.ts +++ b/server/apps/immich/src/api-v1/user/user-repository.ts @@ -86,7 +86,7 @@ export class UserRepository implements IUserRepository { if (user.isAdmin) { const adminUser = await this.userRepository.findOne({ where: { isAdmin: true } }); - if (adminUser) { + if (adminUser && adminUser.id !== id) { throw new BadRequestException('Admin user exists'); } diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index fb8bce007..d01a7d1a7 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -1707,6 +1707,42 @@ ] } }, + "/auth/change-password": { + "post": { + "operationId": "changePassword", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangePasswordDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponseDto" + } + } + } + } + }, + "tags": [ + "Authentication" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, "/auth/logout": { "post": { "operationId": "logout", @@ -3258,6 +3294,23 @@ "authStatus" ] }, + "ChangePasswordDto": { + "type": "object", + "properties": { + "password": { + "type": "string", + "example": "password" + }, + "newPassword": { + "type": "string", + "example": "password" + } + }, + "required": [ + "password", + "newPassword" + ] + }, "LogoutResponseDto": { "type": "object", "properties": { diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 156fd87f6..38b5be8cd 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.38.2 + * The version of the OpenAPI document: 1.39.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). @@ -481,6 +481,25 @@ export const AssetTypeEnum = { export type AssetTypeEnum = typeof AssetTypeEnum[keyof typeof AssetTypeEnum]; +/** + * + * @export + * @interface ChangePasswordDto + */ +export interface ChangePasswordDto { + /** + * + * @type {string} + * @memberof ChangePasswordDto + */ + 'password': string; + /** + * + * @type {string} + * @memberof ChangePasswordDto + */ + 'newPassword': string; +} /** * * @export @@ -4171,6 +4190,45 @@ export const AuthenticationApiAxiosParamCreator = function (configuration?: Conf options: localVarRequestOptions, }; }, + /** + * + * @param {ChangePasswordDto} changePasswordDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + changePassword: async (changePasswordDto: ChangePasswordDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'changePasswordDto' is not null or undefined + assertParamExists('changePassword', 'changePasswordDto', changePasswordDto) + const localVarPath = `/auth/change-password`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(changePasswordDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {LoginCredentialDto} loginCredentialDto @@ -4288,6 +4346,16 @@ export const AuthenticationApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.adminSignUp(signUpDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {ChangePasswordDto} changePasswordDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async changePassword(changePasswordDto: ChangePasswordDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.changePassword(changePasswordDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {LoginCredentialDto} loginCredentialDto @@ -4335,6 +4403,15 @@ export const AuthenticationApiFactory = function (configuration?: Configuration, adminSignUp(signUpDto: SignUpDto, options?: any): AxiosPromise { return localVarFp.adminSignUp(signUpDto, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {ChangePasswordDto} changePasswordDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + changePassword(changePasswordDto: ChangePasswordDto, options?: any): AxiosPromise { + return localVarFp.changePassword(changePasswordDto, options).then((request) => request(axios, basePath)); + }, /** * * @param {LoginCredentialDto} loginCredentialDto @@ -4381,6 +4458,17 @@ export class AuthenticationApi extends BaseAPI { return AuthenticationApiFp(this.configuration).adminSignUp(signUpDto, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {ChangePasswordDto} changePasswordDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AuthenticationApi + */ + public changePassword(changePasswordDto: ChangePasswordDto, options?: AxiosRequestConfig) { + return AuthenticationApiFp(this.configuration).changePassword(changePasswordDto, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {LoginCredentialDto} loginCredentialDto diff --git a/web/src/api/open-api/base.ts b/web/src/api/open-api/base.ts index f00f196d8..5cb76e447 100644 --- a/web/src/api/open-api/base.ts +++ b/web/src/api/open-api/base.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.38.2 + * The version of the OpenAPI document: 1.39.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/api/open-api/common.ts b/web/src/api/open-api/common.ts index a946fabb5..79a5fd933 100644 --- a/web/src/api/open-api/common.ts +++ b/web/src/api/open-api/common.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.38.2 + * The version of the OpenAPI document: 1.39.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/api/open-api/configuration.ts b/web/src/api/open-api/configuration.ts index 48794159a..3d13f6f92 100644 --- a/web/src/api/open-api/configuration.ts +++ b/web/src/api/open-api/configuration.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.38.2 + * The version of the OpenAPI document: 1.39.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/api/open-api/index.ts b/web/src/api/open-api/index.ts index f0b9d9c78..c9ec6d14c 100644 --- a/web/src/api/open-api/index.ts +++ b/web/src/api/open-api/index.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.38.2 + * The version of the OpenAPI document: 1.39.0 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/lib/components/user-settings-page/user-settings-list.svelte b/web/src/lib/components/user-settings-page/user-settings-list.svelte index a7a967715..0f2589dfd 100644 --- a/web/src/lib/components/user-settings-page/user-settings-list.svelte +++ b/web/src/lib/components/user-settings-page/user-settings-list.svelte @@ -1,27 +1,154 @@ - +
- +
+
+
+ - + + + + + + +
+ +
+
+
+
+
+
+ + +
+
+
+
+ + + + + + +
+ +
+
+
+