79700: specs for modal, auth check for idleness tracking & stop blocking at token success

This commit is contained in:
Marie Verdonck
2021-06-03 14:29:50 +02:00
parent 38387d1a0f
commit e88baa1995
6 changed files with 151 additions and 18 deletions

View File

@@ -263,7 +263,6 @@ export class AuthEffects {
filter((action: Action) => !IDLE_TIMER_IGNORE_TYPES.includes(action.type)),
// Using switchMap the timer will be interrupted and restarted if a new action comes in, so idleness timer restarts
switchMap(() => {
this.authService.isAuthenticated();
return timer(environment.auth.ui.timeUntilIdle);
}),
map(() => {

View File

@@ -193,6 +193,7 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
return Object.assign({}, state, {
authToken: (action as RefreshTokenSuccessAction).payload,
refreshing: false,
blocking: false
});
case AuthActionTypes.ADD_MESSAGE:

View File

@@ -2,7 +2,12 @@ import { map, take } from 'rxjs/operators';
import { Component, Inject, OnInit, Optional, Input } from '@angular/core';
import { Router } from '@angular/router';
import { combineLatest as combineLatestObservable, Observable, of } from 'rxjs';
import {
combineLatest as observableCombineLatest,
combineLatest as combineLatestObservable,
Observable,
of
} from 'rxjs';
import { Store } from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
@@ -84,18 +89,19 @@ export class RootComponent implements OnInit {
map(([collapsed, mobile]) => collapsed || mobile)
);
this.authService.isUserIdle().subscribe((userIdle: boolean) => {
if (userIdle) {
if (!this.idleModalOpen) {
const modalRef = this.modalService.open(IdleModalComponent);
this.idleModalOpen = true;
modalRef.componentInstance.response.pipe(take(1)).subscribe((closed: boolean) => {
if (closed) {
this.idleModalOpen = false;
}
});
observableCombineLatest([this.authService.isUserIdle(), this.authService.isAuthenticated()])
.subscribe(([userIdle, authenticated]) => {
if (userIdle && authenticated) {
if (!this.idleModalOpen) {
const modalRef = this.modalService.open(IdleModalComponent);
this.idleModalOpen = true;
modalRef.componentInstance.response.pipe(take(1)).subscribe((closed: boolean) => {
if (closed) {
this.idleModalOpen = false;
}
});
}
}
}
});
}
}

View File

@@ -0,0 +1,128 @@
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
import { IdleModalComponent } from './idle-modal.component';
import { AuthService } from '../../core/auth/auth.service';
import { By } from '@angular/platform-browser';
describe('IdleModalComponent', () => {
let component: IdleModalComponent;
let fixture: ComponentFixture<IdleModalComponent>;
let debugElement: DebugElement;
const modalStub = jasmine.createSpyObj('modalStub', ['close']);
const authServiceStub = jasmine.createSpyObj('authService', ['setIdle', 'logout']);
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [IdleModalComponent],
providers: [
{ provide: NgbActiveModal, useValue: modalStub },
{ provide: AuthService, useValue: authServiceStub }
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(IdleModalComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
describe('extendSessionPressed', () => {
beforeEach(fakeAsync(() => {
spyOn(component.response, 'next');
component.extendSessionPressed();
}));
it('should set idle to false', () => {
expect(authServiceStub.setIdle).toHaveBeenCalledWith(false);
});
it('should close the modal', () => {
expect(modalStub.close).toHaveBeenCalled();
});
it('response \'closed\' should have true as next', () => {
expect(component.response.next).toHaveBeenCalledWith(true);
});
});
describe('logOutPressed', () => {
beforeEach(() => {
component.logOutPressed();
});
it('should logout', () => {
expect(authServiceStub.logout).toHaveBeenCalled();
});
it('should close the modal', () => {
expect(modalStub.close).toHaveBeenCalled();
});
});
describe('closePressed', () => {
beforeEach(fakeAsync(() => {
spyOn(component.response, 'next');
component.closePressed();
}));
it('should set idle to false', () => {
expect(authServiceStub.setIdle).toHaveBeenCalledWith(false);
});
it('should close the modal', () => {
expect(modalStub.close).toHaveBeenCalled();
});
it('response \'closed\' should have true as next', () => {
expect(component.response.next).toHaveBeenCalledWith(true);
});
});
describe('when the click method emits on extend session button', () => {
beforeEach(fakeAsync(() => {
spyOn(component, 'extendSessionPressed');
debugElement.query(By.css('button.confirm')).triggerEventHandler('click', {
preventDefault: () => {/**/
}
});
tick();
fixture.detectChanges();
}));
it('should call the extendSessionPressed method on the component', () => {
expect(component.extendSessionPressed).toHaveBeenCalled();
});
});
describe('when the click method emits on log out button', () => {
beforeEach(fakeAsync(() => {
spyOn(component, 'logOutPressed');
debugElement.query(By.css('button.cancel')).triggerEventHandler('click', {
preventDefault: () => {/**/
}
});
tick();
fixture.detectChanges();
}));
it('should call the logOutPressed method on the component', () => {
expect(component.logOutPressed).toHaveBeenCalled();
});
});
describe('when the click method emits on close button', () => {
beforeEach(fakeAsync(() => {
spyOn(component, 'closePressed');
debugElement.query(By.css('.close')).triggerEventHandler('click', {
preventDefault: () => {/**/
}
});
tick();
fixture.detectChanges();
}));
it('should call the closePressed method on the component', () => {
expect(component.closePressed).toHaveBeenCalled();
});
});
});

View File

@@ -3697,7 +3697,7 @@
"idle-modal.header": "Session will expire soon",
"idle-modal.info": "For security reasons, user sessions expire after {{ timeToExpire }} minutes of inactivity. Your session will expire soon. Would you like to extend it or log out?",
"idle-modal.info": "For security reasons, user sessions expire after {{ timeToExpire }} minutes of inactivity. Your session will expire soon. Would you like to extend it or log out?",
"idle-modal.log-out": "Log out",

View File

@@ -48,17 +48,16 @@ export const environment: GlobalConfig = {
ui: {
// the amount of time before the idle warning is shown
// timeUntilIdle: 15 * 60 * 1000, // 15 minutes
timeUntilIdle: 1 * 60 * 1000, // 1 minutes
timeUntilIdle: 30 * 1000, // 30 seconds
// the amount of time the user has to react after the idle warning is shown before they are logged out.
// idleGracePeriod: 5 * 60 * 1000, // 5 minutes
idleGracePeriod: 1 * 60 * 1000, // 1 minutes
idleGracePeriod: 1 * 60 * 1000, // 1 minute
},
// Authority REST settings
rest: {
// If the rest token expires in less than this amount of time, it will be refreshed automatically.
// This is independent from the idle warning.
// timeLeftBeforeTokenRefresh: 2 * 60 * 1000, // 2 minutes
timeLeftBeforeTokenRefresh: 0.25 * 60 * 1000, // 25 seconds
timeLeftBeforeTokenRefresh: 2 * 60 * 1000, // 2 minutes
},
},
// Form settings