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