mirror of https://github.com/halo-dev/halo
feat: add support for force verify email during user registration (#5320)
#### What type of PR is this? /kind feature /kind improvement /area core /area console /kind api-change #### What this PR does / why we need it: 增加对用户注册时必须验证邮箱的支持 #### Which issue(s) this PR fixes: Fixes #5016 #### Special notes for your reviewer: `regRequireVerifyEmail` 为 `false` 时与现在的注册行为一致 为 `true` 时注册页显示验证码校验相关,注册成功后 `UserSpec.emailVerified` 即为 `true` 没有判断邮件通知是否开启,与现有的邮箱验证一致,如未开启则收不到邮件 #### Does this PR introduce a user-facing change? ```release-note 增加对用户注册时必须验证邮箱的支持 ```pull/5407/head
parent
9e676712e4
commit
50fbe37be8
|
@ -67,6 +67,7 @@ public class SystemSetting {
|
||||||
public static class User {
|
public static class User {
|
||||||
public static final String GROUP = "user";
|
public static final String GROUP = "user";
|
||||||
Boolean allowRegistration;
|
Boolean allowRegistration;
|
||||||
|
Boolean mustVerifyEmailOnRegistration;
|
||||||
String defaultRole;
|
String defaultRole;
|
||||||
String avatarPolicy;
|
String avatarPolicy;
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,13 +55,11 @@ public class GlobalInfoEndpoint {
|
||||||
handleBasicSetting(info, configMap);
|
handleBasicSetting(info, configMap);
|
||||||
handlePostSlugGenerationStrategy(info, configMap);
|
handlePostSlugGenerationStrategy(info, configMap);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class GlobalInfo {
|
public static class GlobalInfo {
|
||||||
|
|
||||||
private URL externalUrl;
|
private URL externalUrl;
|
||||||
|
|
||||||
private boolean useAbsolutePermalink;
|
private boolean useAbsolutePermalink;
|
||||||
|
@ -85,6 +83,8 @@ public class GlobalInfoEndpoint {
|
||||||
private String postSlugGenerationStrategy;
|
private String postSlugGenerationStrategy;
|
||||||
|
|
||||||
private List<SocialAuthProvider> socialAuthProviders;
|
private List<SocialAuthProvider> socialAuthProviders;
|
||||||
|
|
||||||
|
private Boolean mustVerifyEmailOnRegistration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
@ -117,12 +117,14 @@ public class GlobalInfoEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleUserSetting(GlobalInfo info, ConfigMap configMap) {
|
private void handleUserSetting(GlobalInfo info, ConfigMap configMap) {
|
||||||
var user = SystemSetting.get(configMap, User.GROUP, User.class);
|
var userSetting = SystemSetting.get(configMap, User.GROUP, User.class);
|
||||||
if (user == null) {
|
if (userSetting == null) {
|
||||||
info.setAllowRegistration(false);
|
info.setAllowRegistration(false);
|
||||||
|
info.setMustVerifyEmailOnRegistration(false);
|
||||||
} else {
|
} else {
|
||||||
info.setAllowRegistration(
|
info.setAllowRegistration(
|
||||||
user.getAllowRegistration() != null && user.getAllowRegistration());
|
userSetting.getAllowRegistration() != null && userSetting.getAllowRegistration());
|
||||||
|
info.setMustVerifyEmailOnRegistration(userSetting.getMustVerifyEmailOnRegistration());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,4 +27,21 @@ public interface EmailVerificationService {
|
||||||
* @throws EmailVerificationFailed if send failed
|
* @throws EmailVerificationFailed if send failed
|
||||||
*/
|
*/
|
||||||
Mono<Void> verify(String username, String code);
|
Mono<Void> verify(String username, String code);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send verification code.
|
||||||
|
* The only difference is use email as username.
|
||||||
|
*
|
||||||
|
* @param email email to send must not be blank
|
||||||
|
*/
|
||||||
|
Mono<Void> sendRegisterVerificationCode(String email);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify email by given code.
|
||||||
|
*
|
||||||
|
* @param email email as username to verify email must not be blank
|
||||||
|
* @param code code to verify email must not be blank
|
||||||
|
* @throws EmailVerificationFailed if send failed
|
||||||
|
*/
|
||||||
|
Mono<Boolean> verifyRegisterVerificationCode(String email, String code);
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,6 +102,19 @@ public class EmailVerificationServiceImpl implements EmailVerificationService {
|
||||||
.then();
|
.then();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> sendRegisterVerificationCode(String email) {
|
||||||
|
Assert.state(StringUtils.isNotBlank(email), "Email must not be blank");
|
||||||
|
return sendVerificationNotification(email, email);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> verifyRegisterVerificationCode(String email, String code) {
|
||||||
|
Assert.state(StringUtils.isNotBlank(email), "Username must not be blank");
|
||||||
|
Assert.state(StringUtils.isNotBlank(code), "Code must not be blank");
|
||||||
|
return Mono.just(emailVerificationManager.verifyCode(email, email, code));
|
||||||
|
}
|
||||||
|
|
||||||
Mono<Void> sendVerificationNotification(String username, String email) {
|
Mono<Void> sendVerificationNotification(String username, String email) {
|
||||||
var code = emailVerificationManager.generateCode(username, email);
|
var code = emailVerificationManager.generateCode(username, email);
|
||||||
var subscribeNotification = autoSubscribeVerificationEmailNotification(email);
|
var subscribeNotification = autoSubscribeVerificationEmailNotification(email);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package run.halo.app.theme.endpoint;
|
package run.halo.app.theme.endpoint;
|
||||||
|
|
||||||
|
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
|
||||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||||
|
@ -11,6 +12,7 @@ import io.github.resilience4j.reactor.ratelimiter.operator.RateLimiterOperator;
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
@ -29,8 +31,14 @@ import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.User;
|
import run.halo.app.core.extension.User;
|
||||||
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
import run.halo.app.core.extension.endpoint.CustomEndpoint;
|
||||||
import run.halo.app.core.extension.service.EmailPasswordRecoveryService;
|
import run.halo.app.core.extension.service.EmailPasswordRecoveryService;
|
||||||
|
import run.halo.app.core.extension.service.EmailVerificationService;
|
||||||
import run.halo.app.core.extension.service.UserService;
|
import run.halo.app.core.extension.service.UserService;
|
||||||
import run.halo.app.extension.GroupVersion;
|
import run.halo.app.extension.GroupVersion;
|
||||||
|
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||||
|
import run.halo.app.infra.SystemSetting;
|
||||||
|
import run.halo.app.infra.ValidationUtils;
|
||||||
|
import run.halo.app.infra.exception.AccessDeniedException;
|
||||||
|
import run.halo.app.infra.exception.EmailVerificationFailed;
|
||||||
import run.halo.app.infra.exception.RateLimitExceededException;
|
import run.halo.app.infra.exception.RateLimitExceededException;
|
||||||
import run.halo.app.infra.utils.IpAddressUtils;
|
import run.halo.app.infra.utils.IpAddressUtils;
|
||||||
|
|
||||||
|
@ -48,6 +56,8 @@ public class PublicUserEndpoint implements CustomEndpoint {
|
||||||
private final ReactiveUserDetailsService reactiveUserDetailsService;
|
private final ReactiveUserDetailsService reactiveUserDetailsService;
|
||||||
private final EmailPasswordRecoveryService emailPasswordRecoveryService;
|
private final EmailPasswordRecoveryService emailPasswordRecoveryService;
|
||||||
private final RateLimiterRegistry rateLimiterRegistry;
|
private final RateLimiterRegistry rateLimiterRegistry;
|
||||||
|
private final SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||||
|
private final EmailVerificationService emailVerificationService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RouterFunction<ServerResponse> endpoint() {
|
public RouterFunction<ServerResponse> endpoint() {
|
||||||
|
@ -62,6 +72,22 @@ public class PublicUserEndpoint implements CustomEndpoint {
|
||||||
)
|
)
|
||||||
.response(responseBuilder().implementation(User.class))
|
.response(responseBuilder().implementation(User.class))
|
||||||
)
|
)
|
||||||
|
.POST("/users/-/send-register-verify-email", this::sendRegisterVerifyEmail,
|
||||||
|
builder -> builder.operationId("SendRegisterVerifyEmail")
|
||||||
|
.description(
|
||||||
|
"Send registration verification email, which can be called when "
|
||||||
|
+ "mustVerifyEmailOnRegistration in user settings is true"
|
||||||
|
)
|
||||||
|
.tag(tag)
|
||||||
|
.requestBody(requestBodyBuilder()
|
||||||
|
.required(true)
|
||||||
|
.implementation(RegisterVerifyEmailRequest.class)
|
||||||
|
)
|
||||||
|
.response(responseBuilder()
|
||||||
|
.responseCode(HttpStatus.NO_CONTENT.toString())
|
||||||
|
.implementation(Void.class)
|
||||||
|
)
|
||||||
|
)
|
||||||
.POST("/users/-/send-password-reset-email", this::sendPasswordResetEmail,
|
.POST("/users/-/send-password-reset-email", this::sendPasswordResetEmail,
|
||||||
builder -> builder.operationId("SendPasswordResetEmail")
|
builder -> builder.operationId("SendPasswordResetEmail")
|
||||||
.description("Send password reset email when forgot password")
|
.description("Send password reset email when forgot password")
|
||||||
|
@ -126,6 +152,9 @@ public class PublicUserEndpoint implements CustomEndpoint {
|
||||||
@Schema(requiredMode = REQUIRED) String token) {
|
@Schema(requiredMode = REQUIRED) String token) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record RegisterVerifyEmailRequest(@Schema(requiredMode = REQUIRED) String email) {
|
||||||
|
}
|
||||||
|
|
||||||
private Mono<ServerResponse> sendPasswordResetEmail(ServerRequest request) {
|
private Mono<ServerResponse> sendPasswordResetEmail(ServerRequest request) {
|
||||||
return request.bodyToMono(PasswordResetEmailRequest.class)
|
return request.bodyToMono(PasswordResetEmailRequest.class)
|
||||||
.flatMap(passwordResetRequest -> {
|
.flatMap(passwordResetRequest -> {
|
||||||
|
@ -154,6 +183,30 @@ public class PublicUserEndpoint implements CustomEndpoint {
|
||||||
|
|
||||||
private Mono<ServerResponse> signUp(ServerRequest request) {
|
private Mono<ServerResponse> signUp(ServerRequest request) {
|
||||||
return request.bodyToMono(SignUpRequest.class)
|
return request.bodyToMono(SignUpRequest.class)
|
||||||
|
.doOnNext(signUpRequest -> signUpRequest.user().getSpec().setEmailVerified(false))
|
||||||
|
.flatMap(signUpRequest -> environmentFetcher.fetch(SystemSetting.User.GROUP,
|
||||||
|
SystemSetting.User.class)
|
||||||
|
.map(user -> BooleanUtils.isTrue(user.getMustVerifyEmailOnRegistration()))
|
||||||
|
.defaultIfEmpty(false)
|
||||||
|
.flatMap(mustVerifyEmailOnRegistration -> {
|
||||||
|
if (!mustVerifyEmailOnRegistration) {
|
||||||
|
return Mono.just(signUpRequest);
|
||||||
|
}
|
||||||
|
if (!StringUtils.isNumeric(signUpRequest.verifyCode)) {
|
||||||
|
return Mono.error(new EmailVerificationFailed());
|
||||||
|
}
|
||||||
|
return emailVerificationService.verifyRegisterVerificationCode(
|
||||||
|
signUpRequest.user().getSpec().getEmail(),
|
||||||
|
signUpRequest.verifyCode)
|
||||||
|
.flatMap(verified -> {
|
||||||
|
if (BooleanUtils.isNotTrue(verified)) {
|
||||||
|
return Mono.error(new EmailVerificationFailed());
|
||||||
|
}
|
||||||
|
signUpRequest.user().getSpec().setEmailVerified(true);
|
||||||
|
return Mono.just(signUpRequest);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
)
|
||||||
.flatMap(signUpRequest ->
|
.flatMap(signUpRequest ->
|
||||||
userService.signUp(signUpRequest.user(), signUpRequest.password())
|
userService.signUp(signUpRequest.user(), signUpRequest.password())
|
||||||
)
|
)
|
||||||
|
@ -168,6 +221,35 @@ public class PublicUserEndpoint implements CustomEndpoint {
|
||||||
.onErrorMap(RequestNotPermitted.class, RateLimitExceededException::new);
|
.onErrorMap(RequestNotPermitted.class, RateLimitExceededException::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Mono<ServerResponse> sendRegisterVerifyEmail(ServerRequest request) {
|
||||||
|
return request.bodyToMono(RegisterVerifyEmailRequest.class)
|
||||||
|
.switchIfEmpty(Mono.error(
|
||||||
|
() -> new ServerWebInputException("Required request body is missing."))
|
||||||
|
)
|
||||||
|
.map(emailReq -> {
|
||||||
|
var email = emailReq.email();
|
||||||
|
if (!ValidationUtils.isValidEmail(email)) {
|
||||||
|
throw new ServerWebInputException("Invalid email address.");
|
||||||
|
}
|
||||||
|
return email;
|
||||||
|
})
|
||||||
|
.flatMap(email -> environmentFetcher.fetch(SystemSetting.User.GROUP,
|
||||||
|
SystemSetting.User.class)
|
||||||
|
.map(config -> BooleanUtils.isTrue(config.getMustVerifyEmailOnRegistration()))
|
||||||
|
.defaultIfEmpty(false)
|
||||||
|
.doOnNext(mustVerifyEmailOnRegistration -> {
|
||||||
|
if (!mustVerifyEmailOnRegistration) {
|
||||||
|
throw new AccessDeniedException("Email verification is not required.");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.transformDeferred(sendRegisterEmailVerificationCodeRateLimiter(email))
|
||||||
|
.flatMap(s -> emailVerificationService.sendRegisterVerificationCode(email)
|
||||||
|
.onErrorMap(RequestNotPermitted.class, RateLimitExceededException::new))
|
||||||
|
.onErrorMap(RequestNotPermitted.class, RateLimitExceededException::new)
|
||||||
|
)
|
||||||
|
.then(ServerResponse.ok().build());
|
||||||
|
}
|
||||||
|
|
||||||
private <T> RateLimiterOperator<T> getRateLimiterForSignUp(ServerWebExchange exchange) {
|
private <T> RateLimiterOperator<T> getRateLimiterForSignUp(ServerWebExchange exchange) {
|
||||||
var clientIp = IpAddressUtils.getClientIp(exchange.getRequest());
|
var clientIp = IpAddressUtils.getClientIp(exchange.getRequest());
|
||||||
var rateLimiter = rateLimiterRegistry.rateLimiter("signup-from-ip-" + clientIp,
|
var rateLimiter = rateLimiterRegistry.rateLimiter("signup-from-ip-" + clientIp,
|
||||||
|
@ -187,7 +269,17 @@ public class PublicUserEndpoint implements CustomEndpoint {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private <T> RateLimiterOperator<T> sendRegisterEmailVerificationCodeRateLimiter(String email) {
|
||||||
|
String rateLimiterKey = "send-register-verify-email:" + email;
|
||||||
|
var rateLimiter =
|
||||||
|
rateLimiterRegistry.rateLimiter(rateLimiterKey, "send-email-verification-code");
|
||||||
|
return RateLimiterOperator.of(rateLimiter);
|
||||||
|
}
|
||||||
|
|
||||||
record SignUpRequest(@Schema(requiredMode = REQUIRED) User user,
|
record SignUpRequest(@Schema(requiredMode = REQUIRED) User user,
|
||||||
@Schema(requiredMode = REQUIRED, minLength = 6) String password) {
|
@Schema(requiredMode = REQUIRED, minLength = 6) String password,
|
||||||
|
@Schema(requiredMode = NOT_REQUIRED, minLength = 6, maxLength = 6)
|
||||||
|
String verifyCode
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,10 @@ spec:
|
||||||
name: allowRegistration
|
name: allowRegistration
|
||||||
label: "开放注册"
|
label: "开放注册"
|
||||||
value: false
|
value: false
|
||||||
|
- $formkit: checkbox
|
||||||
|
name: mustVerifyEmailOnRegistration
|
||||||
|
label: "注册需验证邮箱(请确保启用邮件通知)"
|
||||||
|
value: false
|
||||||
- $formkit: roleSelect
|
- $formkit: roleSelect
|
||||||
name: defaultRole
|
name: defaultRole
|
||||||
label: "默认角色"
|
label: "默认角色"
|
||||||
|
|
|
@ -3,6 +3,7 @@ package run.halo.app.theme.endpoint;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ -21,6 +22,8 @@ import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.User;
|
import run.halo.app.core.extension.User;
|
||||||
import run.halo.app.core.extension.service.UserService;
|
import run.halo.app.core.extension.service.UserService;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
|
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||||
|
import run.halo.app.infra.SystemSetting;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link PublicUserEndpoint}.
|
* Tests for {@link PublicUserEndpoint}.
|
||||||
|
@ -36,7 +39,8 @@ class PublicUserEndpointTest {
|
||||||
private ServerSecurityContextRepository securityContextRepository;
|
private ServerSecurityContextRepository securityContextRepository;
|
||||||
@Mock
|
@Mock
|
||||||
private ReactiveUserDetailsService reactiveUserDetailsService;
|
private ReactiveUserDetailsService reactiveUserDetailsService;
|
||||||
|
@Mock
|
||||||
|
SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||||
@Mock
|
@Mock
|
||||||
RateLimiterRegistry rateLimiterRegistry;
|
RateLimiterRegistry rateLimiterRegistry;
|
||||||
|
|
||||||
|
@ -67,6 +71,9 @@ class PublicUserEndpointTest {
|
||||||
.password("123456")
|
.password("123456")
|
||||||
.authorities("test-role")
|
.authorities("test-role")
|
||||||
.build()));
|
.build()));
|
||||||
|
SystemSetting.User userSetting = mock(SystemSetting.User.class);
|
||||||
|
when(environmentFetcher.fetch(SystemSetting.User.GROUP, SystemSetting.User.class))
|
||||||
|
.thenReturn(Mono.just(userSetting));
|
||||||
|
|
||||||
when(rateLimiterRegistry.rateLimiter("signup-from-ip-127.0.0.1", "signup"))
|
when(rateLimiterRegistry.rateLimiter("signup-from-ip-127.0.0.1", "signup"))
|
||||||
.thenReturn(RateLimiter.ofDefaults("signup"));
|
.thenReturn(RateLimiter.ofDefaults("signup"));
|
||||||
|
@ -74,7 +81,7 @@ class PublicUserEndpointTest {
|
||||||
webClient.post()
|
webClient.post()
|
||||||
.uri("/users/-/signup")
|
.uri("/users/-/signup")
|
||||||
.header("X-Forwarded-For", "127.0.0.1")
|
.header("X-Forwarded-For", "127.0.0.1")
|
||||||
.bodyValue(new PublicUserEndpoint.SignUpRequest(user, "fake-password"))
|
.bodyValue(new PublicUserEndpoint.SignUpRequest(user, "fake-password", ""))
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().isOk();
|
.expectStatus().isOk();
|
||||||
|
|
||||||
|
|
|
@ -36,15 +36,19 @@ import {
|
||||||
RequestArgs,
|
RequestArgs,
|
||||||
BaseAPI,
|
BaseAPI,
|
||||||
RequiredError,
|
RequiredError,
|
||||||
|
operationServerMap,
|
||||||
} from "../base";
|
} from "../base";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { PasswordResetEmailRequest } from "../models";
|
import { PasswordResetEmailRequest } from "../models";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
import { RegisterVerifyEmailRequest } from "../models";
|
||||||
|
// @ts-ignore
|
||||||
import { ResetPasswordRequest } from "../models";
|
import { ResetPasswordRequest } from "../models";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { SignUpRequest } from "../models";
|
import { SignUpRequest } from "../models";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { User } from "../models";
|
import { User } from "../models";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ApiHaloRunV1alpha1UserApi - axios parameter creator
|
* ApiHaloRunV1alpha1UserApi - axios parameter creator
|
||||||
* @export
|
* @export
|
||||||
|
@ -183,6 +187,67 @@ export const ApiHaloRunV1alpha1UserApiAxiosParamCreator = function (
|
||||||
options: localVarRequestOptions,
|
options: localVarRequestOptions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Send registration verification email, which can be called when mustVerifyEmailOnRegistration in user settings is true
|
||||||
|
* @param {RegisterVerifyEmailRequest} registerVerifyEmailRequest
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
sendRegisterVerifyEmail: async (
|
||||||
|
registerVerifyEmailRequest: RegisterVerifyEmailRequest,
|
||||||
|
options: AxiosRequestConfig = {}
|
||||||
|
): Promise<RequestArgs> => {
|
||||||
|
// verify required parameter 'registerVerifyEmailRequest' is not null or undefined
|
||||||
|
assertParamExists(
|
||||||
|
"sendRegisterVerifyEmail",
|
||||||
|
"registerVerifyEmailRequest",
|
||||||
|
registerVerifyEmailRequest
|
||||||
|
);
|
||||||
|
const localVarPath = `/apis/api.halo.run/v1alpha1/users/-/send-register-verify-email`;
|
||||||
|
// 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 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(
|
||||||
|
registerVerifyEmailRequest,
|
||||||
|
localVarRequestOptions,
|
||||||
|
configuration
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Sign up a new user
|
* Sign up a new user
|
||||||
* @param {SignUpRequest} signUpRequest
|
* @param {SignUpRequest} signUpRequest
|
||||||
|
@ -273,12 +338,18 @@ export const ApiHaloRunV1alpha1UserApiFp = function (
|
||||||
resetPasswordRequest,
|
resetPasswordRequest,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
return createRequestFunction(
|
const index = configuration?.serverIndex ?? 0;
|
||||||
localVarAxiosArgs,
|
const operationBasePath =
|
||||||
globalAxios,
|
operationServerMap["ApiHaloRunV1alpha1UserApi.resetPasswordByToken"]?.[
|
||||||
BASE_PATH,
|
index
|
||||||
configuration
|
]?.url;
|
||||||
);
|
return (axios, basePath) =>
|
||||||
|
createRequestFunction(
|
||||||
|
localVarAxiosArgs,
|
||||||
|
globalAxios,
|
||||||
|
BASE_PATH,
|
||||||
|
configuration
|
||||||
|
)(axios, operationBasePath || basePath);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Send password reset email when forgot password
|
* Send password reset email when forgot password
|
||||||
|
@ -297,12 +368,48 @@ export const ApiHaloRunV1alpha1UserApiFp = function (
|
||||||
passwordResetEmailRequest,
|
passwordResetEmailRequest,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
return createRequestFunction(
|
const index = configuration?.serverIndex ?? 0;
|
||||||
localVarAxiosArgs,
|
const operationBasePath =
|
||||||
globalAxios,
|
operationServerMap[
|
||||||
BASE_PATH,
|
"ApiHaloRunV1alpha1UserApi.sendPasswordResetEmail"
|
||||||
configuration
|
]?.[index]?.url;
|
||||||
);
|
return (axios, basePath) =>
|
||||||
|
createRequestFunction(
|
||||||
|
localVarAxiosArgs,
|
||||||
|
globalAxios,
|
||||||
|
BASE_PATH,
|
||||||
|
configuration
|
||||||
|
)(axios, operationBasePath || basePath);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Send registration verification email, which can be called when mustVerifyEmailOnRegistration in user settings is true
|
||||||
|
* @param {RegisterVerifyEmailRequest} registerVerifyEmailRequest
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async sendRegisterVerifyEmail(
|
||||||
|
registerVerifyEmailRequest: RegisterVerifyEmailRequest,
|
||||||
|
options?: AxiosRequestConfig
|
||||||
|
): Promise<
|
||||||
|
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>
|
||||||
|
> {
|
||||||
|
const localVarAxiosArgs =
|
||||||
|
await localVarAxiosParamCreator.sendRegisterVerifyEmail(
|
||||||
|
registerVerifyEmailRequest,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
const index = configuration?.serverIndex ?? 0;
|
||||||
|
const operationBasePath =
|
||||||
|
operationServerMap[
|
||||||
|
"ApiHaloRunV1alpha1UserApi.sendRegisterVerifyEmail"
|
||||||
|
]?.[index]?.url;
|
||||||
|
return (axios, basePath) =>
|
||||||
|
createRequestFunction(
|
||||||
|
localVarAxiosArgs,
|
||||||
|
globalAxios,
|
||||||
|
BASE_PATH,
|
||||||
|
configuration
|
||||||
|
)(axios, operationBasePath || basePath);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Sign up a new user
|
* Sign up a new user
|
||||||
|
@ -320,12 +427,16 @@ export const ApiHaloRunV1alpha1UserApiFp = function (
|
||||||
signUpRequest,
|
signUpRequest,
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
return createRequestFunction(
|
const index = configuration?.serverIndex ?? 0;
|
||||||
localVarAxiosArgs,
|
const operationBasePath =
|
||||||
globalAxios,
|
operationServerMap["ApiHaloRunV1alpha1UserApi.signUp"]?.[index]?.url;
|
||||||
BASE_PATH,
|
return (axios, basePath) =>
|
||||||
configuration
|
createRequestFunction(
|
||||||
);
|
localVarAxiosArgs,
|
||||||
|
globalAxios,
|
||||||
|
BASE_PATH,
|
||||||
|
configuration
|
||||||
|
)(axios, operationBasePath || basePath);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -376,6 +487,23 @@ export const ApiHaloRunV1alpha1UserApiFactory = function (
|
||||||
)
|
)
|
||||||
.then((request) => request(axios, basePath));
|
.then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Send registration verification email, which can be called when mustVerifyEmailOnRegistration in user settings is true
|
||||||
|
* @param {ApiHaloRunV1alpha1UserApiSendRegisterVerifyEmailRequest} requestParameters Request parameters.
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
sendRegisterVerifyEmail(
|
||||||
|
requestParameters: ApiHaloRunV1alpha1UserApiSendRegisterVerifyEmailRequest,
|
||||||
|
options?: AxiosRequestConfig
|
||||||
|
): AxiosPromise<void> {
|
||||||
|
return localVarFp
|
||||||
|
.sendRegisterVerifyEmail(
|
||||||
|
requestParameters.registerVerifyEmailRequest,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
.then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Sign up a new user
|
* Sign up a new user
|
||||||
* @param {ApiHaloRunV1alpha1UserApiSignUpRequest} requestParameters Request parameters.
|
* @param {ApiHaloRunV1alpha1UserApiSignUpRequest} requestParameters Request parameters.
|
||||||
|
@ -428,6 +556,20 @@ export interface ApiHaloRunV1alpha1UserApiSendPasswordResetEmailRequest {
|
||||||
readonly passwordResetEmailRequest: PasswordResetEmailRequest;
|
readonly passwordResetEmailRequest: PasswordResetEmailRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request parameters for sendRegisterVerifyEmail operation in ApiHaloRunV1alpha1UserApi.
|
||||||
|
* @export
|
||||||
|
* @interface ApiHaloRunV1alpha1UserApiSendRegisterVerifyEmailRequest
|
||||||
|
*/
|
||||||
|
export interface ApiHaloRunV1alpha1UserApiSendRegisterVerifyEmailRequest {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {RegisterVerifyEmailRequest}
|
||||||
|
* @memberof ApiHaloRunV1alpha1UserApiSendRegisterVerifyEmail
|
||||||
|
*/
|
||||||
|
readonly registerVerifyEmailRequest: RegisterVerifyEmailRequest;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request parameters for signUp operation in ApiHaloRunV1alpha1UserApi.
|
* Request parameters for signUp operation in ApiHaloRunV1alpha1UserApi.
|
||||||
* @export
|
* @export
|
||||||
|
@ -488,6 +630,25 @@ export class ApiHaloRunV1alpha1UserApi extends BaseAPI {
|
||||||
.then((request) => request(this.axios, this.basePath));
|
.then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send registration verification email, which can be called when mustVerifyEmailOnRegistration in user settings is true
|
||||||
|
* @param {ApiHaloRunV1alpha1UserApiSendRegisterVerifyEmailRequest} requestParameters Request parameters.
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof ApiHaloRunV1alpha1UserApi
|
||||||
|
*/
|
||||||
|
public sendRegisterVerifyEmail(
|
||||||
|
requestParameters: ApiHaloRunV1alpha1UserApiSendRegisterVerifyEmailRequest,
|
||||||
|
options?: AxiosRequestConfig
|
||||||
|
) {
|
||||||
|
return ApiHaloRunV1alpha1UserApiFp(this.configuration)
|
||||||
|
.sendRegisterVerifyEmail(
|
||||||
|
requestParameters.registerVerifyEmailRequest,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
.then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign up a new user
|
* Sign up a new user
|
||||||
* @param {ApiHaloRunV1alpha1UserApiSignUpRequest} requestParameters Request parameters.
|
* @param {ApiHaloRunV1alpha1UserApiSignUpRequest} requestParameters Request parameters.
|
||||||
|
|
|
@ -73,3 +73,16 @@ export class RequiredError extends Error {
|
||||||
this.name = "RequiredError";
|
this.name = "RequiredError";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ServerMap {
|
||||||
|
[key: string]: {
|
||||||
|
url: string,
|
||||||
|
description: string,
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const operationServerMap: ServerMap = {};
|
||||||
|
|
|
@ -13,19 +13,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface ConfigurationParameters {
|
export interface ConfigurationParameters {
|
||||||
apiKey?:
|
apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
|
||||||
| string
|
|
||||||
| Promise<string>
|
|
||||||
| ((name: string) => string)
|
|
||||||
| ((name: string) => Promise<string>);
|
|
||||||
username?: string;
|
username?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
accessToken?:
|
accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
|
||||||
| string
|
|
||||||
| Promise<string>
|
|
||||||
| ((name?: string, scopes?: string[]) => string)
|
|
||||||
| ((name?: string, scopes?: string[]) => Promise<string>);
|
|
||||||
basePath?: string;
|
basePath?: string;
|
||||||
|
serverIndex?: number;
|
||||||
baseOptions?: any;
|
baseOptions?: any;
|
||||||
formDataCtor?: new () => any;
|
formDataCtor?: new () => any;
|
||||||
}
|
}
|
||||||
|
@ -36,11 +29,7 @@ export class Configuration {
|
||||||
* @param name security name
|
* @param name security name
|
||||||
* @memberof Configuration
|
* @memberof Configuration
|
||||||
*/
|
*/
|
||||||
apiKey?:
|
apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
|
||||||
| string
|
|
||||||
| Promise<string>
|
|
||||||
| ((name: string) => string)
|
|
||||||
| ((name: string) => Promise<string>);
|
|
||||||
/**
|
/**
|
||||||
* parameter for basic security
|
* parameter for basic security
|
||||||
*
|
*
|
||||||
|
@ -61,11 +50,7 @@ export class Configuration {
|
||||||
* @param scopes oauth2 scope
|
* @param scopes oauth2 scope
|
||||||
* @memberof Configuration
|
* @memberof Configuration
|
||||||
*/
|
*/
|
||||||
accessToken?:
|
accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
|
||||||
| string
|
|
||||||
| Promise<string>
|
|
||||||
| ((name?: string, scopes?: string[]) => string)
|
|
||||||
| ((name?: string, scopes?: string[]) => Promise<string>);
|
|
||||||
/**
|
/**
|
||||||
* override base path
|
* override base path
|
||||||
*
|
*
|
||||||
|
@ -73,6 +58,13 @@ export class Configuration {
|
||||||
* @memberof Configuration
|
* @memberof Configuration
|
||||||
*/
|
*/
|
||||||
basePath?: string;
|
basePath?: string;
|
||||||
|
/**
|
||||||
|
* override server index
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof Configuration
|
||||||
|
*/
|
||||||
|
serverIndex?: number;
|
||||||
/**
|
/**
|
||||||
* base options for axios calls
|
* base options for axios calls
|
||||||
*
|
*
|
||||||
|
@ -95,6 +87,7 @@ export class Configuration {
|
||||||
this.password = param.password;
|
this.password = param.password;
|
||||||
this.accessToken = param.accessToken;
|
this.accessToken = param.accessToken;
|
||||||
this.basePath = param.basePath;
|
this.basePath = param.basePath;
|
||||||
|
this.serverIndex = param.serverIndex;
|
||||||
this.baseOptions = param.baseOptions;
|
this.baseOptions = param.baseOptions;
|
||||||
this.formDataCtor = param.formDataCtor;
|
this.formDataCtor = param.formDataCtor;
|
||||||
}
|
}
|
||||||
|
@ -110,14 +103,7 @@ export class Configuration {
|
||||||
* @return True if the given MIME is JSON, false otherwise.
|
* @return True if the given MIME is JSON, false otherwise.
|
||||||
*/
|
*/
|
||||||
public isJsonMime(mime: string): boolean {
|
public isJsonMime(mime: string): boolean {
|
||||||
const jsonMime: RegExp = new RegExp(
|
const jsonMime: RegExp = new RegExp("^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$", "i");
|
||||||
"^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$",
|
return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === "application/json-patch+json");
|
||||||
"i"
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
mime !== null &&
|
|
||||||
(jsonMime.test(mime) ||
|
|
||||||
mime.toLowerCase() === "application/json-patch+json")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,6 +148,7 @@ export * from "./reason-type-notifier-matrix";
|
||||||
export * from "./reason-type-notifier-request";
|
export * from "./reason-type-notifier-request";
|
||||||
export * from "./reason-type-spec";
|
export * from "./reason-type-spec";
|
||||||
export * from "./ref";
|
export * from "./ref";
|
||||||
|
export * from "./register-verify-email-request";
|
||||||
export * from "./reply";
|
export * from "./reply";
|
||||||
export * from "./reply-list";
|
export * from "./reply-list";
|
||||||
export * from "./reply-request";
|
export * from "./reply-request";
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/* 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 RegisterVerifyEmailRequest
|
||||||
|
*/
|
||||||
|
export interface RegisterVerifyEmailRequest {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof RegisterVerifyEmailRequest
|
||||||
|
*/
|
||||||
|
email: string;
|
||||||
|
}
|
|
@ -24,14 +24,20 @@ import { User } from "./user";
|
||||||
export interface SignUpRequest {
|
export interface SignUpRequest {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {any}
|
||||||
* @memberof SignUpRequest
|
* @memberof SignUpRequest
|
||||||
*/
|
*/
|
||||||
password: string;
|
password: any;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {User}
|
* @type {User}
|
||||||
* @memberof SignUpRequest
|
* @memberof SignUpRequest
|
||||||
*/
|
*/
|
||||||
user: User;
|
user: User;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {any}
|
||||||
|
* @memberof SignUpRequest
|
||||||
|
*/
|
||||||
|
verifyCode?: any;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted } from "vue";
|
import { ref, onMounted, reactive, computed, type ComputedRef } from "vue";
|
||||||
import { submitForm } from "@formkit/core";
|
import { submitForm } from "@formkit/core";
|
||||||
import { Toast, VButton } from "@halo-dev/components";
|
import { Toast, VButton } from "@halo-dev/components";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import { useRouteQuery } from "@vueuse/router";
|
import { useRouteQuery } from "@vueuse/router";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { useMutation } from "@tanstack/vue-query";
|
||||||
|
import { useIntervalFn } from "@vueuse/shared";
|
||||||
|
import { useGlobalInfoStore } from "@/stores/global-info";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
@ -31,6 +34,7 @@ const formState = ref({
|
||||||
email: "",
|
email: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
verifyCode: "",
|
||||||
});
|
});
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
|
@ -40,8 +44,14 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
const login = useRouteQuery<string>("login");
|
const login = useRouteQuery<string>("login");
|
||||||
const name = useRouteQuery<string>("name");
|
const name = useRouteQuery<string>("name");
|
||||||
|
const globalInfoStore = useGlobalInfoStore();
|
||||||
|
const signUpCond = reactive({
|
||||||
|
mustVerifyEmailOnRegistration: false,
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
signUpCond.mustVerifyEmailOnRegistration =
|
||||||
|
globalInfoStore.globalInfo?.mustVerifyEmailOnRegistration || false;
|
||||||
if (login.value) {
|
if (login.value) {
|
||||||
formState.value.user.metadata.name = login.value;
|
formState.value.user.metadata.name = login.value;
|
||||||
}
|
}
|
||||||
|
@ -49,6 +59,16 @@ onMounted(() => {
|
||||||
formState.value.user.spec.displayName = name.value;
|
formState.value.user.spec.displayName = name.value;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const emailRegex = new RegExp("^[\\w\\-.]+@([\\w-]+\\.)+[\\w-]{2,}$");
|
||||||
|
const emailValidation: ComputedRef<
|
||||||
|
// please see https://github.com/formkit/formkit/blob/bd5cf1c378d358ed3aba7b494713af20b6c909ab/packages/inputs/src/props.ts#L660
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
string | Array<[rule: string, ...args: any]>
|
||||||
|
> = computed(() => {
|
||||||
|
if (signUpCond.mustVerifyEmailOnRegistration)
|
||||||
|
return [["required"], ["matches", emailRegex]];
|
||||||
|
else return "required|email|length:0,100";
|
||||||
|
});
|
||||||
|
|
||||||
const handleSignup = async () => {
|
const handleSignup = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -71,6 +91,60 @@ const handleSignup = async () => {
|
||||||
const inputClasses = {
|
const inputClasses = {
|
||||||
outer: "!py-3 first:!pt-0 last:!pb-0",
|
outer: "!py-3 first:!pt-0 last:!pb-0",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// the code below is copied from console/uc-src/modules/profile/components/EmailVerifyModal.vue
|
||||||
|
const timer = ref(0);
|
||||||
|
const { pause, resume, isActive } = useIntervalFn(
|
||||||
|
() => {
|
||||||
|
if (timer.value <= 0) {
|
||||||
|
pause();
|
||||||
|
} else {
|
||||||
|
timer.value--;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
1000,
|
||||||
|
{
|
||||||
|
immediate: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { mutate: sendVerifyCode, isLoading: isSending } = useMutation({
|
||||||
|
mutationKey: ["send-verify-code"],
|
||||||
|
mutationFn: async () => {
|
||||||
|
if (!formState.value.user.spec.email.match(emailRegex)) {
|
||||||
|
Toast.error(t("core.signup.fields.email.matchFailed"));
|
||||||
|
throw new Error("email is illegal");
|
||||||
|
}
|
||||||
|
return await apiClient.common.user.sendRegisterVerifyEmail({
|
||||||
|
registerVerifyEmailRequest: {
|
||||||
|
email: formState.value.user.spec.email,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess() {
|
||||||
|
Toast.success(
|
||||||
|
t("core.signup.fields.verify_code.operations.send_code.toast_success")
|
||||||
|
);
|
||||||
|
timer.value = 60;
|
||||||
|
resume();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const sendVerifyCodeButtonText = computed(() => {
|
||||||
|
if (isSending.value) {
|
||||||
|
return t(
|
||||||
|
"core.signup.fields.verify_code.operations.send_code.buttons.sending"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return isActive.value
|
||||||
|
? t(
|
||||||
|
"core.signup.fields.verify_code.operations.send_code.buttons.countdown",
|
||||||
|
{
|
||||||
|
timer: timer.value,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: t("core.signup.fields.verify_code.operations.send_code.buttons.send");
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -123,8 +197,30 @@ const inputClasses = {
|
||||||
:validation-label="$t('core.signup.fields.email.placeholder')"
|
:validation-label="$t('core.signup.fields.email.placeholder')"
|
||||||
type="email"
|
type="email"
|
||||||
name="email"
|
name="email"
|
||||||
validation="required|email|length:0,100"
|
:validation="emailValidation"
|
||||||
|
:validation-messages="{
|
||||||
|
matches: $t('core.signup.fields.email.matchFailed'),
|
||||||
|
}"
|
||||||
></FormKit>
|
></FormKit>
|
||||||
|
<FormKit
|
||||||
|
v-if="signUpCond.mustVerifyEmailOnRegistration"
|
||||||
|
v-model="formState.verifyCode"
|
||||||
|
type="number"
|
||||||
|
name="code"
|
||||||
|
:placeholder="$t('core.signup.fields.verify_code.placeholder')"
|
||||||
|
validation="required"
|
||||||
|
>
|
||||||
|
<template #suffix>
|
||||||
|
<VButton
|
||||||
|
:loading="isSending"
|
||||||
|
:disabled="isActive"
|
||||||
|
class="rounded-none border-y-0 border-l border-r-0 tabular-nums"
|
||||||
|
@click="sendVerifyCode"
|
||||||
|
>
|
||||||
|
{{ sendVerifyCodeButtonText }}
|
||||||
|
</VButton>
|
||||||
|
</template>
|
||||||
|
</FormKit>
|
||||||
<FormKit
|
<FormKit
|
||||||
v-model="formState.password"
|
v-model="formState.password"
|
||||||
name="password"
|
name="password"
|
||||||
|
|
|
@ -32,6 +32,17 @@ core:
|
||||||
placeholder: Display name
|
placeholder: Display name
|
||||||
email:
|
email:
|
||||||
placeholder: Email
|
placeholder: Email
|
||||||
|
matchFailed: The email format is wrong or the service provider is not supported.
|
||||||
|
verify_code:
|
||||||
|
placeholder: Verification code
|
||||||
|
operations:
|
||||||
|
send_code:
|
||||||
|
buttons:
|
||||||
|
sending: sending
|
||||||
|
send: Send Code
|
||||||
|
countdown: "resend after {timer} seconds"
|
||||||
|
toast_success: verification code sent
|
||||||
|
toast_email_empty: please enter your email address
|
||||||
password:
|
password:
|
||||||
placeholder: Password
|
placeholder: Password
|
||||||
password_confirm:
|
password_confirm:
|
||||||
|
|
|
@ -32,6 +32,17 @@ core:
|
||||||
placeholder: 名称
|
placeholder: 名称
|
||||||
email:
|
email:
|
||||||
placeholder: 电子邮箱
|
placeholder: 电子邮箱
|
||||||
|
matchFailed: 邮箱格式错误或服务商不受支持
|
||||||
|
verify_code:
|
||||||
|
placeholder: 验证码
|
||||||
|
operations:
|
||||||
|
send_code:
|
||||||
|
buttons:
|
||||||
|
sending: 发送中
|
||||||
|
send: 发送验证码
|
||||||
|
countdown: "{timer} 秒后重发"
|
||||||
|
toast_success: 验证码已发送
|
||||||
|
toast_email_empty: 请输入电子邮箱
|
||||||
password:
|
password:
|
||||||
placeholder: 密码
|
placeholder: 密码
|
||||||
password_confirm:
|
password_confirm:
|
||||||
|
|
|
@ -32,6 +32,17 @@ core:
|
||||||
placeholder: 名稱
|
placeholder: 名稱
|
||||||
email:
|
email:
|
||||||
placeholder: 電子郵箱
|
placeholder: 電子郵箱
|
||||||
|
matchFailed: 郵箱格式錯誤或服務商不受支援
|
||||||
|
verify_code:
|
||||||
|
placeholder: 驗證碼
|
||||||
|
operations:
|
||||||
|
send_code:
|
||||||
|
buttons:
|
||||||
|
countdown: "{timer} 秒後重發"
|
||||||
|
send: 發送驗證碼
|
||||||
|
sending: 發送中
|
||||||
|
toast_email_empty: 請輸入電子郵件信箱
|
||||||
|
toast_success: 驗證碼已發送
|
||||||
password:
|
password:
|
||||||
placeholder: 密碼
|
placeholder: 密碼
|
||||||
password_confirm:
|
password_confirm:
|
||||||
|
|
|
@ -13,6 +13,7 @@ export interface GlobalInfo {
|
||||||
dataInitialized: boolean;
|
dataInitialized: boolean;
|
||||||
favicon?: string;
|
favicon?: string;
|
||||||
postSlugGenerationStrategy: ModeType;
|
postSlugGenerationStrategy: ModeType;
|
||||||
|
mustVerifyEmailOnRegistration: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Info {
|
export interface Info {
|
||||||
|
|
Loading…
Reference in New Issue