Skip to content

Commit fb7a097

Browse files
authored
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. ![Screenshot From 2025-04-02 16-33-22](https://github.com/user-attachments/assets/1caf0550-f80f-42e4-8db6-747ff1035f63) #### Which issue(s) this PR fixes: Fixes #7308 #### Does this PR introduce a user-facing change? ```release-note 修复注册时未验证邮箱是否已被占用的问题 ```
1 parent 2a6bedc commit fb7a097

File tree

7 files changed

+87
-1
lines changed

7 files changed

+87
-1
lines changed

Diff for: api/src/main/java/run/halo/app/core/user/service/UserService.java

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public interface UserService {
2525

2626
Flux<User> listByEmail(String email);
2727

28+
Mono<Boolean> checkEmailAlreadyVerified(String email);
29+
2830
String encryptPassword(String rawPassword);
2931

3032
Mono<User> disable(String username);

Diff for: application/src/main/java/run/halo/app/core/user/service/impl/UserServiceImpl.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import run.halo.app.infra.SystemSetting;
4444
import run.halo.app.infra.ValidationUtils;
4545
import run.halo.app.infra.exception.DuplicateNameException;
46+
import run.halo.app.infra.exception.EmailAlreadyTakenException;
4647
import run.halo.app.infra.exception.EmailVerificationFailed;
4748
import run.halo.app.infra.exception.UnsatisfiedAttributeValueException;
4849
import run.halo.app.infra.exception.UserNotFoundException;
@@ -206,7 +207,12 @@ public Mono<User> signUp(SignUpData signUpData) {
206207
.switchIfEmpty(Mono.error(() ->
207208
new EmailVerificationFailed("Invalid email captcha.", null)
208209
))
209-
.doOnNext(spec::setEmailVerified)
210+
.then(this.checkEmailAlreadyVerified(signUpData.getEmail()))
211+
.filter(has -> !has)
212+
.switchIfEmpty(Mono.error(
213+
() -> new EmailAlreadyTakenException("Email is already taken")
214+
))
215+
.doOnNext(v -> spec.setEmailVerified(true))
210216
.then();
211217
}
212218
return verifyEmail.then(Mono.defer(() -> {
@@ -278,6 +284,14 @@ public Flux<User> listByEmail(String email) {
278284
return client.listAll(User.class, listOptions, defaultSort());
279285
}
280286

287+
@Override
288+
public Mono<Boolean> checkEmailAlreadyVerified(String email) {
289+
return listByEmail(email)
290+
// TODO Use index query in the future
291+
.filter(u -> u.getSpec().isEmailVerified())
292+
.hasElements();
293+
}
294+
281295
@Override
282296
public String encryptPassword(String rawPassword) {
283297
return passwordEncoder.encode(rawPassword);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package run.halo.app.infra.exception;
2+
3+
import java.net.URI;
4+
import org.springframework.web.server.ServerWebInputException;
5+
6+
/**
7+
* Exception thrown when email is already verified and taken.
8+
*
9+
* @author johnniang
10+
*/
11+
public class EmailAlreadyTakenException extends ServerWebInputException {
12+
13+
public static final URI TYPE = URI.create("https://halo.run/errors/email-already-taken");
14+
15+
public EmailAlreadyTakenException(String reason) {
16+
super(reason);
17+
setType(TYPE);
18+
}
19+
20+
}

Diff for: application/src/main/java/run/halo/app/security/preauth/PreAuthSignUpEndpoint.java

+10
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import run.halo.app.core.user.service.UserService;
3232
import run.halo.app.infra.actuator.GlobalInfoService;
3333
import run.halo.app.infra.exception.DuplicateNameException;
34+
import run.halo.app.infra.exception.EmailAlreadyTakenException;
3435
import run.halo.app.infra.exception.EmailVerificationFailed;
3536
import run.halo.app.infra.exception.RateLimitExceededException;
3637
import run.halo.app.infra.exception.RequestBodyValidationException;
@@ -111,6 +112,15 @@ RouterFunction<ServerResponse> preAuthSignUpEndpoints() {
111112
"Invalid Email Code"));
112113
}
113114
)
115+
.doOnError(EmailAlreadyTakenException.class, e -> {
116+
bindingResult.addError(new FieldError("form",
117+
"email",
118+
signUpData.getEmail(),
119+
true,
120+
new String[] {"signup.error.email.already-taken"},
121+
null,
122+
"Email Already Taken"));
123+
})
114124
.doOnError(RateLimitExceededException.class,
115125
e -> model.put("error", "rate-limit-exceeded")
116126
)

Diff for: application/src/main/resources/config/i18n/messages.properties

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ problemDetail.comment.waitingForApproval=Comment is awaiting approval.
8888
title.visibility.identification.private=(Private)
8989
signup.error.confirm-password-not-match=The confirmation password does not match the password.
9090
signup.error.email-code.invalid=Invalid email code.
91+
signup.error.email.already-taken=Email address is already taken.
9192

9293
validation.error.email.pattern=The email format is incorrect
9394
validation.error.username.pattern=The username can only be lowercase and can only contain letters, numbers, hyphens, and dots, starting and ending with characters.

Diff for: application/src/main/resources/config/i18n/messages_zh.properties

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ problemDetail.comment.waitingForApproval=评论审核中。
6161
title.visibility.identification.private=(私有)
6262
signup.error.confirm-password-not-match=确认密码与密码不匹配。
6363
signup.error.email-code.invalid=邮箱验证码无效。
64+
signup.error.email.already-taken=邮箱地址已被注册。
6465

6566
validation.error.email.pattern=邮箱格式不正确
6667
validation.error.username.pattern=用户名只能小写且只能包含字母、数字、中划线和点,以字符开头和结尾

Diff for: application/src/test/java/run/halo/app/core/user/service/impl/UserServiceImplTest.java

+38
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import static org.mockito.ArgumentMatchers.assertArg;
1212
import static org.mockito.ArgumentMatchers.eq;
1313
import static org.mockito.ArgumentMatchers.isA;
14+
import static org.mockito.ArgumentMatchers.same;
1415
import static org.mockito.Mockito.doReturn;
1516
import static org.mockito.Mockito.mock;
1617
import static org.mockito.Mockito.never;
@@ -30,6 +31,7 @@
3031
import org.mockito.Mock;
3132
import org.mockito.junit.jupiter.MockitoExtension;
3233
import org.springframework.context.ApplicationEventPublisher;
34+
import org.springframework.data.domain.Sort;
3335
import org.springframework.security.crypto.password.PasswordEncoder;
3436
import org.springframework.web.server.ServerWebInputException;
3537
import reactor.core.publisher.Flux;
@@ -39,17 +41,20 @@
3941
import run.halo.app.core.extension.RoleBinding;
4042
import run.halo.app.core.extension.RoleBinding.Subject;
4143
import run.halo.app.core.extension.User;
44+
import run.halo.app.core.user.service.EmailVerificationService;
4245
import run.halo.app.core.user.service.RoleService;
4346
import run.halo.app.core.user.service.SignUpData;
4447
import run.halo.app.core.user.service.UserPostCreatingHandler;
4548
import run.halo.app.core.user.service.UserPreCreatingHandler;
4649
import run.halo.app.event.user.PasswordChangedEvent;
50+
import run.halo.app.extension.ListOptions;
4751
import run.halo.app.extension.Metadata;
4852
import run.halo.app.extension.ReactiveExtensionClient;
4953
import run.halo.app.extension.exception.ExtensionNotFoundException;
5054
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
5155
import run.halo.app.infra.SystemSetting;
5256
import run.halo.app.infra.exception.DuplicateNameException;
57+
import run.halo.app.infra.exception.EmailAlreadyTakenException;
5358
import run.halo.app.infra.exception.UnsatisfiedAttributeValueException;
5459
import run.halo.app.infra.exception.UserNotFoundException;
5560
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
@@ -75,6 +80,9 @@ class UserServiceImplTest {
7580
@Mock
7681
ExtensionGetter extensionGetter;
7782

83+
@Mock
84+
EmailVerificationService emailVerificationService;
85+
7886
@InjectMocks
7987
UserServiceImpl userService;
8088

@@ -375,6 +383,36 @@ void signUpWhenRegistrationUsernameExists() {
375383
.verify();
376384
}
377385

386+
@Test
387+
void signUpWhenEmailAlreadyTaken() {
388+
SystemSetting.User userSetting = new SystemSetting.User();
389+
userSetting.setAllowRegistration(true);
390+
userSetting.setMustVerifyEmailOnRegistration(true);
391+
userSetting.setDefaultRole("fake-role");
392+
when(environmentFetcher.fetch(eq(SystemSetting.User.GROUP),
393+
eq(SystemSetting.User.class)))
394+
.thenReturn(Mono.just(userSetting));
395+
when(passwordEncoder.encode(eq("fake-password"))).thenReturn("fake-password");
396+
when(emailVerificationService.verifyRegisterVerificationCode("[email protected]",
397+
"fakeCode"))
398+
.thenReturn(Mono.just(true));
399+
when(client.listAll(same(User.class), any(ListOptions.class), any(Sort.class)))
400+
.thenReturn(Flux.from(Mono.fromSupplier(() -> {
401+
var user = new User();
402+
user.setSpec(new User.UserSpec());
403+
user.getSpec().setEmailVerified(true);
404+
return user;
405+
})));
406+
407+
var signUpData = createSignUpData("fake-user", "fake-password");
408+
signUpData.setEmail("[email protected]");
409+
signUpData.setEmailCode("fakeCode");
410+
userService.signUp(signUpData)
411+
.as(StepVerifier::create)
412+
.expectError(EmailAlreadyTakenException.class)
413+
.verify();
414+
}
415+
378416
@Test
379417
void signUpWhenRegistrationSuccessfully() {
380418
SystemSetting.User userSetting = new SystemSetting.User();

0 commit comments

Comments
 (0)