angular-oauth2-oidc
Version:
Support for OAuth 2 and OpenId Connect (OIDC) in Angular.
1,159 lines (1,150 loc) • 77.7 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core'), require('@angular/common/http'), require('rxjs'), require('rxjs/operators'), require('@angular/common'), require('jsrsasign')) :
typeof define === 'function' && define.amd ? define('angular-oauth2-oidc', ['exports', '@angular/core', '@angular/common/http', 'rxjs', 'rxjs/operators', '@angular/common', 'jsrsasign'], factory) :
(factory((global['angular-oauth2-oidc'] = {}),global.ng.core,global.ng.common.http,global.rxjs,global.Rx.Observable.prototype,global.ng.common,global.jsrsasign));
}(this, (function (exports,core,http,rxjs,operators,common,jsrsasign) { 'use strict';
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/* global Reflect, Promise */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
function __awaiter(thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
function __values(o) {
var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0;
if (m) return m.call(o);
return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
}
function __read(o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
}
function __spread() {
for (var ar = [], i = 0; i < arguments.length; i++)
ar = ar.concat(__read(arguments[i]));
return ar;
}
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: core.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$$1, storage, tokenValidationHandler, config, urlHelper, logger) {
var _this = _super.call(this) || this;
_this.ngZone = ngZone;
_this.http = http$$1;
_this.config = config;
_this.urlHelper = urlHelper;
_this.logger = logger;
_this.discoveryDocumentLoaded = false;
_this.state = '';
_this.eventsSubject = new rxjs.Subject();
_this.discoveryDocumentLoadedSubject = new rxjs.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(operators.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(operators.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(operators.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 = rxjs.of(new OAuthInfoEvent('token_expires', 'access_token'))
.pipe(operators.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 = rxjs.of(new OAuthInfoEvent('token_expires', 'id_token'))
.pipe(operators.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 http.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 http.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 http.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 http.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 http.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 http.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(operators.filter(function (e) { return e instanceof OAuthErrorEvent; }), operators.first());
var success = this.events.pipe(operators.filter(function (e) { return e.type === 'silently_refreshed'; }), operators.first());
var timeout = rxjs.of(new OAuthErrorEvent('silent_refresh_timeout', null)).pipe(operators.delay(this.silentRefreshTimeout));
return rxjs.race([errors, success, timeout])
.pipe(operators.tap(function (e) {
if (e.type === 'silent_refresh_timeout') {
_this.eventsSubject.next(e);
}
}), operators.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(operators.filter(function (e) { return e.type === 'silently_refreshed' ||
e.type === 'silent_refresh_timeout' ||
e.type === 'silent_refresh_error'; }), operators.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(operators.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 = {