Refactor permission handling and authentication services

Replaced `PermissionClaim` enum with an OpenAPI-defined schema `PermissionClaimDto` for consistency across frontend and backend. Refactored authentication flow to utilize `AuthService` on the frontend, consolidating JWT handling logic. Removed redundant methods like `saveJwt` and integrated robust permission management throughout the application.
This commit is contained in:
2025-06-21 23:15:46 +02:00
parent 07646e8c42
commit 32a454c034
8 changed files with 144 additions and 60 deletions
+5 -19
View File
@@ -1,12 +1,12 @@
import {Component, Input, OnInit} from '@angular/core';
import {HeaderComponent} from '../header/header.component';
import {LoginService} from '../../api';
import {MatDialog} from '@angular/material/dialog';
import {ActivatedRoute} from '@angular/router';
import {LoginDialogComponent} from '../login/login.component';
import {KeyValuePipe, NgForOf, NgIf} from '@angular/common';
import {FormType} from './form_type';
import {MatButton} from '@angular/material/button';
import {AuthService} from '../services/auth.service';
@Component({
selector: 'app-forms',
@@ -26,7 +26,7 @@ export class FormsComponent implements OnInit {
public type: FormType | undefined;
constructor(private loginService: LoginService,
constructor(private authService: AuthService,
private dialog: MatDialog,
private route: ActivatedRoute,
) {
@@ -34,16 +34,14 @@ export class FormsComponent implements OnInit {
const code = params.get('code');
if (code) {
this.loginService.login(code).subscribe(jwt => this.saveJwt(jwt as JsonWebKey)); //TODO handle error
} else {
this.authService.login(code).subscribe();
} else if (!this.authService.checkAuthStatus()) {
const dialogRef = this.dialog.open(LoginDialogComponent, {
width: '400px',
disableClose: true
});
dialogRef.afterClosed().subscribe(jwt => {
this.saveJwt(jwt as JsonWebKey)
});
dialogRef.afterClosed().subscribe();
}
});
}
@@ -61,18 +59,6 @@ export class FormsComponent implements OnInit {
});
}
private saveJwt(jwt: JsonWebKey) {
const claims = this.extractJwtClaims(jwt);
const authorizations = claims?.authorizations || [];
}
private extractJwtClaims(jwt: JsonWebKey): any {
const token = jwt.toString();
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
return JSON.parse(window.atob(base64));
}
protected readonly FormType = FormType;
protected readonly Object = Object;
+4 -15
View File
@@ -5,9 +5,8 @@ import {MatButtonModule} from '@angular/material/button';
import {MatInputModule} from '@angular/material/input';
import {MatFormFieldModule} from '@angular/material/form-field';
import {NgIf} from '@angular/common';
import {LoginService} from '../../api';
import {MatSnackBar} from '@angular/material/snack-bar';
import {CookieService} from 'ngx-cookie-service';
import {AuthService} from '../services/auth.service';
@Component({
selector: 'app-login',
@@ -31,9 +30,8 @@ export class LoginDialogComponent {
constructor(
public dialogRef: MatDialogRef<LoginDialogComponent>,
private fb: FormBuilder,
private loginService: LoginService,
private snackBar: MatSnackBar,
private cookieService: CookieService
private authService: AuthService,
private snackBar: MatSnackBar
) {
this.loginForm = this.fb.group({
code: ['', [
@@ -55,9 +53,8 @@ export class LoginDialogComponent {
return;
}
this.snackBar.open('Logging in...', '', {duration: 2000});
this.loginService.login(this.loginForm.value.code).subscribe({
this.authService.login(this.loginForm.value.code).subscribe({
next: (jwt) => {
this.saveJwt(jwt as JsonWebKey);
this.dialogRef.close(jwt);
},
error: () => {
@@ -68,14 +65,6 @@ export class LoginDialogComponent {
});
}
private saveJwt(jwt: JsonWebKey) {
this.cookieService.set('jwt', jwt.toString(), {
path: '/',
secure: true,
sameSite: 'Strict'
});
}
public formHasError() {
return this.loginForm.get('code')?.hasError('required');
}
+115
View File
@@ -0,0 +1,115 @@
import {Injectable} from '@angular/core';
import {LoginService} from '../../api';
import {CookieService} from 'ngx-cookie-service';
import {BehaviorSubject, Observable, throwError} from 'rxjs';
import {catchError, tap} from 'rxjs/operators';
import {MatSnackBar} from '@angular/material/snack-bar';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private isAuthenticatedSubject = new BehaviorSubject<boolean>(false);
public isAuthenticated$ = this.isAuthenticatedSubject.asObservable();
private userClaimsSubject = new BehaviorSubject<any>(null);
public userClaims$ = this.userClaimsSubject.asObservable();
constructor(
private loginService: LoginService,
private cookieService: CookieService,
private snackBar: MatSnackBar
) {
// Check if user is already logged in on service initialization
this.checkAuthStatus();
}
/**
* Attempt to login with the provided code
*/
public login(code: string): Observable<any> {
return this.loginService.login(code).pipe(
tap(jwt => {
this.saveJwt(jwt as JsonWebKey);
this.isAuthenticatedSubject.next(true);
}),
catchError(error => {
this.snackBar.open('Login failed', '', {duration: 2000});
return throwError(() => error);
})
);
}
/**
* Log the user out by removing the JWT
*/
public logout(): void {
this.cookieService.delete('jwt', '/');
this.isAuthenticatedSubject.next(false);
this.userClaimsSubject.next(null);
}
/**
* Check if the user is authenticated
*/
public checkAuthStatus(): boolean {
const jwt = this.getJwt();
if (jwt) {
try {
const claims = this.extractJwtClaims(jwt as JsonWebKey);
// Check if token is expired
const currentTime = Math.floor(Date.now() / 1000);
if (claims.exp && claims.exp < currentTime) {
this.logout();
return false;
}
this.userClaimsSubject.next(claims);
this.isAuthenticatedSubject.next(true);
return true;
} catch (e) {
this.logout();
}
}
return false;
}
/**
* Get the JWT from cookies
*/
public getJwt(): string | null {
return this.cookieService.check('jwt') ? this.cookieService.get('jwt') : null;
}
/**
* Save the JWT to cookies
*/
private saveJwt(jwt: JsonWebKey): void {
this.cookieService.set('jwt', jwt.toString(), {
path: '/',
secure: true,
sameSite: 'Strict'
});
const claims = this.extractJwtClaims(jwt);
this.userClaimsSubject.next(claims);
}
/**
* Extract claims from JWT
*/
private extractJwtClaims(jwt: JsonWebKey): any {
const token = jwt.toString();
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
return JSON.parse(window.atob(base64));
}
/**
* Get user authorizations from claims
*/
public getUserAuthorizations(): string[] {
const claims = this.userClaimsSubject.getValue();
return claims?.authorizations || [];
}
}