UNPKG

oidc-client-rx

Version:

ReactiveX enhanced OIDC and OAuth2 protocol support for browser-based JavaScript applications

160 lines (159 loc) 8.62 kB
import { Injectable, inject } from "@outposts/injection-js"; import { BehaviorSubject, of, throwError } from "rxjs"; import { map, retry, switchMap } from "rxjs/operators"; import { DataService } from "../api/data.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 { FlowHelper } from "../utils/flowHelper/flow-helper.service.js"; import { TokenHelperService } from "../utils/tokenHelper/token-helper.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; } const DEFAULT_USERRESULT = { userData: null, allUserData: [] }; class UserService { get userData$() { return this.userDataInternal$.asObservable(); } getAndPersistUserDataInStore(currentConfiguration, allConfigs, isRenewProcess = false, idToken, decodedIdToken) { const _idToken = idToken || this.storagePersistenceService.getIdToken(currentConfiguration); const _decodedIdToken = decodedIdToken || this.tokenHelperService.getPayloadFromToken(_idToken, false, currentConfiguration); const existingUserDataFromStorage = this.getUserDataFromStore(currentConfiguration); const haveUserData = !!existingUserDataFromStorage; const isCurrentFlowImplicitFlowWithAccessToken = this.flowHelper.isCurrentFlowImplicitFlowWithAccessToken(currentConfiguration); const isCurrentFlowCodeFlow = this.flowHelper.isCurrentFlowCodeFlow(currentConfiguration); const accessToken = this.storagePersistenceService.getAccessToken(currentConfiguration); if (!(isCurrentFlowImplicitFlowWithAccessToken || isCurrentFlowCodeFlow)) { this.loggerService.logDebug(currentConfiguration, `authCallback idToken flow with accessToken ${accessToken}`); this.setUserDataToStore(_decodedIdToken, currentConfiguration, allConfigs); return of(_decodedIdToken); } const { renewUserInfoAfterTokenRenew } = currentConfiguration; if (!isRenewProcess || renewUserInfoAfterTokenRenew || !haveUserData) return this.getUserDataOidcFlowAndSave(_decodedIdToken.sub, currentConfiguration, allConfigs).pipe(switchMap((userData)=>{ this.loggerService.logDebug(currentConfiguration, 'Received user data: ', userData); if (userData) { this.loggerService.logDebug(currentConfiguration, 'accessToken: ', accessToken); return of(userData); } return throwError(()=>new Error('Received no user data, request failed')); })); return of(existingUserDataFromStorage); } getUserDataFromStore(currentConfiguration) { if (!currentConfiguration) return throwError(()=>new Error('Please provide a configuration before setting up the module')); return this.storagePersistenceService.read('userData', currentConfiguration) || null; } publishUserDataIfExists(currentConfiguration, allConfigs) { const userData = this.getUserDataFromStore(currentConfiguration); if (userData) this.fireUserDataEvent(currentConfiguration, allConfigs, userData); } setUserDataToStore(userData, currentConfiguration, allConfigs) { this.storagePersistenceService.write('userData', userData, currentConfiguration); this.fireUserDataEvent(currentConfiguration, allConfigs, userData); } resetUserDataInStore(currentConfiguration, allConfigs) { this.storagePersistenceService.remove('userData', currentConfiguration); this.fireUserDataEvent(currentConfiguration, allConfigs, null); } getUserDataOidcFlowAndSave(idTokenSub, currentConfiguration, allConfigs) { return this.getIdentityUserData(currentConfiguration).pipe(map((data)=>{ if (this.validateUserDataSubIdToken(currentConfiguration, idTokenSub, null == data ? void 0 : data.sub)) { this.setUserDataToStore(data, currentConfiguration, allConfigs); return data; } this.loggerService.logWarning(currentConfiguration, 'User data sub does not match sub in id_token, resetting'); this.resetUserDataInStore(currentConfiguration, allConfigs); return null; })); } getIdentityUserData(currentConfiguration) { const token = this.storagePersistenceService.getAccessToken(currentConfiguration); const authWellKnownEndPoints = this.storagePersistenceService.read('authWellKnownEndPoints', currentConfiguration); if (!authWellKnownEndPoints) { this.loggerService.logWarning(currentConfiguration, 'init check session: authWellKnownEndpoints is undefined'); return throwError(()=>new Error('authWellKnownEndpoints is undefined')); } const userInfoEndpoint = authWellKnownEndPoints.userInfoEndpoint; if (!userInfoEndpoint) { this.loggerService.logError(currentConfiguration, 'init check session: authWellKnownEndpoints.userinfo_endpoint is undefined; set auto_userinfo = false in config'); return throwError(()=>new Error('authWellKnownEndpoints.userinfo_endpoint is undefined')); } return this.oidcDataService.get(userInfoEndpoint, currentConfiguration, token).pipe(retry(2)); } validateUserDataSubIdToken(currentConfiguration, idTokenSub, userDataSub) { if (!idTokenSub) return false; if (!userDataSub) return false; if (idTokenSub.toString() !== userDataSub.toString()) { this.loggerService.logDebug(currentConfiguration, 'validateUserDataSubIdToken failed', idTokenSub, userDataSub); return false; } return true; } fireUserDataEvent(currentConfiguration, allConfigs, passedUserData) { const userData = this.composeSingleOrMultipleUserDataObject(currentConfiguration, allConfigs, passedUserData); this.userDataInternal$.next(userData); const { configId } = currentConfiguration; this.eventService.fireEvent(EventTypes.UserDataChanged, { configId, userData: passedUserData }); } composeSingleOrMultipleUserDataObject(currentConfiguration, allConfigs, passedUserData) { const hasManyConfigs = allConfigs.length > 1; if (!hasManyConfigs) { const { configId } = currentConfiguration; return this.composeSingleUserDataResult(configId ?? '', passedUserData); } const allUserData = allConfigs.map((config)=>{ const currentConfigId = currentConfiguration.configId ?? ''; const configId = config.configId ?? ''; if (this.currentConfigIsToUpdate(currentConfigId, config)) return { configId, userData: passedUserData }; const alreadySavedUserData = this.storagePersistenceService.read('userData', config) || null; return { configId, userData: alreadySavedUserData }; }); return { userData: null, allUserData }; } composeSingleUserDataResult(configId, userData) { return { userData, allUserData: [ { configId, userData } ] }; } currentConfigIsToUpdate(configId, config) { return config.configId === configId; } constructor(){ this.userDataInternal$ = new BehaviorSubject(DEFAULT_USERRESULT); this.loggerService = inject(LoggerService); this.tokenHelperService = inject(TokenHelperService); this.flowHelper = inject(FlowHelper); this.oidcDataService = inject(DataService); this.storagePersistenceService = inject(StoragePersistenceService); this.eventService = inject(PublicEventsService); } } UserService = _ts_decorate([ Injectable() ], UserService); export { UserService };