Skip to content

Commit

Permalink
feat: Add log off due to inactivity. (#2173)
Browse files Browse the repository at this point in the history
  • Loading branch information
defectiveAi authored Oct 24, 2023
1 parent ea3184e commit fbd7d9a
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 4 deletions.
2 changes: 1 addition & 1 deletion apps/dh/app-dh/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1.90mb"
"maximumError": "2.00mb"
},
{
"type": "anyComponentStyle",
Expand Down
10 changes: 9 additions & 1 deletion libs/dh/core/shell/src/lib/dh-core-shell.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,20 @@ import { getTranslocoTestingModule } from '@energinet-datahub/dh/shared/test-uti
import { MsalServiceFake } from '@energinet-datahub/dh/shared/test-util-auth';

import { DhCoreShellComponent } from './dh-core-shell.component';
import { WattModalService } from '@energinet-datahub/watt/modal';
import { MatDialogModule } from '@angular/material/dialog';
import { importProvidersFrom } from '@angular/core';

describe(DhCoreShellComponent, () => {
beforeEach(async () => {
view = await render(DhCoreShellComponent, {
imports: [getTranslocoTestingModule(), HttpClientTestingModule],
providers: [MsalServiceFake, danishDatetimeProviders],
providers: [
MsalServiceFake,
danishDatetimeProviders,
WattModalService,
importProvidersFrom([MatDialogModule]),
],
});
});

Expand Down
9 changes: 8 additions & 1 deletion libs/dh/core/shell/src/lib/dh-core-shell.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { DhTopBarStore } from '@energinet-datahub/dh-shared-data-access-top-bar'

import { DhPrimaryNavigationComponent } from './dh-primary-navigation.component';
import {
DhInactivityDetectionService,
DhSelectedActorComponent,
DhSignupMitIdComponent,
} from '@energinet-datahub/dh/shared/feature-authorization';
Expand All @@ -53,7 +54,13 @@ import { ApolloModule } from 'apollo-angular';
export class DhCoreShellComponent {
titleTranslationKey$ = this.dhTopBarStore.titleTranslationKey$;

constructor(private authService: MsalService, private dhTopBarStore: DhTopBarStore) {}
constructor(
private readonly authService: MsalService,
private readonly dhTopBarStore: DhTopBarStore,
inactivityDetection: DhInactivityDetectionService
) {
inactivityDetection.trackInactivity();
}

logout() {
this.authService.logout();
Expand Down
11 changes: 10 additions & 1 deletion libs/dh/core/shell/src/lib/dh-core-shell.providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,26 @@ import { applicationInsightsProviders } from '@energinet-datahub/dh/shared/util-
import { dhAuthorizationInterceptor } from '@energinet-datahub/dh/shared/feature-authorization';
import { danishLocalProviders } from '@energinet-datahub/gf/configuration-danish-locale';
import { HIGHLIGHT_OPTIONS, HighlightOptions } from 'ngx-highlightjs';
import { WattModalService } from '@energinet-datahub/watt/modal';
import { MatDialogModule } from '@angular/material/dialog';
import { FormGroupDirective } from '@angular/forms';

export const dhCoreShellProviders = [
importProvidersFrom([MatSnackBarModule, DhApiModule.forRoot(), MsalModule, TranslocoModule]),
importProvidersFrom([
MatDialogModule,
MatSnackBarModule,
DhApiModule.forRoot(),
MsalModule,
TranslocoModule,
]),
FormGroupDirective,
environment.production ? applicationInsightsProviders : [],
dhWattTranslationsProviders,
danishLocalProviders,
translocoProviders,
graphQLProviders,
danishDatetimeProviders,
WattModalService,
MsalService,
{
provide: HTTP_INTERCEPTORS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"yes": "Ja",
"no": "Nej",
"selectedLanguageIso": "da-dk",
"sessionExpirationTitle": "Sessionen udløber snart",
"sessionExpirationContentPartA": "Du har været inaktiv i 2 timer.",
"sessionExpirationContentPartB": "Af sikkerhedsmæssige årsager vil du blive logget af om",
"shell": {
"logout": "Log ud"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"yes": "Yes",
"no": "No",
"selectedLanguageIso": "en-gb",
"sessionExpirationTitle": "Session expires soon",
"sessionExpirationContentPartA": "You have been inactive for 2 hours.",
"sessionExpirationContentPartB": "You will be signed out for to security reasons in",
"shell": {
"logout": "Sign out"
},
Expand Down
1 change: 1 addition & 0 deletions libs/dh/shared/feature-authorization/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@
export * from './lib/permission.guard';
export * from './lib/permission-required.directive';
export * from './lib/dh-authorization.interceptor';
export * from './lib/dh-inactivity-detection.service';
export * from './lib/dh-selected-actor.component';
export * from './lib/dh-signup-mitid.component';
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* @license
* Copyright 2020 Energinet DataHub A/S
*
* Licensed under the Apache License, Version 2.0 (the "License2");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import {
concat,
distinctUntilChanged,
fromEvent,
map,
merge,
of,
startWith,
switchMap,
timer,
} from 'rxjs';
import { WattModalService } from '@energinet-datahub/watt/modal';
import { DhInactivityLogoutComponent } from './dh-inactivity-logout.component';
import { MsalService } from '@azure/msal-angular';

@Injectable({ providedIn: 'root' })
export class DhInactivityDetectionService {
private readonly secondsUntilWarning = 115 * 60;

private readonly inputDetection$ = merge(
fromEvent(document, 'mousemove'),
fromEvent(document, 'mousedown'),
fromEvent(document, 'keydown'),
fromEvent(document, 'wheel'),
fromEvent(document, 'touchmove'),
fromEvent(document, 'touchstart')
);

private readonly userInactive$ = this.inputDetection$.pipe(
startWith(null),
switchMap(() => concat(of(1), timer(this.secondsUntilWarning * 1000))),
distinctUntilChanged(),
map((isActive) => !isActive)
);

constructor(
private readonly modalService: WattModalService,
private readonly msal: MsalService
) {}

public trackInactivity() {
this.userInactive$.subscribe((isInactive) => {
if (isInactive) {
this.modalService.open({
component: DhInactivityLogoutComponent,
onClosed: (result) => result && this.msal.logout(),
});
} else {
this.modalService.close(false);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* @license
* Copyright 2020 Energinet DataHub A/S
*
* Licensed under the Apache License, Version 2.0 (the "License2");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component } from '@angular/core';
import { CommonModule, DatePipe } from '@angular/common';
import { RxPush } from '@rx-angular/template/push';
import { map, take, tap, timer } from 'rxjs';
import { WATT_MODAL, WattDialogRef } from '@energinet-datahub/watt/modal';
import { TranslocoModule } from '@ngneat/transloco';

@Component({
selector: 'dh-inactivity-logout',
styles: [
`
h2 {
text-align: center;
}
`,
],
template: `
<watt-modal *transloco="let t" [size]="'small'" [title]="t('sessionExpirationTitle')">
<p>{{ t('sessionExpirationContentPartA') }}<br />{{ t('sessionExpirationContentPartB') }}</p>
<h2>{{ warningCountdown$ | push | date : 'mm:ss' }}</h2>
</watt-modal>
`,
standalone: true,
imports: [CommonModule, RxPush, DatePipe, TranslocoModule, WATT_MODAL],
})
export class DhInactivityLogoutComponent {
private readonly secondsUntilLogOff = 5 * 60;

readonly warningCountdown$ = timer(0, 1000).pipe(
take(this.secondsUntilLogOff + 1),
tap((elapsed) => elapsed >= this.secondsUntilLogOff && this.dialogRef.close(true)),
map((elapsed) => Math.max(0, this.secondsUntilLogOff - elapsed - 1)),
map((elapsed) => new Date(elapsed * 1000))
);

constructor(private dialogRef: WattDialogRef<DhInactivityLogoutComponent>) {}
}

0 comments on commit fbd7d9a

Please sign in to comment.