feat: add original password verification for password change (#5748)

* feat: add original password verification for password change

* chore: update properties file

* Refine ui

Signed-off-by: Ryan Wang <i@ryanc.cc>

* chore: update properties file

* fix: confirm assword

* fix: unit test case

* feat: add new api for change own password

* chore: regenerate api client

* chore: adapt to UI

* chore: enusre old password not blank

---------

Signed-off-by: Ryan Wang <i@ryanc.cc>
Co-authored-by: Ryan Wang <i@ryanc.cc>
pull/5755/head^2
guqing 2024-04-21 15:07:46 +08:00 committed by GitHub
parent 36b63d6b3c
commit f07c09b243
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 329 additions and 41 deletions

View File

@ -4173,6 +4173,37 @@
]
}
},
"/apis/api.console.halo.run/v1alpha1/users/-/password": {
"put": {
"description": "Change own password of user.",
"operationId": "ChangeOwnPassword",
"requestBody": {
"content": {
"*/*": {
"schema": {
"$ref": "#/components/schemas/ChangeOwnPasswordRequest"
}
}
},
"required": true
},
"responses": {
"default": {
"content": {
"*/*": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
},
"description": "default response"
}
},
"tags": [
"api.console.halo.run/v1alpha1/User"
]
}
},
"/apis/api.console.halo.run/v1alpha1/users/-/send-email-verification-code": {
"post": {
"description": "Send email verification code for user",
@ -4329,8 +4360,8 @@
},
"/apis/api.console.halo.run/v1alpha1/users/{name}/password": {
"put": {
"description": "Change password of user.",
"operationId": "ChangePassword",
"description": "Change anyone password of user for admin.",
"operationId": "ChangeAnyonePassword",
"parameters": [
{
"description": "Name of user. If the name is equal to \u0027-\u0027, it will change the password of current user.",
@ -13152,6 +13183,24 @@
}
}
},
"ChangeOwnPasswordRequest": {
"required": [
"oldPassword",
"password"
],
"type": "object",
"properties": {
"oldPassword": {
"type": "string",
"description": "Old password."
},
"password": {
"minLength": 6,
"type": "string",
"description": "New password."
}
}
},
"ChangePasswordRequest": {
"required": [
"password"
@ -16913,12 +16962,12 @@
},
"visible": {
"type": "string",
"default": "PUBLIC",
"enum": [
"PUBLIC",
"INTERNAL",
"PRIVATE"
],
"default": "PUBLIC"
]
}
}
},
@ -18531,12 +18580,12 @@
},
"visible": {
"type": "string",
"default": "PUBLIC",
"enum": [
"PUBLIC",
"INTERNAL",
"PRIVATE"
],
"default": "PUBLIC"
]
}
}
},

View File

@ -89,6 +89,7 @@ import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
import run.halo.app.infra.SystemSetting;
import run.halo.app.infra.ValidationUtils;
import run.halo.app.infra.exception.RateLimitExceededException;
import run.halo.app.infra.exception.UnsatisfiedAttributeValueException;
import run.halo.app.infra.utils.JsonUtils;
import run.halo.app.security.authentication.twofactor.TwoFactorAuthentication;
@ -160,9 +161,19 @@ public class UserEndpoint implements CustomEndpoint {
.description("User name")
.required(true))
.response(responseBuilder().implementation(UserPermission.class)))
.PUT("/users/{name}/password", this::changePassword,
builder -> builder.operationId("ChangePassword")
.description("Change password of user.")
.PUT("/users/-/password", this::changeOwnPassword,
builder -> builder.operationId("ChangeOwnPassword")
.description("Change own password of user.")
.tag(tag)
.requestBody(requestBodyBuilder()
.required(true)
.implementation(ChangeOwnPasswordRequest.class))
.response(responseBuilder()
.implementation(User.class))
)
.PUT("/users/{name}/password", this::changeAnyonePasswordForAdmin,
builder -> builder.operationId("ChangeAnyonePassword")
.description("Change anyone password of user for admin.")
.tag(tag)
.parameter(parameterBuilder().in(ParameterIn.PATH).name("name")
.description(
@ -520,7 +531,7 @@ public class UserEndpoint implements CustomEndpoint {
.flatMap(updatedUser -> ServerResponse.ok().bodyValue(updatedUser));
}
Mono<ServerResponse> changePassword(ServerRequest request) {
Mono<ServerResponse> changeAnyonePasswordForAdmin(ServerRequest request) {
final var nameInPath = request.pathVariable("name");
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> SELF_USER.equals(nameInPath) ? ctx.getAuthentication().getName()
@ -538,6 +549,40 @@ public class UserEndpoint implements CustomEndpoint {
.bodyValue(updatedUser));
}
Mono<ServerResponse> changeOwnPassword(ServerRequest request) {
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication().getName())
.flatMap(username -> request.bodyToMono(ChangeOwnPasswordRequest.class)
.switchIfEmpty(Mono.defer(() ->
Mono.error(new ServerWebInputException("Request body is empty"))))
.flatMap(changePasswordRequest -> {
var rawOldPassword = changePasswordRequest.oldPassword();
return userService.confirmPassword(username, rawOldPassword)
.filter(Boolean::booleanValue)
.switchIfEmpty(Mono.error(new UnsatisfiedAttributeValueException(
"Old password is incorrect.",
"problemDetail.user.oldPassword.notMatch",
null))
)
.thenReturn(changePasswordRequest);
})
.flatMap(changePasswordRequest -> {
var password = changePasswordRequest.password();
// encode password
return userService.updateWithRawPassword(username, password);
}))
.flatMap(updatedUser -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(updatedUser));
}
record ChangeOwnPasswordRequest(
@Schema(description = "Old password.", requiredMode = REQUIRED)
String oldPassword,
@Schema(description = "New password.", requiredMode = REQUIRED, minLength = 6)
String password) {
}
record ChangePasswordRequest(
@Schema(description = "New password.", requiredMode = REQUIRED, minLength = 6)
String password) {

View File

@ -23,4 +23,6 @@ public interface UserService {
Mono<User> signUp(User user, String password);
Mono<User> createUser(User user, Set<String> roles);
Mono<Boolean> confirmPassword(String username, String rawPassword);
}

View File

@ -176,4 +176,21 @@ public class UserServiceImpl implements UserService {
.flatMap(newUser -> grantRoles(user.getMetadata().getName(), roleNames)))
);
}
@Override
public Mono<Boolean> confirmPassword(String username, String rawPassword) {
return getUser(username)
.filter(user -> {
if (!StringUtils.hasText(user.getSpec().getPassword())) {
// If the password is not set, return true directly.
return true;
}
if (!StringUtils.hasText(rawPassword)) {
return false;
}
return passwordEncoder.matches(rawPassword, user.getSpec().getPassword());
})
.hasElement();
}
}

View File

@ -51,6 +51,7 @@ problemDetail.index.duplicateKey=The value of {0} already exists for unique inde
problemDetail.user.email.verify.maxAttempts=Too many verification attempts, please try again later.
problemDetail.user.password.unsatisfied=The password does not meet the specifications.
problemDetail.user.username.unsatisfied=The username does not meet the specifications.
problemDetail.user.oldPassword.notMatch=The old password does not match.
problemDetail.user.signUpFailed.disallowed=System does not allow new users to register.
problemDetail.user.duplicateName=The username {0} already exists, please rename it and retry.
problemDetail.comment.turnedOff=The comment function has been turned off.

View File

@ -28,6 +28,7 @@ problemDetail.index.duplicateKey=唯一索引 {1} 中的值 {0} 已存在,请
problemDetail.user.email.verify.maxAttempts=尝试次数过多,请稍候再试。
problemDetail.user.password.unsatisfied=密码不符合规范。
problemDetail.user.username.unsatisfied=用户名不符合规范。
problemDetail.user.oldPassword.notMatch=旧密码不匹配。
problemDetail.user.signUpFailed.disallowed=系统不允许注册新用户。
problemDetail.user.duplicateName=用户名 {0} 已存在,请更换用户名后重试。
problemDetail.plugin.version.unsatisfied.requires=插件要求一个最小的系统版本为 {0}, 但当前版本为 {1}。

View File

@ -306,8 +306,11 @@ class UserEndpointTest {
var user = new User();
when(userService.updateWithRawPassword("fake-user", "new-password"))
.thenReturn(Mono.just(user));
when(userService.confirmPassword("fake-user", "old-password"))
.thenReturn(Mono.just(true));
webClient.put().uri("/users/-/password")
.bodyValue(new UserEndpoint.ChangePasswordRequest("new-password"))
.bodyValue(
new UserEndpoint.ChangeOwnPasswordRequest("old-password", "new-password"))
.exchange()
.expectStatus().isOk()
.expectBody(User.class)
@ -319,11 +322,14 @@ class UserEndpointTest {
@Test
void shouldUpdateOtherPasswordCorrectly() {
var user = new User();
when(userService.confirmPassword("another-fake-user", "old-password"))
.thenReturn(Mono.just(true));
when(userService.updateWithRawPassword("another-fake-user", "new-password"))
.thenReturn(Mono.just(user));
webClient.put()
.uri("/users/another-fake-user/password")
.bodyValue(new UserEndpoint.ChangePasswordRequest("new-password"))
.bodyValue(
new UserEndpoint.ChangeOwnPasswordRequest("old-password", "new-password"))
.exchange()
.expectStatus().isOk()
.expectBody(User.class)

View File

@ -485,4 +485,21 @@ class UserServiceImplTest {
return user;
}
}
@Test
void confirmPasswordWhenPasswordNotSet() {
var user = new User();
user.setSpec(new User.UserSpec());
when(client.get(User.class, "fake-user")).thenReturn(Mono.just(user));
userService.confirmPassword("fake-user", "fake-password")
.as(StepVerifier::create)
.expectNext(true)
.verifyComplete();
user.getSpec().setPassword("");
userService.confirmPassword("fake-user", "fake-password")
.as(StepVerifier::create)
.expectNext(true)
.verifyComplete();
}
}

View File

@ -67,7 +67,7 @@ const handleChangePassword = async () => {
const changePasswordRequest = cloneDeep(formState.value);
delete changePasswordRequest.password_confirm;
await apiClient.user.changePassword({
await apiClient.user.changeAnyonePassword({
name: props.user?.metadata.name || "",
changePasswordRequest,
});

View File

@ -100,6 +100,7 @@ models/category-status.ts
models/category-vo-list.ts
models/category-vo.ts
models/category.ts
models/change-own-password-request.ts
models/change-password-request.ts
models/comment-email-owner.ts
models/comment-list.ts

View File

@ -22,6 +22,8 @@ import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObj
// @ts-ignore
import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base';
// @ts-ignore
import { ChangeOwnPasswordRequest } from '../models';
// @ts-ignore
import { ChangePasswordRequest } from '../models';
// @ts-ignore
import { CreateUserRequest } from '../models';
@ -46,17 +48,17 @@ import { VerifyCodeRequest } from '../models';
export const ApiConsoleHaloRunV1alpha1UserApiAxiosParamCreator = function (configuration?: Configuration) {
return {
/**
* Change password of user.
* Change anyone password of user for admin.
* @param {string} name Name of user. If the name is equal to \&#39;-\&#39;, it will change the password of current user.
* @param {ChangePasswordRequest} changePasswordRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
changePassword: async (name: string, changePasswordRequest: ChangePasswordRequest, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
changeAnyonePassword: async (name: string, changePasswordRequest: ChangePasswordRequest, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'name' is not null or undefined
assertParamExists('changePassword', 'name', name)
assertParamExists('changeAnyonePassword', 'name', name)
// verify required parameter 'changePasswordRequest' is not null or undefined
assertParamExists('changePassword', 'changePasswordRequest', changePasswordRequest)
assertParamExists('changeAnyonePassword', 'changePasswordRequest', changePasswordRequest)
const localVarPath = `/apis/api.console.halo.run/v1alpha1/users/{name}/password`
.replace(`{${"name"}}`, encodeURIComponent(String(name)));
// use dummy base URL string because the URL constructor only accepts absolute URLs.
@ -92,6 +94,49 @@ export const ApiConsoleHaloRunV1alpha1UserApiAxiosParamCreator = function (confi
options: localVarRequestOptions,
};
},
/**
* Change own password of user.
* @param {ChangeOwnPasswordRequest} changeOwnPasswordRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
changeOwnPassword: async (changeOwnPasswordRequest: ChangeOwnPasswordRequest, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'changeOwnPasswordRequest' is not null or undefined
assertParamExists('changeOwnPassword', 'changeOwnPasswordRequest', changeOwnPasswordRequest)
const localVarPath = `/apis/api.console.halo.run/v1alpha1/users/-/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: 'PUT', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication BasicAuth required
// http basic authentication required
setBasicAuthToObject(localVarRequestOptions, configuration)
// authentication BearerAuth 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(changeOwnPasswordRequest, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
* Creates a new user.
* @param {CreateUserRequest} createUserRequest
@ -606,16 +651,28 @@ export const ApiConsoleHaloRunV1alpha1UserApiFp = function(configuration?: Confi
const localVarAxiosParamCreator = ApiConsoleHaloRunV1alpha1UserApiAxiosParamCreator(configuration)
return {
/**
* Change password of user.
* Change anyone password of user for admin.
* @param {string} name Name of user. If the name is equal to \&#39;-\&#39;, it will change the password of current user.
* @param {ChangePasswordRequest} changePasswordRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async changePassword(name: string, changePasswordRequest: ChangePasswordRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<User>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.changePassword(name, changePasswordRequest, options);
async changeAnyonePassword(name: string, changePasswordRequest: ChangePasswordRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<User>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.changeAnyonePassword(name, changePasswordRequest, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['ApiConsoleHaloRunV1alpha1UserApi.changePassword']?.[localVarOperationServerIndex]?.url;
const localVarOperationServerBasePath = operationServerMap['ApiConsoleHaloRunV1alpha1UserApi.changeAnyonePassword']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
* Change own password of user.
* @param {ChangeOwnPasswordRequest} changeOwnPasswordRequest
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async changeOwnPassword(changeOwnPasswordRequest: ChangeOwnPasswordRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<User>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.changeOwnPassword(changeOwnPasswordRequest, options);
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
const localVarOperationServerBasePath = operationServerMap['ApiConsoleHaloRunV1alpha1UserApi.changeOwnPassword']?.[localVarOperationServerIndex]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
},
/**
@ -768,13 +825,22 @@ export const ApiConsoleHaloRunV1alpha1UserApiFactory = function (configuration?:
const localVarFp = ApiConsoleHaloRunV1alpha1UserApiFp(configuration)
return {
/**
* Change password of user.
* @param {ApiConsoleHaloRunV1alpha1UserApiChangePasswordRequest} requestParameters Request parameters.
* Change anyone password of user for admin.
* @param {ApiConsoleHaloRunV1alpha1UserApiChangeAnyonePasswordRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
changePassword(requestParameters: ApiConsoleHaloRunV1alpha1UserApiChangePasswordRequest, options?: RawAxiosRequestConfig): AxiosPromise<User> {
return localVarFp.changePassword(requestParameters.name, requestParameters.changePasswordRequest, options).then((request) => request(axios, basePath));
changeAnyonePassword(requestParameters: ApiConsoleHaloRunV1alpha1UserApiChangeAnyonePasswordRequest, options?: RawAxiosRequestConfig): AxiosPromise<User> {
return localVarFp.changeAnyonePassword(requestParameters.name, requestParameters.changePasswordRequest, options).then((request) => request(axios, basePath));
},
/**
* Change own password of user.
* @param {ApiConsoleHaloRunV1alpha1UserApiChangeOwnPasswordRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
changeOwnPassword(requestParameters: ApiConsoleHaloRunV1alpha1UserApiChangeOwnPasswordRequest, options?: RawAxiosRequestConfig): AxiosPromise<User> {
return localVarFp.changeOwnPassword(requestParameters.changeOwnPasswordRequest, options).then((request) => request(axios, basePath));
},
/**
* Creates a new user.
@ -878,26 +944,40 @@ export const ApiConsoleHaloRunV1alpha1UserApiFactory = function (configuration?:
};
/**
* Request parameters for changePassword operation in ApiConsoleHaloRunV1alpha1UserApi.
* Request parameters for changeAnyonePassword operation in ApiConsoleHaloRunV1alpha1UserApi.
* @export
* @interface ApiConsoleHaloRunV1alpha1UserApiChangePasswordRequest
* @interface ApiConsoleHaloRunV1alpha1UserApiChangeAnyonePasswordRequest
*/
export interface ApiConsoleHaloRunV1alpha1UserApiChangePasswordRequest {
export interface ApiConsoleHaloRunV1alpha1UserApiChangeAnyonePasswordRequest {
/**
* Name of user. If the name is equal to \&#39;-\&#39;, it will change the password of current user.
* @type {string}
* @memberof ApiConsoleHaloRunV1alpha1UserApiChangePassword
* @memberof ApiConsoleHaloRunV1alpha1UserApiChangeAnyonePassword
*/
readonly name: string
/**
*
* @type {ChangePasswordRequest}
* @memberof ApiConsoleHaloRunV1alpha1UserApiChangePassword
* @memberof ApiConsoleHaloRunV1alpha1UserApiChangeAnyonePassword
*/
readonly changePasswordRequest: ChangePasswordRequest
}
/**
* Request parameters for changeOwnPassword operation in ApiConsoleHaloRunV1alpha1UserApi.
* @export
* @interface ApiConsoleHaloRunV1alpha1UserApiChangeOwnPasswordRequest
*/
export interface ApiConsoleHaloRunV1alpha1UserApiChangeOwnPasswordRequest {
/**
*
* @type {ChangeOwnPasswordRequest}
* @memberof ApiConsoleHaloRunV1alpha1UserApiChangeOwnPassword
*/
readonly changeOwnPasswordRequest: ChangeOwnPasswordRequest
}
/**
* Request parameters for createUser operation in ApiConsoleHaloRunV1alpha1UserApi.
* @export
@ -1102,14 +1182,25 @@ export interface ApiConsoleHaloRunV1alpha1UserApiVerifyEmailRequest {
*/
export class ApiConsoleHaloRunV1alpha1UserApi extends BaseAPI {
/**
* Change password of user.
* @param {ApiConsoleHaloRunV1alpha1UserApiChangePasswordRequest} requestParameters Request parameters.
* Change anyone password of user for admin.
* @param {ApiConsoleHaloRunV1alpha1UserApiChangeAnyonePasswordRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof ApiConsoleHaloRunV1alpha1UserApi
*/
public changePassword(requestParameters: ApiConsoleHaloRunV1alpha1UserApiChangePasswordRequest, options?: RawAxiosRequestConfig) {
return ApiConsoleHaloRunV1alpha1UserApiFp(this.configuration).changePassword(requestParameters.name, requestParameters.changePasswordRequest, options).then((request) => request(this.axios, this.basePath));
public changeAnyonePassword(requestParameters: ApiConsoleHaloRunV1alpha1UserApiChangeAnyonePasswordRequest, options?: RawAxiosRequestConfig) {
return ApiConsoleHaloRunV1alpha1UserApiFp(this.configuration).changeAnyonePassword(requestParameters.name, requestParameters.changePasswordRequest, options).then((request) => request(this.axios, this.basePath));
}
/**
* Change own password of user.
* @param {ApiConsoleHaloRunV1alpha1UserApiChangeOwnPasswordRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof ApiConsoleHaloRunV1alpha1UserApi
*/
public changeOwnPassword(requestParameters: ApiConsoleHaloRunV1alpha1UserApiChangeOwnPasswordRequest, options?: RawAxiosRequestConfig) {
return ApiConsoleHaloRunV1alpha1UserApiFp(this.configuration).changeOwnPassword(requestParameters.changeOwnPasswordRequest, options).then((request) => request(this.axios, this.basePath));
}
/**

View File

@ -0,0 +1,36 @@
/* tslint:disable */
/* eslint-disable */
/**
* Halo Next API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 2.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
*
* @export
* @interface ChangeOwnPasswordRequest
*/
export interface ChangeOwnPasswordRequest {
/**
* Old password.
* @type {string}
* @memberof ChangeOwnPasswordRequest
*/
'oldPassword': string;
/**
* New password.
* @type {string}
* @memberof ChangeOwnPasswordRequest
*/
'password': string;
}

View File

@ -19,6 +19,7 @@ export * from './category-spec';
export * from './category-status';
export * from './category-vo';
export * from './category-vo-list';
export * from './change-own-password-request';
export * from './change-password-request';
export * from './comment';
export * from './comment-email-owner';

View File

@ -919,7 +919,10 @@ core:
fields:
username:
label: Username
validation: Can only contain numbers, lowercase letters, periods (.), and hyphens (-), and cannot start or end with a period (.) or hyphen (-).
validation: >-
Can only contain numbers, lowercase letters, periods (.), and
hyphens (-), and cannot start or end with a period (.) or hyphen
(-).
display_name:
label: Display name
email:
@ -1117,7 +1120,10 @@ core:
fields:
username:
label: Username
validation: Can only contain numbers, lowercase letters, periods (.), and hyphens (-), and cannot start or end with a period (.) or hyphen (-).
validation: >-
Can only contain numbers, lowercase letters, periods (.), and
hyphens (-), and cannot start or end with a period (.) or hyphen
(-).
display_name:
label: Display name
email:
@ -1135,6 +1141,8 @@ core:
label: New password
confirm_password:
label: Confirm password
old_password:
label: Old password
email_verify_modal:
fields:
code:

View File

@ -1083,6 +1083,8 @@ core:
label: 新密码
confirm_password:
label: 确认密码
old_password:
label: 旧密码
email_verify_modal:
titles:
modify: 修改电子邮箱

View File

@ -1071,6 +1071,8 @@ core:
label: 新密碼
confirm_password:
label: 確認密碼
old_password:
label: 舊密碼
email_verify_modal:
fields:
code:

View File

@ -25,11 +25,13 @@ const emit = defineEmits<{
}>();
interface PasswordChangeFormState {
oldPassword: string;
password: string;
password_confirm?: string;
}
const initialFormState: PasswordChangeFormState = {
oldPassword: "",
password: "",
password_confirm: "",
};
@ -64,12 +66,11 @@ const handleChangePassword = async () => {
try {
saving.value = true;
const changePasswordRequest = cloneDeep(formState.value);
delete changePasswordRequest.password_confirm;
const changeOwnPasswordRequest = cloneDeep(formState.value);
delete changeOwnPasswordRequest.password_confirm;
await apiClient.user.changePassword({
name: "-",
changePasswordRequest,
await apiClient.user.changeOwnPassword({
changeOwnPasswordRequest,
});
onVisibleChange(false);
@ -99,6 +100,14 @@ const handleChangePassword = async () => {
>
<FormKit
id="passwordInput"
:label="
$t('core.uc_profile.change_password_modal.fields.old_password.label')
"
name="oldPassword"
type="password"
validation="required:trim"
></FormKit>
<FormKit
:label="
$t('core.uc_profile.change_password_modal.fields.new_password.label')
"