2019-09-23 10:17:03 +02:00
|
|
|
|
2023-10-13 17:11:46 +02:00
|
|
|
import { HttpErrorResponse } from '@angular/common/http';
|
2017-12-14 11:41:26 +01:00
|
|
|
import { Injectable } from '@angular/core';
|
|
|
|
import { Router } from '@angular/router';
|
2023-10-11 16:53:12 +02:00
|
|
|
import { AppAccount } from '@app/core/model/auth/principal';
|
2019-12-11 15:51:03 +01:00
|
|
|
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
|
|
|
|
import { BaseService } from '@common/base/base.service';
|
2018-11-27 15:13:56 +01:00
|
|
|
import { TranslateService } from '@ngx-translate/core';
|
2023-10-13 17:11:46 +02:00
|
|
|
import { Observable, Subject, forkJoin, from, of } from 'rxjs';
|
|
|
|
import { exhaustMap, map, takeUntil } from 'rxjs/operators';
|
2020-03-26 17:44:12 +01:00
|
|
|
import { ConfigurationService } from '../configuration/configuration.service';
|
2023-10-11 16:53:12 +02:00
|
|
|
import { Guid } from '@common/types/guid';
|
|
|
|
import { KeycloakEventType, KeycloakService } from 'keycloak-angular';
|
|
|
|
import { NgZone } from '@angular/core';
|
|
|
|
import { PrincipalService } from '../http/principal.service';
|
|
|
|
import { AppRole } from '@app/core/common/enum/app-role';
|
|
|
|
|
|
|
|
|
|
|
|
export interface AuthenticationState {
|
|
|
|
loginStatus: LoginStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
export enum LoginStatus {
|
|
|
|
LoggedIn = 0,
|
|
|
|
LoggingOut = 1,
|
|
|
|
LoggedOut = 2
|
|
|
|
}
|
2017-12-14 11:41:26 +01:00
|
|
|
|
|
|
|
@Injectable()
|
2018-11-27 18:33:17 +01:00
|
|
|
export class AuthService extends BaseService {
|
2018-10-05 17:00:54 +02:00
|
|
|
|
2023-10-11 16:53:12 +02:00
|
|
|
public authenticationStateSubject: Subject<AuthenticationState>;
|
|
|
|
private accessToken: string;
|
|
|
|
private appAccount: AppAccount;
|
|
|
|
|
|
|
|
private _authState: boolean;
|
2018-10-05 17:00:54 +02:00
|
|
|
constructor(
|
2023-10-11 16:53:12 +02:00
|
|
|
private installationConfiguration: ConfigurationService,
|
2019-02-15 10:18:14 +01:00
|
|
|
private language: TranslateService,
|
|
|
|
private router: Router,
|
2023-10-11 16:53:12 +02:00
|
|
|
private zone: NgZone,
|
|
|
|
private keycloakService: KeycloakService,
|
2020-03-26 17:44:12 +01:00
|
|
|
private uiNotificationService: UiNotificationService,
|
2023-10-11 16:53:12 +02:00
|
|
|
private principalService: PrincipalService
|
2018-10-05 17:00:54 +02:00
|
|
|
) {
|
2018-11-27 18:33:17 +01:00
|
|
|
super();
|
2020-03-26 17:44:12 +01:00
|
|
|
|
2018-10-05 17:00:54 +02:00
|
|
|
|
2023-10-11 16:53:12 +02:00
|
|
|
this.authenticationStateSubject = new Subject<AuthenticationState>();
|
|
|
|
|
|
|
|
window.addEventListener('storage', (event: StorageEvent) => {
|
|
|
|
// Logout if we receive event that logout action occurred in a different tab.
|
|
|
|
if (
|
|
|
|
event.key &&
|
|
|
|
event.key === 'authState' &&
|
|
|
|
event.newValue === 'false' &&
|
|
|
|
this._authState
|
|
|
|
) {
|
|
|
|
this.clear();
|
|
|
|
this.router.navigate(['/unauthorized'], {
|
|
|
|
queryParams: { returnUrl: this.router.url },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public getAuthenticationStateObservable(): Observable<AuthenticationState> {
|
|
|
|
return this.authenticationStateSubject.asObservable();
|
|
|
|
}
|
|
|
|
|
|
|
|
public beginLogOutProcess(): void {
|
|
|
|
this.authenticationStateSubject.next({
|
|
|
|
loginStatus: LoginStatus.LoggingOut,
|
|
|
|
});
|
2018-10-05 17:00:54 +02:00
|
|
|
}
|
|
|
|
|
2023-08-01 10:23:06 +02:00
|
|
|
public clear(): void {
|
2023-10-11 16:53:12 +02:00
|
|
|
this.authState(false);
|
|
|
|
this.accessToken = undefined;
|
|
|
|
this.appAccount = undefined;
|
2018-10-05 17:00:54 +02:00
|
|
|
}
|
|
|
|
|
2023-10-11 16:53:12 +02:00
|
|
|
private authState(authState?: boolean): boolean {
|
|
|
|
if (authState !== undefined) {
|
|
|
|
this._authState = authState;
|
|
|
|
localStorage.setItem('authState', authState ? 'true' : 'false');
|
|
|
|
if (authState) {
|
|
|
|
this.authenticationStateSubject.next({
|
|
|
|
loginStatus: LoginStatus.LoggedIn,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
this.authenticationStateSubject.next({
|
|
|
|
loginStatus: LoginStatus.LoggedOut,
|
|
|
|
});
|
|
|
|
}
|
2018-10-05 17:00:54 +02:00
|
|
|
}
|
2023-10-11 16:53:12 +02:00
|
|
|
if (this._authState === undefined) {
|
|
|
|
this._authState =
|
|
|
|
localStorage.getItem('authState') === 'true' ? true : false;
|
2020-09-07 17:14:54 +02:00
|
|
|
}
|
2023-10-11 16:53:12 +02:00
|
|
|
return this._authState;
|
2018-10-05 17:00:54 +02:00
|
|
|
}
|
|
|
|
|
2023-10-11 16:53:12 +02:00
|
|
|
public currentAccountIsAuthenticated(): boolean {
|
|
|
|
return this.appAccount && this.appAccount.isAuthenticated;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Should this be name @isAuthenticated@ instead?
|
|
|
|
public hasAccessToken(): boolean {
|
|
|
|
return Boolean(this.currentAuthenticationToken());
|
2018-10-05 17:00:54 +02:00
|
|
|
}
|
|
|
|
|
2023-10-11 16:53:12 +02:00
|
|
|
public currentAuthenticationToken(accessToken?: string): string {
|
|
|
|
if (accessToken) {
|
|
|
|
this.accessToken = accessToken;
|
|
|
|
}
|
|
|
|
return this.accessToken;
|
2018-10-05 17:00:54 +02:00
|
|
|
}
|
|
|
|
|
2023-10-11 16:53:12 +02:00
|
|
|
public userId(): Guid {
|
|
|
|
if (
|
|
|
|
this.appAccount &&
|
|
|
|
this.appAccount.principal &&
|
|
|
|
this.appAccount.principal.subject
|
|
|
|
) {
|
|
|
|
return this.appAccount.principal.subject;
|
|
|
|
}
|
|
|
|
return null;
|
2018-10-05 17:00:54 +02:00
|
|
|
}
|
2019-11-13 16:32:55 +01:00
|
|
|
|
2023-10-11 16:53:12 +02:00
|
|
|
public isLoggedIn(): boolean {
|
|
|
|
return this.authState();
|
|
|
|
}
|
|
|
|
public prepareAuthRequest(observable: Observable<string>, httpParams?: Object): Observable<boolean> {
|
|
|
|
return observable.pipe(
|
|
|
|
map((x) => this.currentAuthenticationToken(x)),
|
|
|
|
exhaustMap(() => forkJoin([
|
2023-10-13 17:11:46 +02:00
|
|
|
this.accessToken ? this.principalService.me(httpParams) : of(null)
|
2023-10-11 16:53:12 +02:00
|
|
|
])),
|
|
|
|
map((item) => {
|
|
|
|
this.currentAccount(item[0]);
|
|
|
|
return true;
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
public refresh(): Observable<boolean> {
|
|
|
|
return this.principalService.me().pipe(
|
|
|
|
map((item) => {
|
|
|
|
this.currentAccount(item);
|
|
|
|
return true;
|
2019-11-13 16:32:55 +01:00
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
2022-05-25 09:30:59 +02:00
|
|
|
|
2023-10-11 16:53:12 +02:00
|
|
|
private currentAccount(
|
|
|
|
appAccount: AppAccount
|
|
|
|
): void {
|
|
|
|
this.appAccount = appAccount;
|
|
|
|
this.authState(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
public getPrincipalName(): string {
|
|
|
|
if (this.appAccount && this.appAccount.principal) {
|
|
|
|
return this.appAccount.principal.name;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public getRoles(): AppRole[] {
|
|
|
|
if (this.appAccount && this.appAccount.roles) {
|
|
|
|
return this.appAccount.roles;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
public getUserProfileEmail(): string {
|
|
|
|
if (this.appAccount && this.appAccount.profile) {
|
|
|
|
return this.appAccount.profile.email;
|
|
|
|
}
|
|
|
|
return null;
|
2022-05-25 09:30:59 +02:00
|
|
|
}
|
2023-10-11 16:53:12 +02:00
|
|
|
|
|
|
|
public getUserProfileLanguage(): string {
|
|
|
|
if (this.appAccount && this.appAccount.profile) {
|
|
|
|
return this.appAccount.profile.language;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public hasAnyRole(roles: AppRole[]): boolean {
|
|
|
|
if (!roles) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return roles.filter((r) => this.hasRole(r)).length > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
public hasRole(role: AppRole): boolean {
|
|
|
|
if (role === undefined) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
!this.appAccount ||
|
|
|
|
!this.appAccount.roles ||
|
|
|
|
this.appAccount.roles.length === 0
|
|
|
|
) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return this.appAccount.roles
|
|
|
|
.includes(role);
|
|
|
|
}
|
|
|
|
|
|
|
|
public getUserProfileCulture(): string {
|
|
|
|
if (this.appAccount && this.appAccount.profile) {
|
|
|
|
return this.appAccount.profile.culture;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public getUserProfileAvatarUrl(): string {
|
|
|
|
if (this.appAccount && this.appAccount.profile) {
|
|
|
|
return this.appAccount.profile.avatarUrl;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public getUserProfileTimezone(): string {
|
|
|
|
if (this.appAccount && this.appAccount.profile) {
|
|
|
|
return this.appAccount.profile.timezone;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public authenticate(returnUrl: string) {
|
|
|
|
this.keycloakService.isLoggedIn().then((isLoggedIn) => {
|
|
|
|
if (!isLoggedIn) {
|
|
|
|
this.keycloakService.login({
|
|
|
|
scope: this.installationConfiguration.keycloak.scope,
|
|
|
|
})
|
|
|
|
.then(() => {
|
|
|
|
console.log('Keycloak Login');
|
|
|
|
this.keycloakService.keycloakEvents$.subscribe({
|
|
|
|
next: (e) => {
|
|
|
|
if (
|
|
|
|
e.type === KeycloakEventType.OnTokenExpired
|
|
|
|
) {
|
|
|
|
this.refreshToken({});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
this.onAuthenticateSuccess(returnUrl);
|
|
|
|
})
|
|
|
|
.catch((error) => this.onAuthenticateError(error));
|
|
|
|
} else {
|
|
|
|
this.zone.run(() => this.router.navigate([returnUrl]));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public refreshToken(httpParams?: Object): Promise<boolean> {
|
|
|
|
return this.keycloakService.updateToken(60).then((isRefreshed) => {
|
|
|
|
if (!isRefreshed) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.prepareAuthRequest(
|
|
|
|
from(this.keycloakService.getToken()),
|
|
|
|
httpParams
|
|
|
|
)
|
|
|
|
.pipe(takeUntil(this._destroyed))
|
|
|
|
.pipe(
|
|
|
|
map(
|
|
|
|
() => {
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
(error) => {
|
|
|
|
this.onAuthenticateError(error);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.toPromise();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
onAuthenticateError(errorResponse: HttpErrorResponse) {
|
|
|
|
this.zone.run(() => {
|
|
|
|
// const error: HttpError =
|
|
|
|
// this.httpErrorHandlingService.getError(errorResponse);
|
|
|
|
this.uiNotificationService.snackBarNotification(
|
|
|
|
// error.getMessagesString(),
|
|
|
|
errorResponse.message,
|
|
|
|
SnackBarNotificationLevel.Warning
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
onAuthenticateSuccess(returnUrl: string): void {
|
|
|
|
this.authState(true);
|
|
|
|
this.uiNotificationService.snackBarNotification(
|
|
|
|
this.language.instant('COMMONS.SNACK-BAR.SUCCESSFUL-LOGIN'),
|
|
|
|
SnackBarNotificationLevel.Success
|
|
|
|
);
|
|
|
|
this.zone.run(() => this.router.navigate([returnUrl]));
|
|
|
|
}
|
|
|
|
|
2017-12-14 11:41:26 +01:00
|
|
|
}
|