UNPKG

angular-oauth2-oidc

Version:

Support for OAuth 2 and OpenId Connect (OIDC) in Angular.

1,150 lines (1,149 loc) 73.6 kB
import { __awaiter, __generator, __extends, __values, __spread } from 'tslib'; import { Injectable, NgZone, Optional, NgModule, InjectionToken } from '@angular/core'; import { HttpClient, HttpHeaders, HttpParams, HTTP_INTERCEPTORS } from '@angular/common/http'; import { Subject, of, race, throwError } from 'rxjs'; import { filter, delay, first, tap, map, catchError } from 'rxjs/operators'; import { CommonModule } from '@angular/common'; import { KEYUTIL, KJUR } from 'jsrsasign'; var LoginOptions = /** @class */ (function () { function LoginOptions() { this.preventClearHashAfterLogin = false; } return LoginOptions; }()); var OAuthLogger = /** @class */ (function () { function OAuthLogger() { } return OAuthLogger; }()); var OAuthStorage = /** @class */ (function () { function OAuthStorage() { } return OAuthStorage; }()); var ReceivedTokens = /** @class */ (function () { function ReceivedTokens() { } return ReceivedTokens; }()); var ValidationHandler = /** @class */ (function () { function ValidationHandler() { } return ValidationHandler; }()); var AbstractValidationHandler = /** @class */ (function () { function AbstractValidationHandler() { } AbstractValidationHandler.prototype.validateAtHash = function (params) { return __awaiter(this, void 0, void 0, function () { var hashAlg, tokenHash, leftMostHalf, tokenHashBase64, atHash, claimsAtHash; return __generator(this, function (_a) { switch (_a.label) { case 0: hashAlg = this.inferHashAlgorithm(params.idTokenHeader); return [4 /*yield*/, this.calcHash(params.accessToken, hashAlg)]; case 1: tokenHash = _a.sent(); leftMostHalf = tokenHash.substr(0, tokenHash.length / 2); tokenHashBase64 = btoa(leftMostHalf); atHash = tokenHashBase64 .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=/g, ''); claimsAtHash = params.idTokenClaims['at_hash'].replace(/=/g, ''); if (atHash !== claimsAtHash) { console.error('exptected at_hash: ' + atHash); console.error('actual at_hash: ' + claimsAtHash); } return [2 /*return*/, atHash === claimsAtHash]; } }); }); }; AbstractValidationHandler.prototype.inferHashAlgorithm = function (jwtHeader) { var alg = jwtHeader['alg']; if (!alg.match(/^.S[0-9]{3}$/)) { throw new Error('Algorithm not supported: ' + alg); } return 'sha-' + alg.substr(2); }; return AbstractValidationHandler; }()); var UrlHelperService = /** @class */ (function () { function UrlHelperService() { } UrlHelperService.prototype.getHashFragmentParams = function (customHashFragment) { var hash = customHashFragment || window.location.hash; hash = decodeURIComponent(hash); if (hash.indexOf('#') !== 0) { return {}; } var questionMarkPosition = hash.indexOf('?'); if (questionMarkPosition > -1) { hash = hash.substr(questionMarkPosition + 1); } else { hash = hash.substr(1); } return this.parseQueryString(hash); }; UrlHelperService.prototype.parseQueryString = function (queryString) { var data = {}; var pairs; var pair; var separatorIndex; var escapedKey; var escapedValue; var key; var value; if (queryString === null) { return data; } pairs = queryString.split('&'); for (var i = 0; i < pairs.length; i++) { pair = pairs[i]; separatorIndex = pair.indexOf('='); if (separatorIndex === -1) { escapedKey = pair; escapedValue = null; } else { escapedKey = pair.substr(0, separatorIndex); escapedValue = pair.substr(separatorIndex + 1); } key = decodeURIComponent(escapedKey); value = decodeURIComponent(escapedValue); if (key.substr(0, 1) === '/') { key = key.substr(1); } data[key] = value; } return data; }; return UrlHelperService; }()); UrlHelperService.decorators = [ { type: Injectable }, ]; var OAuthEvent = /** @class */ (function () { function OAuthEvent(type) { this.type = type; } return OAuthEvent; }()); var OAuthSuccessEvent = /** @class */ (function (_super) { __extends(OAuthSuccessEvent, _super); function OAuthSuccessEvent(type, info) { if (info === void 0) { info = null; } var _this = _super.call(this, type) || this; _this.info = info; return _this; } return OAuthSuccessEvent; }(OAuthEvent)); var OAuthInfoEvent = /** @class */ (function (_super) { __extends(OAuthInfoEvent, _super); function OAuthInfoEvent(type, info) { if (info === void 0) { info = null; } var _this = _super.call(this, type) || this; _this.info = info; return _this; } return OAuthInfoEvent; }(OAuthEvent)); var OAuthErrorEvent = /** @class */ (function (_super) { __extends(OAuthErrorEvent, _super); function OAuthErrorEvent(type, reason, params) { if (params === void 0) { params = null; } var _this = _super.call(this, type) || this; _this.reason = reason; _this.params = params; return _this; } return OAuthErrorEvent; }(OAuthEvent)); function b64DecodeUnicode(str) { var base64 = str.replace(/\-/g, '+').replace(/\_/g, '/'); return decodeURIComponent(atob(base64) .split('') .map(function (c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }) .join('')); } var AuthConfig = /** @class */ (function () { function AuthConfig(json) { this.clientId = ''; this.redirectUri = ''; this.postLogoutRedirectUri = ''; this.loginUrl = ''; this.scope = 'openid profile'; this.resource = ''; this.rngUrl = ''; this.oidc = true; this.requestAccessToken = true; this.options = null; this.issuer = ''; this.logoutUrl = ''; this.clearHashAfterLogin = true; this.tokenEndpoint = null; this.userinfoEndpoint = null; this.responseType = ''; this.showDebugInformation = false; this.silentRefreshRedirectUri = ''; this.silentRefreshMessagePrefix = ''; this.silentRefreshShowIFrame = false; this.siletRefreshTimeout = 1000 * 20; this.silentRefreshTimeout = 1000 * 20; this.dummyClientSecret = null; this.requireHttps = 'remoteOnly'; this.strictDiscoveryDocumentValidation = true; this.jwks = null; this.customQueryParams = null; this.silentRefreshIFrameName = 'angular-oauth-oidc-silent-refresh-iframe'; this.timeoutFactor = 0.75; this.sessionChecksEnabled = false; this.sessionCheckIntervall = 3 * 1000; this.sessionCheckIFrameUrl = null; this.sessionCheckIFrameName = 'angular-oauth-oidc-check-session-iframe'; this.disableAtHashCheck = false; this.skipSubjectCheck = false; this.useIdTokenHintForSilentRefresh = false; this.skipIssuerCheck = false; this.nonceStateSeparator = ';'; this.useHttpBasicAuthForPasswordFlow = false; this.openUri = function (uri) { location.href = uri; }; if (json) { Object.assign(this, json); } } return AuthConfig; }()); var WebHttpUrlEncodingCodec = /** @class */ (function () { function WebHttpUrlEncodingCodec() { } WebHttpUrlEncodingCodec.prototype.encodeKey = function (k) { return encodeURIComponent(k); }; WebHttpUrlEncodingCodec.prototype.encodeValue = function (v) { return encodeURIComponent(v); }; WebHttpUrlEncodingCodec.prototype.decodeKey = function (k) { return decodeURIComponent(k); }; WebHttpUrlEncodingCodec.prototype.decodeValue = function (v) { return decodeURIComponent(v); }; return WebHttpUrlEncodingCodec; }()); var OAuthService = /** @class */ (function (_super) { __extends(OAuthService, _super); function OAuthService(ngZone, http, storage, tokenValidationHandler, config, urlHelper, logger) { var _this = _super.call(this) || this; _this.ngZone = ngZone; _this.http = http; _this.config = config; _this.urlHelper = urlHelper; _this.logger = logger; _this.discoveryDocumentLoaded = false; _this.state = ''; _this.eventsSubject = new Subject(); _this.discoveryDocumentLoadedSubject = new Subject(); _this.grantTypesSupported = []; _this.inImplicitFlow = false; _this.discoveryDocumentLoaded$ = _this.discoveryDocumentLoadedSubject.asObservable(); _this.events = _this.eventsSubject.asObservable(); if (tokenValidationHandler) { _this.tokenValidationHandler = tokenValidationHandler; } if (config) { _this.configure(config); } try { if (storage) { _this.setStorage(storage); } else if (typeof sessionStorage !== 'undefined') { _this.setStorage(sessionStorage); } } catch (e) { console.error('No OAuthStorage provided and cannot access default (sessionStorage).' + 'Consider providing a custom OAuthStorage implementation in your module.', e); } _this.setupRefreshTimer(); return _this; } OAuthService.prototype.configure = function (config) { Object.assign(this, new AuthConfig(), config); this.config = Object.assign((({})), new AuthConfig(), config); if (this.sessionChecksEnabled) { this.setupSessionCheck(); } this.configChanged(); }; OAuthService.prototype.configChanged = function () { }; OAuthService.prototype.restartSessionChecksIfStillLoggedIn = function () { if (this.hasValidIdToken()) { this.initSessionCheck(); } }; OAuthService.prototype.restartRefreshTimerIfStillLoggedIn = function () { this.setupExpirationTimers(); }; OAuthService.prototype.setupSessionCheck = function () { var _this = this; this.events.pipe(filter(function (e) { return e.type === 'token_received'; })).subscribe(function (e) { _this.initSessionCheck(); }); }; OAuthService.prototype.setupAutomaticSilentRefresh = function (params) { var _this = this; if (params === void 0) { params = {}; } this.events.pipe(filter(function (e) { return e.type === 'token_expires'; })).subscribe(function (e) { _this.silentRefresh(params).catch(function (_) { _this.debug('Automatic silent refresh did not work'); }); }); this.restartRefreshTimerIfStillLoggedIn(); }; OAuthService.prototype.loadDiscoveryDocumentAndTryLogin = function (options) { var _this = this; if (options === void 0) { options = null; } return this.loadDiscoveryDocument().then(function (doc) { return _this.tryLogin(options); }); }; OAuthService.prototype.loadDiscoveryDocumentAndLogin = function (options) { var _this = this; if (options === void 0) { options = null; } return this.loadDiscoveryDocumentAndTryLogin(options).then(function (_) { if (!_this.hasValidIdToken() || !_this.hasValidAccessToken()) { _this.initImplicitFlow(); return false; } else { return true; } }); }; OAuthService.prototype.debug = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } if (this.showDebugInformation) { this.logger.debug.apply(console, args); } }; OAuthService.prototype.validateUrlFromDiscoveryDocument = function (url) { var errors = []; var httpsCheck = this.validateUrlForHttps(url); var issuerCheck = this.validateUrlAgainstIssuer(url); if (!httpsCheck) { errors.push('https for all urls required. Also for urls received by discovery.'); } if (!issuerCheck) { errors.push('Every url in discovery document has to start with the issuer url.' + 'Also see property strictDiscoveryDocumentValidation.'); } return errors; }; OAuthService.prototype.validateUrlForHttps = function (url) { if (!url) { return true; } var lcUrl = url.toLowerCase(); if (this.requireHttps === false) { return true; } if ((lcUrl.match(/^http:\/\/localhost($|[:\/])/) || lcUrl.match(/^http:\/\/localhost($|[:\/])/)) && this.requireHttps === 'remoteOnly') { return true; } return lcUrl.startsWith('https://'); }; OAuthService.prototype.validateUrlAgainstIssuer = function (url) { if (!this.strictDiscoveryDocumentValidation) { return true; } if (!url) { return true; } return url.toLowerCase().startsWith(this.issuer.toLowerCase()); }; OAuthService.prototype.setupRefreshTimer = function () { var _this = this; if (typeof window === 'undefined') { this.debug('timer not supported on this plattform'); return; } if (this.hasValidIdToken()) { this.clearAccessTokenTimer(); this.clearIdTokenTimer(); this.setupExpirationTimers(); } this.events.pipe(filter(function (e) { return e.type === 'token_received'; })).subscribe(function (_) { _this.clearAccessTokenTimer(); _this.clearIdTokenTimer(); _this.setupExpirationTimers(); }); }; OAuthService.prototype.setupExpirationTimers = function () { var idTokenExp = this.getIdTokenExpiration() || Number.MAX_VALUE; var accessTokenExp = this.getAccessTokenExpiration() || Number.MAX_VALUE; var useAccessTokenExp = accessTokenExp <= idTokenExp; if (this.hasValidAccessToken() && useAccessTokenExp) { this.setupAccessTokenTimer(); } if (this.hasValidIdToken() && !useAccessTokenExp) { this.setupIdTokenTimer(); } }; OAuthService.prototype.setupAccessTokenTimer = function () { var _this = this; var expiration = this.getAccessTokenExpiration(); var storedAt = this.getAccessTokenStoredAt(); var timeout = this.calcTimeout(storedAt, expiration); this.ngZone.runOutsideAngular(function () { _this.accessTokenTimeoutSubscription = of(new OAuthInfoEvent('token_expires', 'access_token')) .pipe(delay(timeout)) .subscribe(function (e) { _this.ngZone.run(function () { _this.eventsSubject.next(e); }); }); }); }; OAuthService.prototype.setupIdTokenTimer = function () { var _this = this; var expiration = this.getIdTokenExpiration(); var storedAt = this.getIdTokenStoredAt(); var timeout = this.calcTimeout(storedAt, expiration); this.ngZone.runOutsideAngular(function () { _this.idTokenTimeoutSubscription = of(new OAuthInfoEvent('token_expires', 'id_token')) .pipe(delay(timeout)) .subscribe(function (e) { _this.ngZone.run(function () { _this.eventsSubject.next(e); }); }); }); }; OAuthService.prototype.clearAccessTokenTimer = function () { if (this.accessTokenTimeoutSubscription) { this.accessTokenTimeoutSubscription.unsubscribe(); } }; OAuthService.prototype.clearIdTokenTimer = function () { if (this.idTokenTimeoutSubscription) { this.idTokenTimeoutSubscription.unsubscribe(); } }; OAuthService.prototype.calcTimeout = function (storedAt, expiration) { var delta = (expiration - storedAt) * this.timeoutFactor; return delta; }; OAuthService.prototype.setStorage = function (storage) { this._storage = storage; this.configChanged(); }; OAuthService.prototype.loadDiscoveryDocument = function (fullUrl) { var _this = this; if (fullUrl === void 0) { fullUrl = null; } return new Promise(function (resolve, reject) { if (!fullUrl) { fullUrl = _this.issuer || ''; if (!fullUrl.endsWith('/')) { fullUrl += '/'; } fullUrl += '.well-known/openid-configuration'; } if (!_this.validateUrlForHttps(fullUrl)) { reject('issuer must use https, or config value for property requireHttps must allow http'); return; } _this.http.get(fullUrl).subscribe(function (doc) { if (!_this.validateDiscoveryDocument(doc)) { _this.eventsSubject.next(new OAuthErrorEvent('discovery_document_validation_error', null)); reject('discovery_document_validation_error'); return; } _this.loginUrl = doc.authorization_endpoint; _this.logoutUrl = doc.end_session_endpoint || _this.logoutUrl; _this.grantTypesSupported = doc.grant_types_supported; _this.issuer = doc.issuer; _this.tokenEndpoint = doc.token_endpoint; _this.userinfoEndpoint = doc.userinfo_endpoint; _this.jwksUri = doc.jwks_uri; _this.sessionCheckIFrameUrl = doc.check_session_iframe || _this.sessionCheckIFrameUrl; _this.discoveryDocumentLoaded = true; _this.discoveryDocumentLoadedSubject.next(doc); if (_this.sessionChecksEnabled) { _this.restartSessionChecksIfStillLoggedIn(); } _this.loadJwks() .then(function (jwks) { var result = { discoveryDocument: doc, jwks: jwks }; var event = new OAuthSuccessEvent('discovery_document_loaded', result); _this.eventsSubject.next(event); resolve(event); return; }) .catch(function (err) { _this.eventsSubject.next(new OAuthErrorEvent('discovery_document_load_error', err)); reject(err); return; }); }, function (err) { _this.logger.error('error loading discovery document', err); _this.eventsSubject.next(new OAuthErrorEvent('discovery_document_load_error', err)); reject(err); }); }); }; OAuthService.prototype.loadJwks = function () { var _this = this; return new Promise(function (resolve, reject) { if (_this.jwksUri) { _this.http.get(_this.jwksUri).subscribe(function (jwks) { _this.jwks = jwks; _this.eventsSubject.next(new OAuthSuccessEvent('discovery_document_loaded')); resolve(jwks); }, function (err) { _this.logger.error('error loading jwks', err); _this.eventsSubject.next(new OAuthErrorEvent('jwks_load_error', err)); reject(err); }); } else { resolve(null); } }); }; OAuthService.prototype.validateDiscoveryDocument = function (doc) { var errors; if (!this.skipIssuerCheck && doc.issuer !== this.issuer) { this.logger.error('invalid issuer in discovery document', 'expected: ' + this.issuer, 'current: ' + doc.issuer); return false; } errors = this.validateUrlFromDiscoveryDocument(doc.authorization_endpoint); if (errors.length > 0) { this.logger.error('error validating authorization_endpoint in discovery document', errors); return false; } errors = this.validateUrlFromDiscoveryDocument(doc.end_session_endpoint); if (errors.length > 0) { this.logger.error('error validating end_session_endpoint in discovery document', errors); return false; } errors = this.validateUrlFromDiscoveryDocument(doc.token_endpoint); if (errors.length > 0) { this.logger.error('error validating token_endpoint in discovery document', errors); } errors = this.validateUrlFromDiscoveryDocument(doc.userinfo_endpoint); if (errors.length > 0) { this.logger.error('error validating userinfo_endpoint in discovery document', errors); return false; } errors = this.validateUrlFromDiscoveryDocument(doc.jwks_uri); if (errors.length > 0) { this.logger.error('error validating jwks_uri in discovery document', errors); return false; } if (this.sessionChecksEnabled && !doc.check_session_iframe) { this.logger.warn('sessionChecksEnabled is activated but discovery document' + ' does not contain a check_session_iframe field'); } return true; }; OAuthService.prototype.fetchTokenUsingPasswordFlowAndLoadUserProfile = function (userName, password, headers) { var _this = this; if (headers === void 0) { headers = new HttpHeaders(); } return this.fetchTokenUsingPasswordFlow(userName, password, headers).then(function () { return _this.loadUserProfile(); }); }; OAuthService.prototype.loadUserProfile = function () { var _this = this; if (!this.hasValidAccessToken()) { throw new Error('Can not load User Profile without access_token'); } if (!this.validateUrlForHttps(this.userinfoEndpoint)) { throw new Error('userinfoEndpoint must use http, or config value for property requireHttps must allow http'); } return new Promise(function (resolve, reject) { var headers = new HttpHeaders().set('Authorization', 'Bearer ' + _this.getAccessToken()); _this.http.get(_this.userinfoEndpoint, { headers: headers }).subscribe(function (info) { _this.debug('userinfo received', info); var existingClaims = _this.getIdentityClaims() || {}; if (!_this.skipSubjectCheck) { if (_this.oidc && (!existingClaims['sub'] || info.sub !== existingClaims['sub'])) { var err = 'if property oidc is true, the received user-id (sub) has to be the user-id ' + 'of the user that has logged in with oidc.\n' + 'if you are not using oidc but just oauth2 password flow set oidc to false'; reject(err); return; } } info = Object.assign({}, existingClaims, info); _this._storage.setItem('id_token_claims_obj', JSON.stringify(info)); _this.eventsSubject.next(new OAuthSuccessEvent('user_profile_loaded')); resolve(info); }, function (err) { _this.logger.error('error loading user info', err); _this.eventsSubject.next(new OAuthErrorEvent('user_profile_load_error', err)); reject(err); }); }); }; OAuthService.prototype.fetchTokenUsingPasswordFlow = function (userName, password, headers) { var _this = this; if (headers === void 0) { headers = new HttpHeaders(); } if (!this.validateUrlForHttps(this.tokenEndpoint)) { throw new Error('tokenEndpoint must use http, or config value for property requireHttps must allow http'); } return new Promise(function (resolve, reject) { var e_1, _a; var params = new HttpParams({ encoder: new WebHttpUrlEncodingCodec() }) .set('grant_type', 'password') .set('scope', _this.scope) .set('username', userName) .set('password', password); if (_this.useHttpBasicAuthForPasswordFlow) { var header = btoa(_this.clientId + ":" + _this.dummyClientSecret); headers = headers.set('Authorization', 'Basic ' + header); } if (!_this.useHttpBasicAuthForPasswordFlow) { params = params.set('client_id', _this.clientId); } if (!_this.useHttpBasicAuthForPasswordFlow && _this.dummyClientSecret) { params = params.set('client_secret', _this.dummyClientSecret); } if (_this.customQueryParams) { try { for (var _b = __values(Object.getOwnPropertyNames(_this.customQueryParams)), _c = _b.next(); !_c.done; _c = _b.next()) { var key = _c.value; params = params.set(key, _this.customQueryParams[key]); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_1) throw e_1.error; } } } headers = headers.set('Content-Type', 'application/x-www-form-urlencoded'); _this.http .post(_this.tokenEndpoint, params, { headers: headers }) .subscribe(function (tokenResponse) { _this.debug('tokenResponse', tokenResponse); _this.storeAccessTokenResponse(tokenResponse.access_token, tokenResponse.refresh_token, tokenResponse.expires_in, tokenResponse.scope); _this.eventsSubject.next(new OAuthSuccessEvent('token_received')); resolve(tokenResponse); }, function (err) { _this.logger.error('Error performing password flow', err); _this.eventsSubject.next(new OAuthErrorEvent('token_error', err)); reject(err); }); }); }; OAuthService.prototype.refreshToken = function () { var _this = this; if (!this.validateUrlForHttps(this.tokenEndpoint)) { throw new Error('tokenEndpoint must use http, or config value for property requireHttps must allow http'); } return new Promise(function (resolve, reject) { var e_2, _a; var params = new HttpParams() .set('grant_type', 'refresh_token') .set('client_id', _this.clientId) .set('scope', _this.scope) .set('refresh_token', _this._storage.getItem('refresh_token')); if (_this.dummyClientSecret) { params = params.set('client_secret', _this.dummyClientSecret); } if (_this.customQueryParams) { try { for (var _b = __values(Object.getOwnPropertyNames(_this.customQueryParams)), _c = _b.next(); !_c.done; _c = _b.next()) { var key = _c.value; params = params.set(key, _this.customQueryParams[key]); } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_2) throw e_2.error; } } } var headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded'); _this.http .post(_this.tokenEndpoint, params, { headers: headers }) .subscribe(function (tokenResponse) { _this.debug('refresh tokenResponse', tokenResponse); _this.storeAccessTokenResponse(tokenResponse.access_token, tokenResponse.refresh_token, tokenResponse.expires_in, tokenResponse.scope); _this.eventsSubject.next(new OAuthSuccessEvent('token_received')); _this.eventsSubject.next(new OAuthSuccessEvent('token_refreshed')); resolve(tokenResponse); }, function (err) { _this.logger.error('Error performing password flow', err); _this.eventsSubject.next(new OAuthErrorEvent('token_refresh_error', err)); reject(err); }); }); }; OAuthService.prototype.removeSilentRefreshEventListener = function () { if (this.silentRefreshPostMessageEventListener) { window.removeEventListener('message', this.silentRefreshPostMessageEventListener); this.silentRefreshPostMessageEventListener = null; } }; OAuthService.prototype.setupSilentRefreshEventListener = function () { var _this = this; this.removeSilentRefreshEventListener(); this.silentRefreshPostMessageEventListener = function (e) { var expectedPrefix = '#'; if (_this.silentRefreshMessagePrefix) { expectedPrefix += _this.silentRefreshMessagePrefix; } if (!e || !e.data || typeof e.data !== 'string') { return; } var prefixedMessage = e.data; if (!prefixedMessage.startsWith(expectedPrefix)) { return; } var message = '#' + prefixedMessage.substr(expectedPrefix.length); _this.tryLogin({ customHashFragment: message, preventClearHashAfterLogin: true, onLoginError: function (err) { _this.eventsSubject.next(new OAuthErrorEvent('silent_refresh_error', err)); }, onTokenReceived: function () { _this.eventsSubject.next(new OAuthSuccessEvent('silently_refreshed')); } }).catch(function (err) { return _this.debug('tryLogin during silent refresh failed', err); }); }; window.addEventListener('message', this.silentRefreshPostMessageEventListener); }; OAuthService.prototype.silentRefresh = function (params, noPrompt) { var _this = this; if (params === void 0) { params = {}; } if (noPrompt === void 0) { noPrompt = true; } var claims = this.getIdentityClaims() || {}; if (this.useIdTokenHintForSilentRefresh && this.hasValidIdToken()) { params['id_token_hint'] = this.getIdToken(); } if (!this.validateUrlForHttps(this.loginUrl)) { throw new Error('tokenEndpoint must use https, or config value for property requireHttps must allow http'); } if (typeof document === 'undefined') { throw new Error('silent refresh is not supported on this platform'); } var existingIframe = document.getElementById(this.silentRefreshIFrameName); if (existingIframe) { document.body.removeChild(existingIframe); } this.silentRefreshSubject = claims['sub']; var iframe = document.createElement('iframe'); iframe.id = this.silentRefreshIFrameName; this.setupSilentRefreshEventListener(); var redirectUri = this.silentRefreshRedirectUri || this.redirectUri; this.createLoginUrl(null, null, redirectUri, noPrompt, params).then(function (url) { iframe.setAttribute('src', url); if (!_this.silentRefreshShowIFrame) { iframe.style['display'] = 'none'; } document.body.appendChild(iframe); }); var errors = this.events.pipe(filter(function (e) { return e instanceof OAuthErrorEvent; }), first()); var success = this.events.pipe(filter(function (e) { return e.type === 'silently_refreshed'; }), first()); var timeout = of(new OAuthErrorEvent('silent_refresh_timeout', null)).pipe(delay(this.silentRefreshTimeout)); return race([errors, success, timeout]) .pipe(tap(function (e) { if (e.type === 'silent_refresh_timeout') { _this.eventsSubject.next(e); } }), map(function (e) { if (e instanceof OAuthErrorEvent) { throw e; } return e; })) .toPromise(); }; OAuthService.prototype.canPerformSessionCheck = function () { if (!this.sessionChecksEnabled) { return false; } if (!this.sessionCheckIFrameUrl) { console.warn('sessionChecksEnabled is activated but there is no sessionCheckIFrameUrl'); return false; } var sessionState = this.getSessionState(); if (!sessionState) { console.warn('sessionChecksEnabled is activated but there is no session_state'); return false; } if (typeof document === 'undefined') { return false; } return true; }; OAuthService.prototype.setupSessionCheckEventListener = function () { var _this = this; this.removeSessionCheckEventListener(); this.sessionCheckEventListener = function (e) { var origin = e.origin.toLowerCase(); var issuer = _this.issuer.toLowerCase(); _this.debug('sessionCheckEventListener'); if (!issuer.startsWith(origin)) { _this.debug('sessionCheckEventListener', 'wrong origin', origin, 'expected', issuer); } switch (e.data) { case 'unchanged': _this.handleSessionUnchanged(); break; case 'changed': _this.ngZone.run(function () { _this.handleSessionChange(); }); break; case 'error': _this.ngZone.run(function () { _this.handleSessionError(); }); break; } _this.debug('got info from session check inframe', e); }; this.ngZone.runOutsideAngular(function () { window.addEventListener('message', _this.sessionCheckEventListener); }); }; OAuthService.prototype.handleSessionUnchanged = function () { this.debug('session check', 'session unchanged'); }; OAuthService.prototype.handleSessionChange = function () { var _this = this; this.eventsSubject.next(new OAuthInfoEvent('session_changed')); this.stopSessionCheckTimer(); if (this.silentRefreshRedirectUri) { this.silentRefresh().catch(function (_) { return _this.debug('silent refresh failed after session changed'); }); this.waitForSilentRefreshAfterSessionChange(); } else { this.eventsSubject.next(new OAuthInfoEvent('session_terminated')); this.logOut(true); } }; OAuthService.prototype.waitForSilentRefreshAfterSessionChange = function () { var _this = this; this.events .pipe(filter(function (e) { return e.type === 'silently_refreshed' || e.type === 'silent_refresh_timeout' || e.type === 'silent_refresh_error'; }), first()) .subscribe(function (e) { if (e.type !== 'silently_refreshed') { _this.debug('silent refresh did not work after session changed'); _this.eventsSubject.next(new OAuthInfoEvent('session_terminated')); _this.logOut(true); } }); }; OAuthService.prototype.handleSessionError = function () { this.stopSessionCheckTimer(); this.eventsSubject.next(new OAuthInfoEvent('session_error')); }; OAuthService.prototype.removeSessionCheckEventListener = function () { if (this.sessionCheckEventListener) { window.removeEventListener('message', this.sessionCheckEventListener); this.sessionCheckEventListener = null; } }; OAuthService.prototype.initSessionCheck = function () { if (!this.canPerformSessionCheck()) { return; } var existingIframe = document.getElementById(this.sessionCheckIFrameName); if (existingIframe) { document.body.removeChild(existingIframe); } var iframe = document.createElement('iframe'); iframe.id = this.sessionCheckIFrameName; this.setupSessionCheckEventListener(); var url = this.sessionCheckIFrameUrl; iframe.setAttribute('src', url); iframe.style.display = 'none'; document.body.appendChild(iframe); this.startSessionCheckTimer(); }; OAuthService.prototype.startSessionCheckTimer = function () { var _this = this; this.stopSessionCheckTimer(); this.ngZone.runOutsideAngular(function () { _this.sessionCheckTimer = setInterval(_this.checkSession.bind(_this), _this.sessionCheckIntervall); }); }; OAuthService.prototype.stopSessionCheckTimer = function () { if (this.sessionCheckTimer) { clearInterval(this.sessionCheckTimer); this.sessionCheckTimer = null; } }; OAuthService.prototype.checkSession = function () { var iframe = document.getElementById(this.sessionCheckIFrameName); if (!iframe) { this.logger.warn('checkSession did not find iframe', this.sessionCheckIFrameName); } var sessionState = this.getSessionState(); if (!sessionState) { this.stopSessionCheckTimer(); } var message = this.clientId + ' ' + sessionState; iframe.contentWindow.postMessage(message, this.issuer); }; OAuthService.prototype.createLoginUrl = function (state, loginHint, customRedirectUri, noPrompt, params) { var _this = this; if (state === void 0) { state = ''; } if (loginHint === void 0) { loginHint = ''; } if (customRedirectUri === void 0) { customRedirectUri = ''; } if (noPrompt === void 0) { noPrompt = false; } if (params === void 0) { params = {}; } var that = this; var redirectUri; if (customRedirectUri) { redirectUri = customRedirectUri; } else { redirectUri = this.redirectUri; } return this.createAndSaveNonce().then(function (nonce) { var e_3, _a, e_4, _b; if (state) { state = nonce + _this.config.nonceStateSeparator + state; } else { state = nonce; } if (!_this.requestAccessToken && !_this.oidc) { throw new Error('Either requestAccessToken or oidc or both must be true'); } if (_this.config.responseType) { _this.responseType = _this.config.responseType; } else { if (_this.oidc && _this.requestAccessToken) { _this.responseType = 'id_token token'; } else if (_this.oidc && !_this.requestAccessToken) { _this.responseType = 'id_token'; } else { _this.responseType = 'token'; } } var seperationChar = that.loginUrl.indexOf('?') > -1 ? '&' : '?'; var scope = that.scope; if (_this.oidc && !scope.match(/(^|\s)openid($|\s)/)) { scope = 'openid ' + scope; } var url = that.loginUrl + seperationChar + 'response_type=' + encodeURIComponent(that.responseType) + '&client_id=' + encodeURIComponent(that.clientId) + '&state=' + encodeURIComponent(state) + '&redirect_uri=' + encodeURIComponent(redirectUri) + '&scope=' + encodeURIComponent(scope); if (loginHint) { url += '&login_hint=' + encodeURIComponent(loginHint); } if (that.resource) { url += '&resource=' + encodeURIComponent(that.resource); } if (that.oidc) { url += '&nonce=' + encodeURIComponent(nonce); } if (noPrompt) { url += '&prompt=none'; } try { for (var _c = __values(Object.keys(params)), _d = _c.next(); !_d.done; _d = _c.next()) { var key = _d.value; url += '&' + encodeURIComponent(key) + '=' + encodeURIComponent(params[key]); } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (_d && !_d.done && (_a = _c.return)) _a.call(_c); } finally { if (e_3) throw e_3.error; } } if (_this.customQueryParams) { try { for (var _e = __values(Object.getOwnPropertyNames(_this.customQueryParams)), _f = _e.next(); !_f.done; _f = _e.next()) { var key = _f.value; url += '&' + key + '=' + encodeURIComponent(_this.customQueryParams[key]); } } catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (_f && !_f.done && (_b = _e.return)) _b.call(_e); } finally { if (e_4) throw e_4.error; } } } return url; }); }; OAuthService.prototype.initImplicitFlowInternal = function (additionalState, params) { var _this = this; if (additionalState === void 0) { additionalState = ''; } if (params === void 0) { params = ''; } if (this.inImplicitFlow) { return; } this.inImplicitFlow = true; if (!this.validateUrlForHttps(this.loginUrl)) { throw new Error('loginUrl must use http, or config value for property requireHttps must allow http'); } var addParams = {}; var loginHint = null; if (typeof params === 'string') { loginHint = params; } else if (typeof params === 'object') { addParams = params; } this.createLoginUrl(additionalState, loginHint, null, false, addParams) .then(function (url) { location.href = url; }) .catch(function (error) { console.error('Error in initImplicitFlow', error); _this.inImplicitFlow = false; }); }; OAuthService.prototype.initImplicitFlow = function (additionalState, params) { var _this = this; if (additionalState === void 0) { additionalState = ''; } if (params === void 0) { params = ''; } if (this.loginUrl !== '') { this.initImplicitFlowInternal(additionalState, params); } else { this.events .pipe(filter(function (e) { return e.type === 'discovery_document_loaded'; })) .subscribe(function (_) { return _this.initImplicitFlowInternal(additionalState, params); }); } }; OAuthService.prototype.callOnTokenReceivedIfExists = function (options) { var that = this; if (options.onTokenReceived) { var tokenParams = { idClaims: that.getIdentityClaims(), idToken: that.getIdToken(), accessToken: that.getAccessToken(), state: that.state }; options.onTokenReceived(tokenParams); } }; OAuthService.prototype.storeAccessTokenResponse = function (accessToken, refreshToken, expiresIn, grantedScopes) { this._storage.setItem('access_token', accessToken); if (grantedScopes) { this._storage.setItem('granted_scopes', JSON.stringify(grantedScopes.split('+'))); } this._storage.setItem('access_token_stored_at', '' + Date.now()); if (expiresIn) { var expiresInMilliSeconds = expiresIn * 1000; var now = new Date(); var expiresAt = now.getTime() + expiresInMilliSeconds; this._storage.setItem('expires_at', '' + expiresAt); } if (refreshToken) { this._storage.setItem('refresh_token', refreshToken); } }; OAuthService.prototype.tryLogin = function (options) { var _this = this; if (options === void 0) { options = null; } options = options || {}; var parts; if (options.customHashFragment) { parts = this.urlHelper.getHashFragmentParams(options.customHashFragment); } else { parts = this.urlHelper.getHashFragmentParams(); } this.debug('parsed url', parts); var state = parts['state']; var nonceInState = state; if (state) { var idx = state.indexOf(this.config.nonceStateSeparator); if (idx > -1) { nonceInState = state.substr(0, idx); this.state = state.substr(idx + this.config.nonceStateSeparator.length); } } if (parts['error']) { this.debug('error trying to login'); this.handleLoginError(options, parts); var err = new OAuthErrorEvent('token_error', {}, parts); this.eventsSubject.next(err); return Promise.reject(err); } var accessToken = parts['access_token']; var idToken = parts['id_token']; var sessionState = parts['session_state']; var grantedScopes = parts['scope']; if (!this.requestAccessToken && !this.oidc) { return Promise.reject('Either requestAccessToken or oidc (or both) must be true.'); } if (this.requestAccessToken && !accessToken) { return Promise.resolve(false); } if (this.requestAccessToken && !options.disableOAuth2StateCheck && !state) { return Promise.resolve(false); } if (this.oidc && !idToken) { return Promise.resolve(false); } if (this.sessionChecksEnabled && !sessionState) { this.logger.warn('session checks (Session Status Change Notification) ' + 'were activated in the configuration but the id_token ' + 'does not contain a session_state claim'); } if (this.requestAccessToken && !options.disableOAuth2StateCheck) { var success = this.validateNonceForAccessToken(accessToken, nonceInState); if (!success) { var event = new OAuthErrorEvent('invalid_nonce_in_state', null); this.eventsSubject.next(event); return Promise.reject(event); } } if (this.requestAccessToken) { this.storeAccessTokenResponse(accessToken, null, parts['expires_in'] || this.fallbackAccessTokenExpirationTimeInSec, grantedScopes); } if (!this.oidc) { this.eventsSubject.next(new OAuthSuccessEvent('token_received')); if (this.clearHashAfterLogin && !options.preventC