UNPKG

oidc-client-rx

Version:

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

220 lines (219 loc) 15.3 kB
import { Injectable, inject } from "@outposts/injection-js"; import { of } from "rxjs"; import { map, mergeMap } from "rxjs/operators"; import { LoggerService } from "../logging/logger.service.js"; import { StoragePersistenceService } from "../storage/storage-persistence.service.js"; import { EqualityService } from "../utils/equality/equality.service.js"; import { FlowHelper } from "../utils/flowHelper/flow-helper.service.js"; import { TokenHelperService } from "../utils/tokenHelper/token-helper.service.js"; import { StateValidationResult } from "./state-validation-result.js"; import { TokenValidationService } from "./token-validation.service.js"; import { ValidationResult } from "./validation-result.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 StateValidationService { getValidatedStateResult(callbackContext, configuration) { var _callbackContext_authResult; const hasError = Boolean(null == (_callbackContext_authResult = callbackContext.authResult) ? void 0 : _callbackContext_authResult.error); const hasCallbackContext = Boolean(callbackContext); if (!hasCallbackContext || hasError) return of(new StateValidationResult('', '', false, {})); return this.validateState(callbackContext, configuration); } validateState(callbackContext, configuration) { var _callbackContext_authResult, _callbackContext_authResult1; const toReturn = new StateValidationResult(); const authStateControl = this.storagePersistenceService.read('authStateControl', configuration); if (!this.tokenValidationService.validateStateFromHashCallback(null == (_callbackContext_authResult = callbackContext.authResult) ? void 0 : _callbackContext_authResult.state, authStateControl, configuration)) { this.loggerService.logWarning(configuration, 'authCallback incorrect state'); toReturn.state = ValidationResult.StatesDoNotMatch; this.handleUnsuccessfulValidation(configuration); return of(toReturn); } const isCurrentFlowImplicitFlowWithAccessToken = this.flowHelper.isCurrentFlowImplicitFlowWithAccessToken(configuration); const isCurrentFlowCodeFlow = this.flowHelper.isCurrentFlowCodeFlow(configuration); if (isCurrentFlowImplicitFlowWithAccessToken || isCurrentFlowCodeFlow) { var _callbackContext_authResult2; toReturn.accessToken = (null == (_callbackContext_authResult2 = callbackContext.authResult) ? void 0 : _callbackContext_authResult2.access_token) ?? ''; } const disableIdTokenValidation = configuration.disableIdTokenValidation; if (disableIdTokenValidation) { toReturn.state = ValidationResult.Ok; toReturn.authResponseIsValid = true; return of(toReturn); } const isInRefreshTokenFlow = callbackContext.isRenewProcess && !!callbackContext.refreshToken; const hasIdToken = Boolean(null == (_callbackContext_authResult1 = callbackContext.authResult) ? void 0 : _callbackContext_authResult1.id_token); if (isInRefreshTokenFlow && !hasIdToken) { toReturn.state = ValidationResult.Ok; toReturn.authResponseIsValid = true; return of(toReturn); } if (hasIdToken) { var _callbackContext_authResult3; const { clientId, issValidationOff, maxIdTokenIatOffsetAllowedInSeconds, disableIatOffsetValidation, ignoreNonceAfterRefresh, renewTimeBeforeTokenExpiresInSeconds } = configuration; toReturn.idToken = (null == (_callbackContext_authResult3 = callbackContext.authResult) ? void 0 : _callbackContext_authResult3.id_token) ?? ''; toReturn.decodedIdToken = this.tokenHelperService.getPayloadFromToken(toReturn.idToken, false, configuration); return this.tokenValidationService.validateSignatureIdToken(toReturn.idToken, callbackContext.jwtKeys, configuration).pipe(mergeMap((isSignatureIdTokenValid)=>{ if (!isSignatureIdTokenValid) { this.loggerService.logDebug(configuration, 'authCallback Signature validation failed id_token'); toReturn.state = ValidationResult.SignatureFailed; this.handleUnsuccessfulValidation(configuration); return of(toReturn); } const authNonce = this.storagePersistenceService.read('authNonce', configuration); if (!this.tokenValidationService.validateIdTokenNonce(toReturn.decodedIdToken, authNonce, Boolean(ignoreNonceAfterRefresh), configuration)) { this.loggerService.logWarning(configuration, 'authCallback incorrect nonce, did you call the checkAuth() method multiple times?'); toReturn.state = ValidationResult.IncorrectNonce; this.handleUnsuccessfulValidation(configuration); return of(toReturn); } if (!this.tokenValidationService.validateRequiredIdToken(toReturn.decodedIdToken, configuration)) { this.loggerService.logDebug(configuration, 'authCallback Validation, one of the REQUIRED properties missing from id_token'); toReturn.state = ValidationResult.RequiredPropertyMissing; this.handleUnsuccessfulValidation(configuration); return of(toReturn); } if (!isInRefreshTokenFlow && !this.tokenValidationService.validateIdTokenIatMaxOffset(toReturn.decodedIdToken, maxIdTokenIatOffsetAllowedInSeconds ?? 120, Boolean(disableIatOffsetValidation), configuration)) { this.loggerService.logWarning(configuration, 'authCallback Validation, iat rejected id_token was issued too far away from the current time'); toReturn.state = ValidationResult.MaxOffsetExpired; this.handleUnsuccessfulValidation(configuration); return of(toReturn); } const authWellKnownEndPoints = this.storagePersistenceService.read('authWellKnownEndPoints', configuration); if (authWellKnownEndPoints) { if (issValidationOff) this.loggerService.logDebug(configuration, 'iss validation is turned off, this is not recommended!'); else if (!issValidationOff && !this.tokenValidationService.validateIdTokenIss(toReturn.decodedIdToken, authWellKnownEndPoints.issuer, configuration)) { this.loggerService.logWarning(configuration, 'authCallback incorrect iss does not match authWellKnownEndpoints issuer'); toReturn.state = ValidationResult.IssDoesNotMatchIssuer; this.handleUnsuccessfulValidation(configuration); return of(toReturn); } } else { this.loggerService.logWarning(configuration, 'authWellKnownEndpoints is undefined'); toReturn.state = ValidationResult.NoAuthWellKnownEndPoints; this.handleUnsuccessfulValidation(configuration); return of(toReturn); } if (!this.tokenValidationService.validateIdTokenAud(toReturn.decodedIdToken, clientId, configuration)) { this.loggerService.logWarning(configuration, 'authCallback incorrect aud'); toReturn.state = ValidationResult.IncorrectAud; this.handleUnsuccessfulValidation(configuration); return of(toReturn); } if (!this.tokenValidationService.validateIdTokenAzpExistsIfMoreThanOneAud(toReturn.decodedIdToken)) { this.loggerService.logWarning(configuration, 'authCallback missing azp'); toReturn.state = ValidationResult.IncorrectAzp; this.handleUnsuccessfulValidation(configuration); return of(toReturn); } if (!this.tokenValidationService.validateIdTokenAzpValid(toReturn.decodedIdToken, clientId)) { this.loggerService.logWarning(configuration, 'authCallback incorrect azp'); toReturn.state = ValidationResult.IncorrectAzp; this.handleUnsuccessfulValidation(configuration); return of(toReturn); } if (!this.isIdTokenAfterRefreshTokenRequestValid(callbackContext, toReturn.decodedIdToken, configuration)) { this.loggerService.logWarning(configuration, 'authCallback pre, post id_token claims do not match in refresh'); toReturn.state = ValidationResult.IncorrectIdTokenClaimsAfterRefresh; this.handleUnsuccessfulValidation(configuration); return of(toReturn); } if (!isInRefreshTokenFlow && !this.tokenValidationService.validateIdTokenExpNotExpired(toReturn.decodedIdToken, configuration, renewTimeBeforeTokenExpiresInSeconds)) { this.loggerService.logWarning(configuration, 'authCallback id token expired'); toReturn.state = ValidationResult.TokenExpired; this.handleUnsuccessfulValidation(configuration); return of(toReturn); } return this.validateDefault(isCurrentFlowImplicitFlowWithAccessToken, isCurrentFlowCodeFlow, toReturn, configuration, callbackContext); })); } this.loggerService.logDebug(configuration, 'No id_token found, skipping id_token validation'); return this.validateDefault(isCurrentFlowImplicitFlowWithAccessToken, isCurrentFlowCodeFlow, toReturn, configuration, callbackContext); } validateDefault(isCurrentFlowImplicitFlowWithAccessToken, isCurrentFlowCodeFlow, toReturn, configuration, callbackContext) { var _callbackContext_authResult; if (!isCurrentFlowImplicitFlowWithAccessToken && !isCurrentFlowCodeFlow) { toReturn.authResponseIsValid = true; toReturn.state = ValidationResult.Ok; this.handleSuccessfulValidation(configuration); this.handleUnsuccessfulValidation(configuration); return of(toReturn); } if (null == (_callbackContext_authResult = callbackContext.authResult) ? void 0 : _callbackContext_authResult.id_token) { const idTokenHeader = this.tokenHelperService.getHeaderFromToken(toReturn.idToken, false, configuration); if (!isCurrentFlowCodeFlow || !!toReturn.decodedIdToken.at_hash) return this.tokenValidationService.validateIdTokenAtHash(toReturn.accessToken, toReturn.decodedIdToken.at_hash, idTokenHeader.alg, configuration).pipe(map((valid)=>{ if (!valid || !toReturn.accessToken) { this.loggerService.logWarning(configuration, 'authCallback incorrect at_hash'); toReturn.state = ValidationResult.IncorrectAtHash; this.handleUnsuccessfulValidation(configuration); return toReturn; } toReturn.authResponseIsValid = true; toReturn.state = ValidationResult.Ok; this.handleSuccessfulValidation(configuration); return toReturn; })); this.loggerService.logDebug(configuration, 'Code Flow active, and no at_hash in the id_token, skipping check!'); } toReturn.authResponseIsValid = true; toReturn.state = ValidationResult.Ok; this.handleSuccessfulValidation(configuration); return of(toReturn); } isIdTokenAfterRefreshTokenRequestValid(callbackContext, newIdToken, configuration) { const { useRefreshToken, disableRefreshIdTokenAuthTimeValidation } = configuration; if (!useRefreshToken) return true; if (!callbackContext.existingIdToken) return true; const decodedIdToken = this.tokenHelperService.getPayloadFromToken(callbackContext.existingIdToken, false, configuration); if (decodedIdToken.iss !== newIdToken.iss) { this.loggerService.logDebug(configuration, `iss do not match: ${decodedIdToken.iss} ${newIdToken.iss}`); return false; } if (decodedIdToken.azp !== newIdToken.azp) { this.loggerService.logDebug(configuration, `azp do not match: ${decodedIdToken.azp} ${newIdToken.azp}`); return false; } if (decodedIdToken.sub !== newIdToken.sub) { this.loggerService.logDebug(configuration, `sub do not match: ${decodedIdToken.sub} ${newIdToken.sub}`); return false; } if (!this.equalityService.isStringEqualOrNonOrderedArrayEqual(null == decodedIdToken ? void 0 : decodedIdToken.aud, null == newIdToken ? void 0 : newIdToken.aud)) { this.loggerService.logDebug(configuration, `aud in new id_token is not valid: '${null == decodedIdToken ? void 0 : decodedIdToken.aud}' '${newIdToken.aud}'`); return false; } if (disableRefreshIdTokenAuthTimeValidation) return true; if (decodedIdToken.auth_time !== newIdToken.auth_time) { this.loggerService.logDebug(configuration, `auth_time do not match: ${decodedIdToken.auth_time} ${newIdToken.auth_time}`); return false; } return true; } handleSuccessfulValidation(configuration) { const { autoCleanStateAfterAuthentication } = configuration; this.storagePersistenceService.write('authNonce', null, configuration); if (autoCleanStateAfterAuthentication) this.storagePersistenceService.write('authStateControl', '', configuration); this.loggerService.logDebug(configuration, 'authCallback token(s) validated, continue'); } handleUnsuccessfulValidation(configuration) { const { autoCleanStateAfterAuthentication } = configuration; this.storagePersistenceService.write('authNonce', null, configuration); if (autoCleanStateAfterAuthentication) this.storagePersistenceService.write('authStateControl', '', configuration); this.loggerService.logDebug(configuration, 'authCallback token(s) invalid'); } constructor(){ this.storagePersistenceService = inject(StoragePersistenceService); this.tokenValidationService = inject(TokenValidationService); this.tokenHelperService = inject(TokenHelperService); this.loggerService = inject(LoggerService); this.equalityService = inject(EqualityService); this.flowHelper = inject(FlowHelper); } } StateValidationService = _ts_decorate([ Injectable() ], StateValidationService); export { StateValidationService };