UNPKG

@bity/oauth2-auth-code-pkce

Version:

An OAuth 2.0 client that ONLY supports Authorization Code flow with PKCE support.

790 lines (788 loc) 36.7 kB
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.OAuth2AuthCodePKCE = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ "use strict"; /** * An implementation of rfc6749#section-4.1 and rfc7636. */ var __extends = (this && this.__extends) || (function () { 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); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 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) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (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 }; } }; var __spreadArrays = (this && this.__spreadArrays) || function () { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; }; Object.defineProperty(exports, "__esModule", { value: true }); ; ; ; /** * A list of OAuth2AuthCodePKCE errors. */ // To "namespace" all errors. var ErrorOAuth2 = /** @class */ (function () { function ErrorOAuth2() { } ErrorOAuth2.prototype.toString = function () { return 'ErrorOAuth2'; }; return ErrorOAuth2; }()); exports.ErrorOAuth2 = ErrorOAuth2; // For really unknown errors. var ErrorUnknown = /** @class */ (function (_super) { __extends(ErrorUnknown, _super); function ErrorUnknown() { return _super !== null && _super.apply(this, arguments) || this; } ErrorUnknown.prototype.toString = function () { return 'ErrorUnknown'; }; return ErrorUnknown; }(ErrorOAuth2)); exports.ErrorUnknown = ErrorUnknown; // Some generic, internal errors that can happen. var ErrorNoAuthCode = /** @class */ (function (_super) { __extends(ErrorNoAuthCode, _super); function ErrorNoAuthCode() { return _super !== null && _super.apply(this, arguments) || this; } ErrorNoAuthCode.prototype.toString = function () { return 'ErrorNoAuthCode'; }; return ErrorNoAuthCode; }(ErrorOAuth2)); exports.ErrorNoAuthCode = ErrorNoAuthCode; var ErrorInvalidReturnedStateParam = /** @class */ (function (_super) { __extends(ErrorInvalidReturnedStateParam, _super); function ErrorInvalidReturnedStateParam() { return _super !== null && _super.apply(this, arguments) || this; } ErrorInvalidReturnedStateParam.prototype.toString = function () { return 'ErrorInvalidReturnedStateParam'; }; return ErrorInvalidReturnedStateParam; }(ErrorOAuth2)); exports.ErrorInvalidReturnedStateParam = ErrorInvalidReturnedStateParam; var ErrorInvalidJson = /** @class */ (function (_super) { __extends(ErrorInvalidJson, _super); function ErrorInvalidJson() { return _super !== null && _super.apply(this, arguments) || this; } ErrorInvalidJson.prototype.toString = function () { return 'ErrorInvalidJson'; }; return ErrorInvalidJson; }(ErrorOAuth2)); exports.ErrorInvalidJson = ErrorInvalidJson; // Errors that occur across many endpoints var ErrorInvalidScope = /** @class */ (function (_super) { __extends(ErrorInvalidScope, _super); function ErrorInvalidScope() { return _super !== null && _super.apply(this, arguments) || this; } ErrorInvalidScope.prototype.toString = function () { return 'ErrorInvalidScope'; }; return ErrorInvalidScope; }(ErrorOAuth2)); exports.ErrorInvalidScope = ErrorInvalidScope; var ErrorInvalidRequest = /** @class */ (function (_super) { __extends(ErrorInvalidRequest, _super); function ErrorInvalidRequest() { return _super !== null && _super.apply(this, arguments) || this; } ErrorInvalidRequest.prototype.toString = function () { return 'ErrorInvalidRequest'; }; return ErrorInvalidRequest; }(ErrorOAuth2)); exports.ErrorInvalidRequest = ErrorInvalidRequest; var ErrorInvalidToken = /** @class */ (function (_super) { __extends(ErrorInvalidToken, _super); function ErrorInvalidToken() { return _super !== null && _super.apply(this, arguments) || this; } ErrorInvalidToken.prototype.toString = function () { return 'ErrorInvalidToken'; }; return ErrorInvalidToken; }(ErrorOAuth2)); exports.ErrorInvalidToken = ErrorInvalidToken; /** * Possible authorization grant errors given by the redirection from the * authorization server. */ var ErrorAuthenticationGrant = /** @class */ (function (_super) { __extends(ErrorAuthenticationGrant, _super); function ErrorAuthenticationGrant() { return _super !== null && _super.apply(this, arguments) || this; } ErrorAuthenticationGrant.prototype.toString = function () { return 'ErrorAuthenticationGrant'; }; return ErrorAuthenticationGrant; }(ErrorOAuth2)); exports.ErrorAuthenticationGrant = ErrorAuthenticationGrant; var ErrorUnauthorizedClient = /** @class */ (function (_super) { __extends(ErrorUnauthorizedClient, _super); function ErrorUnauthorizedClient() { return _super !== null && _super.apply(this, arguments) || this; } ErrorUnauthorizedClient.prototype.toString = function () { return 'ErrorUnauthorizedClient'; }; return ErrorUnauthorizedClient; }(ErrorAuthenticationGrant)); exports.ErrorUnauthorizedClient = ErrorUnauthorizedClient; var ErrorAccessDenied = /** @class */ (function (_super) { __extends(ErrorAccessDenied, _super); function ErrorAccessDenied() { return _super !== null && _super.apply(this, arguments) || this; } ErrorAccessDenied.prototype.toString = function () { return 'ErrorAccessDenied'; }; return ErrorAccessDenied; }(ErrorAuthenticationGrant)); exports.ErrorAccessDenied = ErrorAccessDenied; var ErrorUnsupportedResponseType = /** @class */ (function (_super) { __extends(ErrorUnsupportedResponseType, _super); function ErrorUnsupportedResponseType() { return _super !== null && _super.apply(this, arguments) || this; } ErrorUnsupportedResponseType.prototype.toString = function () { return 'ErrorUnsupportedResponseType'; }; return ErrorUnsupportedResponseType; }(ErrorAuthenticationGrant)); exports.ErrorUnsupportedResponseType = ErrorUnsupportedResponseType; var ErrorServerError = /** @class */ (function (_super) { __extends(ErrorServerError, _super); function ErrorServerError() { return _super !== null && _super.apply(this, arguments) || this; } ErrorServerError.prototype.toString = function () { return 'ErrorServerError'; }; return ErrorServerError; }(ErrorAuthenticationGrant)); exports.ErrorServerError = ErrorServerError; var ErrorTemporarilyUnavailable = /** @class */ (function (_super) { __extends(ErrorTemporarilyUnavailable, _super); function ErrorTemporarilyUnavailable() { return _super !== null && _super.apply(this, arguments) || this; } ErrorTemporarilyUnavailable.prototype.toString = function () { return 'ErrorTemporarilyUnavailable'; }; return ErrorTemporarilyUnavailable; }(ErrorAuthenticationGrant)); exports.ErrorTemporarilyUnavailable = ErrorTemporarilyUnavailable; /** * A list of possible access token response errors. */ var ErrorAccessTokenResponse = /** @class */ (function (_super) { __extends(ErrorAccessTokenResponse, _super); function ErrorAccessTokenResponse() { return _super !== null && _super.apply(this, arguments) || this; } ErrorAccessTokenResponse.prototype.toString = function () { return 'ErrorAccessTokenResponse'; }; return ErrorAccessTokenResponse; }(ErrorOAuth2)); exports.ErrorAccessTokenResponse = ErrorAccessTokenResponse; var ErrorInvalidClient = /** @class */ (function (_super) { __extends(ErrorInvalidClient, _super); function ErrorInvalidClient() { return _super !== null && _super.apply(this, arguments) || this; } ErrorInvalidClient.prototype.toString = function () { return 'ErrorInvalidClient'; }; return ErrorInvalidClient; }(ErrorAccessTokenResponse)); exports.ErrorInvalidClient = ErrorInvalidClient; var ErrorInvalidGrant = /** @class */ (function (_super) { __extends(ErrorInvalidGrant, _super); function ErrorInvalidGrant() { return _super !== null && _super.apply(this, arguments) || this; } ErrorInvalidGrant.prototype.toString = function () { return 'ErrorInvalidGrant'; }; return ErrorInvalidGrant; }(ErrorAccessTokenResponse)); exports.ErrorInvalidGrant = ErrorInvalidGrant; var ErrorUnsupportedGrantType = /** @class */ (function (_super) { __extends(ErrorUnsupportedGrantType, _super); function ErrorUnsupportedGrantType() { return _super !== null && _super.apply(this, arguments) || this; } ErrorUnsupportedGrantType.prototype.toString = function () { return 'ErrorUnsupportedGrantType'; }; return ErrorUnsupportedGrantType; }(ErrorAccessTokenResponse)); exports.ErrorUnsupportedGrantType = ErrorUnsupportedGrantType; /** * WWW-Authenticate error object structure for less error prone handling. */ var ErrorWWWAuthenticate = /** @class */ (function () { function ErrorWWWAuthenticate() { this.realm = ""; this.error = ""; } return ErrorWWWAuthenticate; }()); exports.ErrorWWWAuthenticate = ErrorWWWAuthenticate; exports.RawErrorToErrorClassMap = { invalid_request: ErrorInvalidRequest, invalid_grant: ErrorInvalidGrant, unauthorized_client: ErrorUnauthorizedClient, access_denied: ErrorAccessDenied, unsupported_response_type: ErrorUnsupportedResponseType, invalid_scope: ErrorInvalidScope, server_error: ErrorServerError, temporarily_unavailable: ErrorTemporarilyUnavailable, invalid_client: ErrorInvalidClient, unsupported_grant_type: ErrorUnsupportedGrantType, invalid_json: ErrorInvalidJson, invalid_token: ErrorInvalidToken, }; /** * Translate the raw error strings returned from the server into error classes. */ function toErrorClass(rawError) { return new (exports.RawErrorToErrorClassMap[rawError] || ErrorUnknown)(); } exports.toErrorClass = toErrorClass; /** * A convience function to turn, for example, `Bearer realm="bity.com", * error="invalid_client"` into `{ realm: "bity.com", error: "invalid_client" * }`. */ function fromWWWAuthenticateHeaderStringToObject(a) { var obj = a .slice("Bearer ".length) .replace(/"/g, '') .split(', ') .map(function (tokens) { var _a; var _b = tokens.split('='), k = _b[0], v = _b[1]; return _a = {}, _a[k] = v, _a; }) .reduce(function (a, c) { return (__assign(__assign({}, a), c)); }, {}); return { realm: obj.realm, error: obj.error }; } exports.fromWWWAuthenticateHeaderStringToObject = fromWWWAuthenticateHeaderStringToObject; /** * HTTP headers that we need to access. */ var HEADER_AUTHORIZATION = "Authorization"; var HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; /** * To store the OAuth client's data between websites due to redirection. */ exports.LOCALSTORAGE_ID = "oauth2authcodepkce"; exports.LOCALSTORAGE_STATE = exports.LOCALSTORAGE_ID + "-state"; /** * The maximum length for a code verifier for the best security we can offer. * Please note the NOTE section of RFC 7636 § 4.1 - the length must be >= 43, * but <= 128, **after** base64 url encoding. This means 32 code verifier bytes * encoded will be 43 bytes, or 96 bytes encoded will be 128 bytes. So 96 bytes * is the highest valid value that can be used. */ exports.RECOMMENDED_CODE_VERIFIER_LENGTH = 96; /** * A sensible length for the state's length, for anti-csrf. */ exports.RECOMMENDED_STATE_LENGTH = 32; /** * Character set to generate code verifier defined in rfc7636. */ var PKCE_CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'; /** * OAuth 2.0 client that ONLY supports authorization code flow, with PKCE. * * Many applications structure their OAuth usage in different ways. This class * aims to provide both flexible and easy ways to use this configuration of * OAuth. * * See `example.ts` for how you'd typically use this. * * For others, review this class's methods. */ var OAuth2AuthCodePKCE = /** @class */ (function () { function OAuth2AuthCodePKCE(config) { this.state = {}; this.config = config; this.recoverState(); return this; } /** * Attach the OAuth logic to all fetch requests and translate errors (either * returned as json or through the WWW-Authenticate header) into nice error * classes. */ OAuth2AuthCodePKCE.prototype.decorateFetchHTTPClient = function (fetch) { var _this = this; return function (url, config) { var rest = []; for (var _i = 2; _i < arguments.length; _i++) { rest[_i - 2] = arguments[_i]; } if (!_this.state.isHTTPDecoratorActive) { return fetch.apply(void 0, __spreadArrays([url, config], rest)); } return _this .getAccessToken() .then(function (_a) { var token = _a.token; var configNew = Object.assign({}, config); if (!configNew.headers) { configNew.headers = {}; } configNew.headers[HEADER_AUTHORIZATION] = "Bearer " + token.value; return fetch.apply(void 0, __spreadArrays([url, configNew], rest)); }) .then(function (res) { if (res.ok) { return res; } if (!res.headers.has(HEADER_WWW_AUTHENTICATE.toLowerCase())) { return res; } var error = toErrorClass(fromWWWAuthenticateHeaderStringToObject(res.headers.get(HEADER_WWW_AUTHENTICATE.toLowerCase())).error); if (error instanceof ErrorInvalidToken) { _this.config .onAccessTokenExpiry(function () { return _this.exchangeRefreshTokenForAccessToken(); }); } return Promise.reject(error); }); }; }; /** * If there is an error, it will be passed back as a rejected Promise. * If there is no code, the user should be redirected via * [fetchAuthorizationCode]. */ OAuth2AuthCodePKCE.prototype.isReturningFromAuthServer = function () { var error = OAuth2AuthCodePKCE.extractParamFromUrl(location.href, 'error'); if (error) { return Promise.reject(toErrorClass(error)); } var code = OAuth2AuthCodePKCE.extractParamFromUrl(location.href, 'code'); if (!code) { return Promise.resolve(false); } var state = JSON.parse(localStorage.getItem(exports.LOCALSTORAGE_STATE) || '{}'); var stateQueryParam = OAuth2AuthCodePKCE.extractParamFromUrl(location.href, 'state'); if (stateQueryParam !== state.stateQueryParam) { console.warn("state query string parameter doesn't match the one sent! Possible malicious activity somewhere."); return Promise.reject(new ErrorInvalidReturnedStateParam()); } state.authorizationCode = code; state.hasAuthCodeBeenExchangedForAccessToken = false; localStorage.setItem(exports.LOCALSTORAGE_STATE, JSON.stringify(state)); this.setState(state); return Promise.resolve(true); }; /** * Fetch an authorization grant via redirection. In a sense this function * doesn't return because of the redirect behavior (uses `location.replace`). * * @param oneTimeParams A way to specify "one time" used query string * parameters during the authorization code fetching process, usually for * values which need to change at run-time. */ OAuth2AuthCodePKCE.prototype.fetchAuthorizationCode = function (oneTimeParams) { return __awaiter(this, void 0, void 0, function () { var _a, clientId, extraAuthorizationParams, redirectUrl, scopes, _b, codeChallenge, codeVerifier, stateQueryParam, url, extraParameters; return __generator(this, function (_c) { switch (_c.label) { case 0: this.assertStateAndConfigArePresent(); _a = this.config, clientId = _a.clientId, extraAuthorizationParams = _a.extraAuthorizationParams, redirectUrl = _a.redirectUrl, scopes = _a.scopes; return [4 /*yield*/, OAuth2AuthCodePKCE .generatePKCECodes()]; case 1: _b = _c.sent(), codeChallenge = _b.codeChallenge, codeVerifier = _b.codeVerifier; stateQueryParam = OAuth2AuthCodePKCE .generateRandomState(exports.RECOMMENDED_STATE_LENGTH); this.state = __assign(__assign({}, this.state), { codeChallenge: codeChallenge, codeVerifier: codeVerifier, stateQueryParam: stateQueryParam, isHTTPDecoratorActive: true }); localStorage.setItem(exports.LOCALSTORAGE_STATE, JSON.stringify(this.state)); url = this.config.authorizationUrl + "?response_type=code&" + ("client_id=" + encodeURIComponent(clientId) + "&") + ("redirect_uri=" + encodeURIComponent(redirectUrl) + "&") + ("scope=" + encodeURIComponent(scopes.join(' ')) + "&") + ("state=" + stateQueryParam + "&") + ("code_challenge=" + encodeURIComponent(codeChallenge) + "&") + "code_challenge_method=S256"; if (extraAuthorizationParams || oneTimeParams) { extraParameters = __assign(__assign({}, extraAuthorizationParams), oneTimeParams); url = url + "&" + OAuth2AuthCodePKCE.objectToQueryString(extraParameters); } location.replace(url); return [2 /*return*/]; } }); }); }; /** * Tries to get the current access token. If there is none * it will fetch another one. If it is expired, it will fire * [onAccessTokenExpiry] but it's up to the user to call the refresh token * function. This is because sometimes not using the refresh token facilities * is easier. */ OAuth2AuthCodePKCE.prototype.getAccessToken = function () { var _this = this; this.assertStateAndConfigArePresent(); var onAccessTokenExpiry = this.config.onAccessTokenExpiry; var _a = this.state, accessToken = _a.accessToken, authorizationCode = _a.authorizationCode, explicitlyExposedTokens = _a.explicitlyExposedTokens, hasAuthCodeBeenExchangedForAccessToken = _a.hasAuthCodeBeenExchangedForAccessToken, refreshToken = _a.refreshToken, scopes = _a.scopes; if (!authorizationCode) { return Promise.reject(new ErrorNoAuthCode()); } if (this.authCodeForAccessTokenRequest) { return this.authCodeForAccessTokenRequest; } if (!this.isAuthorized() || !hasAuthCodeBeenExchangedForAccessToken) { this.authCodeForAccessTokenRequest = this.exchangeAuthCodeForAccessToken(); return this.authCodeForAccessTokenRequest; } // Depending on the server (and config), refreshToken may not be available. if (refreshToken && this.isAccessTokenExpired()) { return onAccessTokenExpiry(function () { return _this.exchangeRefreshTokenForAccessToken(); }); } return Promise.resolve({ token: accessToken, explicitlyExposedTokens: explicitlyExposedTokens, scopes: scopes, refreshToken: refreshToken }); }; /** * Refresh an access token from the remote service. */ OAuth2AuthCodePKCE.prototype.exchangeRefreshTokenForAccessToken = function () { var _this = this; var _a; this.assertStateAndConfigArePresent(); var _b = this.config, extraRefreshParams = _b.extraRefreshParams, clientId = _b.clientId, tokenUrl = _b.tokenUrl; var refreshToken = this.state.refreshToken; if (!refreshToken) { console.warn('No refresh token is present.'); } var url = tokenUrl; var body = "grant_type=refresh_token&" + ("refresh_token=" + ((_a = refreshToken) === null || _a === void 0 ? void 0 : _a.value) + "&") + ("client_id=" + clientId); if (extraRefreshParams) { body = url + "&" + OAuth2AuthCodePKCE.objectToQueryString(extraRefreshParams); } return fetch(url, { method: 'POST', body: body, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }) .then(function (res) { return res.status >= 400 ? res.json().then(function (data) { return Promise.reject(data); }) : res.json(); }) .then(function (json) { var access_token = json.access_token, expires_in = json.expires_in, refresh_token = json.refresh_token, scope = json.scope; var explicitlyExposedTokens = _this.config.explicitlyExposedTokens; var scopes = []; var tokensToExpose = {}; var accessToken = { value: access_token, expiry: (new Date(Date.now() + (parseInt(expires_in) * 1000))).toString() }; _this.state.accessToken = accessToken; if (refresh_token) { var refreshToken_1 = { value: refresh_token }; _this.state.refreshToken = refreshToken_1; } if (explicitlyExposedTokens) { tokensToExpose = Object.fromEntries(explicitlyExposedTokens .map(function (tokenName) { return [tokenName, json[tokenName]]; }) .filter(function (_a) { var _ = _a[0], tokenValue = _a[1]; return tokenValue !== undefined; })); _this.state.explicitlyExposedTokens = tokensToExpose; } if (scope) { // Multiple scopes are passed and delimited by spaces, // despite using the singular name "scope". scopes = scope.split(' '); _this.state.scopes = scopes; } localStorage.setItem(exports.LOCALSTORAGE_STATE, JSON.stringify(_this.state)); var accessContext = { token: accessToken, scopes: scopes }; if (explicitlyExposedTokens) { accessContext.explicitlyExposedTokens = tokensToExpose; } return accessContext; }) .catch(function (data) { var onInvalidGrant = _this.config.onInvalidGrant; var error = data.error || 'There was a network error.'; switch (error) { case 'invalid_grant': onInvalidGrant(function () { return _this.fetchAuthorizationCode(); }); break; default: break; } return Promise.reject(toErrorClass(error)); }); }; /** * Get the scopes that were granted by the authorization server. */ OAuth2AuthCodePKCE.prototype.getGrantedScopes = function () { return this.state.scopes; }; /** * Signals if OAuth HTTP decorating should be active or not. */ OAuth2AuthCodePKCE.prototype.isHTTPDecoratorActive = function (isActive) { this.state.isHTTPDecoratorActive = isActive; localStorage.setItem(exports.LOCALSTORAGE_STATE, JSON.stringify(this.state)); }; /** * Tells if the client is authorized or not. This means the client has at * least once successfully fetched an access token. The access token could be * expired. */ OAuth2AuthCodePKCE.prototype.isAuthorized = function () { return !!this.state.accessToken; }; /** * Checks to see if the access token has expired. */ OAuth2AuthCodePKCE.prototype.isAccessTokenExpired = function () { var accessToken = this.state.accessToken; return Boolean(accessToken && (new Date()) >= (new Date(accessToken.expiry))); }; /** * Resets the state of the client. Equivalent to "logging out" the user. */ OAuth2AuthCodePKCE.prototype.reset = function () { this.setState({}); this.authCodeForAccessTokenRequest = undefined; }; /** * If the state or config are missing, it means the client is in a bad state. * This should never happen, but the check is there just in case. */ OAuth2AuthCodePKCE.prototype.assertStateAndConfigArePresent = function () { if (!this.state || !this.config) { console.error('state:', this.state, 'config:', this.config); throw new Error('state or config is not set.'); } }; /** * Fetch an access token from the remote service. You may pass a custom * authorization grant code for any reason, but this is non-standard usage. */ OAuth2AuthCodePKCE.prototype.exchangeAuthCodeForAccessToken = function (codeOverride) { var _this = this; this.assertStateAndConfigArePresent(); var _a = this.state, _b = _a.authorizationCode, authorizationCode = _b === void 0 ? codeOverride : _b, _c = _a.codeVerifier, codeVerifier = _c === void 0 ? '' : _c; var _d = this.config, clientId = _d.clientId, onInvalidGrant = _d.onInvalidGrant, redirectUrl = _d.redirectUrl; if (!codeVerifier) { console.warn('No code verifier is being sent.'); } else if (!authorizationCode) { console.warn('No authorization grant code is being passed.'); } var url = this.config.tokenUrl; var body = "grant_type=authorization_code&" + ("code=" + encodeURIComponent(authorizationCode || '') + "&") + ("redirect_uri=" + encodeURIComponent(redirectUrl) + "&") + ("client_id=" + encodeURIComponent(clientId) + "&") + ("code_verifier=" + codeVerifier); return fetch(url, { method: 'POST', body: body, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }) .then(function (res) { var jsonPromise = res.json() .catch(function (_) { return ({ error: 'invalid_json' }); }); if (!res.ok) { return jsonPromise.then(function (_a) { var error = _a.error; switch (error) { case 'invalid_grant': onInvalidGrant(function () { return _this.fetchAuthorizationCode(); }); break; default: break; } return Promise.reject(toErrorClass(error)); }); } return jsonPromise.then(function (json) { var access_token = json.access_token, expires_in = json.expires_in, refresh_token = json.refresh_token, scope = json.scope; var explicitlyExposedTokens = _this.config.explicitlyExposedTokens; var scopes = []; var tokensToExpose = {}; _this.state.hasAuthCodeBeenExchangedForAccessToken = true; _this.authCodeForAccessTokenRequest = undefined; var accessToken = { value: access_token, expiry: (new Date(Date.now() + (parseInt(expires_in) * 1000))).toString() }; _this.state.accessToken = accessToken; if (refresh_token) { var refreshToken = { value: refresh_token }; _this.state.refreshToken = refreshToken; } if (explicitlyExposedTokens) { tokensToExpose = Object.fromEntries(explicitlyExposedTokens .map(function (tokenName) { return [tokenName, json[tokenName]]; }) .filter(function (_a) { var _ = _a[0], tokenValue = _a[1]; return tokenValue !== undefined; })); _this.state.explicitlyExposedTokens = tokensToExpose; } if (scope) { // Multiple scopes are passed and delimited by spaces, // despite using the singular name "scope". scopes = scope.split(' '); _this.state.scopes = scopes; } localStorage.setItem(exports.LOCALSTORAGE_STATE, JSON.stringify(_this.state)); var accessContext = { token: accessToken, scopes: scopes }; if (explicitlyExposedTokens) { accessContext.explicitlyExposedTokens = tokensToExpose; } return accessContext; }); }); }; OAuth2AuthCodePKCE.prototype.recoverState = function () { this.state = JSON.parse(localStorage.getItem(exports.LOCALSTORAGE_STATE) || '{}'); return this; }; OAuth2AuthCodePKCE.prototype.setState = function (state) { this.state = state; localStorage.setItem(exports.LOCALSTORAGE_STATE, JSON.stringify(state)); return this; }; /** * Implements *base64url-encode* (RFC 4648 § 5) without padding, which is NOT * the same as regular base64 encoding. */ OAuth2AuthCodePKCE.base64urlEncode = function (value) { var base64 = btoa(value); base64 = base64.replace(/\+/g, '-'); base64 = base64.replace(/\//g, '_'); base64 = base64.replace(/=/g, ''); return base64; }; /** * Extracts a query string parameter. */ OAuth2AuthCodePKCE.extractParamFromUrl = function (url, param) { var queryString = url.split('?'); if (queryString.length < 2) { return ''; } // Account for hash URLs that SPAs usually use. queryString = queryString[1].split('#'); var parts = queryString[0] .split('&') .reduce(function (a, s) { return a.concat(s.split('=')); }, []); if (parts.length < 2) { return ''; } var paramIdx = parts.indexOf(param); return decodeURIComponent(paramIdx >= 0 ? parts[paramIdx + 1] : ''); }; /** * Converts the keys and values of an object to a url query string */ OAuth2AuthCodePKCE.objectToQueryString = function (dict) { return Object.entries(dict).map(function (_a) { var key = _a[0], val = _a[1]; return key + "=" + encodeURIComponent(val); }).join('&'); }; /** * Generates a code_verifier and code_challenge, as specified in rfc7636. */ OAuth2AuthCodePKCE.generatePKCECodes = function () { var output = new Uint32Array(exports.RECOMMENDED_CODE_VERIFIER_LENGTH); crypto.getRandomValues(output); var codeVerifier = OAuth2AuthCodePKCE.base64urlEncode(Array .from(output) .map(function (num) { return PKCE_CHARSET[num % PKCE_CHARSET.length]; }) .join('')); return crypto .subtle .digest('SHA-256', (new TextEncoder()).encode(codeVerifier)) .then(function (buffer) { var hash = new Uint8Array(buffer); var binary = ''; var hashLength = hash.byteLength; for (var i = 0; i < hashLength; i++) { binary += String.fromCharCode(hash[i]); } return binary; }) .then(OAuth2AuthCodePKCE.base64urlEncode) .then(function (codeChallenge) { return ({ codeChallenge: codeChallenge, codeVerifier: codeVerifier }); }); }; /** * Generates random state to be passed for anti-csrf. */ OAuth2AuthCodePKCE.generateRandomState = function (lengthOfState) { var output = new Uint32Array(lengthOfState); crypto.getRandomValues(output); return Array .from(output) .map(function (num) { return PKCE_CHARSET[num % PKCE_CHARSET.length]; }) .join(''); }; return OAuth2AuthCodePKCE; }()); exports.OAuth2AuthCodePKCE = OAuth2AuthCodePKCE; },{}]},{},[1])(1) });