SSO implementiert
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
<mat-card-content>
|
||||
<div class="status-row">
|
||||
<mat-icon aria-hidden="true">verified_user</mat-icon>
|
||||
<span>{{ auth.user()?.verified ? 'E-Mail verifiziert' : 'E-Mail nicht verifiziert' }}</span>
|
||||
<span>SSO-Konto aktiv</span>
|
||||
</div>
|
||||
|
||||
<div class="status-row">
|
||||
|
||||
@@ -41,15 +41,6 @@
|
||||
<mat-icon aria-hidden="true">login</mat-icon>
|
||||
Login
|
||||
</a>
|
||||
<a
|
||||
mat-flat-button
|
||||
routerLink="/register"
|
||||
routerLinkActive="active-link"
|
||||
ariaCurrentWhenActive="page"
|
||||
>
|
||||
<mat-icon aria-hidden="true">person_add</mat-icon>
|
||||
Registrieren
|
||||
</a>
|
||||
}
|
||||
</mat-toolbar>
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@ import { Routes } from '@angular/router';
|
||||
import { authGuard } from './auth/auth.guard';
|
||||
import { unauthGuard } from './auth/unauth.guard';
|
||||
import { LoginComponent } from './auth/login/login.component';
|
||||
import { RegisterComponent } from './auth/register/register.component';
|
||||
import { VerifyEmailComponent } from './auth/verify-email/verify-email.component';
|
||||
import { SsoCallbackComponent } from './auth/sso-callback/sso-callback.component';
|
||||
import { ListDetailComponent } from './lists/list-detail/list-detail.component';
|
||||
import { ListsComponent } from './lists/lists.component';
|
||||
import { TemplatesComponent } from './templates/templates.component';
|
||||
@@ -12,12 +11,7 @@ import { TemplateDetailComponent } from './templates/template-detail/template-de
|
||||
export const routes: Routes = [
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'dashboard' },
|
||||
{ path: 'login', component: LoginComponent, canActivate: [unauthGuard] },
|
||||
{ path: 'register', component: RegisterComponent, canActivate: [unauthGuard] },
|
||||
{ path: 'verify-email', component: VerifyEmailComponent, canActivate: [unauthGuard] },
|
||||
{
|
||||
path: 'auth',
|
||||
children: [{ path: 'verify-email', component: VerifyEmailComponent }],
|
||||
},
|
||||
{ path: 'auth/sso/callback', component: SsoCallbackComponent },
|
||||
{
|
||||
path: 'dashboard',
|
||||
loadComponent: () =>
|
||||
|
||||
@@ -9,8 +9,16 @@
|
||||
align-items: start;
|
||||
padding: 1.25rem;
|
||||
background:
|
||||
linear-gradient(140deg, color-mix(in srgb, var(--mat-sys-primary) 14%, transparent), transparent 38%),
|
||||
linear-gradient(320deg, color-mix(in srgb, var(--mat-sys-tertiary) 12%, transparent), transparent 36%),
|
||||
linear-gradient(
|
||||
140deg,
|
||||
color-mix(in srgb, var(--mat-sys-primary) 14%, transparent),
|
||||
transparent 38%
|
||||
),
|
||||
linear-gradient(
|
||||
320deg,
|
||||
color-mix(in srgb, var(--mat-sys-tertiary) 12%, transparent),
|
||||
transparent 36%
|
||||
),
|
||||
var(--mat-sys-surface-container);
|
||||
}
|
||||
|
||||
@@ -21,7 +29,9 @@
|
||||
border-radius: 8px;
|
||||
background: var(--mat-sys-surface);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
transition: box-shadow 0.3s ease, transform 0.3s ease;
|
||||
transition:
|
||||
box-shadow 0.3s ease,
|
||||
transform 0.3s ease;
|
||||
}
|
||||
|
||||
.auth-card:hover {
|
||||
@@ -54,7 +64,11 @@
|
||||
border: 1px solid color-mix(in srgb, var(--mat-sys-primary) 16%, transparent);
|
||||
border-radius: 8px;
|
||||
background:
|
||||
linear-gradient(145deg, color-mix(in srgb, var(--mat-sys-primary) 18%, transparent), transparent),
|
||||
linear-gradient(
|
||||
145deg,
|
||||
color-mix(in srgb, var(--mat-sys-primary) 18%, transparent),
|
||||
transparent
|
||||
),
|
||||
var(--mat-sys-surface-container-low);
|
||||
color: var(--mat-sys-primary);
|
||||
}
|
||||
@@ -101,6 +115,18 @@
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.sso-login-button {
|
||||
width: 100%;
|
||||
min-height: 48px;
|
||||
margin-top: 1rem;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.sso-login-button mat-progress-spinner {
|
||||
display: inline-flex;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.auth-card mat-card-actions {
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
|
||||
@@ -25,9 +25,7 @@ export const authInterceptor: HttpInterceptorFn = (
|
||||
}
|
||||
|
||||
return auth.refreshSession().pipe(
|
||||
switchMap((response) =>
|
||||
next(withAccessToken(request, response.accessToken)),
|
||||
),
|
||||
switchMap((response) => next(withAccessToken(request, response.accessToken))),
|
||||
catchError((refreshError: unknown) => {
|
||||
auth.logout();
|
||||
void router.navigateByUrl('/login');
|
||||
@@ -70,6 +68,7 @@ function isAuthRequest(request: HttpRequest<unknown>): boolean {
|
||||
return [
|
||||
'/api/auth/login',
|
||||
'/api/auth/register',
|
||||
'/api/auth/sso',
|
||||
'/api/auth/refresh',
|
||||
'/api/auth/resend-verification',
|
||||
'/api/auth/verify-email',
|
||||
|
||||
@@ -4,7 +4,6 @@ export interface PublicUser {
|
||||
id: string;
|
||||
email: string;
|
||||
name?: string;
|
||||
verified: boolean;
|
||||
onboardingCompleted: boolean;
|
||||
taskDigestPreference: TaskDigestPreference;
|
||||
}
|
||||
@@ -26,15 +25,6 @@ export interface RegisterResponse {
|
||||
user: PublicUser;
|
||||
}
|
||||
|
||||
export interface VerifyEmailResponse {
|
||||
message: string;
|
||||
user: PublicUser;
|
||||
}
|
||||
|
||||
export interface ResendVerificationResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface LoginRequest {
|
||||
email: string;
|
||||
password: string;
|
||||
|
||||
@@ -8,9 +8,7 @@ import {
|
||||
PublicUserSearchResult,
|
||||
RegisterRequest,
|
||||
RegisterResponse,
|
||||
ResendVerificationResponse,
|
||||
TaskDigestPreference,
|
||||
VerifyEmailResponse,
|
||||
} from './auth.models';
|
||||
|
||||
const ACCESS_TOKEN_KEY = 'listify.accessToken';
|
||||
@@ -33,21 +31,26 @@ export class AuthService {
|
||||
.pipe(tap((response) => this.storeSession(response)));
|
||||
}
|
||||
|
||||
startSsoLogin(): void {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.location.href = `${this.apiUrl}/sso/login`;
|
||||
}
|
||||
}
|
||||
|
||||
completeSsoLogin(response: AuthTokenResponse): void {
|
||||
this.storeSession(response);
|
||||
}
|
||||
|
||||
exchangeSsoCode(code: string, state: string): Observable<AuthTokenResponse> {
|
||||
return this.http
|
||||
.post<AuthTokenResponse>(`${this.apiUrl}/sso/exchange`, { code, state })
|
||||
.pipe(tap((response) => this.storeSession(response)));
|
||||
}
|
||||
|
||||
register(data: RegisterRequest): Observable<RegisterResponse> {
|
||||
return this.http.post<RegisterResponse>(`${this.apiUrl}/register`, data);
|
||||
}
|
||||
|
||||
verifyEmail(token: string): Observable<VerifyEmailResponse> {
|
||||
const params = new HttpParams().set('token', token);
|
||||
return this.http.get<VerifyEmailResponse>(`${this.apiUrl}/verify-email`, { params });
|
||||
}
|
||||
|
||||
resendVerificationEmail(email: string): Observable<ResendVerificationResponse> {
|
||||
return this.http.post<ResendVerificationResponse>(`${this.apiUrl}/resend-verification`, {
|
||||
email,
|
||||
});
|
||||
}
|
||||
|
||||
loadCurrentUser(): Observable<PublicUser> {
|
||||
return this.http.get<PublicUser>(`${this.apiUrl}/me`).pipe(tap((user) => this.storeUser(user)));
|
||||
}
|
||||
|
||||
@@ -5,81 +5,25 @@
|
||||
</div>
|
||||
<mat-card-header>
|
||||
<mat-card-title>Willkommen zurueck</mat-card-title>
|
||||
<mat-card-subtitle>Melden Sie sich mit Ihrem Listify-Konto an</mat-card-subtitle>
|
||||
<mat-card-subtitle>Melden Sie sich mit Ihrem SSO-Konto an</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
<form [formGroup]="form" (ngSubmit)="submit()" class="auth-form">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>E-Mail</mat-label>
|
||||
<input matInput type="email" formControlName="email" autocomplete="email" />
|
||||
<mat-icon matSuffix aria-hidden="true">mail</mat-icon>
|
||||
@if (form.controls.email.hasError('required')) {
|
||||
<mat-error>E-Mail ist erforderlich</mat-error>
|
||||
} @else if (form.controls.email.hasError('email')) {
|
||||
<mat-error>Bitte geben Sie eine gueltige E-Mail ein</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Passwort</mat-label>
|
||||
<input
|
||||
matInput
|
||||
[type]="hidePassword ? 'password' : 'text'"
|
||||
formControlName="password"
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
<button
|
||||
mat-icon-button
|
||||
matSuffix
|
||||
type="button"
|
||||
[attr.aria-label]="hidePassword ? 'Passwort anzeigen' : 'Passwort verbergen'"
|
||||
(click)="hidePassword = !hidePassword"
|
||||
>
|
||||
<mat-icon aria-hidden="true">{{ hidePassword ? 'visibility' : 'visibility_off' }}</mat-icon>
|
||||
</button>
|
||||
@if (form.controls.password.hasError('required')) {
|
||||
<mat-error>Passwort ist erforderlich</mat-error>
|
||||
} @else if (form.controls.password.hasError('minlength')) {
|
||||
<mat-error>Mindestens 8 Zeichen</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<button mat-flat-button color="primary" type="submit" [disabled]="loading">
|
||||
@if (loading) {
|
||||
<mat-progress-spinner mode="indeterminate" diameter="18" />
|
||||
} @else {
|
||||
<mat-icon aria-hidden="true">login</mat-icon>
|
||||
}
|
||||
Einloggen
|
||||
</button>
|
||||
|
||||
<div class="divider-container">
|
||||
<span class="divider-text">oder</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
mat-stroked-button
|
||||
type="button"
|
||||
[disabled]="resendingVerification || loading"
|
||||
(click)="resendVerificationEmail()"
|
||||
color="accent"
|
||||
>
|
||||
@if (resendingVerification) {
|
||||
<mat-progress-spinner mode="indeterminate" diameter="18" />
|
||||
} @else {
|
||||
<mat-icon aria-hidden="true">mark_email_unread</mat-icon>
|
||||
}
|
||||
Verifizierungsmail erneut senden
|
||||
</button>
|
||||
</form>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
type="button"
|
||||
class="sso-login-button"
|
||||
[disabled]="loading"
|
||||
(click)="loginWithSso()"
|
||||
>
|
||||
@if (loading) {
|
||||
<mat-progress-spinner mode="indeterminate" diameter="18" />
|
||||
} @else {
|
||||
<mat-icon aria-hidden="true">login</mat-icon>
|
||||
}
|
||||
Mit SSO anmelden
|
||||
</button>
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions>
|
||||
<span>Neu hier?</span>
|
||||
<a mat-flat-button routerLink="/register" color="primary" class="register-link">
|
||||
Konto erstellen
|
||||
</a>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</section>
|
||||
|
||||
@@ -1,45 +1,21 @@
|
||||
import { Component, inject, OnInit } from '@angular/core';
|
||||
import { NonNullableFormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { Router, RouterLink } from '@angular/router';
|
||||
import { finalize } from 'rxjs';
|
||||
import { Router } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { AuthService } from '../auth.service';
|
||||
import { getAuthErrorMessage } from '../error-message';
|
||||
import { OnboardingService } from '../../onboarding/onboarding.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
RouterLink,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatFormFieldModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatSnackBarModule,
|
||||
],
|
||||
imports: [MatButtonModule, MatCardModule, MatIconModule, MatProgressSpinnerModule],
|
||||
templateUrl: './login.component.html',
|
||||
styleUrl: '../auth-page.scss',
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
private readonly auth = inject(AuthService);
|
||||
private readonly formBuilder = inject(NonNullableFormBuilder);
|
||||
private readonly router = inject(Router);
|
||||
private readonly snackBar = inject(MatSnackBar);
|
||||
private readonly onboarding = inject(OnboardingService);
|
||||
|
||||
protected readonly form = this.formBuilder.group({
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
password: ['', [Validators.required, Validators.minLength(8)]],
|
||||
});
|
||||
protected loading = false;
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -47,51 +23,9 @@ export class LoginComponent implements OnInit {
|
||||
void this.router.navigateByUrl('/lists');
|
||||
}
|
||||
}
|
||||
protected resendingVerification = false;
|
||||
protected hidePassword = true;
|
||||
|
||||
submit(): void {
|
||||
if (this.form.invalid) {
|
||||
this.form.markAllAsTouched();
|
||||
return;
|
||||
}
|
||||
|
||||
loginWithSso(): void {
|
||||
this.loading = true;
|
||||
this.auth
|
||||
.login(this.form.getRawValue())
|
||||
.pipe(finalize(() => (this.loading = false)))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.snackBar.open('Login erfolgreich.', 'OK', { duration: 3000 });
|
||||
if (!this.onboarding.startForCurrentUser()) {
|
||||
void this.router.navigateByUrl('/account');
|
||||
}
|
||||
},
|
||||
error: (error: unknown) => {
|
||||
this.snackBar.open(getAuthErrorMessage(error), 'OK', { duration: 5000 });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
resendVerificationEmail(): void {
|
||||
const emailControl = this.form.controls.email;
|
||||
|
||||
if (emailControl.invalid) {
|
||||
emailControl.markAsTouched();
|
||||
return;
|
||||
}
|
||||
|
||||
this.resendingVerification = true;
|
||||
this.auth
|
||||
.resendVerificationEmail(emailControl.value)
|
||||
.pipe(finalize(() => (this.resendingVerification = false)))
|
||||
.subscribe({
|
||||
next: (response) => {
|
||||
this.snackBar.open(response.message, 'OK', { duration: 6000 });
|
||||
},
|
||||
error: (error: unknown) => {
|
||||
this.snackBar.open(getAuthErrorMessage(error), 'OK', { duration: 5000 });
|
||||
},
|
||||
});
|
||||
this.auth.startSsoLogin();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<section class="auth-page">
|
||||
<mat-card class="auth-card">
|
||||
<div class="auth-logo">
|
||||
<mat-icon>{{ failed ? 'error' : 'login' }}</mat-icon>
|
||||
</div>
|
||||
<mat-card-header>
|
||||
<mat-card-title>
|
||||
{{ failed ? 'Anmeldung fehlgeschlagen' : 'Anmeldung wird abgeschlossen' }}
|
||||
</mat-card-title>
|
||||
</mat-card-header>
|
||||
|
||||
@if (!failed) {
|
||||
<mat-card-content>
|
||||
<mat-progress-spinner mode="indeterminate" diameter="32" />
|
||||
</mat-card-content>
|
||||
} @else if (errorMessage) {
|
||||
<mat-card-content>
|
||||
<p>{{ errorMessage }}</p>
|
||||
</mat-card-content>
|
||||
}
|
||||
</mat-card>
|
||||
</section>
|
||||
@@ -0,0 +1,115 @@
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { Router } from '@angular/router';
|
||||
import { finalize } from 'rxjs';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { AuthTokenResponse } from '../auth.models';
|
||||
import { AuthService } from '../auth.service';
|
||||
import { OnboardingService } from '../../onboarding/onboarding.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-sso-callback',
|
||||
imports: [MatCardModule, MatIconModule, MatProgressSpinnerModule],
|
||||
templateUrl: './sso-callback.component.html',
|
||||
styleUrl: '../auth-page.scss',
|
||||
})
|
||||
export class SsoCallbackComponent implements OnInit {
|
||||
private readonly auth = inject(AuthService);
|
||||
private readonly onboarding = inject(OnboardingService);
|
||||
private readonly router = inject(Router);
|
||||
|
||||
protected failed = false;
|
||||
protected loading = false;
|
||||
protected errorMessage = '';
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.exchangeAuthorizationCode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = this.readAuthResponse();
|
||||
|
||||
if (!response) {
|
||||
this.failed = true;
|
||||
this.errorMessage = 'Die SSO-Antwort war unvollstaendig.';
|
||||
window.setTimeout(() => void this.router.navigateByUrl('/login'), 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
this.auth.completeSsoLogin(response);
|
||||
|
||||
if (!this.onboarding.startForCurrentUser()) {
|
||||
void this.router.navigateByUrl('/account');
|
||||
}
|
||||
}
|
||||
|
||||
private exchangeAuthorizationCode(): boolean {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const code = params.get('code');
|
||||
const state = params.get('state');
|
||||
|
||||
if (!code || !state) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
this.auth
|
||||
.exchangeSsoCode(code, state)
|
||||
.pipe(finalize(() => (this.loading = false)))
|
||||
.subscribe({
|
||||
next: () => this.completeLoginNavigation(),
|
||||
error: (error: unknown) => {
|
||||
this.failed = true;
|
||||
this.errorMessage = this.toErrorMessage(error);
|
||||
window.setTimeout(() => void this.router.navigateByUrl('/login'), 3000);
|
||||
},
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private completeLoginNavigation(): void {
|
||||
window.history.replaceState(null, '', '/auth/sso/callback');
|
||||
|
||||
if (!this.onboarding.startForCurrentUser()) {
|
||||
void this.router.navigateByUrl('/account');
|
||||
}
|
||||
}
|
||||
|
||||
private readAuthResponse(): AuthTokenResponse | null {
|
||||
const params = new URLSearchParams(window.location.hash.replace(/^#/, ''));
|
||||
const accessToken = params.get('accessToken');
|
||||
const refreshToken = params.get('refreshToken');
|
||||
const userJson = params.get('user');
|
||||
|
||||
if (!accessToken || !refreshToken || !userJson) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return {
|
||||
accessToken,
|
||||
refreshToken,
|
||||
user: JSON.parse(userJson) as AuthTokenResponse['user'],
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private toErrorMessage(error: unknown): string {
|
||||
if (error instanceof HttpErrorResponse) {
|
||||
const responseError = error.error as { message?: unknown } | null;
|
||||
|
||||
if (typeof responseError?.message === 'string') {
|
||||
return responseError.message;
|
||||
}
|
||||
|
||||
return error.message;
|
||||
}
|
||||
|
||||
return 'Die SSO-Anmeldung konnte nicht abgeschlossen werden.';
|
||||
}
|
||||
}
|
||||
@@ -9,29 +9,17 @@
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
<div class="verification-state" [class.success]="state() === 'success'" [class.error]="state() === 'error' || state() === 'missing-token'">
|
||||
@if (state() === 'loading') {
|
||||
<mat-progress-spinner mode="indeterminate" diameter="44" />
|
||||
} @else if (state() === 'success') {
|
||||
<mat-icon class="state-icon" aria-hidden="true">mark_email_read</mat-icon>
|
||||
} @else {
|
||||
<mat-icon class="state-icon" aria-hidden="true">error</mat-icon>
|
||||
}
|
||||
|
||||
<div class="verification-state success">
|
||||
<mat-icon class="state-icon" aria-hidden="true">mark_email_read</mat-icon>
|
||||
<p>{{ message() }}</p>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions align="end">
|
||||
@if (state() === 'success') {
|
||||
<a mat-flat-button routerLink="/login">
|
||||
<mat-icon aria-hidden="true">login</mat-icon>
|
||||
Zum Login
|
||||
</a>
|
||||
} @else if (state() !== 'loading') {
|
||||
<a mat-button routerLink="/register">Neu registrieren</a>
|
||||
<a mat-flat-button routerLink="/login">Zum Login</a>
|
||||
}
|
||||
<a mat-flat-button routerLink="/login">
|
||||
<mat-icon aria-hidden="true">login</mat-icon>
|
||||
Zum Login
|
||||
</a>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</section>
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { Component, OnInit, inject, signal } from '@angular/core';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
import { Router, RouterLink } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { AuthService } from '../auth.service';
|
||||
import { getAuthErrorMessage } from '../error-message';
|
||||
|
||||
type VerificationState = 'loading' | 'success' | 'error' | 'missing-token';
|
||||
|
||||
@Component({
|
||||
selector: 'app-verify-email',
|
||||
@@ -16,18 +12,15 @@ type VerificationState = 'loading' | 'success' | 'error' | 'missing-token';
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatIconModule,
|
||||
MatProgressSpinnerModule,
|
||||
],
|
||||
templateUrl: './verify-email.component.html',
|
||||
styleUrl: '../auth-page.scss',
|
||||
})
|
||||
export class VerifyEmailComponent implements OnInit {
|
||||
private readonly auth = inject(AuthService);
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
private readonly router = inject(Router);
|
||||
|
||||
protected readonly state = signal<VerificationState>('loading');
|
||||
protected readonly message = signal('E-Mail wird bestätigt.');
|
||||
protected readonly message = signal('E-Mail-Verifikation ist nicht mehr erforderlich.');
|
||||
protected readonly email = signal<string | null>(null);
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -35,25 +28,5 @@ export class VerifyEmailComponent implements OnInit {
|
||||
void this.router.navigateByUrl('/lists');
|
||||
return;
|
||||
}
|
||||
|
||||
const token = this.route.snapshot.queryParamMap.get('token');
|
||||
|
||||
if (!token) {
|
||||
this.state.set('missing-token');
|
||||
this.message.set('Der Verifikationslink enthält keinen Token.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.auth.verifyEmail(token).subscribe({
|
||||
next: (response) => {
|
||||
this.email.set(response.user.email);
|
||||
this.message.set(response.message);
|
||||
this.state.set('success');
|
||||
},
|
||||
error: (error: unknown) => {
|
||||
this.message.set(getAuthErrorMessage(error));
|
||||
this.state.set('error');
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user