Skip to content

Fix the repeat registration with the email already verified #7323

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public interface UserService {

Flux<User> listByEmail(String email);

Mono<Boolean> checkEmailAlreadyVerified(String email);

String encryptPassword(String rawPassword);

Mono<User> disable(String username);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import run.halo.app.infra.SystemSetting;
import run.halo.app.infra.ValidationUtils;
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.UnsatisfiedAttributeValueException;
import run.halo.app.infra.exception.UserNotFoundException;
Expand Down Expand Up @@ -206,7 +207,12 @@ public Mono<User> signUp(SignUpData signUpData) {
.switchIfEmpty(Mono.error(() ->
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();
}
return verifyEmail.then(Mono.defer(() -> {
Expand Down Expand Up @@ -278,6 +284,14 @@ public Flux<User> listByEmail(String email) {
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
public String encryptPassword(String rawPassword) {
return passwordEncoder.encode(rawPassword);
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import run.halo.app.core.user.service.UserService;
import run.halo.app.infra.actuator.GlobalInfoService;
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.RateLimitExceededException;
import run.halo.app.infra.exception.RequestBodyValidationException;
Expand Down Expand Up @@ -111,6 +112,15 @@ RouterFunction<ServerResponse> preAuthSignUpEndpoints() {
"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,
e -> model.put("error", "rate-limit-exceeded")
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ problemDetail.comment.waitingForApproval=Comment is awaiting approval.
title.visibility.identification.private=(Private)
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.already-taken=Email address is already taken.

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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ problemDetail.comment.waitingForApproval=评论审核中。
title.visibility.identification.private=(私有)
signup.error.confirm-password-not-match=确认密码与密码不匹配。
signup.error.email-code.invalid=邮箱验证码无效。
signup.error.email.already-taken=邮箱地址已被注册。

validation.error.email.pattern=邮箱格式不正确
validation.error.username.pattern=用户名只能小写且只能包含字母、数字、中划线和点,以字符开头和结尾
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import static org.mockito.ArgumentMatchers.assertArg;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
Expand All @@ -30,6 +31,7 @@
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Sort;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.server.ServerWebInputException;
import reactor.core.publisher.Flux;
Expand All @@ -39,17 +41,20 @@
import run.halo.app.core.extension.RoleBinding;
import run.halo.app.core.extension.RoleBinding.Subject;
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.SignUpData;
import run.halo.app.core.user.service.UserPostCreatingHandler;
import run.halo.app.core.user.service.UserPreCreatingHandler;
import run.halo.app.event.user.PasswordChangedEvent;
import run.halo.app.extension.ListOptions;
import run.halo.app.extension.Metadata;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.extension.exception.ExtensionNotFoundException;
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
import run.halo.app.infra.SystemSetting;
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.UserNotFoundException;
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
Expand All @@ -75,6 +80,9 @@ class UserServiceImplTest {
@Mock
ExtensionGetter extensionGetter;

@Mock
EmailVerificationService emailVerificationService;

@InjectMocks
UserServiceImpl userService;

Expand Down Expand Up @@ -375,6 +383,36 @@ void signUpWhenRegistrationUsernameExists() {
.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("[email protected]",
"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("[email protected]");
signUpData.setEmailCode("fakeCode");
userService.signUp(signUpData)
.as(StepVerifier::create)
.expectError(EmailAlreadyTakenException.class)
.verify();
}

@Test
void signUpWhenRegistrationSuccessfully() {
SystemSetting.User userSetting = new SystemSetting.User();
Expand Down
Loading