Improve email verification flow by adding verified email pre-fill, validation handling, and dialog-based verification support.
This commit is contained in:
parent
641083732d
commit
4ccce7e190
|
|
@ -73,33 +73,23 @@ public class AppealController implements AppealsApi {
|
||||||
log.debug("Retrieving mail by uuid and address");
|
log.debug("Retrieving mail by uuid and address");
|
||||||
|
|
||||||
EmailVerification verifiedMail = sqlSession.getMapper(EmailVerificationMapper.class)
|
EmailVerification verifiedMail = sqlSession.getMapper(EmailVerificationMapper.class)
|
||||||
.findByUserAndEmail(appeal.uuid(), appeal.email());
|
.findByUserAndEmail(appeal.uuid(), appeal.email().toLowerCase());
|
||||||
emailVerificationCompletableFuture.complete(Optional.ofNullable(verifiedMail));
|
emailVerificationCompletableFuture.complete(Optional.ofNullable(verifiedMail));
|
||||||
});
|
});
|
||||||
Optional<EmailVerification> optionalEmailVerification = emailVerificationCompletableFuture.join();
|
Optional<EmailVerification> optionalEmailVerification = emailVerificationCompletableFuture.join();
|
||||||
|
|
||||||
if (optionalEmailVerification.isEmpty()) {
|
if (optionalEmailVerification.isEmpty()) {
|
||||||
return ResponseEntity.ok().body(new AppealResponseDto(
|
return ResponseEntity.badRequest().build();
|
||||||
appeal.id().toString(),
|
|
||||||
"Your appeal has been saved and a verification mail has been send, please verify your email " +
|
|
||||||
"address by clicking the link in your email. Once it is verified we will review your appeal.",
|
|
||||||
false));
|
|
||||||
}
|
}
|
||||||
EmailVerification emailVerification = optionalEmailVerification.get();
|
EmailVerification emailVerification = optionalEmailVerification.get();
|
||||||
if (!emailVerification.verified()) {
|
if (!emailVerification.verified()) {
|
||||||
return ResponseEntity.ok().body(new AppealResponseDto(
|
return ResponseEntity.badRequest().build();
|
||||||
appeal.id().toString(),
|
|
||||||
"Your appeal has been saved and a verification mail has been resend, please verify your email " +
|
|
||||||
"address by clicking the link in your email. Once it is verified we will review your appeal.",
|
|
||||||
false
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
AppealResponseDto appealResponseDto = new AppealResponseDto(
|
|
||||||
appeal.id().toString(),
|
|
||||||
"Your appeal has been submitted. You will be notified when it has been reviewed.",
|
|
||||||
true);
|
|
||||||
return ResponseEntity.ok().body(appealResponseDto);
|
|
||||||
}
|
}
|
||||||
|
AppealResponseDto appealResponseDto = new AppealResponseDto(
|
||||||
|
appeal.id().toString(),
|
||||||
|
"Your appeal has been submitted. You will be notified when it has been reviewed.",
|
||||||
|
true);
|
||||||
|
return ResponseEntity.ok().body(appealResponseDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -42,19 +42,20 @@ public class MailVerificationService {
|
||||||
public EmailVerification submitEmail(UUID userUuid, String email) {
|
public EmailVerification submitEmail(UUID userUuid, String email) {
|
||||||
String code = generateCode();
|
String code = generateCode();
|
||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
|
final String finalEmail = email.toLowerCase();
|
||||||
|
|
||||||
CompletableFuture<EmailVerification> future = new CompletableFuture<>();
|
CompletableFuture<EmailVerification> future = new CompletableFuture<>();
|
||||||
Connection.getConnection(Databases.DEFAULT)
|
Connection.getConnection(Databases.DEFAULT)
|
||||||
.runQuery(sql -> {
|
.runQuery(sql -> {
|
||||||
EmailVerificationMapper mapper = sql.getMapper(EmailVerificationMapper.class);
|
EmailVerificationMapper mapper = sql.getMapper(EmailVerificationMapper.class);
|
||||||
EmailVerification existing = mapper.findByUserAndEmail(userUuid, email);
|
EmailVerification existing = mapper.findByUserAndEmail(userUuid, finalEmail);
|
||||||
EmailVerification toPersist;
|
EmailVerification toPersist;
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
toPersist = new EmailVerification(UUID.randomUUID(), userUuid, email, code, false, now, null, now);
|
toPersist = new EmailVerification(UUID.randomUUID(), userUuid, finalEmail, code, false, now, null, now);
|
||||||
mapper.insert(toPersist);
|
mapper.insert(toPersist);
|
||||||
} else {
|
} else {
|
||||||
mapper.updateCodeAndLastSent(existing.id(), code, now);
|
mapper.updateCodeAndLastSent(existing.id(), code, now);
|
||||||
toPersist = new EmailVerification(existing.id(), userUuid, email, code, false, existing.createdAt(), null, now);
|
toPersist = new EmailVerification(existing.id(), userUuid, finalEmail, code, false, existing.createdAt(), null, now);
|
||||||
}
|
}
|
||||||
future.complete(toPersist);
|
future.complete(toPersist);
|
||||||
});
|
});
|
||||||
|
|
@ -82,14 +83,16 @@ public class MailVerificationService {
|
||||||
public EmailVerification resend(UUID userUuid, String email) {
|
public EmailVerification resend(UUID userUuid, String email) {
|
||||||
String code = generateCode();
|
String code = generateCode();
|
||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
|
final String finalEmail = email.toLowerCase();
|
||||||
CompletableFuture<EmailVerification> future = new CompletableFuture<>();
|
CompletableFuture<EmailVerification> future = new CompletableFuture<>();
|
||||||
Connection.getConnection(Databases.DEFAULT)
|
Connection.getConnection(Databases.DEFAULT)
|
||||||
.runQuery(sql -> {
|
.runQuery(sql -> {
|
||||||
EmailVerificationMapper mapper = sql.getMapper(EmailVerificationMapper.class);
|
EmailVerificationMapper mapper = sql.getMapper(EmailVerificationMapper.class);
|
||||||
EmailVerification existing = mapper.findByUserAndEmail(userUuid, email);
|
EmailVerification existing = mapper.findByUserAndEmail(userUuid, finalEmail);
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
mapper.updateCodeAndLastSent(existing.id(), code, now);
|
mapper.updateCodeAndLastSent(existing.id(), code, now);
|
||||||
future.complete(new EmailVerification(existing.id(), userUuid, email, code, false, existing.createdAt(), null, now));
|
future.complete(new EmailVerification(existing.id(), userUuid,
|
||||||
|
finalEmail, code, false, existing.createdAt(), null, now));
|
||||||
} else {
|
} else {
|
||||||
future.complete(null);
|
future.complete(null);
|
||||||
}
|
}
|
||||||
|
|
@ -102,13 +105,14 @@ public class MailVerificationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean delete(UUID userUuid, String email) {
|
public boolean delete(UUID userUuid, String email) {
|
||||||
|
final String finalEmail = email.toLowerCase();
|
||||||
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
||||||
Connection.getConnection(Databases.DEFAULT)
|
Connection.getConnection(Databases.DEFAULT)
|
||||||
.runQuery(sql -> {
|
.runQuery(sql -> {
|
||||||
EmailVerificationMapper mapper = sql.getMapper(EmailVerificationMapper.class);
|
EmailVerificationMapper mapper = sql.getMapper(EmailVerificationMapper.class);
|
||||||
EmailVerification existing = mapper.findByUserAndEmail(userUuid, email);
|
EmailVerification existing = mapper.findByUserAndEmail(userUuid, finalEmail);
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
mapper.deleteByUserAndEmail(userUuid, email);
|
mapper.deleteByUserAndEmail(userUuid, finalEmail);
|
||||||
future.complete(true);
|
future.complete(true);
|
||||||
} else {
|
} else {
|
||||||
future.complete(false);
|
future.complete(false);
|
||||||
|
|
@ -122,7 +126,7 @@ public class MailVerificationService {
|
||||||
MimeMessage message = mailSender.createMimeMessage();
|
MimeMessage message = mailSender.createMimeMessage();
|
||||||
MimeMessageHelper helper = new MimeMessageHelper(message, true);
|
MimeMessageHelper helper = new MimeMessageHelper(message, true);
|
||||||
helper.setFrom(fromEmail);
|
helper.setFrom(fromEmail);
|
||||||
helper.setTo(emailVerification.email());
|
helper.setTo(emailVerification.email().toLowerCase());
|
||||||
helper.setSubject("Your verification code");
|
helper.setSubject("Your verification code");
|
||||||
helper.setText("Your verification code is: " + emailVerification.verificationCode(), false);
|
helper.setText("Your verification code is: " + emailVerification.verificationCode(), false);
|
||||||
mailSender.send(message);
|
mailSender.send(message);
|
||||||
|
|
|
||||||
|
|
@ -75,10 +75,14 @@
|
||||||
<section class="formPage">
|
<section class="formPage">
|
||||||
<div class="description">
|
<div class="description">
|
||||||
<h2>Please enter your email.</h2>
|
<h2>Please enter your email.</h2>
|
||||||
<p style="font-style: italic">It does not have to be your minecraft email.</p>
|
<p style="font-style: italic">It does not have to be your minecraft email. You will have to verify
|
||||||
|
it</p>
|
||||||
<mat-form-field appearance="fill" style="width: 100%;">
|
<mat-form-field appearance="fill" style="width: 100%;">
|
||||||
<mat-label>Email</mat-label>
|
<mat-label>Email</mat-label>
|
||||||
<input matInput formControlName="email" placeholder="Email">
|
<input matInput
|
||||||
|
formControlName="email"
|
||||||
|
placeholder="Email"
|
||||||
|
[defaultValue]="verifiedEmails().length > 0 ? verifiedEmails()[0] : ''">
|
||||||
@if (form.controls.email.invalid && form.controls.email.touched) {
|
@if (form.controls.email.invalid && form.controls.email.touched) {
|
||||||
<mat-error>
|
<mat-error>
|
||||||
@if (form.controls.email.errors?.['required']) {
|
@if (form.controls.email.errors?.['required']) {
|
||||||
|
|
@ -90,7 +94,7 @@
|
||||||
}
|
}
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<button mat-raised-button (click)="nextPage()" [disabled]="form.controls.email.invalid">
|
<button mat-raised-button (click)="validateMailOrNextPage()" [disabled]="form.controls.email.invalid">
|
||||||
Next
|
Next
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ import {MatFormFieldModule} from '@angular/material/form-field';
|
||||||
import {MatSelectModule} from '@angular/material/select';
|
import {MatSelectModule} from '@angular/material/select';
|
||||||
import {MatInputModule} from '@angular/material/input';
|
import {MatInputModule} from '@angular/material/input';
|
||||||
import {HistoryFormatService} from '@pages/reference/bans/history-format.service';
|
import {HistoryFormatService} from '@pages/reference/bans/history-format.service';
|
||||||
|
import {MatDialog} from '@angular/material/dialog';
|
||||||
|
import {SentComponent} from '@pages/forms/sent/sent.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-appeal',
|
selector: 'app-appeal',
|
||||||
|
|
@ -52,7 +54,11 @@ export class AppealComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
protected history = signal<PunishmentHistory[] | null>(null);
|
protected history = signal<PunishmentHistory[] | null>(null);
|
||||||
protected selectedPunishment = signal<PunishmentHistory | null>(null);
|
protected selectedPunishment = signal<PunishmentHistory | null>(null);
|
||||||
private emails = signal<EmailEntry[]>([]);
|
private emails = signal<EmailEntry[]>([]);
|
||||||
protected verifiedEmails = computed(() => this.emails().filter(email => email.verified));
|
protected verifiedEmails = computed(() => this.emails()
|
||||||
|
.filter(email => email.verified)
|
||||||
|
.map(email => email.email.toLowerCase()));
|
||||||
|
protected validatedMail = signal<boolean>(false);
|
||||||
|
protected dialog = inject(MatDialog);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private elementRef: ElementRef,
|
private elementRef: ElementRef,
|
||||||
|
|
@ -64,7 +70,12 @@ export class AppealComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
});
|
});
|
||||||
this.mailService.getUserEmails().subscribe(emails => {
|
this.mailService.getUserEmails().subscribe(emails => {
|
||||||
this.emails.set(emails);
|
this.emails.set(emails);
|
||||||
})
|
});
|
||||||
|
this.form.valueChanges.subscribe(() => {
|
||||||
|
if (this.form.getRawValue().email.toLowerCase() in this.verifiedEmails()) {
|
||||||
|
this.validatedMail.set(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
@ -202,6 +213,21 @@ export class AppealComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
onPunishmentSelected($event: PunishmentHistory) {
|
onPunishmentSelected($event: PunishmentHistory) {
|
||||||
this.selectedPunishment.set($event);
|
this.selectedPunishment.set($event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected validateMailOrNextPage() {
|
||||||
|
if (this.validatedMail()) {
|
||||||
|
this.nextPage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dialogRef = this.dialog.open(SentComponent, {
|
||||||
|
data: {email: this.form.getRawValue().email},
|
||||||
|
});
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
if (result === true) {
|
||||||
|
this.validatedMail.set(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Appeal {
|
interface Appeal {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
import {Component, inject, Input, input, signal} from '@angular/core';
|
import {Component, Inject, inject, input, signal} from '@angular/core';
|
||||||
import {FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators} from '@angular/forms';
|
import {FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||||
import {MatInput, MatLabel} from '@angular/material/input';
|
import {MatInput, MatLabel} from '@angular/material/input';
|
||||||
import {MatFormFieldModule} from '@angular/material/form-field';
|
import {MatFormFieldModule} from '@angular/material/form-field';
|
||||||
import {MailService, SubmitEmail, VerifyCode} from '@api';
|
import {MailService, SubmitEmail, VerifyCode} from '@api';
|
||||||
import {AuthService} from '@services/auth.service';
|
import {AuthService} from '@services/auth.service';
|
||||||
import {MatButtonModule} from '@angular/material/button';
|
import {MatButtonModule} from '@angular/material/button';
|
||||||
import {MatDialogActions, MatDialogContent, MatDialogRef, MatDialogTitle} from '@angular/material/dialog';
|
import {
|
||||||
|
MAT_DIALOG_DATA,
|
||||||
|
MatDialogActions,
|
||||||
|
MatDialogContent,
|
||||||
|
MatDialogRef,
|
||||||
|
MatDialogTitle
|
||||||
|
} from '@angular/material/dialog';
|
||||||
import {interval, Subscription} from 'rxjs';
|
import {interval, Subscription} from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|
@ -38,10 +44,11 @@ export class SentComponent {
|
||||||
|
|
||||||
private mailService = inject(MailService);
|
private mailService = inject(MailService);
|
||||||
private authService = inject(AuthService);
|
private authService = inject(AuthService);
|
||||||
|
protected readonly email: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public dialogRef: MatDialogRef<SentComponent>,
|
public dialogRef: MatDialogRef<SentComponent>,
|
||||||
@Input() public email: string
|
@Inject(MAT_DIALOG_DATA) public data: InputMail
|
||||||
) {
|
) {
|
||||||
this.form = new FormGroup({
|
this.form = new FormGroup({
|
||||||
code: new FormControl('', {
|
code: new FormControl('', {
|
||||||
|
|
@ -49,6 +56,8 @@ export class SentComponent {
|
||||||
validators: [Validators.required, Validators.minLength(6), Validators.maxLength(6)]
|
validators: [Validators.required, Validators.minLength(6), Validators.maxLength(6)]
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
this.email = data.email;
|
||||||
|
this.mailService.submitEmailForVerification({email: this.email.toLowerCase()}).subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSubmit() {
|
public onSubmit() {
|
||||||
|
|
@ -81,7 +90,6 @@ export class SentComponent {
|
||||||
|
|
||||||
this.mailService.resendVerificationEmail(submitEmail).subscribe({
|
this.mailService.resendVerificationEmail(submitEmail).subscribe({
|
||||||
next: (response) => {
|
next: (response) => {
|
||||||
// Start cooldown timer
|
|
||||||
this.startResendCooldown();
|
this.startResendCooldown();
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
|
|
@ -147,3 +155,7 @@ interface VerifyMailData {
|
||||||
verified: boolean;
|
verified: boolean;
|
||||||
mail: string;
|
mail: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface InputMail {
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user