oidc-client-rx
Version:
ReactiveX enhanced OIDC and OAuth2 protocol support for browser-based JavaScript applications
160 lines (159 loc) • 8.62 kB
JavaScript
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 };