New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@bity/oauth2-auth-code-pkce

Package Overview
Dependencies
Maintainers
2
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@bity/oauth2-auth-code-pkce - npm Package Compare versions

Comparing version 2.0.1 to 2.1.0

tests/panel.html

328

index.js
/**
* 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 () {

@@ -53,3 +66,168 @@ __assign = Object.assign || function(t) {

;
;
/**
* A list of OAuth2AuthCodePKCE errors.
*/
// To "namespace" all errors.
var ErrorOAuth2 = /** @class */ (function () {
function ErrorOAuth2() {
}
return ErrorOAuth2;
}());
export { ErrorOAuth2 };
// For really unknown errors.
var ErrorUnknown = /** @class */ (function (_super) {
__extends(ErrorUnknown, _super);
function ErrorUnknown() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorUnknown;
}(ErrorOAuth2));
export { 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;
}
return ErrorNoAuthCode;
}(ErrorOAuth2));
export { ErrorNoAuthCode };
var ErrorInvalidReturnedStateParam = /** @class */ (function (_super) {
__extends(ErrorInvalidReturnedStateParam, _super);
function ErrorInvalidReturnedStateParam() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorInvalidReturnedStateParam;
}(ErrorOAuth2));
export { ErrorInvalidReturnedStateParam };
var ErrorInvalidJson = /** @class */ (function (_super) {
__extends(ErrorInvalidJson, _super);
function ErrorInvalidJson() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorInvalidJson;
}(ErrorOAuth2));
export { 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;
}
return ErrorInvalidScope;
}(ErrorOAuth2));
export { ErrorInvalidScope };
var ErrorInvalidRequest = /** @class */ (function (_super) {
__extends(ErrorInvalidRequest, _super);
function ErrorInvalidRequest() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorInvalidRequest;
}(ErrorOAuth2));
export { ErrorInvalidRequest };
/**
* 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;
}
return ErrorAuthenticationGrant;
}(ErrorOAuth2));
export { ErrorAuthenticationGrant };
var ErrorUnauthorizedClient = /** @class */ (function (_super) {
__extends(ErrorUnauthorizedClient, _super);
function ErrorUnauthorizedClient() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorUnauthorizedClient;
}(ErrorAuthenticationGrant));
export { ErrorUnauthorizedClient };
var ErrorAccessDenied = /** @class */ (function (_super) {
__extends(ErrorAccessDenied, _super);
function ErrorAccessDenied() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorAccessDenied;
}(ErrorAuthenticationGrant));
export { ErrorAccessDenied };
var ErrorUnsupportedResponseType = /** @class */ (function (_super) {
__extends(ErrorUnsupportedResponseType, _super);
function ErrorUnsupportedResponseType() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorUnsupportedResponseType;
}(ErrorAuthenticationGrant));
export { ErrorUnsupportedResponseType };
var ErrorServerError = /** @class */ (function (_super) {
__extends(ErrorServerError, _super);
function ErrorServerError() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorServerError;
}(ErrorAuthenticationGrant));
export { ErrorServerError };
var ErrorTemporarilyUnavailable = /** @class */ (function (_super) {
__extends(ErrorTemporarilyUnavailable, _super);
function ErrorTemporarilyUnavailable() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorTemporarilyUnavailable;
}(ErrorAuthenticationGrant));
export { 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;
}
return ErrorAccessTokenResponse;
}(ErrorOAuth2));
export { ErrorAccessTokenResponse };
var ErrorInvalidClient = /** @class */ (function (_super) {
__extends(ErrorInvalidClient, _super);
function ErrorInvalidClient() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorInvalidClient;
}(ErrorAccessTokenResponse));
export { ErrorInvalidClient };
var ErrorInvalidGrant = /** @class */ (function (_super) {
__extends(ErrorInvalidGrant, _super);
function ErrorInvalidGrant() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorInvalidGrant;
}(ErrorAccessTokenResponse));
export { ErrorInvalidGrant };
var ErrorUnsupportedGrantType = /** @class */ (function (_super) {
__extends(ErrorUnsupportedGrantType, _super);
function ErrorUnsupportedGrantType() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorUnsupportedGrantType;
}(ErrorAccessTokenResponse));
export { ErrorUnsupportedGrantType };
export var 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,
};
export function toErrorClass(rawError) {
return new (RawErrorToErrorClassMap[rawError] || ErrorUnknown)();
}
/**
* To store the OAuth client's data between websites due to redirection.

@@ -87,5 +265,2 @@ */

this.recoverState();
if (this.captureGrantCodeAndNotifyIfReturning()) {
this.getAccessToken();
}
return this;

@@ -104,14 +279,14 @@ }

/**
* If there is an error, it will be passed back as a rejected Promies.
* 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
* [fetchAuthorizationGrant].
* [fetchAuthorizationCode].
*/
OAuth2AuthCodePKCE.prototype.captureGrantCodeAndNotifyIfReturning = function () {
OAuth2AuthCodePKCE.prototype.isReturningFromAuthServer = function () {
var error = OAuth2AuthCodePKCE.extractParamFromUrl(location.href, 'error');
if (error) {
return false;
return Promise.reject(toErrorClass(error));
}
var code = OAuth2AuthCodePKCE.extractParamFromUrl(location.href, 'code');
if (!code) {
return false;
return Promise.resolve(false);
}

@@ -122,8 +297,9 @@ var state = JSON.parse(localStorage.getItem(LOCALSTORAGE_STATE) || '{}');

console.warn("state query string parameter doesn't match the one sent! Possible malicious activity somewhere.");
return false;
return Promise.reject(new ErrorInvalidReturnedStateParam());
}
state.authorizationGrantCode = code;
state.authorizationCode = code;
state.hasAuthCodeBeenExchangedForAccessToken = false;
localStorage.setItem(LOCALSTORAGE_STATE, JSON.stringify(state));
this.setState(state);
return true;
return Promise.resolve(true);
};

@@ -133,10 +309,7 @@ /**

* authorization grant code for any reason, but this is non-standard usage.
*
* This method should never return undefined, but was put here to satisfy the
* TypeScript typechecker.
*/
OAuth2AuthCodePKCE.prototype.fetchAccessTokenWithGrant = function (codeOverride) {
OAuth2AuthCodePKCE.prototype.exchangeAuthCodeForAccessToken = function (codeOverride) {
var _this = this;
this.assertStateAndConfigArePresent();
var _a = this.state, _b = _a.authorizationGrantCode, authorizationGrantCode = _b === void 0 ? codeOverride : _b, _c = _a.codeVerifier, codeVerifier = _c === void 0 ? '' : _c;
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;

@@ -146,3 +319,3 @@ if (!codeVerifier) {

}
else if (!authorizationGrantCode) {
else if (!authorizationCode) {
console.warn('No authorization grant code is being passed.');

@@ -152,3 +325,3 @@ }

var body = "grant_type=authorization_code&"
+ ("code=" + encodeURIComponent(authorizationGrantCode || '') + "&")
+ ("code=" + encodeURIComponent(authorizationCode || '') + "&")
+ ("redirect_uri=" + encodeURIComponent(redirectUrl) + "&")

@@ -164,32 +337,42 @@ + ("client_id=" + encodeURIComponent(clientId) + "&")

})
.then(function (res) { return res.status === 400 ? Promise.reject(res.json()) : res.json(); })
.then(function (_a) {
var access_token = _a.access_token, expires_in = _a.expires_in, refresh_token = _a.refresh_token;
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
.then(function (res) {
var jsonPromise = res.json()
.catch(function (jsonDecodeError) { 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 (_a) {
var access_token = _a.access_token, expires_in = _a.expires_in, refresh_token = _a.refresh_token, scope = _a.scope;
var scopes = [];
_this.state.hasAuthCodeBeenExchangedForAccessToken = true;
var accessToken = {
value: access_token,
expiry: (new Date(Date.now() + (parseInt(expires_in) * 1000))).toString()
};
_this.state.refreshToken = refreshToken;
}
localStorage.setItem(LOCALSTORAGE_STATE, JSON.stringify(_this.state));
return accessToken;
})
.catch(function (jsonPromise) { return jsonPromise.then(function (json) { return Promise.reject(json); }); })
.catch(function (data) {
console.log(data);
var error = data.error || 'There was a network error.';
switch (error) {
case 'invalid_grant':
onInvalidGrant(function () { return _this
.fetchAuthorizationGrant()
.catch(function (error) { return console.error(error); }); });
default:
break;
}
return Promise.reject(error);
_this.state.accessToken = accessToken;
if (refresh_token) {
var refreshToken = {
value: refresh_token
};
_this.state.refreshToken = refreshToken;
}
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(LOCALSTORAGE_STATE, JSON.stringify(_this.state));
return { token: accessToken, scopes: scopes };
});
});

@@ -201,3 +384,3 @@ };

*/
OAuth2AuthCodePKCE.prototype.fetchAuthorizationGrant = function () {
OAuth2AuthCodePKCE.prototype.fetchAuthorizationCode = function () {
return __awaiter(this, void 0, void 0, function () {

@@ -240,4 +423,2 @@ var _a, clientId, redirectUrl, scopes, _b, codeChallenge, codeVerifier, stateQueryParam, url;

* is easier.
*
* Typically you always want to use this over [fetchAccessTokenWithGrant].
*/

@@ -248,21 +429,17 @@ OAuth2AuthCodePKCE.prototype.getAccessToken = function () {

var onAccessTokenExpiry = this.config.onAccessTokenExpiry;
var _a = this.state, accessToken = _a.accessToken, authorizationGrantCode = _a.authorizationGrantCode, refreshToken = _a.refreshToken;
if (!authorizationGrantCode) {
return Promise.reject({ error: 'no_auth_code' });
var _a = this.state, accessToken = _a.accessToken, authorizationCode = _a.authorizationCode, hasAuthCodeBeenExchangedForAccessToken = _a.hasAuthCodeBeenExchangedForAccessToken, refreshToken = _a.refreshToken, scopes = _a.scopes;
if (!authorizationCode) {
return Promise.reject(new ErrorNoAuthCode());
}
if (!accessToken) {
if (!accessToken || !hasAuthCodeBeenExchangedForAccessToken) {
console.log('Getting access token with grant');
return this.fetchAccessTokenWithGrant();
return this.exchangeAuthCodeForAccessToken();
}
// If there's no refresh token, attempt with the auth grant code.
if (!refreshToken && (new Date()) >= (new Date(accessToken.expiry))) {
console.log('Renewing access token with grant');
return onAccessTokenExpiry(function () { return _this.fetchAccessTokenWithGrant(); });
}
if ((new Date()) >= (new Date(accessToken.expiry))) {
// Depending on the server (and config), refreshToken may not be available.
if (refreshToken && (new Date()) >= (new Date(accessToken.expiry))) {
console.log('Renewing access token with refresh token');
return onAccessTokenExpiry(function () { return _this.refreshAccessToken(); });
return onAccessTokenExpiry(function () { return _this.exchangeRefreshTokenForAccessToken(); });
}
console.log('Access token is accessible and valid');
return Promise.resolve(accessToken);
return Promise.resolve({ token: accessToken, scopes: scopes });
};

@@ -272,3 +449,3 @@ /**

*/
OAuth2AuthCodePKCE.prototype.refreshAccessToken = function () {
OAuth2AuthCodePKCE.prototype.exchangeRefreshTokenForAccessToken = function () {
var _this = this;

@@ -293,3 +470,4 @@ this.assertStateAndConfigArePresent();

.then(function (_a) {
var access_token = _a.access_token, expires_in = _a.expires_in, refresh_token = _a.refresh_token;
var access_token = _a.access_token, expires_in = _a.expires_in, refresh_token = _a.refresh_token, scope = _a.scope;
var scopes = [];
var accessToken = {

@@ -306,4 +484,10 @@ value: access_token,

}
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(LOCALSTORAGE_STATE, JSON.stringify(_this.state));
return accessToken;
return { token: accessToken, scopes: scopes };
})

@@ -315,5 +499,4 @@ .catch(function (jsonPromise) { return Promise.reject(jsonPromise); })

case 'invalid_grant':
onInvalidGrant(function () { return _this
.fetchAuthorizationGrant()
.catch(function (error) { return console.error(error); }); });
onInvalidGrant(function () { return _this.fetchAuthorizationCode(); });
break;
default:

@@ -325,2 +508,5 @@ break;

};
OAuth2AuthCodePKCE.prototype.getGrantedScopes = function () {
return this.state.scopes;
};
OAuth2AuthCodePKCE.prototype.recoverState = function () {

@@ -336,4 +522,4 @@ this.state = JSON.parse(localStorage.getItem(LOCALSTORAGE_STATE) || '{}');

/**
* Implements *base64url-encode*, which is NOT the same as regular base64
* encoding.
* Implements *base64url-encode* (RFC 4648 § 5) without padding, which is NOT
* the same as regular base64 encoding.
*/

@@ -340,0 +526,0 @@ OAuth2AuthCodePKCE.base64urlEncode = function (value) {

@@ -8,4 +8,4 @@ /**

clientId: string;
onAccessTokenExpiry: (refreshAccessToken: () => Promise<AccessToken>) => Promise<AccessToken | undefined>;
onInvalidGrant: (refreshGrantOrRefreshToken: () => Promise<void>) => void;
onAccessTokenExpiry: (refreshAccessToken: () => Promise<AccessContext>) => Promise<AccessContext>;
onInvalidGrant: (refreshAuthCodeOrRefreshToken: () => Promise<void>) => void;
redirectUrl: URL;

@@ -22,8 +22,10 @@ scopes: string[];

export interface State {
authorizationGrantCode?: string;
accessToken?: AccessToken;
authorizationCode?: string;
codeChallenge?: string;
codeVerifier?: string;
accessToken?: AccessToken;
hasAuthCodeBeenExchangedForAccessToken?: boolean;
refreshToken?: RefreshToken;
stateQueryParam?: string;
scopes?: string[];
}

@@ -40,5 +42,67 @@

export type Scopes = string[];
export interface AccessContext {
token: AccessToken;
scopes: Scopes;
};
export type URL = string;
/**
* A list of OAuth2AuthCodePKCE errors.
*/
// To "namespace" all errors.
export class ErrorOAuth2 { }
// For really unknown errors.
export class ErrorUnknown extends ErrorOAuth2 { }
// Some generic, internal errors that can happen.
export class ErrorNoAuthCode extends ErrorOAuth2 { }
export class ErrorInvalidReturnedStateParam extends ErrorOAuth2 { }
export class ErrorInvalidJson extends ErrorOAuth2 { }
// Errors that occur across many endpoints
export class ErrorInvalidScope extends ErrorOAuth2 { }
export class ErrorInvalidRequest extends ErrorOAuth2 { }
/**
* Possible authorization grant errors given by the redirection from the
* authorization server.
*/
export class ErrorAuthenticationGrant extends ErrorOAuth2 { }
export class ErrorUnauthorizedClient extends ErrorAuthenticationGrant { }
export class ErrorAccessDenied extends ErrorAuthenticationGrant { }
export class ErrorUnsupportedResponseType extends ErrorAuthenticationGrant { }
export class ErrorServerError extends ErrorAuthenticationGrant { }
export class ErrorTemporarilyUnavailable extends ErrorAuthenticationGrant { }
/**
* A list of possible access token response errors.
*/
export class ErrorAccessTokenResponse extends ErrorOAuth2 { }
export class ErrorInvalidClient extends ErrorAccessTokenResponse { }
export class ErrorInvalidGrant extends ErrorAccessTokenResponse { }
export class ErrorUnsupportedGrantType extends ErrorAccessTokenResponse { }
export const RawErrorToErrorClassMap: { [_: string]: any } = {
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,
};
export function toErrorClass(rawError: string): ErrorOAuth2 {
return new (RawErrorToErrorClassMap[rawError] || ErrorUnknown)();
}
/**
* To store the OAuth client's data between websites due to redirection.

@@ -82,5 +146,2 @@ */

this.recoverState();
if (this.captureGrantCodeAndNotifyIfReturning()) {
this.getAccessToken();
}
return this;

@@ -101,10 +162,10 @@ }

/**
* If there is an error, it will be passed back as a rejected Promies.
* 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
* [fetchAuthorizationGrant].
* [fetchAuthorizationCode].
*/
private captureGrantCodeAndNotifyIfReturning(): boolean {
private isReturningFromAuthServer(): Promise<boolean> {
const error = OAuth2AuthCodePKCE.extractParamFromUrl(location.href, 'error');
if (error) {
return false;
return Promise.reject(toErrorClass(error));
}

@@ -114,3 +175,3 @@

if (!code) {
return false;
return Promise.resolve(false);
}

@@ -123,26 +184,24 @@

console.warn("state query string parameter doesn't match the one sent! Possible malicious activity somewhere.");
return false;
return Promise.reject(new ErrorInvalidReturnedStateParam());
}
state.authorizationGrantCode = code;
state.authorizationCode = code;
state.hasAuthCodeBeenExchangedForAccessToken = false;
localStorage.setItem(LOCALSTORAGE_STATE, JSON.stringify(state));
this.setState(state);
return true;
return Promise.resolve(true);
}
/**
* 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.
*
* This method should never return undefined, but was put here to satisfy the
* TypeScript typechecker.
*/
private fetchAccessTokenWithGrant(
private exchangeAuthCodeForAccessToken(
codeOverride?: string
): Promise<AccessToken> {
): Promise<AccessContext> {
this.assertStateAndConfigArePresent();
const {
authorizationGrantCode = codeOverride,
authorizationCode = codeOverride,
codeVerifier = ''

@@ -154,3 +213,3 @@ } = this.state;

console.warn('No code verifier is being sent.');
} else if (!authorizationGrantCode) {
} else if (!authorizationCode) {
console.warn('No authorization grant code is being passed.');

@@ -161,3 +220,3 @@ }

const body = `grant_type=authorization_code&`
+ `code=${encodeURIComponent(authorizationGrantCode || '')}&`
+ `code=${encodeURIComponent(authorizationCode || '')}&`
+ `redirect_uri=${encodeURIComponent(redirectUrl)}&`

@@ -174,34 +233,46 @@ + `client_id=${encodeURIComponent(clientId)}&`

})
.then(res => res.status === 400 ? Promise.reject(res.json()) : res.json())
.then(({ access_token, expires_in, refresh_token }) => {
const accessToken: AccessToken = {
value: access_token,
expiry: (new Date(Date.now() + (parseInt(expires_in) * 1000))).toString()
};
this.state.accessToken = accessToken;
.then(res => {
const jsonPromise = res.json()
.catch(jsonDecodeError => ({ error: 'invalid_json' }));
if (refresh_token) {
const refreshToken: RefreshToken = {
value: refresh_token
};
this.state.refreshToken = refreshToken;
if (!res.ok) {
return jsonPromise.then(({ error }: any) => {
switch (error) {
case 'invalid_grant':
onInvalidGrant(() => this.fetchAuthorizationCode());
break;
default:
break;
}
return Promise.reject(toErrorClass(error));
});
}
return jsonPromise.then(({ access_token, expires_in, refresh_token, scope }) => {
let scopes = [];
this.state.hasAuthCodeBeenExchangedForAccessToken = true;
localStorage.setItem(LOCALSTORAGE_STATE, JSON.stringify(this.state));
return accessToken;
})
.catch((jsonPromise) => jsonPromise.then((json: any) => Promise.reject(json)))
.catch((data) => {
console.log(data);
const error = data.error || 'There was a network error.';
switch (error) {
case 'invalid_grant':
onInvalidGrant(() => this
.fetchAuthorizationGrant()
.catch(error => console.error(error))
);
default:
break;
}
return Promise.reject(error);
const accessToken: AccessToken = {
value: access_token,
expiry: (new Date(Date.now() + (parseInt(expires_in) * 1000))).toString()
};
this.state.accessToken = accessToken;
if (refresh_token) {
const refreshToken: RefreshToken = {
value: refresh_token
};
this.state.refreshToken = refreshToken;
}
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(LOCALSTORAGE_STATE, JSON.stringify(this.state));
return { token: accessToken, scopes };
});
});

@@ -214,3 +285,3 @@ }

*/
public async fetchAuthorizationGrant(): Promise<void> {
public async fetchAuthorizationCode(): Promise<void> {
this.assertStateAndConfigArePresent();

@@ -251,32 +322,32 @@

* is easier.
*
* Typically you always want to use this over [fetchAccessTokenWithGrant].
*/
public getAccessToken(): Promise<AccessToken | undefined> {
public getAccessToken(): Promise<AccessContext> {
this.assertStateAndConfigArePresent();
const { onAccessTokenExpiry } = this.config;
const { accessToken, authorizationGrantCode, refreshToken } = this.state;
if (!authorizationGrantCode) {
return Promise.reject({ error: 'no_auth_code' });
const {
accessToken,
authorizationCode,
hasAuthCodeBeenExchangedForAccessToken,
refreshToken,
scopes
} = this.state;
if (!authorizationCode) {
return Promise.reject(new ErrorNoAuthCode());
}
if (!accessToken) {
if (!accessToken || !hasAuthCodeBeenExchangedForAccessToken) {
console.log('Getting access token with grant');
return this.fetchAccessTokenWithGrant();
return this.exchangeAuthCodeForAccessToken();
}
// If there's no refresh token, attempt with the auth grant code.
if (!refreshToken && (new Date()) >= (new Date(accessToken.expiry))) {
console.log('Renewing access token with grant');
return onAccessTokenExpiry(() => this.fetchAccessTokenWithGrant());
}
if ((new Date()) >= (new Date(accessToken.expiry))) {
// Depending on the server (and config), refreshToken may not be available.
if (refreshToken && (new Date()) >= (new Date(accessToken.expiry))) {
console.log('Renewing access token with refresh token');
return onAccessTokenExpiry(() => this.refreshAccessToken());
return onAccessTokenExpiry(() => this.exchangeRefreshTokenForAccessToken());
}
console.log('Access token is accessible and valid');
return Promise.resolve(accessToken);
return Promise.resolve({ token: accessToken, scopes });
}

@@ -287,5 +358,5 @@

*/
public refreshAccessToken(): Promise<AccessToken> {
public exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
this.assertStateAndConfigArePresent();
const { onInvalidGrant, tokenUrl } = this.config;

@@ -310,3 +381,5 @@ const { refreshToken } = this.state;

.then(res => res.status === 400 ? Promise.reject(res.json()) : res.json())
.then(({ access_token, expires_in, refresh_token }) => {
.then(({ access_token, expires_in, refresh_token, scope }) => {
let scopes = [];
const accessToken: AccessToken = {

@@ -317,3 +390,3 @@ value: access_token,

this.state.accessToken = accessToken;
if (refresh_token) {

@@ -326,4 +399,11 @@ const refreshToken: RefreshToken = {

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(LOCALSTORAGE_STATE, JSON.stringify(this.state));
return accessToken;
return { token: accessToken, scopes };
})

@@ -335,6 +415,4 @@ .catch(jsonPromise => Promise.reject(jsonPromise))

case 'invalid_grant':
onInvalidGrant(() => this
.fetchAuthorizationGrant()
.catch(error => console.error(error))
);
onInvalidGrant(() => this.fetchAuthorizationCode());
break;
default:

@@ -347,2 +425,6 @@ break;

public getGrantedScopes(): Scopes {
return this.state.scopes;
}
private recoverState(): this {

@@ -360,4 +442,4 @@ this.state = JSON.parse(localStorage.getItem(LOCALSTORAGE_STATE) || '{}');

/**
* Implements *base64url-encode*, which is NOT the same as regular base64
* encoding.
* Implements *base64url-encode* (RFC 4648 § 5) without padding, which is NOT
* the same as regular base64 encoding.
*/

@@ -364,0 +446,0 @@ static base64urlEncode(value: string): string {

@@ -1,1 +0,670 @@

module.exports=function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";var n=this&&this.__assign||function(){return(n=Object.assign||function(e){for(var t,r=1,n=arguments.length;r<n;r++)for(var o in t=arguments[r])Object.prototype.hasOwnProperty.call(t,o)&&(e[o]=t[o]);return e}).apply(this,arguments)},o=this&&this.__awaiter||function(e,t,r,n){return new(r||(r=Promise))((function(o,a){function i(e){try{s(n.next(e))}catch(e){a(e)}}function c(e){try{s(n.throw(e))}catch(e){a(e)}}function s(e){var t;e.done?o(e.value):(t=e.value,t instanceof r?t:new r((function(e){e(t)}))).then(i,c)}s((n=n.apply(e,t||[])).next())}))},a=this&&this.__generator||function(e,t){var r,n,o,a,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return a={next:c(0),throw:c(1),return:c(2)},"function"==typeof Symbol&&(a[Symbol.iterator]=function(){return this}),a;function c(a){return function(c){return function(a){if(r)throw new TypeError("Generator is already executing.");for(;i;)try{if(r=1,n&&(o=2&a[0]?n.return:a[0]?n.throw||((o=n.return)&&o.call(n),0):n.next)&&!(o=o.call(n,a[1])).done)return o;switch(n=0,o&&(a=[2&a[0],o.value]),a[0]){case 0:case 1:o=a;break;case 4:return i.label++,{value:a[1],done:!1};case 5:i.label++,n=a[1],a=[0];continue;case 7:a=i.ops.pop(),i.trys.pop();continue;default:if(!(o=(o=i.trys).length>0&&o[o.length-1])&&(6===a[0]||2===a[0])){i=0;continue}if(3===a[0]&&(!o||a[1]>o[0]&&a[1]<o[3])){i.label=a[1];break}if(6===a[0]&&i.label<o[1]){i.label=o[1],o=a;break}if(o&&i.label<o[2]){i.label=o[2],i.ops.push(a);break}o[2]&&i.ops.pop(),i.trys.pop();continue}a=t.call(e,i)}catch(e){a=[6,e],n=0}finally{r=o=0}if(5&a[0])throw a[1];return{value:a[0]?a[1]:void 0,done:!0}}([a,c])}}};Object.defineProperty(t,"__esModule",{value:!0}),t.LOCALSTORAGE_ID="oauth2authcodepkce",t.LOCALSTORAGE_STATE=t.LOCALSTORAGE_ID+"-state",t.RECOMMENDED_CODE_VERIFIER_LENGTH=128,t.RECOMMENDED_STATE_LENGTH=32;var i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~",c=function(){function e(e){return this.state={},this.config=e,this.recoverState(),this.captureGrantCodeAndNotifyIfReturning()&&this.getAccessToken(),this}return e.prototype.assertStateAndConfigArePresent=function(){if(!this.state||!this.config)throw console.error("state:",this.state,"config:",this.config),new Error("state or config is not set.")},e.prototype.captureGrantCodeAndNotifyIfReturning=function(){if(e.extractParamFromUrl(location.href,"error"))return!1;var r=e.extractParamFromUrl(location.href,"code");if(!r)return!1;var n=JSON.parse(localStorage.getItem(t.LOCALSTORAGE_STATE)||"{}");return e.extractParamFromUrl(location.href,"state")!==n.stateQueryParam?(console.warn("state query string parameter doesn't match the one sent! Possible malicious activity somewhere."),!1):(n.authorizationGrantCode=r,localStorage.setItem(t.LOCALSTORAGE_STATE,JSON.stringify(n)),this.setState(n),!0)},e.prototype.fetchAccessTokenWithGrant=function(e){var r=this;this.assertStateAndConfigArePresent();var n=this.state,o=n.authorizationGrantCode,a=void 0===o?e:o,i=n.codeVerifier,c=void 0===i?"":i,s=this.config,u=s.clientId,f=s.onInvalidGrant,l=s.redirectUrl;c?a||console.warn("No authorization grant code is being passed."):console.warn("No code verifier is being sent.");var h=this.config.tokenUrl,d="grant_type=authorization_code&code="+encodeURIComponent(a||"")+"&redirect_uri="+encodeURIComponent(l)+"&client_id="+encodeURIComponent(u)+"&code_verifier="+c;return fetch(h,{method:"POST",body:d,headers:{"Content-Type":"application/x-www-form-urlencoded"}}).then((function(e){return 400===e.status?Promise.reject(e.json()):e.json()})).then((function(e){var n=e.access_token,o=e.expires_in,a=e.refresh_token,i={value:n,expiry:new Date(Date.now()+1e3*parseInt(o)).toString()};if(r.state.accessToken=i,a){var c={value:a};r.state.refreshToken=c}return localStorage.setItem(t.LOCALSTORAGE_STATE,JSON.stringify(r.state)),i})).catch((function(e){return e.then((function(e){return Promise.reject(e)}))})).catch((function(e){console.log(e);var t=e.error||"There was a network error.";switch(t){case"invalid_grant":f((function(){return r.fetchAuthorizationGrant().catch((function(e){return console.error(e)}))}))}return Promise.reject(t)}))},e.prototype.fetchAuthorizationGrant=function(){return o(this,void 0,void 0,(function(){var r,o,i,c,s,u,f,l,h;return a(this,(function(a){switch(a.label){case 0:return this.assertStateAndConfigArePresent(),r=this.config,o=r.clientId,i=r.redirectUrl,c=r.scopes,[4,e.generatePKCECodes()];case 1:return s=a.sent(),u=s.codeChallenge,f=s.codeVerifier,l=e.generateRandomState(t.RECOMMENDED_STATE_LENGTH),this.state=n(n({},this.state),{codeChallenge:u,codeVerifier:f,stateQueryParam:l}),localStorage.setItem(t.LOCALSTORAGE_STATE,JSON.stringify(this.state)),h=this.config.authorizationUrl+"?response_type=code&client_id="+encodeURIComponent(o)+"&redirect_uri="+encodeURIComponent(i)+"&scope="+encodeURIComponent(c.join(" "))+"&state="+l+"&code_challenge="+encodeURIComponent(u)+"&code_challenge_method=S256",location.replace(h),[2]}}))}))},e.prototype.getAccessToken=function(){var e=this;this.assertStateAndConfigArePresent();var t=this.config.onAccessTokenExpiry,r=this.state,n=r.accessToken,o=r.authorizationGrantCode,a=r.refreshToken;return o?n?!a&&new Date>=new Date(n.expiry)?(console.log("Renewing access token with grant"),t((function(){return e.fetchAccessTokenWithGrant()}))):new Date>=new Date(n.expiry)?(console.log("Renewing access token with refresh token"),t((function(){return e.refreshAccessToken()}))):(console.log("Access token is accessible and valid"),Promise.resolve(n)):(console.log("Getting access token with grant"),this.fetchAccessTokenWithGrant()):Promise.reject({error:"no_auth_code"})},e.prototype.refreshAccessToken=function(){var e=this;this.assertStateAndConfigArePresent();var r=this.config,n=r.onInvalidGrant,o=r.tokenUrl,a=this.state.refreshToken;return a||console.warn("No refresh token is present."),fetch(o,{method:"POST",body:"grant_type=refresh_token&refresh_token="+a,headers:{"Content-Type":"application/x-www-form-urlencoded"}}).then((function(e){return 400===e.status?Promise.reject(e.json()):e.json()})).then((function(r){var n=r.access_token,o=r.expires_in,a=r.refresh_token,i={value:n,expiry:new Date(Date.now()+parseInt(o)).toString()};if(e.state.accessToken=i,a){var c={value:a};e.state.refreshToken=c}return localStorage.setItem(t.LOCALSTORAGE_STATE,JSON.stringify(e.state)),i})).catch((function(e){return Promise.reject(e)})).catch((function(t){var r=t.error||"There was a network error.";switch(r){case"invalid_grant":n((function(){return e.fetchAuthorizationGrant().catch((function(e){return console.error(e)}))}))}return Promise.reject(r)}))},e.prototype.recoverState=function(){return this.state=JSON.parse(localStorage.getItem(t.LOCALSTORAGE_STATE)||"{}"),this},e.prototype.setState=function(e){return this.state=e,localStorage.setItem(t.LOCALSTORAGE_STATE,JSON.stringify(e)),this},e.base64urlEncode=function(e){var t=btoa(e);return t=(t=(t=t.replace(/\+/g,"-")).replace(/\//g,"_")).replace(/=/g,"")},e.extractParamFromUrl=function(e,t){var r=e.split("?");if(r.length<2)return"";var n=r[1].split("&").reduce((function(e,t){return e.concat(t.split("="))}),[]);if(n.length<2)return"";var o=n.indexOf(t);return o>=0?n[o+1]:""},e.generatePKCECodes=function(){var r=new Uint32Array(t.RECOMMENDED_CODE_VERIFIER_LENGTH);crypto.getRandomValues(r);var n=e.base64urlEncode(Array.from(r).map((function(e){return i[e%i.length]})).join(""));return crypto.subtle.digest("SHA-256",(new TextEncoder).encode(n)).then((function(e){for(var t=new Uint8Array(e),r="",n=t.byteLength,o=0;o<n;o++)r+=String.fromCharCode(t[o]);return r})).then(e.base64urlEncode).then((function(e){return{codeChallenge:e,codeVerifier:n}}))},e.generateRandomState=function(e){var t=new Uint32Array(e);return crypto.getRandomValues(t),Array.from(t).map((function(e){return i[e%i.length]})).join("")},e}();t.OAuth2AuthCodePKCE=c}]);
module.exports =
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
"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 };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
;
;
;
/**
* A list of OAuth2AuthCodePKCE errors.
*/
// To "namespace" all errors.
var ErrorOAuth2 = /** @class */ (function () {
function 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;
}
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;
}
return ErrorNoAuthCode;
}(ErrorOAuth2));
exports.ErrorNoAuthCode = ErrorNoAuthCode;
var ErrorInvalidReturnedStateParam = /** @class */ (function (_super) {
__extends(ErrorInvalidReturnedStateParam, _super);
function ErrorInvalidReturnedStateParam() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorInvalidReturnedStateParam;
}(ErrorOAuth2));
exports.ErrorInvalidReturnedStateParam = ErrorInvalidReturnedStateParam;
var ErrorInvalidJson = /** @class */ (function (_super) {
__extends(ErrorInvalidJson, _super);
function ErrorInvalidJson() {
return _super !== null && _super.apply(this, arguments) || this;
}
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;
}
return ErrorInvalidScope;
}(ErrorOAuth2));
exports.ErrorInvalidScope = ErrorInvalidScope;
var ErrorInvalidRequest = /** @class */ (function (_super) {
__extends(ErrorInvalidRequest, _super);
function ErrorInvalidRequest() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorInvalidRequest;
}(ErrorOAuth2));
exports.ErrorInvalidRequest = ErrorInvalidRequest;
/**
* 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;
}
return ErrorAuthenticationGrant;
}(ErrorOAuth2));
exports.ErrorAuthenticationGrant = ErrorAuthenticationGrant;
var ErrorUnauthorizedClient = /** @class */ (function (_super) {
__extends(ErrorUnauthorizedClient, _super);
function ErrorUnauthorizedClient() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorUnauthorizedClient;
}(ErrorAuthenticationGrant));
exports.ErrorUnauthorizedClient = ErrorUnauthorizedClient;
var ErrorAccessDenied = /** @class */ (function (_super) {
__extends(ErrorAccessDenied, _super);
function ErrorAccessDenied() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorAccessDenied;
}(ErrorAuthenticationGrant));
exports.ErrorAccessDenied = ErrorAccessDenied;
var ErrorUnsupportedResponseType = /** @class */ (function (_super) {
__extends(ErrorUnsupportedResponseType, _super);
function ErrorUnsupportedResponseType() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorUnsupportedResponseType;
}(ErrorAuthenticationGrant));
exports.ErrorUnsupportedResponseType = ErrorUnsupportedResponseType;
var ErrorServerError = /** @class */ (function (_super) {
__extends(ErrorServerError, _super);
function ErrorServerError() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorServerError;
}(ErrorAuthenticationGrant));
exports.ErrorServerError = ErrorServerError;
var ErrorTemporarilyUnavailable = /** @class */ (function (_super) {
__extends(ErrorTemporarilyUnavailable, _super);
function ErrorTemporarilyUnavailable() {
return _super !== null && _super.apply(this, arguments) || this;
}
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;
}
return ErrorAccessTokenResponse;
}(ErrorOAuth2));
exports.ErrorAccessTokenResponse = ErrorAccessTokenResponse;
var ErrorInvalidClient = /** @class */ (function (_super) {
__extends(ErrorInvalidClient, _super);
function ErrorInvalidClient() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorInvalidClient;
}(ErrorAccessTokenResponse));
exports.ErrorInvalidClient = ErrorInvalidClient;
var ErrorInvalidGrant = /** @class */ (function (_super) {
__extends(ErrorInvalidGrant, _super);
function ErrorInvalidGrant() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorInvalidGrant;
}(ErrorAccessTokenResponse));
exports.ErrorInvalidGrant = ErrorInvalidGrant;
var ErrorUnsupportedGrantType = /** @class */ (function (_super) {
__extends(ErrorUnsupportedGrantType, _super);
function ErrorUnsupportedGrantType() {
return _super !== null && _super.apply(this, arguments) || this;
}
return ErrorUnsupportedGrantType;
}(ErrorAccessTokenResponse));
exports.ErrorUnsupportedGrantType = ErrorUnsupportedGrantType;
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,
};
function toErrorClass(rawError) {
return new (exports.RawErrorToErrorClassMap[rawError] || ErrorUnknown)();
}
exports.toErrorClass = toErrorClass;
/**
* 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.
*/
exports.RECOMMENDED_CODE_VERIFIER_LENGTH = 128;
/**
* 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;
}
/**
* 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.');
}
};
/**
* 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 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 (jsonDecodeError) { 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 (_a) {
var access_token = _a.access_token, expires_in = _a.expires_in, refresh_token = _a.refresh_token, scope = _a.scope;
var scopes = [];
_this.state.hasAuthCodeBeenExchangedForAccessToken = true;
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 (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));
return { token: accessToken, scopes: scopes };
});
});
};
/**
* Fetch an authorization grant via redirection. In a sense this function
* doesn't return because of the redirect behavior (uses `location.replace`).
*/
OAuth2AuthCodePKCE.prototype.fetchAuthorizationCode = function () {
return __awaiter(this, void 0, void 0, function () {
var _a, clientId, redirectUrl, scopes, _b, codeChallenge, codeVerifier, stateQueryParam, url;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
this.assertStateAndConfigArePresent();
_a = this.config, clientId = _a.clientId, 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 });
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";
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, hasAuthCodeBeenExchangedForAccessToken = _a.hasAuthCodeBeenExchangedForAccessToken, refreshToken = _a.refreshToken, scopes = _a.scopes;
if (!authorizationCode) {
return Promise.reject(new ErrorNoAuthCode());
}
if (!accessToken || !hasAuthCodeBeenExchangedForAccessToken) {
console.log('Getting access token with grant');
return this.exchangeAuthCodeForAccessToken();
}
// Depending on the server (and config), refreshToken may not be available.
if (refreshToken && (new Date()) >= (new Date(accessToken.expiry))) {
console.log('Renewing access token with refresh token');
return onAccessTokenExpiry(function () { return _this.exchangeRefreshTokenForAccessToken(); });
}
console.log('Access token is accessible and valid');
return Promise.resolve({ token: accessToken, scopes: scopes });
};
/**
* Refresh an access token from the remote service.
*/
OAuth2AuthCodePKCE.prototype.exchangeRefreshTokenForAccessToken = function () {
var _this = this;
this.assertStateAndConfigArePresent();
var _a = this.config, onInvalidGrant = _a.onInvalidGrant, tokenUrl = _a.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=" + refreshToken);
return fetch(url, {
method: 'POST',
body: body,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
.then(function (res) { return res.status === 400 ? Promise.reject(res.json()) : res.json(); })
.then(function (_a) {
var access_token = _a.access_token, expires_in = _a.expires_in, refresh_token = _a.refresh_token, scope = _a.scope;
var scopes = [];
var accessToken = {
value: access_token,
expiry: (new Date(Date.now() + parseInt(expires_in))).toString()
};
_this.state.accessToken = accessToken;
if (refresh_token) {
var refreshToken_1 = {
value: refresh_token
};
_this.state.refreshToken = refreshToken_1;
}
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));
return { token: accessToken, scopes: scopes };
})
.catch(function (jsonPromise) { return Promise.reject(jsonPromise); })
.catch(function (data) {
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(error);
});
};
OAuth2AuthCodePKCE.prototype.getGrantedScopes = function () {
return this.state.scopes;
};
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 '';
}
var parts = queryString[1]
.split('&')
.reduce(function (a, s) { return a.concat(s.split('=')); }, []);
if (parts.length < 2) {
return '';
}
var paramIdx = parts.indexOf(param);
return paramIdx >= 0 ? parts[paramIdx + 1] : '';
};
/**
* 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;
/***/ })
/******/ ]);
{
"name": "@bity/oauth2-auth-code-pkce",
"version": "2.0.1",
"version": "2.1.0",
"description": "An OAuth 2.0 client that ONLY supports Authorization Code flow with PKCE support.",

@@ -12,3 +12,3 @@ "main": "index.js",

"build": "npm run build:code",
"serve:examples": "http-server ./ --cors"
"serve:tests": "http-server ./ --cors"
},

@@ -15,0 +15,0 @@ "keywords": [

# OAuth2AuthCodePKCE client
A zero dependency OAuth2 client, that **only** support authorization grant
codes, with PKCE support for client side protection.
A zero dependency OAuth 2.0 client supporting *only* the authorization code
grant ([RFC 6749 § 4.1][]) with PKCE ([RFC 7636][]) for client side protection.
[RFC 6749 § 4.1]: https://tools.ietf.org/html/rfc6749#section-4.1
[RFC 7636]: https://tools.ietf.org/html/rfc7636
Currently the only Type/JavaScript implementation in public existence.

@@ -16,4 +19,4 @@

Run `npm run serve:examples` and navigate to
http://localhost:8080/examples/as-an-es6-browser-module.html
Run `npm run serve:tests` and navigate to
http://localhost:8080/tests/panel.html

@@ -26,6 +29,13 @@ This page acts as a test panel for various scenarios. Play around! :)

Module system|File
---|---
ESModules (import/export)|index.js
CommonJS2 (require e.g. nodejs)|oauth2-auth-code-pkce.js
TypeScript|index.ts
| Module system | File |
|:--------------------------------|:--------------------------|
| ESModules (import/export) | index.js |
| CommonJS2 (require e.g. nodejs) | oauth2-auth-code-pkce.js |
| TypeScript | index.ts |
## Development
### Publishing to NPM
Grab the NPM-generated `bity-oauth2-auth-code-pkce-*.tgz` tarball from CI and
then use `npm publish $tarball` to publish it to NPM.
const path = require('path');
module.exports = {
mode: 'none',
entry: './index.ts',

@@ -5,0 +6,0 @@ module: {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc