mirror of https://github.com/halo-dev/halo
Fix the repeat registration with the email already verified (#7323)
#### What type of PR is this? /kind bug /area core /milestone 2.20.x #### What this PR does / why we need it: This PR fixes the repeat registration with the email already verified.  #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/7308 #### Does this PR introduce a user-facing change? ```release-note 修复注册时未验证邮箱是否已被占用的问题 ```pull/7328/head
parent
2a6bedc73d
commit
fb7a09738a
|
@ -25,6 +25,8 @@ public interface UserService {
|
||||||
|
|
||||||
Flux<User> listByEmail(String email);
|
Flux<User> listByEmail(String email);
|
||||||
|
|
||||||
|
Mono<Boolean> checkEmailAlreadyVerified(String email);
|
||||||
|
|
||||||
String encryptPassword(String rawPassword);
|
String encryptPassword(String rawPassword);
|
||||||
|
|
||||||
Mono<User> disable(String username);
|
Mono<User> disable(String username);
|
||||||
|
|
|
@ -43,6 +43,7 @@ import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||||
import run.halo.app.infra.SystemSetting;
|
import run.halo.app.infra.SystemSetting;
|
||||||
import run.halo.app.infra.ValidationUtils;
|
import run.halo.app.infra.ValidationUtils;
|
||||||
import run.halo.app.infra.exception.DuplicateNameException;
|
import run.halo.app.infra.exception.DuplicateNameException;
|
||||||
|
import run.halo.app.infra.exception.EmailAlreadyTakenException;
|
||||||
import run.halo.app.infra.exception.EmailVerificationFailed;
|
import run.halo.app.infra.exception.EmailVerificationFailed;
|
||||||
import run.halo.app.infra.exception.UnsatisfiedAttributeValueException;
|
import run.halo.app.infra.exception.UnsatisfiedAttributeValueException;
|
||||||
import run.halo.app.infra.exception.UserNotFoundException;
|
import run.halo.app.infra.exception.UserNotFoundException;
|
||||||
|
@ -206,7 +207,12 @@ public class UserServiceImpl implements UserService {
|
||||||
.switchIfEmpty(Mono.error(() ->
|
.switchIfEmpty(Mono.error(() ->
|
||||||
new EmailVerificationFailed("Invalid email captcha.", null)
|
new EmailVerificationFailed("Invalid email captcha.", null)
|
||||||
))
|
))
|
||||||
.doOnNext(spec::setEmailVerified)
|
.then(this.checkEmailAlreadyVerified(signUpData.getEmail()))
|
||||||
|
.filter(has -> !has)
|
||||||
|
.switchIfEmpty(Mono.error(
|
||||||
|
() -> new EmailAlreadyTakenException("Email is already taken")
|
||||||
|
))
|
||||||
|
.doOnNext(v -> spec.setEmailVerified(true))
|
||||||
.then();
|
.then();
|
||||||
}
|
}
|
||||||
return verifyEmail.then(Mono.defer(() -> {
|
return verifyEmail.then(Mono.defer(() -> {
|
||||||
|
@ -278,6 +284,14 @@ public class UserServiceImpl implements UserService {
|
||||||
return client.listAll(User.class, listOptions, defaultSort());
|
return client.listAll(User.class, listOptions, defaultSort());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> checkEmailAlreadyVerified(String email) {
|
||||||
|
return listByEmail(email)
|
||||||
|
// TODO Use index query in the future
|
||||||
|
.filter(u -> u.getSpec().isEmailVerified())
|
||||||
|
.hasElements();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String encryptPassword(String rawPassword) {
|
public String encryptPassword(String rawPassword) {
|
||||||
return passwordEncoder.encode(rawPassword);
|
return passwordEncoder.encode(rawPassword);
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package run.halo.app.infra.exception;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import org.springframework.web.server.ServerWebInputException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when email is already verified and taken.
|
||||||
|
*
|
||||||
|
* @author johnniang
|
||||||
|
*/
|
||||||
|
public class EmailAlreadyTakenException extends ServerWebInputException {
|
||||||
|
|
||||||
|
public static final URI TYPE = URI.create("https://halo.run/errors/email-already-taken");
|
||||||
|
|
||||||
|
public EmailAlreadyTakenException(String reason) {
|
||||||
|
super(reason);
|
||||||
|
setType(TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ import run.halo.app.core.user.service.SignUpData;
|
||||||
import run.halo.app.core.user.service.UserService;
|
import run.halo.app.core.user.service.UserService;
|
||||||
import run.halo.app.infra.actuator.GlobalInfoService;
|
import run.halo.app.infra.actuator.GlobalInfoService;
|
||||||
import run.halo.app.infra.exception.DuplicateNameException;
|
import run.halo.app.infra.exception.DuplicateNameException;
|
||||||
|
import run.halo.app.infra.exception.EmailAlreadyTakenException;
|
||||||
import run.halo.app.infra.exception.EmailVerificationFailed;
|
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.exception.RequestBodyValidationException;
|
import run.halo.app.infra.exception.RequestBodyValidationException;
|
||||||
|
@ -111,6 +112,15 @@ class PreAuthSignUpEndpoint {
|
||||||
"Invalid Email Code"));
|
"Invalid Email Code"));
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
.doOnError(EmailAlreadyTakenException.class, e -> {
|
||||||
|
bindingResult.addError(new FieldError("form",
|
||||||
|
"email",
|
||||||
|
signUpData.getEmail(),
|
||||||
|
true,
|
||||||
|
new String[] {"signup.error.email.already-taken"},
|
||||||
|
null,
|
||||||
|
"Email Already Taken"));
|
||||||
|
})
|
||||||
.doOnError(RateLimitExceededException.class,
|
.doOnError(RateLimitExceededException.class,
|
||||||
e -> model.put("error", "rate-limit-exceeded")
|
e -> model.put("error", "rate-limit-exceeded")
|
||||||
)
|
)
|
||||||
|
|
|
@ -88,6 +88,7 @@ problemDetail.comment.waitingForApproval=Comment is awaiting approval.
|
||||||
title.visibility.identification.private=(Private)
|
title.visibility.identification.private=(Private)
|
||||||
signup.error.confirm-password-not-match=The confirmation password does not match the password.
|
signup.error.confirm-password-not-match=The confirmation password does not match the password.
|
||||||
signup.error.email-code.invalid=Invalid email code.
|
signup.error.email-code.invalid=Invalid email code.
|
||||||
|
signup.error.email.already-taken=Email address is already taken.
|
||||||
|
|
||||||
validation.error.email.pattern=The email format is incorrect
|
validation.error.email.pattern=The email format is incorrect
|
||||||
validation.error.username.pattern=The username can only be lowercase and can only contain letters, numbers, hyphens, and dots, starting and ending with characters.
|
validation.error.username.pattern=The username can only be lowercase and can only contain letters, numbers, hyphens, and dots, starting and ending with characters.
|
||||||
|
|
|
@ -61,6 +61,7 @@ problemDetail.comment.waitingForApproval=评论审核中。
|
||||||
title.visibility.identification.private=(私有)
|
title.visibility.identification.private=(私有)
|
||||||
signup.error.confirm-password-not-match=确认密码与密码不匹配。
|
signup.error.confirm-password-not-match=确认密码与密码不匹配。
|
||||||
signup.error.email-code.invalid=邮箱验证码无效。
|
signup.error.email-code.invalid=邮箱验证码无效。
|
||||||
|
signup.error.email.already-taken=邮箱地址已被注册。
|
||||||
|
|
||||||
validation.error.email.pattern=邮箱格式不正确
|
validation.error.email.pattern=邮箱格式不正确
|
||||||
validation.error.username.pattern=用户名只能小写且只能包含字母、数字、中划线和点,以字符开头和结尾
|
validation.error.username.pattern=用户名只能小写且只能包含字母、数字、中划线和点,以字符开头和结尾
|
||||||
|
|
|
@ -11,6 +11,7 @@ import static org.mockito.ArgumentMatchers.argThat;
|
||||||
import static org.mockito.ArgumentMatchers.assertArg;
|
import static org.mockito.ArgumentMatchers.assertArg;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.ArgumentMatchers.isA;
|
import static org.mockito.ArgumentMatchers.isA;
|
||||||
|
import static org.mockito.ArgumentMatchers.same;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
|
@ -30,6 +31,7 @@ import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.web.server.ServerWebInputException;
|
import org.springframework.web.server.ServerWebInputException;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
@ -39,17 +41,20 @@ import run.halo.app.core.extension.Role;
|
||||||
import run.halo.app.core.extension.RoleBinding;
|
import run.halo.app.core.extension.RoleBinding;
|
||||||
import run.halo.app.core.extension.RoleBinding.Subject;
|
import run.halo.app.core.extension.RoleBinding.Subject;
|
||||||
import run.halo.app.core.extension.User;
|
import run.halo.app.core.extension.User;
|
||||||
|
import run.halo.app.core.user.service.EmailVerificationService;
|
||||||
import run.halo.app.core.user.service.RoleService;
|
import run.halo.app.core.user.service.RoleService;
|
||||||
import run.halo.app.core.user.service.SignUpData;
|
import run.halo.app.core.user.service.SignUpData;
|
||||||
import run.halo.app.core.user.service.UserPostCreatingHandler;
|
import run.halo.app.core.user.service.UserPostCreatingHandler;
|
||||||
import run.halo.app.core.user.service.UserPreCreatingHandler;
|
import run.halo.app.core.user.service.UserPreCreatingHandler;
|
||||||
import run.halo.app.event.user.PasswordChangedEvent;
|
import run.halo.app.event.user.PasswordChangedEvent;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.extension.exception.ExtensionNotFoundException;
|
import run.halo.app.extension.exception.ExtensionNotFoundException;
|
||||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||||
import run.halo.app.infra.SystemSetting;
|
import run.halo.app.infra.SystemSetting;
|
||||||
import run.halo.app.infra.exception.DuplicateNameException;
|
import run.halo.app.infra.exception.DuplicateNameException;
|
||||||
|
import run.halo.app.infra.exception.EmailAlreadyTakenException;
|
||||||
import run.halo.app.infra.exception.UnsatisfiedAttributeValueException;
|
import run.halo.app.infra.exception.UnsatisfiedAttributeValueException;
|
||||||
import run.halo.app.infra.exception.UserNotFoundException;
|
import run.halo.app.infra.exception.UserNotFoundException;
|
||||||
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
|
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
|
||||||
|
@ -75,6 +80,9 @@ class UserServiceImplTest {
|
||||||
@Mock
|
@Mock
|
||||||
ExtensionGetter extensionGetter;
|
ExtensionGetter extensionGetter;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
EmailVerificationService emailVerificationService;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
UserServiceImpl userService;
|
UserServiceImpl userService;
|
||||||
|
|
||||||
|
@ -375,6 +383,36 @@ class UserServiceImplTest {
|
||||||
.verify();
|
.verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void signUpWhenEmailAlreadyTaken() {
|
||||||
|
SystemSetting.User userSetting = new SystemSetting.User();
|
||||||
|
userSetting.setAllowRegistration(true);
|
||||||
|
userSetting.setMustVerifyEmailOnRegistration(true);
|
||||||
|
userSetting.setDefaultRole("fake-role");
|
||||||
|
when(environmentFetcher.fetch(eq(SystemSetting.User.GROUP),
|
||||||
|
eq(SystemSetting.User.class)))
|
||||||
|
.thenReturn(Mono.just(userSetting));
|
||||||
|
when(passwordEncoder.encode(eq("fake-password"))).thenReturn("fake-password");
|
||||||
|
when(emailVerificationService.verifyRegisterVerificationCode("fake@example.com",
|
||||||
|
"fakeCode"))
|
||||||
|
.thenReturn(Mono.just(true));
|
||||||
|
when(client.listAll(same(User.class), any(ListOptions.class), any(Sort.class)))
|
||||||
|
.thenReturn(Flux.from(Mono.fromSupplier(() -> {
|
||||||
|
var user = new User();
|
||||||
|
user.setSpec(new User.UserSpec());
|
||||||
|
user.getSpec().setEmailVerified(true);
|
||||||
|
return user;
|
||||||
|
})));
|
||||||
|
|
||||||
|
var signUpData = createSignUpData("fake-user", "fake-password");
|
||||||
|
signUpData.setEmail("fake@example.com");
|
||||||
|
signUpData.setEmailCode("fakeCode");
|
||||||
|
userService.signUp(signUpData)
|
||||||
|
.as(StepVerifier::create)
|
||||||
|
.expectError(EmailAlreadyTakenException.class)
|
||||||
|
.verify();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void signUpWhenRegistrationSuccessfully() {
|
void signUpWhenRegistrationSuccessfully() {
|
||||||
SystemSetting.User userSetting = new SystemSetting.User();
|
SystemSetting.User userSetting = new SystemSetting.User();
|
||||||
|
|
Loading…
Reference in New Issue