feat(authentication): Added idle-functionality to logout inactive users after a certain time of inactivity. (TV-535)
Squashed commit of the following:
commit ca11dd079dca884634ead2696899cfedbfc826f1
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date: Wed Nov 10 14:45:48 2021 +0100
Made changes after PR
commit a8a51ecdabf0d32aa67b814eee530c9a01d405a9
Merge: b1677aff 3b6ce438
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date: Wed Nov 10 12:47:54 2021 +0100
Merge branch 'develop' into feature/TV-535-idle-functionality
commit b1677aff5210288f4a86ba235dd1acb5d415f71f
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date: Wed Nov 10 09:17:55 2021 +0100
Added better check to avoid blank screens
commit 0129f3f6a1d4884d3f669b109bc9b8667fc6281c
Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se>
Date: Wed Nov 10 09:12:55 2021 +0100
Added idle functionality
This commit is contained in:
@@ -1,3 +1,19 @@
|
|||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
|
||||||
<msfa-toast-list></msfa-toast-list>
|
<msfa-toast-list></msfa-toast-list>
|
||||||
|
|
||||||
|
<digi-ng-dialog
|
||||||
|
[afActive]="userIsIdle$ | async"
|
||||||
|
(afOnPrimaryClick)="setUserAsActive()"
|
||||||
|
(afOnSecondaryClick)="logout()"
|
||||||
|
(afOnInactive)="setUserAsActive()"
|
||||||
|
afHeading="Du verkar inaktiv"
|
||||||
|
afHeadingLevel="h2"
|
||||||
|
afPrimaryButtonText="Fortsätt sessionen"
|
||||||
|
afSecondaryButtonText="Logga ut"
|
||||||
|
>
|
||||||
|
<p>Din session är på väg att löpa ut på grund av inaktivitet. Vill du fortsätta eller logga ut?</p>
|
||||||
|
<p *ngIf="timeLeftBeforeLogout$ | async as timeLeftBeforeLogout">
|
||||||
|
Du blir automatiskt utloggad om <time>{{timeLeftBeforeLogout}}</time>
|
||||||
|
</p>
|
||||||
|
</digi-ng-dialog>
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
|||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||||
import { environment } from '@msfa-environment';
|
import { environment } from '@msfa-environment';
|
||||||
|
import { AuthenticationService } from '@msfa-services/api/authentication.service';
|
||||||
|
import { IdleService } from '@msfa-services/api/idle.service';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
import { filter, map, switchMap } from 'rxjs/operators';
|
import { filter, map, switchMap } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -12,11 +15,16 @@ import { filter, map, switchMap } from 'rxjs/operators';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
|
userIsIdle$: Observable<boolean> = this.idleService.isIdle$;
|
||||||
|
timeLeftBeforeLogout$: Observable<string> = this.idleService.timeLeftBeforeLogout$;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DOCUMENT) private document: Document,
|
@Inject(DOCUMENT) private document: Document,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private titleService: Title
|
private titleService: Title,
|
||||||
|
private idleService: IdleService,
|
||||||
|
private authenticationService: AuthenticationService
|
||||||
) {
|
) {
|
||||||
this.document.body.dataset.version = environment.version;
|
this.document.body.dataset.version = environment.version;
|
||||||
|
|
||||||
@@ -31,6 +39,14 @@ export class AppComponent {
|
|||||||
this.titleService.setTitle(`${pageTitle ? `${pageTitle} - ` : ''}Mina sidor för fristående aktörer`);
|
this.titleService.setTitle(`${pageTitle ? `${pageTitle} - ` : ''}Mina sidor för fristående aktörer`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logout(): void {
|
||||||
|
this.authenticationService.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
setUserAsActive(): void {
|
||||||
|
this.idleService.setActive();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function traverseUntilNoChildRoute(route: ActivatedRoute): ActivatedRoute {
|
function traverseUntilNoChildRoute(route: ActivatedRoute): ActivatedRoute {
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
|
import { DigiNgDialogModule } from '@af/digi-ng/_dialog/dialog';
|
||||||
import { registerLocaleData } from '@angular/common';
|
import { registerLocaleData } from '@angular/common';
|
||||||
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||||
import localeSe from '@angular/common/locales/sv';
|
import localeSe from '@angular/common/locales/sv';
|
||||||
import { ErrorHandler, LOCALE_ID, NgModule, Provider } from '@angular/core';
|
import { ErrorHandler, LOCALE_ID, NgModule, Provider } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { ApmErrorHandler } from '@elastic/apm-rum-angular';
|
import { ApmErrorHandler } from '@elastic/apm-rum-angular';
|
||||||
import { environment } from '@msfa-environment';
|
import { environment } from '@msfa-environment';
|
||||||
import { AuthInterceptor } from '@msfa-interceptors/auth.interceptor';
|
import { AuthInterceptor } from '@msfa-interceptors/auth.interceptor';
|
||||||
|
import { CustomErrorHandler } from '@msfa-interceptors/custom-error-handler';
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { ToastListModule } from './components/toast-list/toast-list.module';
|
import { ToastListModule } from './components/toast-list/toast-list.module';
|
||||||
import { LoggingModule } from './logging.module';
|
import { LoggingModule } from './logging.module';
|
||||||
import { AvropModule } from './pages/avrop/avrop.module';
|
import { AvropModule } from './pages/avrop/avrop.module';
|
||||||
import { CustomErrorHandler } from '@msfa-interceptors/custom-error-handler';
|
|
||||||
|
|
||||||
registerLocaleData(localeSe);
|
registerLocaleData(localeSe);
|
||||||
const providers: Provider[] = [
|
const providers: Provider[] = [
|
||||||
@@ -30,7 +31,15 @@ if (environment.production) {
|
|||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AppComponent],
|
declarations: [AppComponent],
|
||||||
imports: [LoggingModule, BrowserModule, HttpClientModule, AppRoutingModule, ToastListModule, AvropModule],
|
imports: [
|
||||||
|
LoggingModule,
|
||||||
|
BrowserModule,
|
||||||
|
HttpClientModule,
|
||||||
|
AppRoutingModule,
|
||||||
|
ToastListModule,
|
||||||
|
AvropModule,
|
||||||
|
DigiNgDialogModule,
|
||||||
|
],
|
||||||
providers,
|
providers,
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
export const AUTH_TOKEN_KEY = 'id_token';
|
export const AUTH_TOKEN_KEY = 'id_token';
|
||||||
export const AUTH_TOKEN_EXPIRE_KEY = 'expires_at';
|
export const AUTH_TOKEN_EXPIRE_KEY = 'expires_at';
|
||||||
|
export const AUTH_TOKEN_EXPIRES_IN_KEY = 'expires_in';
|
||||||
export const SELECTED_ORGANIZATION_NUMBER_KEY = 'selected_orgnr';
|
export const SELECTED_ORGANIZATION_NUMBER_KEY = 'selected_orgnr';
|
||||||
|
|
||||||
export const ALL_LOCAL_STORAGE_KEYS = {
|
export const ALL_LOCAL_STORAGE_KEYS = [
|
||||||
AUTH_TOKEN_KEY,
|
AUTH_TOKEN_KEY,
|
||||||
AUTH_TOKEN_EXPIRE_KEY,
|
AUTH_TOKEN_EXPIRE_KEY,
|
||||||
|
AUTH_TOKEN_EXPIRES_IN_KEY,
|
||||||
SELECTED_ORGANIZATION_NUMBER_KEY,
|
SELECTED_ORGANIZATION_NUMBER_KEY,
|
||||||
};
|
];
|
||||||
|
|||||||
@@ -15,20 +15,35 @@ export class AuthGuard implements CanActivate {
|
|||||||
return this.authenticationService.isLoggedIn$.pipe(
|
return this.authenticationService.isLoggedIn$.pipe(
|
||||||
switchMap(loggedIn => {
|
switchMap(loggedIn => {
|
||||||
if (loggedIn) {
|
if (loggedIn) {
|
||||||
return of(true);
|
return this.authenticationService.isTokenValid$.pipe(
|
||||||
|
switchMap(({ isValid, isRefreshable }) => {
|
||||||
|
if (isValid) {
|
||||||
|
return of(true);
|
||||||
|
}
|
||||||
|
if (isRefreshable) {
|
||||||
|
return this.authenticationService.refreshToken$();
|
||||||
|
}
|
||||||
|
this.redirectToLoginPage();
|
||||||
|
return of(false);
|
||||||
|
})
|
||||||
|
);
|
||||||
} else if (route.queryParams.code) {
|
} else if (route.queryParams.code) {
|
||||||
return this.authenticationService.login$(route.queryParams.code).pipe(map(result => !!result));
|
return this.authenticationService.login$(route.queryParams.code).pipe(map(result => !!result));
|
||||||
}
|
}
|
||||||
|
|
||||||
void this.authenticationService.removeLocalStorageData();
|
this.redirectToLoginPage();
|
||||||
|
|
||||||
if (environment.ciam.clientId) {
|
|
||||||
document.location.href = `${environment.ciam.loginUrl}&client_id=${environment.ciam.clientId}&redirect_uri=${window.location.origin}`;
|
|
||||||
} else {
|
|
||||||
void this.router.navigateByUrl(environment.ciam.loginUrl);
|
|
||||||
}
|
|
||||||
return of(false);
|
return of(false);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
redirectToLoginPage(): void {
|
||||||
|
this.authenticationService.removeLocalStorageData();
|
||||||
|
|
||||||
|
if (environment.ciam.clientId) {
|
||||||
|
document.location.href = `${environment.ciam.loginUrl}&client_id=${environment.ciam.clientId}&redirect_uri=${window.location.origin}`;
|
||||||
|
} else {
|
||||||
|
void this.router.navigateByUrl(environment.ciam.loginUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ export interface Ciam {
|
|||||||
clientId: string;
|
clientId: string;
|
||||||
loginUrl: string;
|
loginUrl: string;
|
||||||
logoutUrl: string;
|
logoutUrl: string;
|
||||||
|
refreshUrl: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export interface Environment {
|
|||||||
clientId?: string;
|
clientId?: string;
|
||||||
loginUrl: string;
|
loginUrl: string;
|
||||||
logoutUrl: string;
|
logoutUrl: string;
|
||||||
|
refreshUrl: string;
|
||||||
};
|
};
|
||||||
activeFeatures: Feature[];
|
activeFeatures: Feature[];
|
||||||
elastic?: {
|
elastic?: {
|
||||||
|
|||||||
@@ -1,28 +1,47 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { ALL_LOCAL_STORAGE_KEYS, AUTH_TOKEN_EXPIRE_KEY, AUTH_TOKEN_KEY } from '@msfa-constants/local-storage-keys';
|
import {
|
||||||
|
ALL_LOCAL_STORAGE_KEYS,
|
||||||
|
AUTH_TOKEN_EXPIRES_IN_KEY,
|
||||||
|
AUTH_TOKEN_EXPIRE_KEY,
|
||||||
|
AUTH_TOKEN_KEY,
|
||||||
|
} from '@msfa-constants/local-storage-keys';
|
||||||
import { environment } from '@msfa-environment';
|
import { environment } from '@msfa-environment';
|
||||||
import { AuthenticationResponse } from '@msfa-models/api/authentication.response.model';
|
import { AuthenticationResponse } from '@msfa-models/api/authentication.response.model';
|
||||||
import { Authentication, mapAuthApiResponseToAuthenticationResult } from '@msfa-models/authentication.model';
|
import { Authentication, mapAuthApiResponseToAuthenticationResult } from '@msfa-models/authentication.model';
|
||||||
import { add, isBefore } from 'date-fns';
|
import { add, isBefore, sub } from 'date-fns';
|
||||||
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
|
||||||
import { map, tap } from 'rxjs/operators';
|
import { catchError, distinctUntilChanged, filter, map, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class AuthenticationService {
|
export class AuthenticationService {
|
||||||
|
private readonly _refreshBaseUrl = environment.ciam.refreshUrl;
|
||||||
private _token$ = new BehaviorSubject<string>(null);
|
private _token$ = new BehaviorSubject<string>(null);
|
||||||
private _expiration$ = new BehaviorSubject<number>(null);
|
private _expiresAt$ = new BehaviorSubject<number>(null);
|
||||||
|
private _expiresIn$ = new BehaviorSubject<number>(null);
|
||||||
|
|
||||||
isLoggedIn$: Observable<boolean> = combineLatest([this._token$, this._expiration$]).pipe(
|
isLoggedIn$: Observable<boolean> = this._token$.pipe(
|
||||||
map(([token, expiration]) => {
|
distinctUntilChanged(),
|
||||||
if (token && expiration) {
|
map(token => !!token)
|
||||||
return isBefore(new Date(), expiration);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
// We set token validity at 1 minute prior expiration time.
|
||||||
|
// Refresh will be possible until expiration time.
|
||||||
|
// TODO: Refesh solution
|
||||||
|
isTokenValid$: Observable<{ isValid: boolean; isRefreshable: boolean }> = combineLatest([
|
||||||
|
this._token$,
|
||||||
|
this._expiresAt$,
|
||||||
|
]).pipe(
|
||||||
|
filter(([token, expiresAt]) => !!(token && expiresAt)),
|
||||||
|
map(([, expiresAt]) => {
|
||||||
|
return {
|
||||||
|
isValid: isBefore(new Date(), sub(expiresAt, { minutes: 1 })),
|
||||||
|
isRefreshable: false,
|
||||||
|
// isRefreshable: isBefore(new Date(), expiresAt),
|
||||||
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -30,13 +49,44 @@ export class AuthenticationService {
|
|||||||
return `${environment.api.url}/auth/token?accessCode=${code}`;
|
return `${environment.api.url}/auth/token?accessCode=${code}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setSession(authenticationResult: Authentication): void {
|
public get expiresAtDate(): number {
|
||||||
const expiresAt = add(new Date(), { seconds: authenticationResult.expiresIn });
|
return this._expiresAt$.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
this._token$.next(authenticationResult.idToken);
|
refreshToken$(): Observable<boolean> {
|
||||||
localStorage.setItem(AUTH_TOKEN_KEY, authenticationResult.idToken);
|
return this.httpClient.get(`${this._refreshBaseUrl}`, { responseType: 'blob' }).pipe(
|
||||||
this._expiration$.next(expiresAt.valueOf());
|
tap(() => {
|
||||||
localStorage.setItem(AUTH_TOKEN_EXPIRE_KEY, JSON.stringify(expiresAt.valueOf()));
|
this._setSession({
|
||||||
|
idToken: this._token$.getValue(),
|
||||||
|
expiresIn: 3600,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
map(() => true),
|
||||||
|
catchError((error: HttpErrorResponse) => {
|
||||||
|
// TODO: Information dialog that the user is logged out?
|
||||||
|
console.error('Could not refresh token... User needs to login again: ', error);
|
||||||
|
this._setSession(null);
|
||||||
|
return of(false);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setSession(authenticationResult: Authentication | null): void {
|
||||||
|
if (!authenticationResult) {
|
||||||
|
this.removeLocalStorageData();
|
||||||
|
this._token$.next(null);
|
||||||
|
this._expiresAt$.next(null);
|
||||||
|
this._expiresIn$.next(null);
|
||||||
|
} else {
|
||||||
|
const expiresAt = add(new Date(), { seconds: authenticationResult.expiresIn });
|
||||||
|
|
||||||
|
this._expiresIn$.next(authenticationResult.expiresIn);
|
||||||
|
localStorage.setItem(AUTH_TOKEN_EXPIRES_IN_KEY, authenticationResult.expiresIn.toString());
|
||||||
|
this._token$.next(authenticationResult.idToken);
|
||||||
|
localStorage.setItem(AUTH_TOKEN_KEY, authenticationResult.idToken);
|
||||||
|
this._expiresAt$.next(expiresAt.valueOf());
|
||||||
|
localStorage.setItem(AUTH_TOKEN_EXPIRE_KEY, JSON.stringify(expiresAt.valueOf()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
login$(authorizationCodeFromCiam: string): Observable<Authentication> {
|
login$(authorizationCodeFromCiam: string): Observable<Authentication> {
|
||||||
@@ -52,21 +102,23 @@ export class AuthenticationService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getLocalStorageData(): { id_token: string; expires_at: number } {
|
private get _localStorageData(): { id_token: string; expires_at: number; expires_in: number } {
|
||||||
const id_token = localStorage.getItem(AUTH_TOKEN_KEY);
|
const id_token = localStorage.getItem(AUTH_TOKEN_KEY);
|
||||||
const expiration = localStorage.getItem(AUTH_TOKEN_EXPIRE_KEY);
|
const expiresAt = localStorage.getItem(AUTH_TOKEN_EXPIRE_KEY);
|
||||||
|
const expiresIn = localStorage.getItem(AUTH_TOKEN_EXPIRES_IN_KEY);
|
||||||
|
|
||||||
return id_token && expiration
|
return id_token && expiresAt && expiresIn
|
||||||
? {
|
? {
|
||||||
id_token,
|
id_token,
|
||||||
expires_at: +JSON.parse(expiration),
|
expires_at: +JSON.parse(expiresAt),
|
||||||
|
expires_in: +expiresIn,
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeLocalStorageData(): void {
|
public removeLocalStorageData(): void {
|
||||||
Object.values(ALL_LOCAL_STORAGE_KEYS).forEach(value => {
|
ALL_LOCAL_STORAGE_KEYS.forEach(key => {
|
||||||
localStorage.removeItem(value);
|
localStorage.removeItem(key);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,11 +127,12 @@ export class AuthenticationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient, private router: Router) {
|
constructor(private httpClient: HttpClient, private router: Router) {
|
||||||
const localStorageData = this._getLocalStorageData();
|
const localStorageData = this._localStorageData;
|
||||||
|
|
||||||
if (localStorageData) {
|
if (localStorageData) {
|
||||||
this._token$.next(localStorageData.id_token);
|
this._token$.next(localStorageData.id_token);
|
||||||
this._expiration$.next(localStorageData.expires_at);
|
this._expiresAt$.next(localStorageData.expires_at);
|
||||||
|
this._expiresIn$.next(localStorageData.expires_in);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive';
|
||||||
|
import { BehaviorSubject, fromEvent, merge, Observable } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { AuthenticationService } from './authentication.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class IdleService extends UnsubscribeDirective {
|
||||||
|
private _eventsArray$: Observable<Event>[] = [
|
||||||
|
fromEvent(document, 'click'),
|
||||||
|
fromEvent(document, 'wheel'),
|
||||||
|
fromEvent(document, 'mousemove'),
|
||||||
|
fromEvent(document, 'touchstart'),
|
||||||
|
fromEvent(document, 'keyup'),
|
||||||
|
fromEvent(window, 'resize'),
|
||||||
|
fromEvent(window, 'scroll'),
|
||||||
|
];
|
||||||
|
private _allEvents$ = merge(...this._eventsArray$);
|
||||||
|
private _isIdle$ = new BehaviorSubject<boolean>(false);
|
||||||
|
public isIdle$: Observable<boolean> = this._isIdle$.asObservable();
|
||||||
|
private _isActive$ = new BehaviorSubject<boolean>(true);
|
||||||
|
public isActive$: Observable<boolean> = this._isActive$.asObservable();
|
||||||
|
private _timeLeftBeforeLogoutInSeconds$ = new BehaviorSubject<number>(2 * 60);
|
||||||
|
|
||||||
|
private _idleAfterMS = 10 * 60 * 1000; // 10 minutes
|
||||||
|
private _autoLogoutAfterMS = 2 * 60 * 1000; // 2 minutes
|
||||||
|
private _idleTimeout;
|
||||||
|
private _autoLogoutTimeout;
|
||||||
|
private _autoLogoutInterval;
|
||||||
|
|
||||||
|
constructor(private authenticationService: AuthenticationService) {
|
||||||
|
super();
|
||||||
|
super.unsubscribeOnDestroy(
|
||||||
|
this._allEvents$.subscribe(() => {
|
||||||
|
this._setNewTimeoutIfActive();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this._setNewTimeoutIfActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setNewTimeoutIfActive(): void {
|
||||||
|
if (this._isActive$.getValue()) {
|
||||||
|
this._clearTimeouts();
|
||||||
|
this._setIdleTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setIdleTimeout(): void {
|
||||||
|
this._idleTimeout = setTimeout(() => {
|
||||||
|
this._isActive$.next(false);
|
||||||
|
this._isIdle$.next(true);
|
||||||
|
this._setAutoLogoutTimeout();
|
||||||
|
}, this._idleAfterMS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setAutoLogoutTimeout(): void {
|
||||||
|
this._autoLogoutTimeout = setTimeout(() => {
|
||||||
|
this.authenticationService.logout();
|
||||||
|
}, this._autoLogoutAfterMS);
|
||||||
|
|
||||||
|
this._autoLogoutInterval = setInterval(() => {
|
||||||
|
const currentTimeLeft = this._timeLeftBeforeLogoutInSeconds$.getValue();
|
||||||
|
this._timeLeftBeforeLogoutInSeconds$.next(currentTimeLeft - 1);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _clearTimeouts(): void {
|
||||||
|
clearTimeout(this._autoLogoutTimeout);
|
||||||
|
clearTimeout(this._idleTimeout);
|
||||||
|
clearInterval(this._autoLogoutInterval);
|
||||||
|
this._timeLeftBeforeLogoutInSeconds$.next(2 * 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setActive(): void {
|
||||||
|
this._isIdle$.next(false);
|
||||||
|
this._isActive$.next(true);
|
||||||
|
this._clearTimeouts();
|
||||||
|
this._setIdleTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get timeLeftBeforeLogout$(): Observable<string> {
|
||||||
|
return this._timeLeftBeforeLogoutInSeconds$.pipe(
|
||||||
|
map(timeLeftInSeconds => {
|
||||||
|
const minutes = Math.floor(timeLeftInSeconds / 60);
|
||||||
|
const seconds = timeLeftInSeconds - minutes * 60;
|
||||||
|
|
||||||
|
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,16 +4,19 @@ export const CIAM_TEST: Ciam = {
|
|||||||
clientId: '5d08c2e4-763e-42f6-b858-24e4773bb83d',
|
clientId: '5d08c2e4-763e-42f6-b858-24e4773bb83d',
|
||||||
loginUrl: 'https://ciam-test.arbetsformedlingen.se:8443/uas/oauth2/authorization?response_type=code&scope=openid',
|
loginUrl: 'https://ciam-test.arbetsformedlingen.se:8443/uas/oauth2/authorization?response_type=code&scope=openid',
|
||||||
logoutUrl: 'https://ciam-test.arbetsformedlingen.se:8443/uas/logout',
|
logoutUrl: 'https://ciam-test.arbetsformedlingen.se:8443/uas/logout',
|
||||||
|
refreshUrl: '/refreshToken',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CIAM_PROD: Ciam = {
|
export const CIAM_PROD: Ciam = {
|
||||||
clientId: '71010833-e445-4bbc-926a-775247b7a6e3',
|
clientId: '71010833-e445-4bbc-926a-775247b7a6e3',
|
||||||
loginUrl: 'https://ciam.arbetsformedlingen.se/uas/oauth2/authorization?response_type=code&scope=openid',
|
loginUrl: 'https://ciam.arbetsformedlingen.se/uas/oauth2/authorization?response_type=code&scope=openid',
|
||||||
logoutUrl: 'https://ciam.arbetsformedlingen.se/uas/logout',
|
logoutUrl: 'https://ciam.arbetsformedlingen.se/uas/logout',
|
||||||
|
refreshUrl: '/refreshToken',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CIAM_MOCK: Ciam = {
|
export const CIAM_MOCK: Ciam = {
|
||||||
clientId: '',
|
clientId: '',
|
||||||
loginUrl: '/mock-login',
|
loginUrl: '/mock-login',
|
||||||
logoutUrl: '/mock-login',
|
logoutUrl: '/mock-login',
|
||||||
|
refreshUrl: '/refreshToken',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,5 +11,12 @@
|
|||||||
"pathRewrite": {
|
"pathRewrite": {
|
||||||
"^/logging": ""
|
"^/logging": ""
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/refreshToken": {
|
||||||
|
"target": "https://ciam-test.arbetsformedlingen.se:8443/uas/refresh",
|
||||||
|
"secure": false,
|
||||||
|
"changeOrigin": true,
|
||||||
|
"logLevel": "debug",
|
||||||
|
"pathRewrite": { "^/refreshToken": "" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,15 +72,16 @@ http {
|
|||||||
proxy_pass $ELASTIC_SERVER_URL;
|
proxy_pass $ELASTIC_SERVER_URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Ciam Refresh token
|
||||||
|
location /refreshToken {
|
||||||
|
#proxy_http_version 1.1;
|
||||||
|
proxy_pass $CIAM_REFRESH_URL;
|
||||||
|
}
|
||||||
|
|
||||||
error_page 404 /404.html;
|
error_page 404 /404.html;
|
||||||
location = /40x.html {
|
location = /40x.html {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
error_page 500 502 503 504 /50x.html;
|
error_page 500 502 503 504 /50x.html;
|
||||||
location = /50x.html {
|
location = /50x.html {}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
export CIAM_REFRESH_URL
|
||||||
export ELASTIC_SERVER_URL
|
export ELASTIC_SERVER_URL
|
||||||
|
|
||||||
envsubst '${ELASTIC_SERVER_URL} ' < /usr/share/container-scripts/nginx/nginx-start/nginx.template > /etc/nginx/nginx.conf
|
envsubst '${ELASTIC_SERVER_URL} ${CIAM_REFRESH_URL}' < /usr/share/container-scripts/nginx/nginx-start/nginx.template > /etc/nginx/nginx.conf
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pipeline {
|
|||||||
echo '### Generating build tag... ###'
|
echo '### Generating build tag... ###'
|
||||||
script {
|
script {
|
||||||
def packageJson = readJSON file: 'package.json'
|
def packageJson = readJSON file: 'package.json'
|
||||||
BUILD_TAG = "dev_v${packageJson.version}_${env.BUILD_NUMBER}_${CURRENT_COMMIT}"
|
BUILD_TAG = "playground_v${packageJson.version}_${env.BUILD_NUMBER}_${CURRENT_COMMIT}"
|
||||||
echo '### Build tag ###'
|
echo '### Build tag ###'
|
||||||
echo "This is the build tag: ${BUILD_TAG}"
|
echo "This is the build tag: ${BUILD_TAG}"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user