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

oauth2-popup-flow

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

oauth2-popup-flow - npm Package Compare versions

Comparing version 0.0.1 to 0.1.0

license.md

2

dist/oauth2-popup-flow.js

@@ -1,2 +0,2 @@

!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.OAuth2PopupFlow=t():e.OAuth2PopupFlow=t()}(window,function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";var r=this&&this.__assign||Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var o in t=arguments[n])Object.prototype.hasOwnProperty.call(t,o)&&(e[o]=t[o]);return e},o=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{u(r.next(e))}catch(e){i(e)}}function s(e){try{u(r.throw(e))}catch(e){i(e)}}function u(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}u((r=r.apply(e,t||[])).next())})},i=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=r[2&i[0]?"return":i[0]?"throw":"next"])&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[0,o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]<o[3])){a.label=i[1];break}if(6===i[0]&&a.label<o[1]){a.label=o[1],o=i;break}if(o&&a.label<o[2]){a.label=o[2],a.ops.push(i);break}o[2]&&a.ops.pop(),a.trys.pop();continue}i=t.call(e,a)}catch(e){i=[6,e],r=0}finally{n=o=0}if(5&i[0])throw i[1];return{value:i[0]?i[1]:void 0,done:!0}}([i,s])}}};Object.defineProperty(t,"__esModule",{value:!0});var a=function(){function e(e){this.authorizationUrl=e.authorizationUrl,this.clientId=e.clientId,this.redirectUri=e.redirectUri,this.scope=e.scope,this.responseType=e.responseType||"token",this.accessTokenStorageKey=e.accessTokenStorageKey||"token",this.accessTokenResponseKey=e.accessTokenResponseKey||"access_token",this.storage=e.storage||localStorage,this.pollingTime=e.pollingTime||200,this.additionalAuthorizationParameters=e.additionalAuthorizationParameters||{},this.tokenValidator=e.tokenValidator,this.beforePopup=e.beforePopup}return Object.defineProperty(e.prototype,"_rawToken",{get:function(){return this.storage.getItem(this.accessTokenStorageKey)||void 0},set:function(e){null!==e&&void 0!==e&&this.storage.setItem(this.accessTokenStorageKey,e)},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"_rawTokenPayload",{get:function(){var t=this._rawToken;if(t){var n=t.split(".")[1];if(n){var r=atob(n);return e.jsonParseOrUndefined(r)}}},enumerable:!0,configurable:!0}),e.prototype.tryLoginPopup=function(){return o(this,void 0,void 0,function(){var t;return i(this,function(n){switch(n.label){case 0:return this.loggedIn()?[2,!0]:this.beforePopup?[4,Promise.resolve(this.beforePopup())]:[3,2];case 1:n.sent(),n.label=2;case 2:return(t=open(this.authorizationUrl+"?"+e.encodeObjectToUri(r({client_id:this.clientId,response_type:this.responseType,redirect_uri:this.redirectUri,scope:this.scope},this.additionalAuthorizationParameters))))?[4,this.authenticated()]:[2,!1];case 3:return n.sent(),t.close(),[2,!0]}})})},e.prototype.logout=function(){this.storage.removeItem(this.accessTokenStorageKey)},e.prototype.loggedIn=function(){var e=this._rawTokenPayload;if(!e)return!1;if(this.tokenValidator){var t=this._rawToken;if(!t)throw new Error("Token was falsy but token payload was not.");if(!this.tokenValidator({payload:e,token:t}))return!1}var n=e.exp;return!!n&&!((new Date).getTime()>1e3*n)},e.prototype.handleRedirect=function(){if(!location.href.startsWith(this.redirectUri))return!1;var t=location.hash;if(!t)return!1;var n=/#(.*)/.exec(t);if(!n)return!1;var r=n[1],o=e.decodeUriToObject(r)[this.accessTokenResponseKey];return!!o&&(this._rawToken=o,!0)},e.prototype.authenticated=function(){return o(this,void 0,void 0,function(){return i(this,function(t){switch(t.label){case 0:return this.loggedIn()?[3,2]:[4,e.time(this.pollingTime)];case 1:return t.sent(),[3,0];case 2:return[2]}})})},e.prototype.token=function(){return o(this,void 0,void 0,function(){var e;return i(this,function(t){switch(t.label){case 0:return this.loggedIn()?[3,2]:(this.tryLoginPopup(),[4,this.authenticated()]);case 1:t.sent(),t.label=2;case 2:if(!(e=this._rawToken))throw new Error("Token was falsy after being authenticated.");return[2,e]}})})},e.prototype.tokenPayload=function(){return o(this,void 0,void 0,function(){var e;return i(this,function(t){switch(t.label){case 0:return this.loggedIn()?[3,2]:(this.tryLoginPopup(),[4,this.authenticated()]);case 1:t.sent(),t.label=2;case 2:if(!(e=this._rawTokenPayload))throw new Error("Token payload was falsy after being authenticated.");return[2,e]}})})},e.jsonParseOrUndefined=function(e){try{return JSON.parse(e)}catch(e){return}},e.time=function(e){return new Promise(function(e){return setTimeout(function(){return e("TIMER")})})},e.decodeUri=function(e){try{return decodeURIComponent(e)}catch(t){return e}},e.encodeObjectToUri=function(e){return Object.keys(e).map(function(t){return{key:t,value:e[t]}}).map(function(e){var t=e.key,n=e.value;return encodeURIComponent(t)+"="+encodeURIComponent(n)}).join("&")},e.decodeUriToObject=function(e){var t=this;return e.split("&").reduce(function(e,n){var r=n.split("="),o=r[0],i=r[1],a=t.decodeUri(o),s=t.decodeUri(i);return e[a]=s,e},{})},e}();t.OAuth2PopupFlow=a}])});
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.OAuth2PopupFlow=t():e.OAuth2PopupFlow=t()}(window,function(){return function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){"use strict";var r=this&&this.__assign||Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++)for(var o in t=arguments[n])Object.prototype.hasOwnProperty.call(t,o)&&(e[o]=t[o]);return e},o=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))(function(o,i){function a(e){try{u(r.next(e))}catch(e){i(e)}}function s(e){try{u(r.throw(e))}catch(e){i(e)}}function u(e){e.done?o(e.value):new n(function(t){t(e.value)}).then(a,s)}u((r=r.apply(e,t||[])).next())})},i=this&&this.__generator||function(e,t){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=r[2&i[0]?"return":i[0]?"throw":"next"])&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[0,o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=(o=a.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]<o[3])){a.label=i[1];break}if(6===i[0]&&a.label<o[1]){a.label=o[1],o=i;break}if(o&&a.label<o[2]){a.label=o[2],a.ops.push(i);break}o[2]&&a.ops.pop(),a.trys.pop();continue}i=t.call(e,a)}catch(e){i=[6,e],r=0}finally{n=o=0}if(5&i[0])throw i[1];return{value:i[0]?i[1]:void 0,done:!0}}([i,s])}}};Object.defineProperty(t,"__esModule",{value:!0});var a=function(){function e(e){this.authorizationUri=e.authorizationUri,this.clientId=e.clientId,this.redirectUri=e.redirectUri,this.scope=e.scope,this.responseType=e.responseType||"token",this.accessTokenStorageKey=e.accessTokenStorageKey||"token",this.accessTokenResponseKey=e.accessTokenResponseKey||"access_token",this.storage=e.storage||window.localStorage,this.pollingTime=e.pollingTime||200,this.additionalAuthorizationParameters=e.additionalAuthorizationParameters,this.tokenValidator=e.tokenValidator,this.beforePopup=e.beforePopup,this.afterResponse=e.afterResponse}return Object.defineProperty(e.prototype,"_rawToken",{get:function(){return this.storage.getItem(this.accessTokenStorageKey)||void 0},set:function(e){null!==e&&void 0!==e&&this.storage.setItem(this.accessTokenStorageKey,e)},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"_rawTokenPayload",{get:function(){var t=this._rawToken;if(t){var n=t.split(".")[1];if(n){var r=window.atob(n);return e.jsonParseOrUndefined(r)}}},enumerable:!0,configurable:!0}),e.prototype.loggedIn=function(){var e=this._rawTokenPayload;if(!e)return!1;if(this.tokenValidator){var t=this._rawToken;if(!this.tokenValidator({payload:e,token:t}))return!1}var n=e.exp;return!!n&&!((new Date).getTime()>1e3*n)},e.prototype.tokenExpired=function(){var e=this._rawTokenPayload;if(!e)return!1;var t=e.exp;return!!t&&!((new Date).getTime()<=1e3*t)},e.prototype.logout=function(){this.storage.removeItem(this.accessTokenStorageKey)},e.prototype.handleRedirect=function(){if(!window.location.href.startsWith(this.redirectUri))return"REDIRECT_URI_MISMATCH";var t=window.location.hash;if(!t)return"FALSY_HASH";var n=/#(.*)/.exec(t);if(!n)return"NO_HASH_MATCH";var r=n[1],o=e.decodeUriToObject(r);this.afterResponse&&this.afterResponse(o);var i=o[this.accessTokenResponseKey];return i?(this._rawToken=i,window.location.hash="","SUCCESS"):"FALSY_TOKEN"},e.prototype.tryLoginPopup=function(){return o(this,void 0,void 0,function(){var t,n;return i(this,function(o){switch(o.label){case 0:return this.loggedIn()?[2,"ALREADY_LOGGED_IN"]:this.beforePopup?[4,Promise.resolve(this.beforePopup())]:[3,2];case 1:o.sent(),o.label=2;case 2:return t="function"==typeof this.additionalAuthorizationParameters?this.additionalAuthorizationParameters():"object"==typeof this.additionalAuthorizationParameters?this.additionalAuthorizationParameters:{},(n=window.open(this.authorizationUri+"?"+e.encodeObjectToUri(r({client_id:this.clientId,response_type:this.responseType,redirect_uri:this.redirectUri,scope:this.scope},t))))?[4,this.authenticated()]:[2,"POPUP_FAILED"];case 3:return o.sent(),n.close(),[2,"SUCCESS"]}})})},e.prototype.authenticated=function(){return o(this,void 0,void 0,function(){return i(this,function(t){switch(t.label){case 0:return this.loggedIn()?[3,2]:[4,e.time(this.pollingTime)];case 1:return t.sent(),[3,0];case 2:return[2]}})})},e.prototype.token=function(){return o(this,void 0,void 0,function(){var e;return i(this,function(t){switch(t.label){case 0:return[4,this.authenticated()];case 1:if(t.sent(),!(e=this._rawToken))throw new Error("Token was falsy after being authenticated.");return[2,e]}})})},e.prototype.tokenPayload=function(){return o(this,void 0,void 0,function(){var e;return i(this,function(t){switch(t.label){case 0:return[4,this.authenticated()];case 1:if(t.sent(),!(e=this._rawTokenPayload))throw new Error("Token payload was falsy after being authenticated.");return[2,e]}})})},e.jsonParseOrUndefined=function(e){try{return JSON.parse(e)}catch(e){return}},e.time=function(e){return new Promise(function(t){return window.setTimeout(function(){return t("TIMER")},e)})},e.decodeUri=function(e){try{return decodeURIComponent(e)}catch(t){return e}},e.encodeObjectToUri=function(e){return Object.keys(e).map(function(t){return{key:t,value:e[t]}}).map(function(e){var t=e.key,n=e.value;return encodeURIComponent(t)+"="+encodeURIComponent(n)}).join("&")},e.decodeUriToObject=function(e){var t=this;return e.split("&").reduce(function(e,n){var r=n.split("="),o=r[0],i=r[1],a=t.decodeUri(o),s=t.decodeUri(i);return e[a]=s,e},{})},e}();t.OAuth2PopupFlow=a}])});
//# sourceMappingURL=oauth2-popup-flow.js.map
{
"name": "oauth2-popup-flow",
"version": "0.0.1",
"version": "0.1.0",
"description": "A very simple oauth2 implicit flow library that uses window.open.",

@@ -8,4 +8,5 @@ "main": "./dist/oauth2-popup-flow.js",

"scripts": {
"test": "nyc jasmine-ts \"src/**/*.spec.ts\"",
"build": "webpack -p"
"test": "nyc jasmine-ts \"src/**/*.spec.ts\" | coveralls",
"build": "webpack -p",
"docs": "typedoc --out docs/ src/"
},

@@ -21,2 +22,3 @@ "author": "Rico Kahler",

"awesome-typescript-loader": "^3.5.0",
"coveralls": "^3.0.0",
"jasmine-spec-reporter": "^4.2.1",

@@ -26,2 +28,3 @@ "jasmine-ts": "^0.2.1",

"nyc": "^11.4.1",
"typedoc": "^0.10.0",
"typescript": "^2.7.2",

@@ -39,10 +42,8 @@ "webpack": "^4.0.1",

"reporter": [
"lcov",
"text"
"text-lcov"
],
"extension": [
".ts"
],
"report-dir": "docs"
]
}
}
}

@@ -1,2 +0,2 @@

import { OAuth2PopupFlow, OAuth2PopupFlowOptions } from './';
import { OAuth2PopupFlow } from './';

@@ -44,4 +44,17 @@ interface ExampleTokenPayload {

it('calls `setTimeout` and returns `TIMER`', async () => {
const timer = await OAuth2PopupFlow.time(10);
expect(timer).toBe('TIMER');
function fiveMilliseconds() {
return new Promise<5>(resolve => setTimeout(() => resolve(5), 5));
}
const race = await Promise.race([
OAuth2PopupFlow.time(10),
fiveMilliseconds(),
]);
expect(race).toBe(5);
const otherRace = await Promise.race([
OAuth2PopupFlow.time(0),
fiveMilliseconds(),
]);
expect(otherRace).toBe('TIMER');
});

@@ -81,3 +94,4 @@ });

function beforePopup() { }
function tokenValidator(options: { token: string, payload: ExampleTokenPayload }) {
function afterResponse() { }
function tokenValidator() {
return true;

@@ -87,3 +101,3 @@ }

const storage = {} as Storage;
const storage = createTestStorage();

@@ -94,3 +108,3 @@ const options = {

additionalAuthorizationParameters,
authorizationUrl: 'http://example.com/oauth/authorize',
authorizationUri: 'http://example.com/oauth/authorize',
beforePopup,

@@ -104,2 +118,3 @@ clientId: 'test_client_id',

tokenValidator,
afterResponse,
};

@@ -112,3 +127,3 @@

expect(auth.additionalAuthorizationParameters).toBe(additionalAuthorizationParameters);
expect(auth.authorizationUrl).toBe(options.authorizationUrl);
expect(auth.authorizationUri).toBe(options.authorizationUri);
expect(auth.beforePopup).toBe(beforePopup);

@@ -122,24 +137,10 @@ expect(auth.clientId).toBe(options.clientId);

expect(auth.tokenValidator).toBe(tokenValidator);
expect(auth.afterResponse).toBe(afterResponse);
});
it('uses the default `responseType` of `token` when none is present', () => {
function beforePopup() { }
function tokenValidator(options: { token: string, payload: ExampleTokenPayload }) {
return true;
}
const additionalAuthorizationParameters = { foo: 'bar', };
const storage = {} as Storage;
const options = {
accessTokenResponseKey: 'test_response_key',
accessTokenStorageKey: 'test_storage_key',
additionalAuthorizationParameters,
authorizationUrl: 'http://example.com/oauth/authorize',
beforePopup,
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'test_client_id',
pollingTime: Math.random(),
redirectUri: 'http://localhost:8080/redirect',
scope: 'test scope',
storage,
tokenValidator,
};

@@ -150,37 +151,9 @@

expect(auth.responseType).toBe('token');
// copied from other tests
expect(auth.accessTokenResponseKey).toBe(options.accessTokenResponseKey);
expect(auth.accessTokenStorageKey).toBe(options.accessTokenStorageKey);
expect(auth.additionalAuthorizationParameters).toBe(additionalAuthorizationParameters);
expect(auth.authorizationUrl).toBe(options.authorizationUrl);
expect(auth.beforePopup).toBe(beforePopup);
expect(auth.clientId).toBe(options.clientId);
expect(auth.pollingTime).toBe(options.pollingTime);
expect(auth.redirectUri).toBe(options.redirectUri);
expect(auth.scope).toBe(options.scope);
expect(auth.storage).toBe(storage);
expect(auth.tokenValidator).toBe(tokenValidator);
});
it('uses the default `accessTokenStorageKey` of `token` when none is present', () => {
function beforePopup() { }
function tokenValidator(options: { token: string, payload: ExampleTokenPayload }) {
return true;
}
const additionalAuthorizationParameters = { foo: 'bar', };
const storage = {} as Storage;
const options = {
accessTokenResponseKey: 'test_response_key',
additionalAuthorizationParameters,
authorizationUrl: 'http://example.com/oauth/authorize',
beforePopup,
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'test_client_id',
pollingTime: Math.random(),
redirectUri: 'http://localhost:8080/redirect',
responseType: 'test_token',
scope: 'test scope',
storage,
tokenValidator,
};

@@ -191,37 +164,9 @@

expect(auth.accessTokenStorageKey).toBe('token');
// copied from other tests
expect(auth.accessTokenResponseKey).toBe(options.accessTokenResponseKey);
expect(auth.additionalAuthorizationParameters).toBe(additionalAuthorizationParameters);
expect(auth.authorizationUrl).toBe(options.authorizationUrl);
expect(auth.beforePopup).toBe(beforePopup);
expect(auth.clientId).toBe(options.clientId);
expect(auth.pollingTime).toBe(options.pollingTime);
expect(auth.redirectUri).toBe(options.redirectUri);
expect(auth.responseType).toBe(options.responseType);
expect(auth.scope).toBe(options.scope);
expect(auth.storage).toBe(storage);
expect(auth.tokenValidator).toBe(tokenValidator);
});
it('uses the default `accessTokenResponseKey` of `access_token` when none is present', () => {
function beforePopup() { }
function tokenValidator(options: { token: string, payload: ExampleTokenPayload }) {
return true;
}
const additionalAuthorizationParameters = { foo: 'bar', };
const storage = {} as Storage;
const options = {
accessTokenStorageKey: 'test_storage_key',
additionalAuthorizationParameters,
authorizationUrl: 'http://example.com/oauth/authorize',
beforePopup,
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'test_client_id',
pollingTime: Math.random(),
redirectUri: 'http://localhost:8080/redirect',
responseType: 'test_token',
scope: 'test scope',
storage,
tokenValidator,
};

@@ -232,35 +177,9 @@

expect(auth.accessTokenResponseKey).toBe('access_token');
// copied from other tests
expect(auth.accessTokenStorageKey).toBe(options.accessTokenStorageKey);
expect(auth.additionalAuthorizationParameters).toBe(additionalAuthorizationParameters);
expect(auth.authorizationUrl).toBe(options.authorizationUrl);
expect(auth.beforePopup).toBe(beforePopup);
expect(auth.clientId).toBe(options.clientId);
expect(auth.pollingTime).toBe(options.pollingTime);
expect(auth.redirectUri).toBe(options.redirectUri);
expect(auth.responseType).toBe(options.responseType);
expect(auth.scope).toBe(options.scope);
expect(auth.storage).toBe(storage);
expect(auth.tokenValidator).toBe(tokenValidator);
});
it('uses the default `storage` of `localStorage` when none is present', () => {
function beforePopup() { }
function tokenValidator(options: { token: string, payload: ExampleTokenPayload }) {
return true;
}
const additionalAuthorizationParameters = { foo: 'bar', };
it('uses the default `storage` of `window.localStorage` when none is present', () => {
const options = {
accessTokenResponseKey: 'test_response_key',
accessTokenStorageKey: 'test_storage_key',
additionalAuthorizationParameters,
authorizationUrl: 'http://example.com/oauth/authorize',
beforePopup,
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'test_client_id',
pollingTime: Math.random(),
redirectUri: 'http://localhost:8080/redirect',
responseType: 'test_token',
scope: 'test scope',
tokenValidator,
};

@@ -271,30 +190,6 @@

expect(auth.storage).toBe(window.localStorage);
expect(auth.accessTokenResponseKey).toBe(options.accessTokenResponseKey);
expect(auth.accessTokenStorageKey).toBe(options.accessTokenStorageKey);
expect(auth.additionalAuthorizationParameters).toBe(additionalAuthorizationParameters);
expect(auth.authorizationUrl).toBe(options.authorizationUrl);
expect(auth.beforePopup).toBe(beforePopup);
expect(auth.clientId).toBe(options.clientId);
expect(auth.pollingTime).toBe(options.pollingTime);
expect(auth.redirectUri).toBe(options.redirectUri);
expect(auth.responseType).toBe(options.responseType);
expect(auth.scope).toBe(options.scope);
expect(auth.tokenValidator).toBe(tokenValidator);
});
it('uses the default `pollingTime` of `200` when none is present', () => {
function beforePopup() { }
function tokenValidator(options: { token: string, payload: ExampleTokenPayload }) {
return true;
}
const additionalAuthorizationParameters = { foo: 'bar', };
const storage = {} as Storage;
const options = {
accessTokenResponseKey: 'test_response_key',
accessTokenStorageKey: 'test_storage_key',
additionalAuthorizationParameters,
authorizationUrl: 'http://example.com/oauth/authorize',
beforePopup,
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'test_client_id',

@@ -304,4 +199,2 @@ redirectUri: 'http://localhost:8080/redirect',

scope: 'test scope',
storage,
tokenValidator,
};

@@ -312,55 +205,3 @@

expect(auth.pollingTime).toBe(200);
// copied from other tests
expect(auth.accessTokenResponseKey).toBe(options.accessTokenResponseKey);
expect(auth.accessTokenStorageKey).toBe(options.accessTokenStorageKey);
expect(auth.additionalAuthorizationParameters).toBe(additionalAuthorizationParameters);
expect(auth.authorizationUrl).toBe(options.authorizationUrl);
expect(auth.beforePopup).toBe(beforePopup);
expect(auth.clientId).toBe(options.clientId);
expect(auth.redirectUri).toBe(options.redirectUri);
expect(auth.responseType).toBe(options.responseType);
expect(auth.scope).toBe(options.scope);
expect(auth.storage).toBe(storage);
expect(auth.tokenValidator).toBe(tokenValidator);
});
it('uses the default `additionalAuthorizationParameters` of `{}` when none is present', () => {
function beforePopup() { }
function tokenValidator(options: { token: string, payload: ExampleTokenPayload }) {
return true;
}
const storage = {} as Storage;
const options = {
accessTokenResponseKey: 'test_response_key',
accessTokenStorageKey: 'test_storage_key',
authorizationUrl: 'http://example.com/oauth/authorize',
beforePopup,
clientId: 'test_client_id',
pollingTime: Math.random(),
redirectUri: 'http://localhost:8080/redirect',
responseType: 'test_token',
scope: 'test scope',
storage,
tokenValidator,
};
const auth = new OAuth2PopupFlow(options);
expect(auth.additionalAuthorizationParameters).toEqual({});
// copied from other tests
expect(auth.accessTokenResponseKey).toBe(options.accessTokenResponseKey);
expect(auth.accessTokenStorageKey).toBe(options.accessTokenStorageKey);
expect(auth.authorizationUrl).toBe(options.authorizationUrl);
expect(auth.beforePopup).toBe(beforePopup);
expect(auth.clientId).toBe(options.clientId);
expect(auth.pollingTime).toBe(options.pollingTime);
expect(auth.redirectUri).toBe(options.redirectUri);
expect(auth.responseType).toBe(options.responseType);
expect(auth.scope).toBe(options.scope);
expect(auth.storage).toBe(storage);
expect(auth.tokenValidator).toBe(tokenValidator);
});
});

@@ -373,3 +214,3 @@

const auth = new OAuth2PopupFlow({
authorizationUrl: 'http://example.com/oauth/authorize',
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',

@@ -389,3 +230,3 @@ redirectUri: 'http://localhost:8080/redirect',

const auth = new OAuth2PopupFlow({
authorizationUrl: 'http://example.com/oauth/authorize',
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',

@@ -407,3 +248,3 @@ redirectUri: 'http://localhost:8080/redirect',

const auth = new OAuth2PopupFlow({
authorizationUrl: 'http://example.com/oauth/authorize',
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',

@@ -436,3 +277,3 @@ redirectUri: 'http://localhost:8080/redirect',

const auth = new OAuth2PopupFlow({
authorizationUrl: 'http://example.com/oauth/authorize',
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',

@@ -457,3 +298,3 @@ redirectUri: 'http://localhost:8080/redirect',

const auth = new OAuth2PopupFlow({
authorizationUrl: 'http://example.com/oauth/authorize',
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',

@@ -472,3 +313,3 @@ redirectUri: 'http://localhost:8080/redirect',

const auth = new OAuth2PopupFlow({
authorizationUrl: 'http://example.com/oauth/authorize',
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',

@@ -492,3 +333,3 @@ redirectUri: 'http://localhost:8080/redirect',

const auth = new OAuth2PopupFlow<ExampleTokenPayload>({
authorizationUrl: 'http://example.com/oauth/authorize',
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',

@@ -521,3 +362,3 @@ redirectUri: 'http://localhost:8080/redirect',

const auth = new OAuth2PopupFlow<ExampleTokenPayload>({
authorizationUrl: 'http://example.com/oauth/authorize',
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',

@@ -533,2 +374,35 @@ redirectUri: 'http://localhost:8080/redirect',

});
it('passes through the `tokenValidator` with `true`', () => {
const storage = createTestStorage();
const examplePayload = {
foo: 'something',
bar: 5,
exp: Math.floor(new Date().getTime() / 1000) + 1000,
};
const exampleToken = [
'blah blah header',
window.btoa(JSON.stringify(examplePayload)),
'this is the signature section'
].join('.');
storage._storage.token = exampleToken;
let tokenValidatorCalled = false;
const auth = new OAuth2PopupFlow<ExampleTokenPayload>({
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: 'http://localhost:8080/redirect',
scope: 'openid profile',
storage,
tokenValidator: ({ token, payload }) => {
expect(token).toBe(exampleToken);
expect(payload).toEqual(examplePayload);
tokenValidatorCalled = true;
return true;
},
});
expect(auth.loggedIn()).toBe(true);
expect(tokenValidatorCalled).toBe(true);
});
it('returns `false` if there is a `tokenValidator` and that returns false', () => {

@@ -551,3 +425,3 @@ const storage = createTestStorage();

const auth = new OAuth2PopupFlow<ExampleTokenPayload>({
authorizationUrl: 'http://example.com/oauth/authorize',
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',

@@ -583,3 +457,3 @@ redirectUri: 'http://localhost:8080/redirect',

const auth = new OAuth2PopupFlow<ExampleTokenPayload>({
authorizationUrl: 'http://example.com/oauth/authorize',
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',

@@ -608,3 +482,3 @@ redirectUri: 'http://localhost:8080/redirect',

const auth = new OAuth2PopupFlow<ExampleTokenPayload>({
authorizationUrl: 'http://example.com/oauth/authorize',
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',

@@ -633,3 +507,3 @@ redirectUri: 'http://localhost:8080/redirect',

const auth = new OAuth2PopupFlow<ExampleTokenPayload>({
authorizationUrl: 'http://example.com/oauth/authorize',
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',

@@ -644,2 +518,588 @@ redirectUri: 'http://localhost:8080/redirect',

});
describe('tokenExpired', () => {
it('returns `false` if the `_rawTokenPayload` is undefined', () => {
const storage = createTestStorage();
const auth = new OAuth2PopupFlow<ExampleTokenPayload>({
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: 'http://localhost:8080/redirect',
scope: 'openid profile',
storage,
});
storage._storage.token = undefined;
expect(auth.tokenExpired()).toBe(false);
});
it('returns `false` if the `exp` in the payload is falsy', () => {
const storage = createTestStorage();
const examplePayload = {
foo: 'something',
bar: 5,
exp: 0,
};
const exampleToken = [
'blah blah header',
window.btoa(JSON.stringify(examplePayload)),
'this is the signature section'
].join('.');
storage._storage.token = exampleToken;
const auth = new OAuth2PopupFlow<ExampleTokenPayload>({
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: 'http://localhost:8080/redirect',
scope: 'openid profile',
storage,
});
expect(auth.tokenExpired()).toBe(false);
});
it('returns `false` if the token is not expired', () => {
const storage = createTestStorage();
const examplePayload = {
foo: 'something',
bar: 5,
exp: Math.floor(new Date().getTime() / 1000) + 1000,
};
const exampleToken = [
'blah blah header',
window.btoa(JSON.stringify(examplePayload)),
'this is the signature section'
].join('.');
storage._storage.token = exampleToken;
const auth = new OAuth2PopupFlow<ExampleTokenPayload>({
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: 'http://localhost:8080/redirect',
scope: 'openid profile',
storage,
});
expect(auth.tokenExpired()).toBe(false);
});
it('returns `true` if the token is expired', () => {
const storage = createTestStorage();
const examplePayload = {
foo: 'something',
bar: 5,
exp: Math.floor(new Date().getTime() / 1000) - 1000,
};
const exampleToken = [
'blah blah header',
window.btoa(JSON.stringify(examplePayload)),
'this is the signature section'
].join('.');
storage._storage.token = exampleToken;
const auth = new OAuth2PopupFlow<ExampleTokenPayload>({
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: 'http://localhost:8080/redirect',
scope: 'openid profile',
storage,
});
expect(auth.tokenExpired()).toBe(true);
});
});
describe('logout', () => {
it('should remove the token from storage', () => {
const storage = createTestStorage();
const examplePayload = {
foo: 'something',
bar: 5,
exp: Math.floor(new Date().getTime() / 1000) + 1000,
};
const exampleToken = [
'blah blah header',
window.btoa(JSON.stringify(examplePayload)),
'this is the signature section'
].join('.');
storage._storage.token = exampleToken;
const auth = new OAuth2PopupFlow<ExampleTokenPayload>({
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: 'http://localhost:8080/redirect',
scope: 'openid profile',
storage,
});
expect(auth.loggedIn()).toBe(true);
auth.logout();
expect(auth.loggedIn()).toBe(false);
});
});
describe('handleRedirect', () => {
it('returns early with `REDIRECT_URI_MISMATCH` if location doesn\'t match the redirect', () => {
const storage = createTestStorage();
const options = {
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: 'http://localhost:8080/redirect',
scope: 'openid profile',
storage,
};
const auth = new OAuth2PopupFlow<ExampleTokenPayload>(options);
window.location.hash = 'something%20else';
const result = auth.handleRedirect();
expect(result).toBe('REDIRECT_URI_MISMATCH');
});
it('returns early with `FALSY_HASH` if the hash is falsy', () => {
const storage = createTestStorage();
const options = {
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: '',
scope: 'openid profile',
storage,
};
const auth = new OAuth2PopupFlow<ExampleTokenPayload>(options);
window.location.hash = '';
const result = auth.handleRedirect();
expect(result).toBe('FALSY_HASH');
});
it('returns early with `NO_HASH_MATCH` if hash doesn\'t match /#(.*)/', () => {
const storage = createTestStorage();
const options = {
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: '',
scope: 'openid profile',
storage,
};
const auth = new OAuth2PopupFlow<ExampleTokenPayload>(options);
window.location.hash = 'shouldn\t match';
const result = auth.handleRedirect();
expect(result).toBe('NO_HASH_MATCH');
});
it('calls `afterResponse` with the `decodeUriToObject`', () => {
const storage = createTestStorage();
let afterResponseCalled = false;
const objectToEncode = {
access_token: 'fake access token',
one: 'something',
two: 'something else',
};
const options = {
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: '',
scope: 'openid profile',
storage,
afterResponse: (obj: { [key: string]: string | undefined }) => {
expect(obj).toEqual(objectToEncode);
afterResponseCalled = true;
}
};
const auth = new OAuth2PopupFlow<ExampleTokenPayload>(options);
window.location.hash = `#${OAuth2PopupFlow.encodeObjectToUri(objectToEncode)}`;
const result = auth.handleRedirect();
expect(result).toBe('SUCCESS');
expect(afterResponseCalled).toBe(true);
});
it('returns early with `false` if `rawToken` is falsy', () => {
const storage = createTestStorage();
const options = {
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: '',
scope: 'openid profile',
storage,
};
const auth = new OAuth2PopupFlow<ExampleTokenPayload>(options);
window.location.hash = `#${OAuth2PopupFlow.encodeObjectToUri({
access_token: '',
})}`;
const result = auth.handleRedirect();
expect(result).toBe('FALSY_TOKEN');
});
it('returns `SUCCESS` setting the `_rawToken` and clearing the hash if token is valid', () => {
const storage = createTestStorage();
const options = {
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: '',
scope: 'openid profile',
storage,
};
window.location.hash = `#${OAuth2PopupFlow.encodeObjectToUri({
access_token: 'some token thing',
})}`;
const auth = new OAuth2PopupFlow<ExampleTokenPayload>(options);
const result = auth.handleRedirect();
expect(result).toBe('SUCCESS');
expect(storage.getItem('token')).toBe('some token thing');
expect(window.location.hash).toBe('');
});
});
describe('tryLoginPopup', () => {
it('returns `ALREADY_LOGGED_IN` if already `loggedIn()`', async () => {
const storage = createTestStorage();
const examplePayload = {
foo: 'something',
bar: 5,
exp: Math.floor(new Date().getTime() / 1000) + 1000,
};
const exampleToken = [
'blah blah header',
window.btoa(JSON.stringify(examplePayload)),
'this is the signature section'
].join('.');
storage._storage.token = exampleToken;
const options = {
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: '',
scope: 'openid profile',
storage,
};
const auth = new OAuth2PopupFlow<ExampleTokenPayload>(options);
expect(auth.loggedIn()).toBe(true);
expect(await auth.tryLoginPopup()).toBe('ALREADY_LOGGED_IN');
});
it('doesn\'t call `beforePopup` if it doesn\'t exist', async () => {
const storage = createTestStorage();
(window as any).open = () => undefined;
const options = {
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: '',
scope: 'openid profile',
storage,
};
const auth = new OAuth2PopupFlow<ExampleTokenPayload>(options);
expect(auth.loggedIn()).toBe(false);
expect(await auth.tryLoginPopup()).toBe('POPUP_FAILED');
});
it('calls `beforePopup` synchronously', async () => {
const storage = createTestStorage();
(window as any).open = () => undefined;
let beforePopupCalled = false;
const options = {
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: '',
scope: 'openid profile',
storage,
beforePopup: () => {
beforePopupCalled = true;
},
};
const auth = new OAuth2PopupFlow<ExampleTokenPayload>(options);
expect(auth.loggedIn()).toBe(false);
expect(await auth.tryLoginPopup()).toBe('POPUP_FAILED');
expect(beforePopupCalled).toBe(true);
});
it('calls `beforePopup` asynchronously', async () => {
const storage = createTestStorage();
(window as any).open = () => undefined;
let beforePopupCalled = false;
const options = {
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: '',
scope: 'openid profile',
storage,
beforePopup: async () => {
expect(await OAuth2PopupFlow.time(0)).toBe('TIMER');
beforePopupCalled = true;
},
};
const auth = new OAuth2PopupFlow<ExampleTokenPayload>(options);
expect(auth.loggedIn()).toBe(false);
expect(await auth.tryLoginPopup()).toBe('POPUP_FAILED');
expect(beforePopupCalled).toBe(true);
});
it('calls `additionalAuthorizationParameters` if it is a function', async () => {
const storage = createTestStorage();
let openCalled = false;
(window as any).open = (url: string) => {
expect(url.includes('foo=bar')).toBe(true);
openCalled = true;
};
const options = {
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: '',
scope: 'openid profile',
storage,
additionalAuthorizationParameters: () => {
return {
foo: 'bar'
};
}
};
const auth = new OAuth2PopupFlow<ExampleTokenPayload>(options);
expect(await auth.tryLoginPopup()).toBe('POPUP_FAILED');
expect(openCalled).toBe(true);
});
it('uses `additionalAuthorizationParameters` if it is an object', async () => {
const storage = createTestStorage();
let openCalled = false;
(window as any).open = (url: string) => {
expect(url.includes('foo=bar')).toBe(true);
openCalled = true;
};
const options = {
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: '',
scope: 'openid profile',
storage,
additionalAuthorizationParameters: { foo: 'bar' },
};
const auth = new OAuth2PopupFlow<ExampleTokenPayload>(options);
expect(await auth.tryLoginPopup()).toBe('POPUP_FAILED');
expect(openCalled).toBe(true);
});
it('returns `SUCCESS` and calls `close` on the popup', async () => {
const storage = createTestStorage();
let closedCalled = false;
(window as any).open = () => ({
close: () => {
closedCalled = true;
},
});
const options = {
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: '',
scope: 'openid profile',
storage,
};
const examplePayload = {
foo: 'something',
bar: 5,
exp: Math.floor(new Date().getTime() / 1000) + 1000,
};
const exampleToken = [
'blah blah header',
window.btoa(JSON.stringify(examplePayload)),
'this is the signature section'
].join('.');
const auth = new OAuth2PopupFlow<ExampleTokenPayload>(options);
OAuth2PopupFlow.time(0).then(() => {
storage._storage.token = exampleToken;
})
expect(auth.loggedIn()).toBe(false);
expect(await auth.tryLoginPopup()).toBe('SUCCESS');
expect(closedCalled).toBe(true);
});
});
describe('authenticated', () => {
it('only resolves after a `loggedIn()` is truthy', async () => {
const storage = createTestStorage();
const options = {
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: '',
scope: 'openid profile',
storage,
};
const examplePayload = {
foo: 'something',
bar: 5,
exp: Math.floor(new Date().getTime() / 1000) + 1000,
};
const exampleToken = [
'blah blah header',
window.btoa(JSON.stringify(examplePayload)),
'this is the signature section'
].join('.');
const auth = new OAuth2PopupFlow<ExampleTokenPayload>(options);
OAuth2PopupFlow.time(10).then(() => {
storage._storage.token = exampleToken;
});
expect(auth.loggedIn()).toBe(false);
// this won't resolve and the test will fail unless `loggedIn` is truthy
await auth.authenticated();
});
});
describe('token', () => {
it('returns the `_rawToken` if `loggedIn()`', async () => {
const storage = createTestStorage();
const options = {
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: '',
scope: 'openid profile',
storage,
};
const auth = new OAuth2PopupFlow<ExampleTokenPayload>(options);
const examplePayload = {
foo: 'something',
bar: 5,
exp: Math.floor(new Date().getTime() / 1000) + 1000,
};
const exampleToken = [
'blah blah header',
window.btoa(JSON.stringify(examplePayload)),
'this is the signature section'
].join('.');
storage._storage.token = exampleToken;
const token = await auth.token();
expect(token).toEqual(exampleToken);
});
it('throws if `_rawToken` was falsy after being authenticated', async () => {
const storage = createTestStorage();
const options = {
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: '',
scope: 'openid profile',
storage,
};
const auth = new OAuth2PopupFlow<ExampleTokenPayload>(options);
expect(auth.loggedIn()).toBe(false);
spyOn(auth, 'authenticated');
let catchCalled = false;
try {
await auth.token();
} catch (e) {
expect(e.message).toBe('Token was falsy after being authenticated.');
catchCalled = true;
} finally {
expect(catchCalled).toBe(true);
}
});
});
describe('tokenPayload', () => {
it('returns the `_rawToken` if `loggedIn()`', async () => {
const storage = createTestStorage();
const options = {
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: '',
scope: 'openid profile',
storage,
};
const auth = new OAuth2PopupFlow<ExampleTokenPayload>(options);
const examplePayload = {
foo: 'something',
bar: 5,
exp: Math.floor(new Date().getTime() / 1000) + 1000,
};
const exampleToken = [
'blah blah header',
window.btoa(JSON.stringify(examplePayload)),
'this is the signature section'
].join('.');
storage._storage.token = exampleToken;
const payload = await auth.tokenPayload();
expect(payload).toEqual(examplePayload);
});
it('throws if `_rawToken` was falsy after being authenticated', async () => {
const storage = createTestStorage();
const options = {
authorizationUri: 'http://example.com/oauth/authorize',
clientId: 'some_test_client',
redirectUri: '',
scope: 'openid profile',
storage,
};
const auth = new OAuth2PopupFlow<ExampleTokenPayload>(options);
expect(auth.loggedIn()).toBe(false);
spyOn(auth, 'authenticated');
let catchCalled = false;
try {
await auth.tokenPayload();
} catch (e) {
expect(e.message).toBe('Token payload was falsy after being authenticated.');
catchCalled = true;
} finally {
expect(catchCalled).toBe(true);
}
});
});
});

@@ -0,18 +1,198 @@

/**
* The type of the configuration object used to create a `OAuth2PopupFlow`
*
* Each property has a JSDOC description to explain what it does.
*/
export interface OAuth2PopupFlowOptions<TokenPayload extends { exp: number }> {
authorizationUrl: string,
/**
* REQUIRED
* The full URI of the authorization endpoint provided by the authorization server.
*
* e.g. `https://example.com/oauth/authorize`
*/
authorizationUri: string,
/**
* REQUIRED
* The client ID of your application provided by the authorization server.
*
* This client ID is sent to the authorization server using `authorizationUrl` endpoint in the
* query portion of the URL along with the other parameters.
* This value will be URL encoded like so:
*
* `https://example.com/oauth/authorize?client_id=SOME_CLIENT_ID_VALUE...`
*/
clientId: string,
/**
* REQUIRED
* The URI that the authorization server will to redirect after the user has been authenticated.
* This redirect URI *must* be a URI from *your application* and it must also be registered with
* the authorization server. Some authorities call this a "callback URLs" or "login URLs" etc.
*
* > e.g. `http://localhost:4200/redirect` for local testing
* >
* > or `https://my-application.com/redirect` for prod
*
* This redirect URI is sent to the authorization server using `authorizationUrl` endpoint in the
* query portion of the URL along with the other parameters.
* This value will be URL encoded like so:
*
* `https://example.com/oauth/authorize?redirect_URI=http%3A%2F%2Flocalhost%2Fredirect...`
*/
redirectUri: string,
/**
* REQUIRED
* A list permission separated by spaces that is the scope of permissions your application is
* requesting from the authorization server. If the user is logging in the first time, it may ask
* them to approve those permission before authorizing your application.
*
* > e.g. `openid profile`
*
* The scopes are sent to the authorization server using `authorizationUrl` endpoint in the
* query portion of the URL along with the other parameters.
* This value will be URL encoded like so:
*
* `https://example.com/oauth/authorize?scope=openid%20profile...`
*/
scope: string,
/**
* OPTIONAL
* `response_type` is an argument to be passed to the authorization server via the
* `authorizationUri` endpoint in the query portion of the URL.
*
* Most implementations of oauth2 use the default value of `token` to tell the authorization
* server to start the implicit grant flow but you may override that value with this option.
*
* For example, Auth0--an OAuth2 authority/authorization server--requires the value `id_token`
* instead of `token` for the implicit flow.
*
* The response type is sent to the authorization server using `authorizationUrl` endpoint in the
* query portion of the URL along with the other parameters.
* This value will be URL encoded like so:
*
* `https://example.com/oauth/authorize?response_type=token...`
*/
responseType?: string,
/**
* OPTIONAL
* The key used to save the token in the given storage. The default key is `token` so the token
* would be persisted in `localStorage.getItem('token')` if `localStorage` was the configured
* `Storage`.
*/
accessTokenStorageKey?: string,
/**
* OPTIONAL
* During `handleRedirect`, the method will try to parse `window.location.hash` to an object using
* `OAuth2PopupFlow.decodeUriToObject`. After that object has been decoded, this property
* determines the key to use that will retrieve the token from that object.
*
* By default it is `access_token` but you you may need to change that e.g. Auth0 uses `id_token`.
*/
accessTokenResponseKey?: string,
/**
* OPTIONAL
* The storage implementation of choice. It can be `localStorage` or `sessionStorage` or something
* else. By default, this is `localStorage` and `localStorage` is the preferred `Storage`.
*/
storage?: Storage,
/**
* OPTIONAL
* The `authenticated` method periodically checks `loggedIn()` and resolves when `loggedIn()`
* returns `true`.
*
* This property is how long it will wait between checks. By default it is `200`.
*/
pollingTime?: number,
additionalAuthorizationParameters?: { [key: string]: string },
/**
* OPTIONAL
* Some oauth authorities require additional parameters to be passed to the `authorizationUri`
* URL in order for the implicit grant flow to work.
*
* For example: [Auth0--an OAuth2 authority/authorization server--requires the parameters
* `nonce`][0]
* be passed along with every call to the `authorizationUri`. You can do that like so:
*
* ```ts
* const auth = new OAuth2PopupFlow({
* authorizationUri: 'https://example.com/oauth/authorize',
* clientId: 'foo_client',
* redirectUri: 'http://localhost:8080/redirect',
* scope: 'openid profile',
* // this can be a function or static object
* additionalAuthorizationParameters: () => {
* // in prod, consider something more cryptographic
* const nonce = Math.floor(Math.random() * 1000).toString();
* localStorage.setItem('nonce', nonce);
* return { nonce };
* // `nonce` will now be encoded in the URL like so:
* // https://example.com/oauth/authorize?client_id=foo_client...nonce=1234
* },
* // the token returned by Auth0, has the `nonce` in the payload
* // you can add this additional check now
* tokenValidator: ({ payload }) => {
* const storageNonce = parseInt(localStorage.getItem('nonce'), 10);
* const payloadNonce = parseInt(payload.nonce, 10);
* return storageNonce === payloadNonce;
* },
* });
* ```
*
* [0]: https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-nonce
*/
additionalAuthorizationParameters?: (() => { [key: string]: string }) | { [key: string]: string },
/**
* OPTIONAL
* This function intercepts the `loggedIn` method and causes it to return early with `false` if
* this function itself returns `false`. Use this function to validate claims in the token payload
* or token.
*
* [For example: validating the `nonce`:][0]
*
* ```ts
* const auth = new OAuth2PopupFlow({
* authorizationUri: 'https://example.com/oauth/authorize',
* clientId: 'foo_client',
* redirectUri: 'http://localhost:8080/redirect',
* scope: 'openid profile',
* // this can be a function or static object
* additionalAuthorizationParameters: () => {
* // in prod, consider something more cryptographic
* const nonce = Math.floor(Math.random() * 1000).toString();
* localStorage.setItem('nonce', nonce);
* return { nonce };
* // `nonce` will now be encoded in the URL like so:
* // https://example.com/oauth/authorize?client_id=foo_client...nonce=1234
* },
* // the token returned by Auth0, has the `nonce` in the payload
* // you can add this additional check now
* tokenValidator: ({ payload }) => {
* const storageNonce = parseInt(localStorage.getItem('nonce'), 10);
* const payloadNonce = parseInt(payload.nonce, 10);
* return storageNonce === payloadNonce;
* },
* });
* ```
*
* [0]: https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-nonce
*/
tokenValidator?: (options: { payload: TokenPayload, token: string }) => boolean,
/**
* OPTIONAL
* A hook that runs in `tryLoginPopup` before any popup is opened. This function can return a
* `Promise` and the popup will not open until it resolves.
*
* A typical use case would be to wait a certain amount of time before opening the popup to let
* the user see why the popup is happening.
*/
beforePopup?: () => any | Promise<any>,
/**
* OPTIONAL
* A hook that runs in `handleRedirect` that takes in the result of the hash payload from the
* authorization server. Use this hook to grab more from the response or to debug the response
* from the authorization URL.
*/
afterResponse?: (authorizationResponse: { [key: string]: string | undefined }) => void,
}
export class OAuth2PopupFlow<TokenPayload extends { exp: number }> {
authorizationUrl: string;
authorizationUri: string;
clientId: string;

@@ -26,8 +206,9 @@ redirectUri: string;

pollingTime: number;
additionalAuthorizationParameters: { [key: string]: string };
additionalAuthorizationParameters?: (() => { [key: string]: string }) | { [key: string]: string };
tokenValidator?: (options: { payload: TokenPayload, token: string }) => boolean;
beforePopup?: () => any | Promise<any>;
afterResponse?: (authorizationResponse: { [key: string]: string | undefined }) => void;
constructor(options: OAuth2PopupFlowOptions<TokenPayload>) {
this.authorizationUrl = options.authorizationUrl;
this.authorizationUri = options.authorizationUri;
this.clientId = options.clientId;

@@ -41,5 +222,6 @@ this.redirectUri = options.redirectUri;

this.pollingTime = options.pollingTime || 200;
this.additionalAuthorizationParameters = options.additionalAuthorizationParameters || {};
this.additionalAuthorizationParameters = options.additionalAuthorizationParameters;
this.tokenValidator = options.tokenValidator;
this.beforePopup = options.beforePopup;
this.afterResponse = options.afterResponse;
}

@@ -71,2 +253,6 @@

/**
* A simple synchronous method that returns whether or not the user is logged in by checking
* whether or not their token is present and not expired.
*/
loggedIn() {

@@ -84,3 +270,3 @@ const decodedPayload = this._rawTokenPayload;

if (new Date().getTime() > exp * 1000) { return false; }
if (new Date().getTime() > (exp * 1000)) { return false; }

@@ -90,2 +276,22 @@ return true;

/**
* Returns true only if there is a token in storage and that token is expired. Use this to method
* in conjunction with `loggedIn` to display a message like "you need to *re*login" vs "you need
* to login".
*/
tokenExpired() {
const decodedPayload = this._rawTokenPayload;
if (!decodedPayload) { return false; }
const exp = decodedPayload.exp;
if (!exp) { return false; }
if (new Date().getTime() <= (exp * 1000)) { return false; }
return true;
}
/**
* Deletes the token from the given storage causing `loggedIn` to return false on its next call.
*/
logout() {

@@ -95,20 +301,37 @@ this.storage.removeItem(this.accessTokenStorageKey);

/**
* Call this method in a route of the `redirectUri`. This method takes the value of the hash at
* `window.location.hash` and attempts to grab the token from the URL.
*
* If the method was able to grab the token, it will return `'SUCCESS'` else it will return a
* different string.
*/
handleRedirect() {
const locationHref = location.href;
if (!locationHref.startsWith(this.redirectUri)) { return false; }
const rawHash = location.hash;
if (!rawHash) { return false; }
const locationHref = window.location.href;
if (!locationHref.startsWith(this.redirectUri)) { return 'REDIRECT_URI_MISMATCH'; }
const rawHash = window.location.hash;
if (!rawHash) { return 'FALSY_HASH'; }
const hashMatch = /#(.*)/.exec(rawHash);
if (!hashMatch) { return false; }
if (!hashMatch) { return 'NO_HASH_MATCH'; }
const hash = hashMatch[1];
const authorizationResponse = OAuth2PopupFlow.decodeUriToObject(hash);
if (this.afterResponse) {
this.afterResponse(authorizationResponse);
}
const rawToken = authorizationResponse[this.accessTokenResponseKey];
if (!rawToken) { return false; }
if (!rawToken) { return 'FALSY_TOKEN'; }
this._rawToken = rawToken;
return true;
window.location.hash = '';
return 'SUCCESS';
}
/**
* Tries to open a popup to login the user in. If the user is already `loggedIn()` it will
* immediately return `'ALREADY_LOGGED_IN'`. If the popup fails to open, it will immediately
* return `'POPUP_FAILED'` else it will wait for `loggedIn()` to be `true` and eventually
* return `'SUCCESS'`.
*/
async tryLoginPopup() {
if (this.loggedIn()) { return true; }
if (this.loggedIn()) { return 'ALREADY_LOGGED_IN'; }

@@ -119,3 +342,10 @@ if (this.beforePopup) {

const popup = open(`${this.authorizationUrl}?${OAuth2PopupFlow.encodeObjectToUri({
const additionalParams = (/*if*/ typeof this.additionalAuthorizationParameters === 'function'
? this.additionalAuthorizationParameters()
: /*if*/ typeof this.additionalAuthorizationParameters === 'object'
? this.additionalAuthorizationParameters
: {}
);
const popup = window.open(`${this.authorizationUri}?${OAuth2PopupFlow.encodeObjectToUri({
client_id: this.clientId,

@@ -125,5 +355,5 @@ response_type: this.responseType,

scope: this.scope,
...this.additionalAuthorizationParameters,
...additionalParams,
})}`);
if (!popup) { return false; }
if (!popup) { return 'POPUP_FAILED'; }

@@ -133,5 +363,10 @@ await this.authenticated();

popup.close();
return true;
return 'SUCCESS';
}
/**
* A promise that does not resolve until `loggedIn()` is true. This uses the `pollingTime`
* to wait until checking if `loggedIn()` is `true`.
*/
async authenticated() {

@@ -143,7 +378,8 @@ while (!this.loggedIn()) {

/**
* If the user is `loggedIn()`, the token will be returned immediate, else it will open a popup
* and wait until the user is `loggedIn()` (i.e. a new token has been added).
*/
async token() {
if (!this.loggedIn()) {
this.tryLoginPopup();
await this.authenticated();
}
await this.authenticated();
const token = this._rawToken;

@@ -156,7 +392,8 @@ if (!token) {

/**
* If the user is `loggedIn()`, the token payload will be returned immediate, else it will open a
* popup and wait until the user is `loggedIn()` (i.e. a new token has been added).
*/
async tokenPayload() {
if (!this.loggedIn()) {
this.tryLoginPopup();
await this.authenticated();
}
await this.authenticated();
const payload = this._rawTokenPayload;

@@ -169,6 +406,9 @@ if (!payload) {

/**
* wraps `JSON.parse` and return `undefined` if the parsing failed
*/
static jsonParseOrUndefined<T = {}>(json: string) {
try {
return JSON.parse(json) as T;
} catch {
} catch (e) {
return undefined;

@@ -178,4 +418,10 @@ }

/**
* wraps `setTimeout` in a `Promise` that resolves to `'TIMER'`
*/
static time(milliseconds: number) {
return new Promise<'TIMER'>(resolve => setTimeout(() => resolve('TIMER')));
return new Promise<'TIMER'>(resolve => window.setTimeout(
() => resolve('TIMER'),
milliseconds
));
}

@@ -189,3 +435,3 @@

return decodeURIComponent(str);
} catch {
} catch (e) {
return str;

@@ -195,2 +441,7 @@ }

/**
* Encodes an object of strings to a URL
*
* `{one: 'two', buckle: 'shoes or something'}` ==> `one=two&buckle=shoes%20or%20something`
*/
static encodeObjectToUri(obj: { [key: string]: string }) {

@@ -205,2 +456,7 @@ return (Object

/**
* Decodes a URL string to an object of string
*
* `one=two&buckle=shoes%20or%20something` ==> `{one: 'two', buckle: 'shoes or something'}`
*/
static decodeUriToObject(str: string) {

@@ -207,0 +463,0 @@ return str.split('&').reduce((decoded, keyValuePair) => {

@@ -5,4 +5,9 @@ {

"module": "commonjs",
"lib": ["dom", "es2015"],
"strict": true
"lib": [
"dom",
"es2015"
],
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true
},

@@ -9,0 +14,0 @@ "exclude": [

Sorry, the diff of this file is not supported yet

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