Add email re-validation handling with UI feedback and backend validation to prevent duplicate email verification attempts

This commit is contained in:
akastijn 2025-08-23 22:59:22 +02:00
parent d1da1296bb
commit 42b11eecf1
4 changed files with 44 additions and 5 deletions

View File

@ -33,6 +33,14 @@ public class MailController implements MailApi {
@RateLimit(limit = 5, timeValue = 1, timeUnit = TimeUnit.HOURS, key = "mailSubmit") @RateLimit(limit = 5, timeValue = 1, timeUnit = TimeUnit.HOURS, key = "mailSubmit")
public ResponseEntity<MailResponseDto> submitEmailForVerification(SubmitEmailDto submitEmailDto) { public ResponseEntity<MailResponseDto> submitEmailForVerification(SubmitEmailDto submitEmailDto) {
UUID uuid = AuthenticatedUuid.getAuthenticatedUserUuid(); UUID uuid = AuthenticatedUuid.getAuthenticatedUserUuid();
boolean emailAlreadyVerified = mailVerificationService.listAll(uuid).stream()
.filter(EmailVerification::verified)
.map(EmailVerification::email)
.anyMatch(mail -> mail.equalsIgnoreCase(submitEmailDto.getEmail()));
if (emailAlreadyVerified) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Email already verified for user");
}
EmailVerification saved = mailVerificationService.submitEmail(uuid, submitEmailDto.getEmail()); EmailVerification saved = mailVerificationService.submitEmail(uuid, submitEmailDto.getEmail());
MailResponseDto response = new MailResponseDto() MailResponseDto response = new MailResponseDto()
.email(saved.email()) .email(saved.email())

View File

@ -93,6 +93,14 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
@if (emailIsValid()) {
<div class="valid-email">
<ng-container matSuffix>
<mat-icon>check</mat-icon>
<span>Your have already validated your email, and can continue to the next page!</span>
</ng-container>
</div>
}
</div> </div>
<button mat-raised-button (click)="validateMailOrNextPage()" [disabled]="form.controls.email.invalid"> <button mat-raised-button (click)="validateMailOrNextPage()" [disabled]="form.controls.email.invalid">
Next Next

View File

@ -89,3 +89,23 @@ main {
max-width: 75ch; max-width: 75ch;
text-align: left; text-align: left;
} }
.valid-email {
display: flex;
align-items: center;
color: #4CAF50;
margin: 10px 0;
padding: 8px 12px;
border-radius: 4px;
background-color: rgba(76, 175, 80, 0.1);
}
.valid-email mat-icon {
color: #4CAF50;
margin-right: 10px;
}
.valid-email span {
color: #4CAF50;
font-weight: 500;
}

View File

@ -58,7 +58,7 @@ export class AppealComponent implements OnInit, OnDestroy, AfterViewInit {
protected verifiedEmails = computed(() => this.emails() protected verifiedEmails = computed(() => this.emails()
.filter(email => email.verified) .filter(email => email.verified)
.map(email => email.email.toLowerCase())); .map(email => email.email.toLowerCase()));
protected validatedMail = signal<boolean>(false); protected emailIsValid = signal<boolean>(false);
protected dialog = inject(MatDialog); protected dialog = inject(MatDialog);
constructor( constructor(
@ -73,10 +73,13 @@ export class AppealComponent implements OnInit, OnDestroy, AfterViewInit {
this.emails.set(emails); this.emails.set(emails);
}); });
this.form.valueChanges.subscribe(() => { this.form.valueChanges.subscribe(() => {
if (this.form.getRawValue().email.toLowerCase() in this.verifiedEmails()) { if (this.verifiedEmails().includes(this.form.getRawValue().email.toLowerCase())) {
this.validatedMail.set(true); this.emailIsValid.set(true);
} else {
this.emailIsValid.set(false);
} }
}); });
} }
ngOnInit() { ngOnInit() {
@ -225,7 +228,7 @@ export class AppealComponent implements OnInit, OnDestroy, AfterViewInit {
} }
protected validateMailOrNextPage() { protected validateMailOrNextPage() {
if (this.validatedMail()) { if (this.emailIsValid()) {
this.nextPage(); this.nextPage();
return; return;
} }
@ -234,7 +237,7 @@ export class AppealComponent implements OnInit, OnDestroy, AfterViewInit {
}); });
dialogRef.afterClosed().subscribe(result => { dialogRef.afterClosed().subscribe(result => {
if (result === true) { if (result === true) {
this.validatedMail.set(true); this.emailIsValid.set(true);
} }
}); });
} }