From a3f9cef88351960043e3bf4afd9f76cdf463dcf0 Mon Sep 17 00:00:00 2001 From: Krrish Ghimire Date: Fri, 10 Jul 2026 15:11:20 +0545 Subject: [PATCH] optimize account creation and verification --- .../java/com/krrishg/dto/RegisterRequest.java | 8 -- .../com/krrishg/dto/SetPasswordRequest.java | 3 + .../src/main/java/com/krrishg/model/User.java | 2 +- .../java/com/krrishg/service/AuthService.java | 11 ++- .../com/krrishg/service/EmailService.java | 3 +- backend/src/main/resources/application.yml | 2 +- .../controller/AuthControllerTest.java | 4 +- .../com/krrishg/service/AuthServiceTest.java | 33 ++++---- frontend/src/app/app.routes.ts | 5 ++ .../src/app/auth/login/login.component.ts | 3 + .../app/auth/register/register.component.ts | 19 +---- .../resend-verification.component.ts | 83 +++++++++++++++++++ .../verify-email/verify-email.component.ts | 11 ++- .../src/app/core/services/auth.service.ts | 8 +- 14 files changed, 135 insertions(+), 60 deletions(-) create mode 100644 frontend/src/app/auth/resend-verification/resend-verification.component.ts diff --git a/backend/src/main/java/com/krrishg/dto/RegisterRequest.java b/backend/src/main/java/com/krrishg/dto/RegisterRequest.java index 99e72fc..a40713f 100644 --- a/backend/src/main/java/com/krrishg/dto/RegisterRequest.java +++ b/backend/src/main/java/com/krrishg/dto/RegisterRequest.java @@ -2,7 +2,6 @@ package com.krrishg.dto; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; import lombok.Data; @Data @@ -11,11 +10,4 @@ public class RegisterRequest { @NotBlank(message = "Email is required") @Email(message = "Invalid email format") 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; } diff --git a/backend/src/main/java/com/krrishg/dto/SetPasswordRequest.java b/backend/src/main/java/com/krrishg/dto/SetPasswordRequest.java index c5c3f5f..5becbbb 100644 --- a/backend/src/main/java/com/krrishg/dto/SetPasswordRequest.java +++ b/backend/src/main/java/com/krrishg/dto/SetPasswordRequest.java @@ -16,4 +16,7 @@ public class SetPasswordRequest { @NotBlank @Size(min = 8, max = 100) private String password; + + @NotBlank(message = "Name is required") + private String name; } diff --git a/backend/src/main/java/com/krrishg/model/User.java b/backend/src/main/java/com/krrishg/model/User.java index 54be04d..36ab613 100644 --- a/backend/src/main/java/com/krrishg/model/User.java +++ b/backend/src/main/java/com/krrishg/model/User.java @@ -29,7 +29,7 @@ public class User { @Column(name = "password") private String password; - @Column(name = "name", nullable = false) + @Column(name = "name") private String name; @Column(name = "avatar_url") diff --git a/backend/src/main/java/com/krrishg/service/AuthService.java b/backend/src/main/java/com/krrishg/service/AuthService.java index f253335..423e89c 100644 --- a/backend/src/main/java/com/krrishg/service/AuthService.java +++ b/backend/src/main/java/com/krrishg/service/AuthService.java @@ -69,8 +69,6 @@ public class AuthService { User user = User.builder() .email(email) - .password(passwordEncoder.encode(request.getPassword())) - .name(request.getName()) .emailVerified(false) .build(); @@ -88,12 +86,12 @@ public class AuthService { User user = userRepository.findByEmail(email) .orElseThrow(() -> new IllegalArgumentException("Invalid email or password")); - if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { - throw new IllegalArgumentException("Invalid email or password"); + if (!user.isEmailVerified() || user.getPassword() == null) { + throw new IllegalArgumentException("Please verify your email address before logging in"); } - if (!user.isEmailVerified()) { - throw new IllegalArgumentException("Please verify your email address before logging in"); + if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { + throw new IllegalArgumentException("Invalid email or password"); } return generateAuthResponse(user); @@ -174,6 +172,7 @@ public class AuthService { User user = userRepository.findById(token.getUserId()) .orElseThrow(() -> new IllegalArgumentException("User not found")); + user.setName(request.getName()); user.setPassword(passwordEncoder.encode(request.getPassword())); userRepository.save(user); diff --git a/backend/src/main/java/com/krrishg/service/EmailService.java b/backend/src/main/java/com/krrishg/service/EmailService.java index 4d8f782..7a73ac1 100644 --- a/backend/src/main/java/com/krrishg/service/EmailService.java +++ b/backend/src/main/java/com/krrishg/service/EmailService.java @@ -28,6 +28,7 @@ public class EmailService { public void sendVerificationEmail(String to, String name, String 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 html = """ @@ -61,7 +62,7 @@ public class EmailService { - """.formatted(name, link); + """.formatted(displayName, link); sendHtml(to, subject, html); } diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index a6879da..41529b6 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -68,7 +68,7 @@ indie: master-key-path: ${INDIE_ENCRYPTION_KEY_PATH:config/encryption.key} 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} llm: diff --git a/backend/src/test/java/com/krrishg/controller/AuthControllerTest.java b/backend/src/test/java/com/krrishg/controller/AuthControllerTest.java index e017f1a..5229dde 100644 --- a/backend/src/test/java/com/krrishg/controller/AuthControllerTest.java +++ b/backend/src/test/java/com/krrishg/controller/AuthControllerTest.java @@ -52,8 +52,6 @@ class AuthControllerTest { RegisterRequest request = new RegisterRequest(); request.setEmail("test@example.com"); - request.setPassword("password123"); - request.setName("Test"); mockMvc.perform(post("/api/auth/register") .contentType(MediaType.APPLICATION_JSON) @@ -218,6 +216,7 @@ class AuthControllerTest { SetPasswordRequest request = new SetPasswordRequest(); request.setSetupToken("setup-token-123"); request.setPassword("new-password"); + request.setName("Test"); mockMvc.perform(post("/api/auth/set-password") .contentType(MediaType.APPLICATION_JSON) @@ -233,6 +232,7 @@ class AuthControllerTest { SetPasswordRequest request = new SetPasswordRequest(); request.setSetupToken("bad-token"); request.setPassword("new-password"); + request.setName("Test"); mockMvc.perform(post("/api/auth/set-password") .contentType(MediaType.APPLICATION_JSON) diff --git a/backend/src/test/java/com/krrishg/service/AuthServiceTest.java b/backend/src/test/java/com/krrishg/service/AuthServiceTest.java index 96b00f5..c5271cf 100644 --- a/backend/src/test/java/com/krrishg/service/AuthServiceTest.java +++ b/backend/src/test/java/com/krrishg/service/AuthServiceTest.java @@ -43,8 +43,6 @@ class AuthServiceTest { void registerCreatesUserAndSendsVerification() { RegisterRequest request = new RegisterRequest(); request.setEmail("new@example.com"); - request.setPassword("password123"); - request.setName("New User"); RegistrationResponse response = authService.register(request); @@ -53,12 +51,21 @@ class AuthServiceTest { 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 void registerSendsVerificationEmail() { RegisterRequest request = new RegisterRequest(); request.setEmail("emailtest@example.com"); - request.setPassword("password123"); - request.setName("Email Test"); authService.register(request); @@ -79,8 +86,6 @@ class AuthServiceTest { RegisterRequest request = new RegisterRequest(); request.setEmail("verified@example.com"); - request.setPassword("password123"); - request.setName("Should Not Matter"); RegistrationResponse response = authService.register(request); @@ -100,8 +105,6 @@ class AuthServiceTest { RegisterRequest request = new RegisterRequest(); request.setEmail("unverified@example.com"); - request.setPassword("newpassword"); - request.setName("New Name"); RegistrationResponse response = authService.register(request); @@ -114,8 +117,6 @@ class AuthServiceTest { void registerNormalizesEmailCase() { RegisterRequest request = new RegisterRequest(); request.setEmail("UpperCase@Example.com"); - request.setPassword("password123"); - request.setName("User"); authService.register(request); @@ -126,11 +127,10 @@ class AuthServiceTest { void loginWithValidCredentialsAndVerifiedEmailReturnsTokens() { RegisterRequest reg = new RegisterRequest(); reg.setEmail("login@example.com"); - reg.setPassword("password123"); - reg.setName("Login User"); authService.register(reg); User user = userRepository.findByEmail("login@example.com").get(); + user.setPassword("{stub}password123"); user.setEmailVerified(true); userRepository.save(user); @@ -149,8 +149,6 @@ class AuthServiceTest { void loginThrowsWhenEmailNotVerified() { RegisterRequest reg = new RegisterRequest(); reg.setEmail("unverified@example.com"); - reg.setPassword("password123"); - reg.setName("Unverified"); authService.register(reg); LoginRequest login = new LoginRequest(); @@ -166,11 +164,10 @@ class AuthServiceTest { void loginThrowsOnWrongPassword() { RegisterRequest reg = new RegisterRequest(); reg.setEmail("wrongpw@example.com"); - reg.setPassword("correctpw"); - reg.setName("User"); authService.register(reg); User user = userRepository.findByEmail("wrongpw@example.com").get(); + user.setPassword("{stub}correctpw"); user.setEmailVerified(true); userRepository.save(user); @@ -315,11 +312,13 @@ class AuthServiceTest { SetPasswordRequest request = new SetPasswordRequest(); request.setSetupToken("setup-token-123"); request.setPassword("new-password"); + request.setName("New Name"); authService.setPassword(request); User updatedUser = userRepository.findById(user.getId()).get(); assertTrue(updatedUser.getPassword().contains("new-password")); + assertEquals("New Name", updatedUser.getName()); VerificationToken updatedToken = tokenRepository.findByToken("verify-token").get(); assertTrue(updatedToken.isSetupTokenUsed()); @@ -480,8 +479,6 @@ class AuthServiceTest { void refreshTokenReturnsNewTokens() { RegisterRequest reg = new RegisterRequest(); reg.setEmail("refresh@example.com"); - reg.setPassword("password123"); - reg.setName("Refresh User"); authService.register(reg); User user = userRepository.findByEmail("refresh@example.com").get(); diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index be970ae..91e4561 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -18,6 +18,11 @@ export const routes: Routes = [ loadComponent: () => import('./auth/register/register.component').then(m => m.RegisterComponent), canActivate: [LoginGuard], }, + { + path: 'resend-verification', + loadComponent: () => import('./auth/resend-verification/resend-verification.component').then(m => m.ResendVerificationComponent), + canActivate: [LoginGuard], + }, { path: 'dashboard', loadComponent: () => import('./dashboard/dashboard.component').then(m => m.DashboardComponent), diff --git a/frontend/src/app/auth/login/login.component.ts b/frontend/src/app/auth/login/login.component.ts index 51bef2c..388adda 100644 --- a/frontend/src/app/auth/login/login.component.ts +++ b/frontend/src/app/auth/login/login.component.ts @@ -64,6 +64,9 @@ import { NgIf } from '@angular/common'; Don't have an account? Register

+

+ Resend verification email +

`, diff --git a/frontend/src/app/auth/register/register.component.ts b/frontend/src/app/auth/register/register.component.ts index e7bc730..3f94983 100644 --- a/frontend/src/app/auth/register/register.component.ts +++ b/frontend/src/app/auth/register/register.component.ts @@ -26,12 +26,6 @@ import { AuthService } from '../../core/services/auth.service';
- - Name - - Name is required - - Email @@ -39,13 +33,6 @@ import { AuthService } from '../../core/services/auth.service'; Invalid email format - - Password - - Password is required - At least 8 characters - -
{{ error }}
+
+ +

+ Back to login +

+ + + `, +}) +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; + }, + }); + } +} diff --git a/frontend/src/app/auth/verify-email/verify-email.component.ts b/frontend/src/app/auth/verify-email/verify-email.component.ts index 2bab7ac..8988d50 100644 --- a/frontend/src/app/auth/verify-email/verify-email.component.ts +++ b/frontend/src/app/auth/verify-email/verify-email.component.ts @@ -62,8 +62,14 @@ type PageState = 'check-email' | 'verifying' | 'verified' | 'set-password' | 'er
-

Set your password

+

Set your name and password

+ + Name + + Name is required + + New Password @@ -120,6 +126,7 @@ export class VerifyEmailComponent implements OnInit { passwordError = ''; passwordForm = this.fb.group({ + name: ['', Validators.required], password: ['', [Validators.required, Validators.minLength(8)]], confirmPassword: ['', Validators.required], }, { validators: this.passwordsMatch }); @@ -211,7 +218,7 @@ export class VerifyEmailComponent implements OnInit { this.passwordLoading = true; 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: () => { this.router.navigate(['/login'], { queryParams: { verified: 'true' } }); }, diff --git a/frontend/src/app/core/services/auth.service.ts b/frontend/src/app/core/services/auth.service.ts index 2eb2778..86d8431 100644 --- a/frontend/src/app/core/services/auth.service.ts +++ b/frontend/src/app/core/services/auth.service.ts @@ -14,8 +14,8 @@ export class AuthService { constructor(private http: HttpClient) {} - register(name: string, email: string, password: string): Observable<{ message: string }> { - return this.http.post<{ message: string }>('/api/auth/register', { name, email, password }); + register(email: string): Observable<{ message: string }> { + return this.http.post<{ message: string }>('/api/auth/register', { email }); } login(email: string, password: string): Observable { @@ -41,8 +41,8 @@ export class AuthService { return this.http.post<{ message: string }>('/api/auth/resend-verification', { email }); } - setPassword(setupToken: string, password: string): Observable { - return this.http.post('/api/auth/set-password', { setupToken, password }); + setPassword(setupToken: string, password: string, name: string): Observable { + return this.http.post('/api/auth/set-password', { setupToken, password, name }); } getAccessToken(): string | null {