import { computed, effect, inject, Injectable, signal } from '@angular/core'; import { KEYCLOAK_EVENT_SIGNAL, KeycloakEventType, ReadyArgs, typeEventArgs, } from 'keycloak-angular'; import Keycloak, { KeycloakProfile } from 'keycloak-js'; import { defaults as apiDefaults, Claim } from '../generated-api/api'; export enum AuthenticationState { Authenticated, Unauthenticated, Unknown, } @Injectable({ providedIn: 'root', }) export class Authentication { private readonly keycloak = inject(Keycloak); private readonly keycloakSignal = inject(KEYCLOAK_EVENT_SIGNAL); private readonly _authenticationState = signal( AuthenticationState.Unknown, ); private readonly _userInfo = signal(null); private readonly _claims = signal | null>(null); public readonly authenticationState = this._authenticationState.asReadonly(); public readonly loggedIn = computed(() => { return this.authenticationState() === AuthenticationState.Authenticated; }); public readonly userInfo = this._userInfo.asReadonly(); public readonly claims = this._claims.asReadonly(); public constructor() { effect(() => { const event = this.keycloakSignal(); apiDefaults.headers['Authorization'] = this.keycloak.token ? `Bearer ${this.keycloak.token}` : undefined; switch (event?.type) { case KeycloakEventType.Ready: this._authenticationState.set( typeEventArgs(event.args) ? AuthenticationState.Authenticated : AuthenticationState.Unauthenticated, ); break; case KeycloakEventType.AuthSuccess: this._authenticationState.set( AuthenticationState.Authenticated, ); break; case KeycloakEventType.AuthLogout: this._authenticationState.set( AuthenticationState.Unauthenticated, ); break; case KeycloakEventType.AuthError: this._authenticationState.set( AuthenticationState.Unauthenticated, ); break; case KeycloakEventType.AuthRefreshError: this._authenticationState.set( AuthenticationState.Unauthenticated, ); break; case KeycloakEventType.AuthRefreshSuccess: this._authenticationState.set( AuthenticationState.Authenticated, ); break; case KeycloakEventType.TokenExpired: this._authenticationState.set( AuthenticationState.Unauthenticated, ); break; default: break; } }); effect(async () => { const authenticationState = this.authenticationState(); if (authenticationState !== AuthenticationState.Authenticated) { this._userInfo.set(null); } const profile = await this.keycloak.loadUserProfile(); const claims: Partial> = {}; for (const claim of Object.values(Claim)) { if (this.keycloak.hasRealmRole(claim)) { claims[claim] = true; } else { claims[claim] = false; } } this._userInfo.set(profile); this._claims.set(claims as Record); }); } public async login(location?: string): Promise { if (this._authenticationState() === AuthenticationState.Authenticated) { return; } var redirectUri = location ? `${window.location.origin}/${location}` : `${window.location.origin}/login`; return await this.keycloak.login({ redirectUri: redirectUri, locale: 'de-DE', //TODO loginHint:'' }); } public async logout(): Promise { return await this.keycloak.logout({ redirectUri: window.location.origin + '/login', }); } }