Skip to content

Commit

Permalink
adding the new checksession
Browse files Browse the repository at this point in the history
  • Loading branch information
FabianGosebrink committed Apr 17, 2020
1 parent a4f297e commit 1750ee2
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@ export enum EventTypes {
* This only works in the AppModule Constructor
*/
ConfigLoaded,

ModuleSetup,

CheckSessionChanged,
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { OidcClientNotification } from './notification';
export class EventsService {
private notify = new ReplaySubject<OidcClientNotification>(1);

fireEvent(type: EventTypes, value: any) {
fireEvent(type: EventTypes, value?: any) {
this.notify.next({ type, value });
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import { EventTypes } from './event-types';

export interface OidcClientNotification {
type: EventTypes;
value: any;
value?: any;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Injectable, NgZone } from '@angular/core';
import { from, Observable, Observer, Subject } from 'rxjs';
import { take } from 'rxjs/operators';
import { ConfigurationProvider } from '../config';
import { EventsService, EventTypes } from '../events';
import { LoggerService } from '../logging/logger.service';
import { StoragePersistanceService } from '../storage';
import { IFrameService } from './existing-iframe.service';
Expand All @@ -12,148 +11,144 @@ const IFRAME_FOR_CHECK_SESSION_IDENTIFIER = 'myiFrameForCheckSession';

@Injectable()
export class OidcSecurityCheckSession {
private sessionIframe: any;
private iframeMessageEvent: any;
private scheduledHeartBeat: any;
public checkSessionReceived = false;
private scheduledHeartBeatRunning: NodeJS.Timeout;
private lastIFrameRefresh = 0;
private outstandingMessages = 0;
private heartBeatInterval = 3000;
private iframeRefreshInterval = 60000;
private checkSessionChanged = new Subject<any>();

public get onCheckSessionChanged(): Observable<any> {
return this.checkSessionChanged.asObservable();
}

constructor(
private storagePersistanceService: StoragePersistanceService,
private loggerService: LoggerService,
private iFrameService: IFrameService,
private zone: NgZone,
private eventService: EventsService,
private readonly configurationProvider: ConfigurationProvider
) {}

private doesSessionExist(): boolean {
const existingIFrame = this.iFrameService.getExistingIFrame(IFRAME_FOR_CHECK_SESSION_IDENTIFIER);
startCheckingSession(clientId: string): void {
if (!!this.scheduledHeartBeatRunning) {
return;
}

if (!existingIFrame) {
return false;
this.init();
this.pollServerSession(clientId);
}

stopCheckingSession(): void {
if (!this.scheduledHeartBeatRunning) {
return;
}

this.sessionIframe = existingIFrame;
return true;
this.clearScheduledHeartBeat();
this.checkSessionReceived = false;
}

private init() {
if (this.lastIFrameRefresh + this.iframeRefreshInterval > Date.now()) {
return from([this]);
}

if (!this.doesSessionExist()) {
this.sessionIframe = this.iFrameService.addIFrameToWindowBody(IFRAME_FOR_CHECK_SESSION_IDENTIFIER);
this.iframeMessageEvent = this.messageHandler.bind(this);
window.addEventListener('message', this.iframeMessageEvent, false);
return;
}

if (!this.configurationProvider.wellKnownEndpoints) {
this.loggerService.logWarning('init check session: authWellKnownEndpoints is undefined. Returning.');
return;
}

const existingIframe = this.getOrCreateIframe();

if (this.configurationProvider.wellKnownEndpoints.checkSessionIframe) {
this.sessionIframe.contentWindow.location.replace(this.configurationProvider.wellKnownEndpoints.checkSessionIframe);
existingIframe.contentWindow.location.replace(this.configurationProvider.wellKnownEndpoints.checkSessionIframe);
} else {
this.loggerService.logWarning('init check session: authWellKnownEndpoints is undefined');
}

return new Observable((observer: Observer<OidcSecurityCheckSession>) => {
this.sessionIframe.onload = () => {
this.lastIFrameRefresh = Date.now();
observer.next(this);
observer.complete();
};
});
}

startCheckingSession(clientId: string): void {
if (this.scheduledHeartBeat) {
return;
this.loggerService.logWarning('init check session: checkSessionIframe is not configured to run');
}

this.pollServerSession(clientId);
}
this.bindMessageEventToIframe();

stopCheckingSession(): void {
if (!this.scheduledHeartBeat) {
return;
}

this.clearScheduledHeartBeat();
existingIframe.onload = () => {
this.lastIFrameRefresh = Date.now();
};
}

private pollServerSession(clientId: string) {
this.outstandingMessages = 0;

const pollServerSessionRecur = () => {
this.init()
.pipe(take(1))
.subscribe(() => {
if (this.sessionIframe && clientId) {
this.loggerService.logDebug(this.sessionIframe);
const sessionState = this.storagePersistanceService.sessionState;
if (sessionState) {
this.outstandingMessages++;
this.sessionIframe.contentWindow.postMessage(
clientId + ' ' + sessionState,
this.configurationProvider.openIDConfiguration.stsServer
);
} else {
this.loggerService.logDebug('OidcSecurityCheckSession pollServerSession session_state is blank');
this.checkSessionChanged.next();
}
} else {
this.loggerService.logWarning('OidcSecurityCheckSession pollServerSession sessionIframe does not exist');
this.loggerService.logDebug(clientId);
this.loggerService.logDebug(this.sessionIframe);
// this.init();
}

// after sending three messages with no response, fail.
if (this.outstandingMessages > 3) {
this.loggerService.logError(
`OidcSecurityCheckSession not receiving check session response messages.
Outstanding messages: ${this.outstandingMessages}. Server unreachable?`
);
this.checkSessionChanged.next();
}
const existingIframe = this.getExistingIframe();
if (existingIframe && clientId) {
this.loggerService.logDebug(existingIframe);
const sessionState = this.storagePersistanceService.sessionState;
if (sessionState) {
this.outstandingMessages++;
existingIframe.contentWindow.postMessage(
clientId + ' ' + sessionState,
this.configurationProvider.openIDConfiguration.stsServer
);
} else {
this.loggerService.logDebug('OidcSecurityCheckSession pollServerSession session_state is blank');
this.eventService.fireEvent(EventTypes.CheckSessionChanged);
}
} else {
this.loggerService.logWarning('OidcSecurityCheckSession pollServerSession sessionIframe does not exist');
this.loggerService.logDebug(clientId);
this.loggerService.logDebug(existingIframe);
}

this.scheduledHeartBeat = setTimeout(pollServerSessionRecur, this.heartBeatInterval);
});
// after sending three messages with no response, fail.
if (this.outstandingMessages > 3) {
this.loggerService.logError(
`OidcSecurityCheckSession not receiving check session response messages.
Outstanding messages: ${this.outstandingMessages}. Server unreachable?`
);
this.eventService.fireEvent(EventTypes.CheckSessionChanged);
}
};

this.outstandingMessages = 0;

this.zone.runOutsideAngular(() => {
this.scheduledHeartBeat = setTimeout(pollServerSessionRecur, this.heartBeatInterval);
this.scheduledHeartBeatRunning = setInterval(pollServerSessionRecur, this.heartBeatInterval);
});
}

private clearScheduledHeartBeat() {
clearTimeout(this.scheduledHeartBeat);
this.scheduledHeartBeat = null;
clearTimeout(this.scheduledHeartBeatRunning);
this.scheduledHeartBeatRunning = null;
}

private messageHandler(e: any) {
const existingIFrame = this.getExistingIframe();
this.outstandingMessages = 0;
if (
this.sessionIframe &&
existingIFrame &&
this.configurationProvider.openIDConfiguration.stsServer.startsWith(e.origin) &&
e.source === this.sessionIframe.contentWindow
e.source === existingIFrame.contentWindow
) {
if (e.data === 'error') {
this.loggerService.logWarning('error from checksession messageHandler');
} else if (e.data === 'changed') {
this.checkSessionChanged.next();
this.checkSessionReceived = true;
this.eventService.fireEvent(EventTypes.CheckSessionChanged, e.data);
} else {
this.loggerService.logDebug(e.data + ' from checksession messageHandler');
}
}
}

private getExistingIframe() {
return this.iFrameService.getExistingIFrame(IFRAME_FOR_CHECK_SESSION_IDENTIFIER);
}

private bindMessageEventToIframe() {
const iframeMessageEvent = this.messageHandler.bind(this);
window.addEventListener('message', iframeMessageEvent, false);
}

private getOrCreateIframe() {
const existingIframe = this.getExistingIframe();

if (!existingIframe) {
return this.iFrameService.addIFrameToWindowBody(IFRAME_FOR_CHECK_SESSION_IDENTIFIER);
}

return existingIframe;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import { OidcSecurityUserService } from './oidc.security.user-service';
@Injectable()
export class OidcSecurityService {
private onModuleSetupInternal = new Subject<boolean>();
private onCheckSessionChangedInternal = new Subject<boolean>();
private onAuthorizationResultInternal = new Subject<AuthorizationResult>();

public get onModuleSetup(): Observable<boolean> {
Expand All @@ -36,11 +35,6 @@ export class OidcSecurityService {
return this.onAuthorizationResultInternal.asObservable();
}

public get onCheckSessionChanged(): Observable<boolean> {
return this.onCheckSessionChangedInternal.asObservable();
}

checkSessionChanged = false;
moduleSetup = false;

private isModuleSetupInternal = new BehaviorSubject<boolean>(false);
Expand All @@ -58,7 +52,7 @@ export class OidcSecurityService {
private oidcDataService: OidcDataService,
private stateValidationService: StateValidationService,
private router: Router,
private oidcSecurityCheckSession: OidcSecurityCheckSession,
private oidcSecurityCheckSessionService: OidcSecurityCheckSession,
private oidcSecuritySilentRenew: OidcSecuritySilentRenew,
private oidcSecurityUserService: OidcSecurityUserService,
private storagePersistanceService: StoragePersistanceService,
Expand Down Expand Up @@ -125,9 +119,9 @@ export class OidcSecurityService {
.pipe(filter(() => this.configurationProvider.openIDConfiguration.startCheckSession))
.subscribe((isSetupAndAuthorized) => {
if (isSetupAndAuthorized) {
this.oidcSecurityCheckSession.startCheckingSession(this.configurationProvider.openIDConfiguration.clientId);
this.oidcSecurityCheckSessionService.startCheckingSession(this.configurationProvider.openIDConfiguration.clientId);
} else {
this.oidcSecurityCheckSession.stopCheckingSession();
this.oidcSecurityCheckSessionService.stopCheckingSession();
}
});
}
Expand All @@ -138,12 +132,6 @@ export class OidcSecurityService {
return;
}

this.oidcSecurityCheckSession.onCheckSessionChanged.subscribe(() => {
this.loggerService.logDebug('onCheckSessionChanged');
this.checkSessionChanged = true;
this.onCheckSessionChangedInternal.next(this.checkSessionChanged);
});

const userData = this.storagePersistanceService.userData;
if (userData) {
this.setUserData(userData);
Expand Down Expand Up @@ -669,7 +657,7 @@ export class OidcSecurityService {

this.resetAuthorizationData(false);

if (this.configurationProvider.openIDConfiguration.startCheckSession && this.checkSessionChanged) {
if (this.serverCheckSessionStateChanged()) {
this.loggerService.logDebug('only local login cleaned up, server session has changed');
} else if (urlHandler) {
urlHandler(url);
Expand All @@ -685,6 +673,12 @@ export class OidcSecurityService {
}
}

private serverCheckSessionStateChanged() {
return (
this.configurationProvider.openIDConfiguration.startCheckSession && this.oidcSecurityCheckSessionService.checkSessionReceived
);
}

refreshSession(): Observable<boolean> {
if (!this.configurationProvider.openIDConfiguration.silentRenewUrl) {
return of(false);
Expand Down Expand Up @@ -805,7 +799,6 @@ export class OidcSecurityService {
}

this.storagePersistanceService.resetStorageData(isRenewProcess);
this.checkSessionChanged = false;
this.setIsAuthorized(false);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

Is Configuration loaded: {{ isConfigurationLoaded }}

CheckSessionChanged
<pre> {{ checkSessionChanged$ | async }}</pre>

<br />
<br />

Expand Down
11 changes: 9 additions & 2 deletions projects/sample-code-flow-http-config/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { OidcSecurityService } from 'angular-auth-oidc-client';
import { EventsService, EventTypes, OidcClientNotification, OidcSecurityService } from 'angular-auth-oidc-client';
import { Observable } from 'rxjs';
import { filter } from 'rxjs/operators';

@Component({
selector: 'app-root',
Expand All @@ -8,9 +10,10 @@ import { OidcSecurityService } from 'angular-auth-oidc-client';
export class AppComponent implements OnInit, OnDestroy {
isAuthenticated: boolean;
isConfigurationLoaded: boolean;
checkSessionChanged$: Observable<OidcClientNotification>;
userData: any;

constructor(public oidcSecurityService: OidcSecurityService) {
constructor(public oidcSecurityService: OidcSecurityService, public eventsService: EventsService) {
this.oidcSecurityService.setupModule();

if (this.oidcSecurityService.moduleSetup) {
Expand All @@ -23,6 +26,10 @@ export class AppComponent implements OnInit, OnDestroy {
}

ngOnInit() {
this.checkSessionChanged$ = this.eventsService
.registerForEvents()
.pipe(filter((notification: OidcClientNotification) => notification.type === EventTypes.CheckSessionChanged));

this.oidcSecurityService.getIsAuthorized().subscribe((auth) => {
this.isAuthenticated = auth;
});
Expand Down

0 comments on commit 1750ee2

Please sign in to comment.