oidc-client-rx
Version:
ReactiveX enhanced OIDC and OAuth2 protocol support for browser-based JavaScript applications
126 lines (125 loc) • 7.99 kB
JavaScript
import { Injectable, inject } from "injection-js";
import { ReplaySubject, forkJoin, of, throwError } from "rxjs";
import { catchError, map, share, switchMap } from "rxjs/operators";
import { AuthStateService } from "../auth-state/auth-state.service.js";
import { ConfigurationService } from "../config/config.service.js";
import { FlowsDataService } from "../flows/flows-data.service.js";
import { ResetAuthDataService } from "../flows/reset-auth-data.service.js";
import { RefreshSessionIframeService } from "../iframe/refresh-session-iframe.service.js";
import { LoggerService } from "../logging/logger.service.js";
import { EventTypes } from "../public-events/event-types.js";
import { PublicEventsService } from "../public-events/public-events.service.js";
import { StoragePersistenceService } from "../storage/storage-persistence.service.js";
import { UserService } from "../user-data/user.service.js";
import { FlowHelper } from "../utils/flowHelper/flow-helper.service.js";
import { IntervalService } from "./interval.service.js";
import { RefreshSessionRefreshTokenService } from "./refresh-session-refresh-token.service.js";
function _ts_decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : null === desc ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if ("object" == typeof Reflect && "function" == typeof Reflect.decorate) r = Reflect.decorate(decorators, target, key, desc);
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
class PeriodicallyTokenCheckService {
startTokenValidationPeriodically(allConfigs, currentConfig) {
const configsWithSilentRenewEnabled = this.getConfigsWithSilentRenewEnabled(allConfigs);
if (configsWithSilentRenewEnabled.length <= 0) return of(void 0);
if (this.intervalService.isTokenValidationRunning()) return of(void 0);
const refreshTimeInSeconds = this.getSmallestRefreshTimeFromConfigs(configsWithSilentRenewEnabled);
const periodicallyCheck$ = this.intervalService.startPeriodicTokenCheck(refreshTimeInSeconds).pipe(switchMap(()=>{
const objectWithConfigIdsAndRefreshEvent = {};
for (const config of configsWithSilentRenewEnabled){
const identifier = config.configId;
const refreshEvent = this.getRefreshEvent(config, allConfigs);
objectWithConfigIdsAndRefreshEvent[identifier] = refreshEvent;
}
return forkJoin(objectWithConfigIdsAndRefreshEvent);
}));
const o$ = periodicallyCheck$.pipe(catchError((error)=>{
this.loggerService.logError(currentConfig, "silent renew failed!", error);
return throwError(()=>error);
}), map((objectWithConfigIds)=>{
for (const [configId, _] of Object.entries(objectWithConfigIds))return void this.configurationService.getOpenIDConfiguration(configId).subscribe((config)=>{
this.loggerService.logDebug(config, "silent renew, periodic check finished!");
if (this.flowHelper.isCurrentFlowCodeFlowWithRefreshTokens(config)) this.flowsDataService.resetSilentRenewRunning(config);
});
}), share({
connector: ()=>new ReplaySubject(1),
resetOnError: false,
resetOnComplete: false,
resetOnRefCountZero: false
}));
this.intervalService.runTokenValidationRunning = o$.subscribe({});
return o$;
}
getRefreshEvent(config, allConfigs) {
const shouldStartRefreshEvent = this.shouldStartPeriodicallyCheckForConfig(config);
if (!shouldStartRefreshEvent) return of(null);
const refreshEvent$ = this.createRefreshEventForConfig(config, allConfigs);
this.publicEventsService.fireEvent(EventTypes.SilentRenewStarted);
return refreshEvent$.pipe(catchError((error)=>{
this.loggerService.logError(config, "silent renew failed!", error);
this.publicEventsService.fireEvent(EventTypes.SilentRenewFailed, error);
this.flowsDataService.resetSilentRenewRunning(config);
return throwError(()=>new Error(error));
}));
}
getSmallestRefreshTimeFromConfigs(configsWithSilentRenewEnabled) {
const result = configsWithSilentRenewEnabled.reduce((prev, curr)=>(prev.tokenRefreshInSeconds ?? 0) < (curr.tokenRefreshInSeconds ?? 0) ? prev : curr);
return result.tokenRefreshInSeconds ?? 0;
}
getConfigsWithSilentRenewEnabled(allConfigs) {
return allConfigs.filter((x)=>x.silentRenew);
}
createRefreshEventForConfig(configuration, allConfigs) {
this.loggerService.logDebug(configuration, "starting silent renew...");
return this.configurationService.getOpenIDConfiguration(configuration.configId).pipe(switchMap((config)=>{
if (!config?.silentRenew) {
this.resetAuthDataService.resetAuthorizationData(config, allConfigs);
return of(null);
}
this.flowsDataService.setSilentRenewRunning(config);
if (this.flowHelper.isCurrentFlowCodeFlowWithRefreshTokens(config)) {
const customParamsRefresh = this.storagePersistenceService.read("storageCustomParamsRefresh", config) || {};
const { customParamsRefreshTokenRequest } = config;
const mergedParams = {
...customParamsRefreshTokenRequest,
...customParamsRefresh
};
return this.refreshSessionRefreshTokenService.refreshSessionWithRefreshTokens(config, allConfigs, mergedParams);
}
const customParams = this.storagePersistenceService.read("storageCustomParamsAuthRequest", config);
return this.refreshSessionIframeService.refreshSessionWithIframe(config, allConfigs, customParams);
}));
}
shouldStartPeriodicallyCheckForConfig(config) {
const idToken = this.authStateService.getIdToken(config);
const isSilentRenewRunning = this.flowsDataService.isSilentRenewRunning(config);
const isCodeFlowInProgress = this.flowsDataService.isCodeFlowInProgress(config);
const userDataFromStore = this.userService.getUserDataFromStore(config);
this.loggerService.logDebug(config, `Checking: silentRenewRunning: ${isSilentRenewRunning}, isCodeFlowInProgress: ${isCodeFlowInProgress} - has idToken: ${!!idToken} - has userData: ${!!userDataFromStore}`);
const shouldBeExecuted = !!userDataFromStore && !isSilentRenewRunning && !!idToken && !isCodeFlowInProgress;
if (!shouldBeExecuted) return false;
const idTokenExpired = this.authStateService.hasIdTokenExpiredAndRenewCheckIsEnabled(config);
const accessTokenExpired = this.authStateService.hasAccessTokenExpiredIfExpiryExists(config);
return idTokenExpired || accessTokenExpired;
}
constructor(){
this.resetAuthDataService = inject(ResetAuthDataService);
this.flowHelper = inject(FlowHelper);
this.flowsDataService = inject(FlowsDataService);
this.loggerService = inject(LoggerService);
this.userService = inject(UserService);
this.authStateService = inject(AuthStateService);
this.refreshSessionIframeService = inject(RefreshSessionIframeService);
this.refreshSessionRefreshTokenService = inject(RefreshSessionRefreshTokenService);
this.intervalService = inject(IntervalService);
this.storagePersistenceService = inject(StoragePersistenceService);
this.publicEventsService = inject(PublicEventsService);
this.configurationService = inject(ConfigurationService);
}
}
PeriodicallyTokenCheckService = _ts_decorate([
Injectable()
], PeriodicallyTokenCheckService);
export { PeriodicallyTokenCheckService };