mirror of https://github.com/halo-dev/halo
feat: require password verification for email updates (#5780)
#### What type of PR is this? /kind feature /milestone 2.15.x /area core #### What this PR does / why we need it: 增加了在用户尝试更新邮箱地址时进行密码验证的步骤。此举提高了安全性,确保邮箱修改操作由经过身份验证的用户执行。 #### Which issue(s) this PR fixes: Fixes #5750 #### Does this PR introduce a user-facing change? ```release-note 更新邮箱地址时需进行密码验证 ```pull/5818/head
parent
cb6836aa8c
commit
1ade8493da
|
@ -19975,13 +19975,17 @@
|
|||
},
|
||||
"VerifyCodeRequest": {
|
||||
"required": [
|
||||
"code"
|
||||
"code",
|
||||
"password"
|
||||
],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -258,26 +258,38 @@ public class UserEndpoint implements CustomEndpoint {
|
|||
.switchIfEmpty(Mono.error(
|
||||
() -> new ServerWebInputException("Request body is required."))
|
||||
)
|
||||
.flatMap(verifyEmailRequest -> ReactiveSecurityContextHolder.getContext()
|
||||
.flatMap(this::doVerifyCode)
|
||||
.then(ServerResponse.ok().build());
|
||||
}
|
||||
|
||||
private Mono<Void> doVerifyCode(VerifyCodeRequest verifyCodeRequest) {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.map(Principal::getName)
|
||||
.map(username -> Tuples.of(username, verifyEmailRequest.code()))
|
||||
)
|
||||
.flatMap(tuple2 -> {
|
||||
var username = tuple2.getT1();
|
||||
var code = tuple2.getT2();
|
||||
.flatMap(username -> verifyPasswordAndCode(username, verifyCodeRequest));
|
||||
}
|
||||
|
||||
private Mono<Void> verifyPasswordAndCode(String username, VerifyCodeRequest verifyCodeRequest) {
|
||||
return userService.confirmPassword(username, verifyCodeRequest.password())
|
||||
.filter(Boolean::booleanValue)
|
||||
.switchIfEmpty(Mono.error(new UnsatisfiedAttributeValueException(
|
||||
"Password is incorrect.", "problemDetail.user.password.notMatch", null)))
|
||||
.flatMap(verified -> verifyEmailCode(username, verifyCodeRequest.code()));
|
||||
}
|
||||
|
||||
private Mono<Void> verifyEmailCode(String username, String code) {
|
||||
return Mono.just(username)
|
||||
.transformDeferred(verificationEmailRateLimiter(username))
|
||||
.flatMap(name -> emailVerificationService.verify(username, code))
|
||||
.onErrorMap(RequestNotPermitted.class, RateLimitExceededException::new);
|
||||
})
|
||||
.then(ServerResponse.ok().build());
|
||||
}
|
||||
|
||||
public record EmailVerifyRequest(@Schema(requiredMode = REQUIRED) String email) {
|
||||
}
|
||||
|
||||
public record VerifyCodeRequest(@Schema(requiredMode = REQUIRED, minLength = 1) String code) {
|
||||
public record VerifyCodeRequest(
|
||||
@Schema(requiredMode = REQUIRED) String password,
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1) String code) {
|
||||
}
|
||||
|
||||
private Mono<ServerResponse> sendEmailVerificationCode(ServerRequest request) {
|
||||
|
|
|
@ -52,6 +52,7 @@ problemDetail.user.email.verify.maxAttempts=Too many verification attempts, plea
|
|||
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.password.notMatch=The 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.
|
||||
|
|
|
@ -29,6 +29,7 @@ problemDetail.user.email.verify.maxAttempts=尝试次数过多,请稍候再试
|
|||
problemDetail.user.password.unsatisfied=密码不符合规范。
|
||||
problemDetail.user.username.unsatisfied=用户名不符合规范。
|
||||
problemDetail.user.oldPassword.notMatch=旧密码不匹配。
|
||||
problemDetail.user.password.notMatch=密码不匹配。
|
||||
problemDetail.user.signUpFailed.disallowed=系统不允许注册新用户。
|
||||
problemDetail.user.duplicateName=用户名 {0} 已存在,请更换用户名后重试。
|
||||
problemDetail.plugin.version.unsatisfied.requires=插件要求一个最小的系统版本为 {0}, 但当前版本为 {1}。
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.springframework.test.web.reactive.server.WebTestClient;
|
|||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.User;
|
||||
import run.halo.app.core.extension.service.EmailVerificationService;
|
||||
import run.halo.app.core.extension.service.UserService;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
|
||||
|
@ -43,6 +44,10 @@ class EmailVerificationCodeTest {
|
|||
ReactiveExtensionClient client;
|
||||
@Mock
|
||||
EmailVerificationService emailVerificationService;
|
||||
|
||||
@Mock
|
||||
UserService userService;
|
||||
|
||||
@InjectMocks
|
||||
UserEndpoint endpoint;
|
||||
|
||||
|
@ -97,9 +102,11 @@ class EmailVerificationCodeTest {
|
|||
void verifyEmail() {
|
||||
when(emailVerificationService.verify(anyString(), anyString()))
|
||||
.thenReturn(Mono.empty());
|
||||
when(userService.confirmPassword(anyString(), anyString()))
|
||||
.thenReturn(Mono.just(true));
|
||||
webClient.post()
|
||||
.uri("/users/-/verify-email")
|
||||
.bodyValue(Map.of("code", "fake-code-1"))
|
||||
.bodyValue(Map.of("code", "fake-code-1", "password", "123456"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk();
|
||||
|
@ -107,7 +114,7 @@ class EmailVerificationCodeTest {
|
|||
// request again to trigger rate limit
|
||||
webClient.post()
|
||||
.uri("/users/-/verify-email")
|
||||
.bodyValue(Map.of("code", "fake-code-2"))
|
||||
.bodyValue(Map.of("code", "fake-code-2", "password", "123456"))
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isEqualTo(HttpStatus.TOO_MANY_REQUESTS);
|
||||
|
|
|
@ -240,6 +240,7 @@ models/register-verify-email-request.ts
|
|||
models/reply-list.ts
|
||||
models/reply-request.ts
|
||||
models/reply-spec.ts
|
||||
models/reply-status.ts
|
||||
models/reply-vo-list.ts
|
||||
models/reply-vo.ts
|
||||
models/reply.ts
|
||||
|
|
|
@ -31,16 +31,16 @@ export const ApiConsoleHaloRunV1alpha1TagApiAxiosParamCreator = function (config
|
|||
return {
|
||||
/**
|
||||
* List Post Tags.
|
||||
* @param {Array<string>} [fieldSelector] Field selector for filtering.
|
||||
* @param {string} [keyword] Keyword for searching.
|
||||
* @param {Array<string>} [labelSelector] Label selector for filtering.
|
||||
* @param {number} [page] The page number. Zero indicates no page.
|
||||
* @param {number} [size] Size of one page. Zero indicates no limit.
|
||||
* @param {Array<string>} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp, name
|
||||
* @param {number} [page] Page number. Default is 0.
|
||||
* @param {number} [size] Size number. Default is 0.
|
||||
* @param {Array<string>} [labelSelector] Label selector. e.g.: hidden!=true
|
||||
* @param {Array<string>} [fieldSelector] Field selector. e.g.: metadata.name==halo
|
||||
* @param {Array<string>} [sort] Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.
|
||||
* @param {string} [keyword] Post tags filtered by keyword.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listPostTags: async (fieldSelector?: Array<string>, keyword?: string, labelSelector?: Array<string>, page?: number, size?: number, sort?: Array<string>, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
listPostTags: async (page?: number, size?: number, labelSelector?: Array<string>, fieldSelector?: Array<string>, sort?: Array<string>, keyword?: string, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/apis/api.console.halo.run/v1alpha1/tags`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
|
@ -61,18 +61,6 @@ export const ApiConsoleHaloRunV1alpha1TagApiAxiosParamCreator = function (config
|
|||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
if (fieldSelector) {
|
||||
localVarQueryParameter['fieldSelector'] = fieldSelector;
|
||||
}
|
||||
|
||||
if (keyword !== undefined) {
|
||||
localVarQueryParameter['keyword'] = keyword;
|
||||
}
|
||||
|
||||
if (labelSelector) {
|
||||
localVarQueryParameter['labelSelector'] = labelSelector;
|
||||
}
|
||||
|
||||
if (page !== undefined) {
|
||||
localVarQueryParameter['page'] = page;
|
||||
}
|
||||
|
@ -81,8 +69,20 @@ export const ApiConsoleHaloRunV1alpha1TagApiAxiosParamCreator = function (config
|
|||
localVarQueryParameter['size'] = size;
|
||||
}
|
||||
|
||||
if (labelSelector) {
|
||||
localVarQueryParameter['labelSelector'] = labelSelector;
|
||||
}
|
||||
|
||||
if (fieldSelector) {
|
||||
localVarQueryParameter['fieldSelector'] = fieldSelector;
|
||||
}
|
||||
|
||||
if (sort) {
|
||||
localVarQueryParameter['sort'] = Array.from(sort);
|
||||
localVarQueryParameter['sort'] = sort;
|
||||
}
|
||||
|
||||
if (keyword !== undefined) {
|
||||
localVarQueryParameter['keyword'] = keyword;
|
||||
}
|
||||
|
||||
|
||||
|
@ -108,17 +108,17 @@ export const ApiConsoleHaloRunV1alpha1TagApiFp = function(configuration?: Config
|
|||
return {
|
||||
/**
|
||||
* List Post Tags.
|
||||
* @param {Array<string>} [fieldSelector] Field selector for filtering.
|
||||
* @param {string} [keyword] Keyword for searching.
|
||||
* @param {Array<string>} [labelSelector] Label selector for filtering.
|
||||
* @param {number} [page] The page number. Zero indicates no page.
|
||||
* @param {number} [size] Size of one page. Zero indicates no limit.
|
||||
* @param {Array<string>} [sort] Sort property and direction of the list result. Supported fields: creationTimestamp, name
|
||||
* @param {number} [page] Page number. Default is 0.
|
||||
* @param {number} [size] Size number. Default is 0.
|
||||
* @param {Array<string>} [labelSelector] Label selector. e.g.: hidden!=true
|
||||
* @param {Array<string>} [fieldSelector] Field selector. e.g.: metadata.name==halo
|
||||
* @param {Array<string>} [sort] Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.
|
||||
* @param {string} [keyword] Post tags filtered by keyword.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async listPostTags(fieldSelector?: Array<string>, keyword?: string, labelSelector?: Array<string>, page?: number, size?: number, sort?: Array<string>, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<TagList>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listPostTags(fieldSelector, keyword, labelSelector, page, size, sort, options);
|
||||
async listPostTags(page?: number, size?: number, labelSelector?: Array<string>, fieldSelector?: Array<string>, sort?: Array<string>, keyword?: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<TagList>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listPostTags(page, size, labelSelector, fieldSelector, sort, keyword, options);
|
||||
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||
const localVarOperationServerBasePath = operationServerMap['ApiConsoleHaloRunV1alpha1TagApi.listPostTags']?.[localVarOperationServerIndex]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||
|
@ -140,7 +140,7 @@ export const ApiConsoleHaloRunV1alpha1TagApiFactory = function (configuration?:
|
|||
* @throws {RequiredError}
|
||||
*/
|
||||
listPostTags(requestParameters: ApiConsoleHaloRunV1alpha1TagApiListPostTagsRequest = {}, options?: RawAxiosRequestConfig): AxiosPromise<TagList> {
|
||||
return localVarFp.listPostTags(requestParameters.fieldSelector, requestParameters.keyword, requestParameters.labelSelector, requestParameters.page, requestParameters.size, requestParameters.sort, options).then((request) => request(axios, basePath));
|
||||
return localVarFp.listPostTags(requestParameters.page, requestParameters.size, requestParameters.labelSelector, requestParameters.fieldSelector, requestParameters.sort, requestParameters.keyword, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -152,46 +152,46 @@ export const ApiConsoleHaloRunV1alpha1TagApiFactory = function (configuration?:
|
|||
*/
|
||||
export interface ApiConsoleHaloRunV1alpha1TagApiListPostTagsRequest {
|
||||
/**
|
||||
* Field selector for filtering.
|
||||
* @type {Array<string>}
|
||||
* @memberof ApiConsoleHaloRunV1alpha1TagApiListPostTags
|
||||
*/
|
||||
readonly fieldSelector?: Array<string>
|
||||
|
||||
/**
|
||||
* Keyword for searching.
|
||||
* @type {string}
|
||||
* @memberof ApiConsoleHaloRunV1alpha1TagApiListPostTags
|
||||
*/
|
||||
readonly keyword?: string
|
||||
|
||||
/**
|
||||
* Label selector for filtering.
|
||||
* @type {Array<string>}
|
||||
* @memberof ApiConsoleHaloRunV1alpha1TagApiListPostTags
|
||||
*/
|
||||
readonly labelSelector?: Array<string>
|
||||
|
||||
/**
|
||||
* The page number. Zero indicates no page.
|
||||
* Page number. Default is 0.
|
||||
* @type {number}
|
||||
* @memberof ApiConsoleHaloRunV1alpha1TagApiListPostTags
|
||||
*/
|
||||
readonly page?: number
|
||||
|
||||
/**
|
||||
* Size of one page. Zero indicates no limit.
|
||||
* Size number. Default is 0.
|
||||
* @type {number}
|
||||
* @memberof ApiConsoleHaloRunV1alpha1TagApiListPostTags
|
||||
*/
|
||||
readonly size?: number
|
||||
|
||||
/**
|
||||
* Sort property and direction of the list result. Supported fields: creationTimestamp, name
|
||||
* Label selector. e.g.: hidden!=true
|
||||
* @type {Array<string>}
|
||||
* @memberof ApiConsoleHaloRunV1alpha1TagApiListPostTags
|
||||
*/
|
||||
readonly labelSelector?: Array<string>
|
||||
|
||||
/**
|
||||
* Field selector. e.g.: metadata.name==halo
|
||||
* @type {Array<string>}
|
||||
* @memberof ApiConsoleHaloRunV1alpha1TagApiListPostTags
|
||||
*/
|
||||
readonly fieldSelector?: Array<string>
|
||||
|
||||
/**
|
||||
* Sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported.
|
||||
* @type {Array<string>}
|
||||
* @memberof ApiConsoleHaloRunV1alpha1TagApiListPostTags
|
||||
*/
|
||||
readonly sort?: Array<string>
|
||||
|
||||
/**
|
||||
* Post tags filtered by keyword.
|
||||
* @type {string}
|
||||
* @memberof ApiConsoleHaloRunV1alpha1TagApiListPostTags
|
||||
*/
|
||||
readonly keyword?: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -209,7 +209,7 @@ export class ApiConsoleHaloRunV1alpha1TagApi extends BaseAPI {
|
|||
* @memberof ApiConsoleHaloRunV1alpha1TagApi
|
||||
*/
|
||||
public listPostTags(requestParameters: ApiConsoleHaloRunV1alpha1TagApiListPostTagsRequest = {}, options?: RawAxiosRequestConfig) {
|
||||
return ApiConsoleHaloRunV1alpha1TagApiFp(this.configuration).listPostTags(requestParameters.fieldSelector, requestParameters.keyword, requestParameters.labelSelector, requestParameters.page, requestParameters.size, requestParameters.sort, options).then((request) => request(this.axios, this.basePath));
|
||||
return ApiConsoleHaloRunV1alpha1TagApiFp(this.configuration).listPostTags(requestParameters.page, requestParameters.size, requestParameters.labelSelector, requestParameters.fieldSelector, requestParameters.sort, requestParameters.keyword, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,12 @@ export interface CommentStatus {
|
|||
* @memberof CommentStatus
|
||||
*/
|
||||
'lastReplyTime'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof CommentStatus
|
||||
*/
|
||||
'observedVersion'?: number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
|
|
|
@ -158,6 +158,7 @@ export * from './reply';
|
|||
export * from './reply-list';
|
||||
export * from './reply-request';
|
||||
export * from './reply-spec';
|
||||
export * from './reply-status';
|
||||
export * from './reply-vo';
|
||||
export * from './reply-vo-list';
|
||||
export * from './reset-password-request';
|
||||
|
|
|
@ -79,8 +79,7 @@ export const PluginStatusLastProbeStateEnum = {
|
|||
Resolved: 'RESOLVED',
|
||||
Started: 'STARTED',
|
||||
Stopped: 'STOPPED',
|
||||
Failed: 'FAILED',
|
||||
Unloaded: 'UNLOADED'
|
||||
Failed: 'FAILED'
|
||||
} as const;
|
||||
|
||||
export type PluginStatusLastProbeStateEnum = typeof PluginStatusLastProbeStateEnum[keyof typeof PluginStatusLastProbeStateEnum];
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/* 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 ReplyStatus
|
||||
*/
|
||||
export interface ReplyStatus {
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof ReplyStatus
|
||||
*/
|
||||
'observedVersion'?: number;
|
||||
}
|
||||
|
|
@ -19,6 +19,9 @@ import { Metadata } from './metadata';
|
|||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import { ReplySpec } from './reply-spec';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import { ReplyStatus } from './reply-status';
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -50,5 +53,11 @@ export interface Reply {
|
|||
* @memberof Reply
|
||||
*/
|
||||
'spec': ReplySpec;
|
||||
/**
|
||||
*
|
||||
* @type {ReplyStatus}
|
||||
* @memberof Reply
|
||||
*/
|
||||
'status': ReplyStatus;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,5 +26,11 @@ export interface VerifyCodeRequest {
|
|||
* @memberof VerifyCodeRequest
|
||||
*/
|
||||
'code': string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof VerifyCodeRequest
|
||||
*/
|
||||
'password': string;
|
||||
}
|
||||
|
||||
|
|
|
@ -1169,6 +1169,9 @@ core:
|
|||
label: Email address
|
||||
new_email:
|
||||
label: New email address
|
||||
password:
|
||||
label: Password
|
||||
help: The login password for the current account
|
||||
operations:
|
||||
send_code:
|
||||
buttons:
|
||||
|
|
|
@ -1114,6 +1114,9 @@ core:
|
|||
label: 电子邮箱
|
||||
code:
|
||||
label: 验证码
|
||||
password:
|
||||
label: 验证密码
|
||||
help: 当前账号的登录密码
|
||||
operations:
|
||||
send_code:
|
||||
buttons:
|
||||
|
|
|
@ -1091,6 +1091,9 @@ core:
|
|||
label: 電子郵件信箱
|
||||
new_email:
|
||||
label: 新電子郵件信箱
|
||||
password:
|
||||
label: 驗證密碼
|
||||
help: 目前帳號的登入密碼
|
||||
operations:
|
||||
send_code:
|
||||
buttons:
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
<script lang="ts" setup>
|
||||
import { Toast, VButton, VSpace } from "@halo-dev/components";
|
||||
|
||||
import { VModal } from "@halo-dev/components";
|
||||
import { ref } from "vue";
|
||||
import { Toast, VButton, VModal, VSpace } from "@halo-dev/components";
|
||||
import { computed, ref } from "vue";
|
||||
import { useMutation, useQueryClient } from "@tanstack/vue-query";
|
||||
import { apiClient } from "@/utils/api-client";
|
||||
import type { VerifyCodeRequest } from "@halo-dev/api-client";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import { useIntervalFn } from "@vueuse/shared";
|
||||
import { computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
@ -81,10 +79,11 @@ const sendVerifyCodeButtonText = computed(() => {
|
|||
|
||||
const { mutate: verifyEmail, isLoading: isVerifying } = useMutation({
|
||||
mutationKey: ["verify-email"],
|
||||
mutationFn: async ({ code }: { code: string }) => {
|
||||
mutationFn: async ({ password, code }: VerifyCodeRequest) => {
|
||||
return await apiClient.user.verifyEmail({
|
||||
verifyCodeRequest: {
|
||||
code: code,
|
||||
password,
|
||||
code,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
@ -98,8 +97,8 @@ const { mutate: verifyEmail, isLoading: isVerifying } = useMutation({
|
|||
},
|
||||
});
|
||||
|
||||
function handleVerify(data: { code: string }) {
|
||||
verifyEmail({ code: data.code });
|
||||
function handleVerify({ password, code }: VerifyCodeRequest) {
|
||||
verifyEmail({ password, code });
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -147,6 +146,13 @@ function handleVerify(data: { code: string }) {
|
|||
</VButton>
|
||||
</template>
|
||||
</FormKit>
|
||||
<FormKit
|
||||
:label="$t('core.uc_profile.email_verify_modal.fields.password.label')"
|
||||
:help="$t('core.uc_profile.email_verify_modal.fields.password.help')"
|
||||
name="password"
|
||||
type="password"
|
||||
validation="required:trim"
|
||||
></FormKit>
|
||||
</FormKit>
|
||||
<template #footer>
|
||||
<VSpace>
|
||||
|
|
Loading…
Reference in New Issue