import {inject, Injectable, signal} from '@angular/core'; import {LoginService} from '@api'; import {Observable, throwError} from 'rxjs'; import {catchError, tap} from 'rxjs/operators'; import {MatSnackBar} from '@angular/material/snack-bar'; import {JwtHelperService} from '@auth0/angular-jwt'; import {JwtClaims} from '@custom-types/jwt_interface' import {LoginDialogComponent} from '@shared-components/login/login.component'; import {MatDialog} from '@angular/material/dialog'; @Injectable({ providedIn: 'root' }) export class AuthService { private dialog: MatDialog = inject(MatDialog) private isAuthenticatedSubject = signal(false); public readonly isAuthenticated$ = this.isAuthenticatedSubject.asReadonly(); private userClaimsSubject = signal(null); private jwtHelper = new JwtHelperService(); private _username = signal(null); public readonly username = this._username.asReadonly(); constructor( private loginService: LoginService, 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 { return this.loginService.login(code).pipe( tap(jwt => { this.saveJwt(jwt); this.isAuthenticatedSubject.set(true); this.reloadUsername(); }), catchError(error => { this.snackBar.open('Login failed', '', {duration: 2000}); return throwError(() => error); }) ); } private reloadUsername() { this.loginService.getUsername().subscribe({ next: (username) => { this._username.set(username.username); }, error: (error) => { return throwError(() => error); } }); } /** * Log the user out by removing the JWT */ public logout(): void { localStorage.removeItem('jwt'); this.isAuthenticatedSubject.set(false); this.userClaimsSubject.set(null); this._username.set(null); } /** * Check if the user is authenticated */ public checkAuthStatus(): boolean { const jwt = this.getJwt(); if (!jwt) { console.log("No JWT found"); const dialogRef = this.dialog.open(LoginDialogComponent, { width: '400px', }) dialogRef.afterClosed().subscribe(result => { console.log(result); }); return false; } try { if (this.jwtHelper.isTokenExpired(jwt)) { this.logout(); console.log("Token expired, logging out"); return false; } const claims = this.extractJwtClaims(jwt); console.log("User claims: ", claims); this.userClaimsSubject.set(claims); this.isAuthenticatedSubject.set(true); if (this.username() == null) { this.reloadUsername(); } return true; } catch (e) { this.logout(); } return false; } /** * Get the JWT from localStorage */ public getJwt(): string | null { return localStorage.getItem('jwt'); } /** * Save the JWT to localStorage */ private saveJwt(jwt: string): void { localStorage.setItem('jwt', jwt); const claims = this.extractJwtClaims(jwt); console.log("Saving user claims: ", claims); this.userClaimsSubject.set(claims); } /** * Extract claims from JWT */ private extractJwtClaims(jwt: string): JwtClaims { return this.jwtHelper.decodeToken(jwt); } /** * Get user authorizations from claims */ public getUserAuthorizations(): string[] { const claims = this.userClaimsSubject(); return claims?.authorities || []; } public hasAccess(requiredAuthorizations: string[]): boolean { const userAuthorizations = this.getUserAuthorizations(); return requiredAuthorizations.some(auth => userAuthorizations.includes(auth)); } public getUuid(): string | null { const jwtClaims = this.userClaimsSubject(); if (jwtClaims === null) { return null; } return jwtClaims.sub ?? null; } }