angular-oauth2-oidc
Version:
Support for OAuth 2 and OpenId Connect (OIDC) in Angular.
1,150 lines (1,149 loc) • 73.6 kB
JavaScript
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