129 lines
4.5 KiB
TypeScript
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',
|
|
});
|
|
}
|
|
}
|