From 2305e836478571679546cf4087b649c3cd63663f Mon Sep 17 00:00:00 2001 From: csimonis Date: Thu, 15 May 2025 12:31:08 +0200 Subject: [PATCH] feat(auth): add recover and reset password functionality --- frontend/src/app/app.routes.ts | 14 ++ .../feature/auth/login/login.component.html | 12 ++ .../app/feature/auth/login/login.component.ts | 5 + .../recover-password.component.html | 162 ++++++++++++++++++ .../recover-password.component.ts | 121 +++++++++++++ frontend/src/app/service/auth.service.ts | 8 + 6 files changed, 322 insertions(+) create mode 100644 frontend/src/app/feature/auth/recover-password/recover-password.component.html create mode 100644 frontend/src/app/feature/auth/recover-password/recover-password.component.ts diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 5c57416..b4bf818 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -19,6 +19,20 @@ export const routes: Routes = [ (m) => m.VerifyEmailComponent ), }, + { + path: 'recover-password', + loadComponent: () => + import('./feature/auth/recover-password/recover-password.component').then( + (m) => m.RecoverPasswordComponent + ), + }, + { + path: 'reset-password', + loadComponent: () => + import('./feature/auth/recover-password/recover-password.component').then( + (m) => m.RecoverPasswordComponent + ), + }, { path: 'game/blackjack', loadComponent: () => import('./feature/game/blackjack/blackjack.component'), diff --git a/frontend/src/app/feature/auth/login/login.component.html b/frontend/src/app/feature/auth/login/login.component.html index 5f4c535..dba76b2 100644 --- a/frontend/src/app/feature/auth/login/login.component.html +++ b/frontend/src/app/feature/auth/login/login.component.html @@ -83,6 +83,18 @@ +
+

+ Passwort vergessen? + +

+
+

Noch kein Konto? diff --git a/frontend/src/app/feature/auth/login/login.component.ts b/frontend/src/app/feature/auth/login/login.component.ts index b5a67a0..946f412 100644 --- a/frontend/src/app/feature/auth/login/login.component.ts +++ b/frontend/src/app/feature/auth/login/login.component.ts @@ -63,4 +63,9 @@ export class LoginComponent { }, }); } + + switchToForgotPassword() { + this.closeDialog.emit(); + this.router.navigate(['/recover-password']); + } } diff --git a/frontend/src/app/feature/auth/recover-password/recover-password.component.html b/frontend/src/app/feature/auth/recover-password/recover-password.component.html new file mode 100644 index 0000000..5879f3d --- /dev/null +++ b/frontend/src/app/feature/auth/recover-password/recover-password.component.html @@ -0,0 +1,162 @@ +

+ +
diff --git a/frontend/src/app/feature/auth/recover-password/recover-password.component.ts b/frontend/src/app/feature/auth/recover-password/recover-password.component.ts new file mode 100644 index 0000000..fc3a4d9 --- /dev/null +++ b/frontend/src/app/feature/auth/recover-password/recover-password.component.ts @@ -0,0 +1,121 @@ +import { Component, EventEmitter, Output, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { AuthService } from '@service/auth.service'; + +@Component({ + selector: 'app-recover-password', + standalone: true, + imports: [ + CommonModule, + ReactiveFormsModule, + ], + templateUrl: './recover-password.component.html', +}) +export class RecoverPasswordComponent { + emailForm: FormGroup; + resetPasswordForm: FormGroup; + errorMessage = signal(''); + successMessage = signal(''); + isLoading = signal(false); + token = ''; + isResetMode = signal(false); + + @Output() closeDialog = new EventEmitter(); + + constructor( + private fb: FormBuilder, + private authService: AuthService, + private router: Router, + private route: ActivatedRoute + ) { + this.emailForm = this.fb.group({ + email: ['', [Validators.required, Validators.email]] + }); + + this.resetPasswordForm = this.fb.group({ + password: ['', [Validators.required, Validators.minLength(8)]], + confirmPassword: ['', [Validators.required]] + }, { + validators: this.passwordMatchValidator + }); + + // Check if we're in reset mode + this.route.queryParamMap.subscribe(params => { + const token = params.get('token'); + if (token) { + this.token = token; + this.isResetMode.set(true); + } + }); + } + + passwordMatchValidator(form: FormGroup) { + const password = form.get('password')?.value; + const confirmPassword = form.get('confirmPassword')?.value; + return password === confirmPassword ? null : { passwordMismatch: true }; + } + + get emailFormControls() { + return this.emailForm.controls; + } + + get resetFormControls() { + return this.resetPasswordForm.controls; + } + + onSubmitEmail(): void { + if (this.emailForm.invalid) { + return; + } + + this.isLoading.set(true); + this.errorMessage.set(''); + this.successMessage.set(''); + + const email = this.emailFormControls['email'].value; + + this.authService.recoverPassword(email).subscribe({ + next: () => { + this.isLoading.set(false); + this.successMessage.set('Wenn ein Konto mit dieser E-Mail existiert, wird eine E-Mail mit weiteren Anweisungen gesendet.'); + this.emailForm.reset(); + }, + error: (err) => { + this.isLoading.set(false); + this.errorMessage.set( + err.error?.message || 'Ein Fehler ist aufgetreten. Bitte versuche es später erneut.' + ); + } + }); + } + + onSubmitReset(): void { + if (this.resetPasswordForm.invalid) { + return; + } + + this.isLoading.set(true); + this.errorMessage.set(''); + this.successMessage.set(''); + + const password = this.resetFormControls['password'].value; + + this.authService.resetPassword(this.token, password).subscribe({ + next: () => { + this.isLoading.set(false); + this.successMessage.set('Dein Passwort wurde erfolgreich zurückgesetzt. Du kannst dich jetzt anmelden.'); + setTimeout(() => { + this.router.navigate([''], { queryParams: { login: true } }); + }, 3000); + }, + error: (err) => { + this.isLoading.set(false); + this.errorMessage.set( + err.error?.message || 'Ein Fehler ist aufgetreten. Bitte versuche es später erneut.' + ); + } + }); + } +} diff --git a/frontend/src/app/service/auth.service.ts b/frontend/src/app/service/auth.service.ts index 657067f..b51a4a1 100644 --- a/frontend/src/app/service/auth.service.ts +++ b/frontend/src/app/service/auth.service.ts @@ -77,6 +77,14 @@ export class AuthService { public verifyEmail(token: string): Observable { return this.http.post(`${this.authUrl}/verify?token=${token}`, null); } + + public recoverPassword(email: string): Observable { + return this.http.post(`${this.authUrl}/recover-password?email=${email}`, null); + } + + public resetPassword(token: string, password: string): Observable { + return this.http.post(`${this.authUrl}/reset-password`, { token, password }); + } private setToken(token: string): void { localStorage.setItem(TOKEN_KEY, token);