Files
Taekwondo-Admin/packages/frontend/src/app/services/authentication.ts
T
2026-03-08 11:09:52 +01:00

129 lines
4.5 KiB
TypeScript

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>(
AuthenticationState.Unknown,
);
private readonly _userInfo = signal<KeycloakProfile | null>(null);
private readonly _claims = signal<Record<Claim, boolean> | 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<ReadyArgs>(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<Record<Claim, boolean>> = {};
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<Claim, boolean>);
});
}
public async login(location?: string): Promise<void> {
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<void> {
return await this.keycloak.logout({
redirectUri: window.location.origin + '/login',
});
}
}