optimize account creation and verification
This commit is contained in:
@@ -2,7 +2,6 @@ package com.krrishg.dto;
|
|||||||
|
|
||||||
import jakarta.validation.constraints.Email;
|
import jakarta.validation.constraints.Email;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.Size;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@@ -11,11 +10,4 @@ public class RegisterRequest {
|
|||||||
@NotBlank(message = "Email is required")
|
@NotBlank(message = "Email is required")
|
||||||
@Email(message = "Invalid email format")
|
@Email(message = "Invalid email format")
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
@NotBlank(message = "Password is required")
|
|
||||||
@Size(min = 8, max = 100, message = "Password must be between 8 and 100 characters")
|
|
||||||
private String password;
|
|
||||||
|
|
||||||
@NotBlank(message = "Name is required")
|
|
||||||
private String name;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,4 +16,7 @@ public class SetPasswordRequest {
|
|||||||
@NotBlank
|
@NotBlank
|
||||||
@Size(min = 8, max = 100)
|
@Size(min = 8, max = 100)
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
|
@NotBlank(message = "Name is required")
|
||||||
|
private String name;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class User {
|
|||||||
@Column(name = "password")
|
@Column(name = "password")
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
@Column(name = "name", nullable = false)
|
@Column(name = "name")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@Column(name = "avatar_url")
|
@Column(name = "avatar_url")
|
||||||
|
|||||||
@@ -69,8 +69,6 @@ public class AuthService {
|
|||||||
|
|
||||||
User user = User.builder()
|
User user = User.builder()
|
||||||
.email(email)
|
.email(email)
|
||||||
.password(passwordEncoder.encode(request.getPassword()))
|
|
||||||
.name(request.getName())
|
|
||||||
.emailVerified(false)
|
.emailVerified(false)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -88,12 +86,12 @@ public class AuthService {
|
|||||||
User user = userRepository.findByEmail(email)
|
User user = userRepository.findByEmail(email)
|
||||||
.orElseThrow(() -> new IllegalArgumentException("Invalid email or password"));
|
.orElseThrow(() -> new IllegalArgumentException("Invalid email or password"));
|
||||||
|
|
||||||
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
|
if (!user.isEmailVerified() || user.getPassword() == null) {
|
||||||
throw new IllegalArgumentException("Invalid email or password");
|
throw new IllegalArgumentException("Please verify your email address before logging in");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.isEmailVerified()) {
|
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
|
||||||
throw new IllegalArgumentException("Please verify your email address before logging in");
|
throw new IllegalArgumentException("Invalid email or password");
|
||||||
}
|
}
|
||||||
|
|
||||||
return generateAuthResponse(user);
|
return generateAuthResponse(user);
|
||||||
@@ -174,6 +172,7 @@ public class AuthService {
|
|||||||
User user = userRepository.findById(token.getUserId())
|
User user = userRepository.findById(token.getUserId())
|
||||||
.orElseThrow(() -> new IllegalArgumentException("User not found"));
|
.orElseThrow(() -> new IllegalArgumentException("User not found"));
|
||||||
|
|
||||||
|
user.setName(request.getName());
|
||||||
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ public class EmailService {
|
|||||||
|
|
||||||
public void sendVerificationEmail(String to, String name, String token) {
|
public void sendVerificationEmail(String to, String name, String token) {
|
||||||
String link = baseUrl + "/verify-email?token=" + token;
|
String link = baseUrl + "/verify-email?token=" + token;
|
||||||
|
String displayName = name != null ? name : to.substring(0, to.indexOf('@'));
|
||||||
String subject = "Verify your Indie account";
|
String subject = "Verify your Indie account";
|
||||||
String html = """
|
String html = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
@@ -61,7 +62,7 @@ public class EmailService {
|
|||||||
</table>
|
</table>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
""".formatted(name, link);
|
""".formatted(displayName, link);
|
||||||
|
|
||||||
sendHtml(to, subject, html);
|
sendHtml(to, subject, html);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ indie:
|
|||||||
master-key-path: ${INDIE_ENCRYPTION_KEY_PATH:config/encryption.key}
|
master-key-path: ${INDIE_ENCRYPTION_KEY_PATH:config/encryption.key}
|
||||||
|
|
||||||
app:
|
app:
|
||||||
base-url: ${INDIE_APP_BASE_URL:http://localhost:4200}
|
base-url: ${INDIE_APP_BASE_URL:https://indie.krrishg.com}
|
||||||
verification-token-expiration-minutes: ${INDIE_VERIFICATION_TOKEN_EXPIRATION:1440}
|
verification-token-expiration-minutes: ${INDIE_VERIFICATION_TOKEN_EXPIRATION:1440}
|
||||||
|
|
||||||
llm:
|
llm:
|
||||||
|
|||||||
@@ -52,8 +52,6 @@ class AuthControllerTest {
|
|||||||
|
|
||||||
RegisterRequest request = new RegisterRequest();
|
RegisterRequest request = new RegisterRequest();
|
||||||
request.setEmail("test@example.com");
|
request.setEmail("test@example.com");
|
||||||
request.setPassword("password123");
|
|
||||||
request.setName("Test");
|
|
||||||
|
|
||||||
mockMvc.perform(post("/api/auth/register")
|
mockMvc.perform(post("/api/auth/register")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
@@ -218,6 +216,7 @@ class AuthControllerTest {
|
|||||||
SetPasswordRequest request = new SetPasswordRequest();
|
SetPasswordRequest request = new SetPasswordRequest();
|
||||||
request.setSetupToken("setup-token-123");
|
request.setSetupToken("setup-token-123");
|
||||||
request.setPassword("new-password");
|
request.setPassword("new-password");
|
||||||
|
request.setName("Test");
|
||||||
|
|
||||||
mockMvc.perform(post("/api/auth/set-password")
|
mockMvc.perform(post("/api/auth/set-password")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
@@ -233,6 +232,7 @@ class AuthControllerTest {
|
|||||||
SetPasswordRequest request = new SetPasswordRequest();
|
SetPasswordRequest request = new SetPasswordRequest();
|
||||||
request.setSetupToken("bad-token");
|
request.setSetupToken("bad-token");
|
||||||
request.setPassword("new-password");
|
request.setPassword("new-password");
|
||||||
|
request.setName("Test");
|
||||||
|
|
||||||
mockMvc.perform(post("/api/auth/set-password")
|
mockMvc.perform(post("/api/auth/set-password")
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ class AuthServiceTest {
|
|||||||
void registerCreatesUserAndSendsVerification() {
|
void registerCreatesUserAndSendsVerification() {
|
||||||
RegisterRequest request = new RegisterRequest();
|
RegisterRequest request = new RegisterRequest();
|
||||||
request.setEmail("new@example.com");
|
request.setEmail("new@example.com");
|
||||||
request.setPassword("password123");
|
|
||||||
request.setName("New User");
|
|
||||||
|
|
||||||
RegistrationResponse response = authService.register(request);
|
RegistrationResponse response = authService.register(request);
|
||||||
|
|
||||||
@@ -53,12 +51,21 @@ class AuthServiceTest {
|
|||||||
assertFalse(userRepository.findByEmail("new@example.com").get().isEmailVerified());
|
assertFalse(userRepository.findByEmail("new@example.com").get().isEmailVerified());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void registerDoesNotSetName() {
|
||||||
|
RegisterRequest request = new RegisterRequest();
|
||||||
|
request.setEmail("noname@example.com");
|
||||||
|
|
||||||
|
authService.register(request);
|
||||||
|
|
||||||
|
User user = userRepository.findByEmail("noname@example.com").get();
|
||||||
|
assertNull(user.getName());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void registerSendsVerificationEmail() {
|
void registerSendsVerificationEmail() {
|
||||||
RegisterRequest request = new RegisterRequest();
|
RegisterRequest request = new RegisterRequest();
|
||||||
request.setEmail("emailtest@example.com");
|
request.setEmail("emailtest@example.com");
|
||||||
request.setPassword("password123");
|
|
||||||
request.setName("Email Test");
|
|
||||||
|
|
||||||
authService.register(request);
|
authService.register(request);
|
||||||
|
|
||||||
@@ -79,8 +86,6 @@ class AuthServiceTest {
|
|||||||
|
|
||||||
RegisterRequest request = new RegisterRequest();
|
RegisterRequest request = new RegisterRequest();
|
||||||
request.setEmail("verified@example.com");
|
request.setEmail("verified@example.com");
|
||||||
request.setPassword("password123");
|
|
||||||
request.setName("Should Not Matter");
|
|
||||||
|
|
||||||
RegistrationResponse response = authService.register(request);
|
RegistrationResponse response = authService.register(request);
|
||||||
|
|
||||||
@@ -100,8 +105,6 @@ class AuthServiceTest {
|
|||||||
|
|
||||||
RegisterRequest request = new RegisterRequest();
|
RegisterRequest request = new RegisterRequest();
|
||||||
request.setEmail("unverified@example.com");
|
request.setEmail("unverified@example.com");
|
||||||
request.setPassword("newpassword");
|
|
||||||
request.setName("New Name");
|
|
||||||
|
|
||||||
RegistrationResponse response = authService.register(request);
|
RegistrationResponse response = authService.register(request);
|
||||||
|
|
||||||
@@ -114,8 +117,6 @@ class AuthServiceTest {
|
|||||||
void registerNormalizesEmailCase() {
|
void registerNormalizesEmailCase() {
|
||||||
RegisterRequest request = new RegisterRequest();
|
RegisterRequest request = new RegisterRequest();
|
||||||
request.setEmail("UpperCase@Example.com");
|
request.setEmail("UpperCase@Example.com");
|
||||||
request.setPassword("password123");
|
|
||||||
request.setName("User");
|
|
||||||
|
|
||||||
authService.register(request);
|
authService.register(request);
|
||||||
|
|
||||||
@@ -126,11 +127,10 @@ class AuthServiceTest {
|
|||||||
void loginWithValidCredentialsAndVerifiedEmailReturnsTokens() {
|
void loginWithValidCredentialsAndVerifiedEmailReturnsTokens() {
|
||||||
RegisterRequest reg = new RegisterRequest();
|
RegisterRequest reg = new RegisterRequest();
|
||||||
reg.setEmail("login@example.com");
|
reg.setEmail("login@example.com");
|
||||||
reg.setPassword("password123");
|
|
||||||
reg.setName("Login User");
|
|
||||||
authService.register(reg);
|
authService.register(reg);
|
||||||
|
|
||||||
User user = userRepository.findByEmail("login@example.com").get();
|
User user = userRepository.findByEmail("login@example.com").get();
|
||||||
|
user.setPassword("{stub}password123");
|
||||||
user.setEmailVerified(true);
|
user.setEmailVerified(true);
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
|
|
||||||
@@ -149,8 +149,6 @@ class AuthServiceTest {
|
|||||||
void loginThrowsWhenEmailNotVerified() {
|
void loginThrowsWhenEmailNotVerified() {
|
||||||
RegisterRequest reg = new RegisterRequest();
|
RegisterRequest reg = new RegisterRequest();
|
||||||
reg.setEmail("unverified@example.com");
|
reg.setEmail("unverified@example.com");
|
||||||
reg.setPassword("password123");
|
|
||||||
reg.setName("Unverified");
|
|
||||||
authService.register(reg);
|
authService.register(reg);
|
||||||
|
|
||||||
LoginRequest login = new LoginRequest();
|
LoginRequest login = new LoginRequest();
|
||||||
@@ -166,11 +164,10 @@ class AuthServiceTest {
|
|||||||
void loginThrowsOnWrongPassword() {
|
void loginThrowsOnWrongPassword() {
|
||||||
RegisterRequest reg = new RegisterRequest();
|
RegisterRequest reg = new RegisterRequest();
|
||||||
reg.setEmail("wrongpw@example.com");
|
reg.setEmail("wrongpw@example.com");
|
||||||
reg.setPassword("correctpw");
|
|
||||||
reg.setName("User");
|
|
||||||
authService.register(reg);
|
authService.register(reg);
|
||||||
|
|
||||||
User user = userRepository.findByEmail("wrongpw@example.com").get();
|
User user = userRepository.findByEmail("wrongpw@example.com").get();
|
||||||
|
user.setPassword("{stub}correctpw");
|
||||||
user.setEmailVerified(true);
|
user.setEmailVerified(true);
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
|
|
||||||
@@ -315,11 +312,13 @@ class AuthServiceTest {
|
|||||||
SetPasswordRequest request = new SetPasswordRequest();
|
SetPasswordRequest request = new SetPasswordRequest();
|
||||||
request.setSetupToken("setup-token-123");
|
request.setSetupToken("setup-token-123");
|
||||||
request.setPassword("new-password");
|
request.setPassword("new-password");
|
||||||
|
request.setName("New Name");
|
||||||
|
|
||||||
authService.setPassword(request);
|
authService.setPassword(request);
|
||||||
|
|
||||||
User updatedUser = userRepository.findById(user.getId()).get();
|
User updatedUser = userRepository.findById(user.getId()).get();
|
||||||
assertTrue(updatedUser.getPassword().contains("new-password"));
|
assertTrue(updatedUser.getPassword().contains("new-password"));
|
||||||
|
assertEquals("New Name", updatedUser.getName());
|
||||||
|
|
||||||
VerificationToken updatedToken = tokenRepository.findByToken("verify-token").get();
|
VerificationToken updatedToken = tokenRepository.findByToken("verify-token").get();
|
||||||
assertTrue(updatedToken.isSetupTokenUsed());
|
assertTrue(updatedToken.isSetupTokenUsed());
|
||||||
@@ -480,8 +479,6 @@ class AuthServiceTest {
|
|||||||
void refreshTokenReturnsNewTokens() {
|
void refreshTokenReturnsNewTokens() {
|
||||||
RegisterRequest reg = new RegisterRequest();
|
RegisterRequest reg = new RegisterRequest();
|
||||||
reg.setEmail("refresh@example.com");
|
reg.setEmail("refresh@example.com");
|
||||||
reg.setPassword("password123");
|
|
||||||
reg.setName("Refresh User");
|
|
||||||
authService.register(reg);
|
authService.register(reg);
|
||||||
|
|
||||||
User user = userRepository.findByEmail("refresh@example.com").get();
|
User user = userRepository.findByEmail("refresh@example.com").get();
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ export const routes: Routes = [
|
|||||||
loadComponent: () => import('./auth/register/register.component').then(m => m.RegisterComponent),
|
loadComponent: () => import('./auth/register/register.component').then(m => m.RegisterComponent),
|
||||||
canActivate: [LoginGuard],
|
canActivate: [LoginGuard],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'resend-verification',
|
||||||
|
loadComponent: () => import('./auth/resend-verification/resend-verification.component').then(m => m.ResendVerificationComponent),
|
||||||
|
canActivate: [LoginGuard],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'dashboard',
|
path: 'dashboard',
|
||||||
loadComponent: () => import('./dashboard/dashboard.component').then(m => m.DashboardComponent),
|
loadComponent: () => import('./dashboard/dashboard.component').then(m => m.DashboardComponent),
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ import { NgIf } from '@angular/common';
|
|||||||
Don't have an account?
|
Don't have an account?
|
||||||
<a routerLink="/register" class="text-indigo-600 font-medium hover:underline">Register</a>
|
<a routerLink="/register" class="text-indigo-600 font-medium hover:underline">Register</a>
|
||||||
</p>
|
</p>
|
||||||
|
<p class="text-center mt-2 text-sm text-gray-500">
|
||||||
|
<a routerLink="/resend-verification" class="text-indigo-600 font-medium hover:underline">Resend verification email</a>
|
||||||
|
</p>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -26,12 +26,6 @@ import { AuthService } from '../../core/services/auth.service';
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()" class="flex flex-col gap-4">
|
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()" class="flex flex-col gap-4">
|
||||||
<mat-form-field appearance="outline" class="w-full">
|
|
||||||
<mat-label>Name</mat-label>
|
|
||||||
<input matInput formControlName="name" placeholder="Your name" />
|
|
||||||
<mat-error *ngIf="registerForm.get('name')?.hasError('required')">Name is required</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="w-full">
|
<mat-form-field appearance="outline" class="w-full">
|
||||||
<mat-label>Email</mat-label>
|
<mat-label>Email</mat-label>
|
||||||
<input matInput type="email" formControlName="email" placeholder="you@example.com" />
|
<input matInput type="email" formControlName="email" placeholder="you@example.com" />
|
||||||
@@ -39,13 +33,6 @@ import { AuthService } from '../../core/services/auth.service';
|
|||||||
<mat-error *ngIf="registerForm.get('email')?.hasError('email')">Invalid email format</mat-error>
|
<mat-error *ngIf="registerForm.get('email')?.hasError('email')">Invalid email format</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="w-full">
|
|
||||||
<mat-label>Password</mat-label>
|
|
||||||
<input matInput type="password" formControlName="password" />
|
|
||||||
<mat-error *ngIf="registerForm.get('password')?.hasError('required')">Password is required</mat-error>
|
|
||||||
<mat-error *ngIf="registerForm.get('password')?.hasError('minlength')">At least 8 characters</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<div *ngIf="error" class="text-red-600 text-sm">{{ error }}</div>
|
<div *ngIf="error" class="text-red-600 text-sm">{{ error }}</div>
|
||||||
|
|
||||||
<button mat-flat-button color="primary" type="submit" [disabled]="loading" class="w-full py-2">
|
<button mat-flat-button color="primary" type="submit" [disabled]="loading" class="w-full py-2">
|
||||||
@@ -66,9 +53,7 @@ import { AuthService } from '../../core/services/auth.service';
|
|||||||
})
|
})
|
||||||
export class RegisterComponent {
|
export class RegisterComponent {
|
||||||
registerForm = this.fb.group({
|
registerForm = this.fb.group({
|
||||||
name: ['', Validators.required],
|
|
||||||
email: ['', [Validators.required, Validators.email]],
|
email: ['', [Validators.required, Validators.email]],
|
||||||
password: ['', [Validators.required, Validators.minLength(8)]],
|
|
||||||
});
|
});
|
||||||
loading = false;
|
loading = false;
|
||||||
error = '';
|
error = '';
|
||||||
@@ -83,8 +68,8 @@ export class RegisterComponent {
|
|||||||
if (this.registerForm.invalid) return;
|
if (this.registerForm.invalid) return;
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.error = '';
|
this.error = '';
|
||||||
const { name, email, password } = this.registerForm.value;
|
const { email } = this.registerForm.value;
|
||||||
this.authService.register(name!, email!, password!).subscribe({
|
this.authService.register(email!).subscribe({
|
||||||
next: () => this.router.navigate(['/verify-email'], { queryParams: { email: email! } }),
|
next: () => this.router.navigate(['/verify-email'], { queryParams: { email: email! } }),
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
this.error = err.error?.message || err.error?.error || 'Registration failed';
|
this.error = err.error?.message || err.error?.error || 'Registration failed';
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { RouterLink } from '@angular/router';
|
||||||
|
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
|
import { NgIf } from '@angular/common';
|
||||||
|
import { AuthService } from '../../core/services/auth.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-resend-verification',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
RouterLink, ReactiveFormsModule, NgIf,
|
||||||
|
MatCardModule, MatFormFieldModule, MatInputModule, MatButtonModule,
|
||||||
|
MatProgressSpinnerModule,
|
||||||
|
],
|
||||||
|
template: `
|
||||||
|
<div class="min-h-screen flex items-center justify-center bg-gray-50 px-4">
|
||||||
|
<mat-card class="w-full max-w-md p-8">
|
||||||
|
<div class="text-center mb-8">
|
||||||
|
<h1 class="text-3xl font-bold text-indigo-600">Indie</h1>
|
||||||
|
<p class="text-gray-500 mt-1">Resend verification email</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form [formGroup]="resendForm" (ngSubmit)="onSubmit()" class="flex flex-col gap-4">
|
||||||
|
<mat-form-field appearance="outline" class="w-full">
|
||||||
|
<mat-label>Email</mat-label>
|
||||||
|
<input matInput type="email" formControlName="email" placeholder="you@example.com" />
|
||||||
|
<mat-error *ngIf="resendForm.get('email')?.hasError('required')">Email is required</mat-error>
|
||||||
|
<mat-error *ngIf="resendForm.get('email')?.hasError('email')">Invalid email format</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<div *ngIf="message" [class.text-green-600]="success" [class.text-red-600]="!success" class="text-sm text-center">{{ message }}</div>
|
||||||
|
|
||||||
|
<button mat-flat-button color="primary" type="submit" [disabled]="loading" class="w-full py-2">
|
||||||
|
<span class="flex items-center justify-center">
|
||||||
|
<mat-spinner *ngIf="loading" diameter="20" class="mr-2"></mat-spinner>
|
||||||
|
{{ loading ? 'Sending...' : 'Send verification email' }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p class="text-center mt-6 text-sm text-gray-500">
|
||||||
|
<a routerLink="/login" class="text-indigo-600 font-medium hover:underline">Back to login</a>
|
||||||
|
</p>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
export class ResendVerificationComponent {
|
||||||
|
resendForm = this.fb.group({
|
||||||
|
email: ['', [Validators.required, Validators.email]],
|
||||||
|
});
|
||||||
|
loading = false;
|
||||||
|
message = '';
|
||||||
|
success = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private authService: AuthService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
if (this.resendForm.invalid) return;
|
||||||
|
this.loading = true;
|
||||||
|
this.message = '';
|
||||||
|
this.authService.resendVerification(this.resendForm.value.email!).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.message = 'If this email is registered, a verification link has been sent.';
|
||||||
|
this.success = true;
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.message = 'Failed to send. Please try again later.';
|
||||||
|
this.success = false;
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,8 +62,14 @@ type PageState = 'check-email' | 'verifying' | 'verified' | 'set-password' | 'er
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="state === 'set-password'">
|
<div *ngIf="state === 'set-password'">
|
||||||
<h2 class="text-xl font-semibold text-gray-800 mb-4 text-center">Set your password</h2>
|
<h2 class="text-xl font-semibold text-gray-800 mb-4 text-center">Set your name and password</h2>
|
||||||
<form [formGroup]="passwordForm" (ngSubmit)="onSetPassword()" class="flex flex-col gap-4">
|
<form [formGroup]="passwordForm" (ngSubmit)="onSetPassword()" class="flex flex-col gap-4">
|
||||||
|
<mat-form-field appearance="outline" class="w-full">
|
||||||
|
<mat-label>Name</mat-label>
|
||||||
|
<input matInput formControlName="name" placeholder="Your name" />
|
||||||
|
<mat-error *ngIf="passwordForm.get('name')?.hasError('required')">Name is required</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="w-full">
|
<mat-form-field appearance="outline" class="w-full">
|
||||||
<mat-label>New Password</mat-label>
|
<mat-label>New Password</mat-label>
|
||||||
<input matInput type="password" formControlName="password" />
|
<input matInput type="password" formControlName="password" />
|
||||||
@@ -120,6 +126,7 @@ export class VerifyEmailComponent implements OnInit {
|
|||||||
passwordError = '';
|
passwordError = '';
|
||||||
|
|
||||||
passwordForm = this.fb.group({
|
passwordForm = this.fb.group({
|
||||||
|
name: ['', Validators.required],
|
||||||
password: ['', [Validators.required, Validators.minLength(8)]],
|
password: ['', [Validators.required, Validators.minLength(8)]],
|
||||||
confirmPassword: ['', Validators.required],
|
confirmPassword: ['', Validators.required],
|
||||||
}, { validators: this.passwordsMatch });
|
}, { validators: this.passwordsMatch });
|
||||||
@@ -211,7 +218,7 @@ export class VerifyEmailComponent implements OnInit {
|
|||||||
this.passwordLoading = true;
|
this.passwordLoading = true;
|
||||||
this.passwordError = '';
|
this.passwordError = '';
|
||||||
|
|
||||||
this.authService.setPassword(this.setupToken, this.passwordForm.value.password!).subscribe({
|
this.authService.setPassword(this.setupToken, this.passwordForm.value.password!, this.passwordForm.value.name!).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.router.navigate(['/login'], { queryParams: { verified: 'true' } });
|
this.router.navigate(['/login'], { queryParams: { verified: 'true' } });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ export class AuthService {
|
|||||||
|
|
||||||
constructor(private http: HttpClient) {}
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
register(name: string, email: string, password: string): Observable<{ message: string }> {
|
register(email: string): Observable<{ message: string }> {
|
||||||
return this.http.post<{ message: string }>('/api/auth/register', { name, email, password });
|
return this.http.post<{ message: string }>('/api/auth/register', { email });
|
||||||
}
|
}
|
||||||
|
|
||||||
login(email: string, password: string): Observable<AuthResponse> {
|
login(email: string, password: string): Observable<AuthResponse> {
|
||||||
@@ -41,8 +41,8 @@ export class AuthService {
|
|||||||
return this.http.post<{ message: string }>('/api/auth/resend-verification', { email });
|
return this.http.post<{ message: string }>('/api/auth/resend-verification', { email });
|
||||||
}
|
}
|
||||||
|
|
||||||
setPassword(setupToken: string, password: string): Observable<void> {
|
setPassword(setupToken: string, password: string, name: string): Observable<void> {
|
||||||
return this.http.post<void>('/api/auth/set-password', { setupToken, password });
|
return this.http.post<void>('/api/auth/set-password', { setupToken, password, name });
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccessToken(): string | null {
|
getAccessToken(): string | null {
|
||||||
|
|||||||
Reference in New Issue
Block a user