argos/dmp-frontend/src/app/core/services/auth/auth.service.ts

344 lines
8.9 KiB
TypeScript
Raw Normal View History

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';
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';
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';
import { AppPermission } from '@app/core/common/enum/permission.enum';
2023-10-11 16:53:12 +02:00
export interface ResolutionContext {
roles: AppRole[];
permissions: AppPermission[];
}
2023-10-11 16:53:12 +02:00
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
public permissionEnum = AppPermission;
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,
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();
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
}
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;
}
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 &&
2023-10-18 17:13:32 +02:00
this.appAccount.principal.userId
2023-10-11 16:53:12 +02:00
) {
2023-10-18 17:13:32 +02:00
return this.appAccount.principal.userId;
2023-10-11 16:53:12 +02:00
}
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) => {
2024-01-09 17:28:04 +01:00
this.currentAccount(item[0]);
2023-10-11 16:53:12 +02:00
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
})
);
}
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;
}
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(() => {
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(
2024-03-27 09:55:49 +01:00
this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-LOGIN'),
2023-10-11 16:53:12 +02:00
SnackBarNotificationLevel.Success
);
this.zone.run(() => this.router.navigate([returnUrl]));
}
public hasPermission(permission: AppPermission): boolean {
// if (!this.installationConfiguration.appServiceEnabled) { return true; } //TODO: maybe reconsider
return this.evaluatePermission(this.appAccount?.permissions || [], permission);
}
private evaluatePermission(availablePermissions: string[], permissionToCheck: string): boolean {
if (!permissionToCheck) { return false; }
if (this.hasRole(AppRole.Admin)) { return true; }
return availablePermissions.map(x => x.toLowerCase()).includes(permissionToCheck.toLowerCase());
}
public hasAnyPermission(permissions: AppPermission[]): boolean {
if (!permissions) { return false; }
return permissions.filter((p) => this.hasPermission(p)).length > 0;
}
public authorize(context: ResolutionContext): boolean {
if (!context || this.hasRole(AppRole.Admin)) { return true; }
let roleAuthorized = false;
if (context.roles && context.roles.length > 0) {
roleAuthorized = this.hasAnyRole(context.roles);
}
let permissionAuthorized = false;
if (context.permissions && context.permissions.length > 0) {
permissionAuthorized = this.hasAnyPermission(context.permissions);
}
if (roleAuthorized || permissionAuthorized) { return true; }
return false;
}
2017-12-14 11:41:26 +01:00
}