From 1d74c3916a3da36e6cc16ba502b83593d29c4279 Mon Sep 17 00:00:00 2001 From: Rishabh Poddar Date: Tue, 15 Sep 2020 03:39:18 +0530 Subject: [PATCH] stores front cookie and anti-csrf in website cookie - fixes #9. Allows for reading of userId and JWT payload on the frontend, securely. --- CHANGELOG.md | 1 + bundle/bundle.js | 2 +- lib/build/axios.d.ts | 2 + lib/build/axios.js | 58 +++++++++++++++++++++- lib/build/index.d.ts | 14 ++++++ lib/build/index.js | 112 ++++++++++++++++++++++++++++++++++++++++++- lib/ts/axios.ts | 47 +++++++++++++++++- lib/ts/index.ts | 108 ++++++++++++++++++++++++++++++++++++++++- 8 files changed, 339 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50f9009b..e5cd554d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Stores Anti CSRF token in cookie that can be shared across sub domains. This value is then read and added to the request header separately. - Compatible with FDI 1.2 +- Adds ability to get userID and JWT payload (securely) from the frontend ## [4.3.0] - 2020-08-20 ### Changed diff --git a/bundle/bundle.js b/bundle/bundle.js index 3f769075..c58f2157 100644 --- a/bundle/bundle.js +++ b/bundle/bundle.js @@ -1,4 +1,4 @@ -var supertokens=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,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},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=13)}([function(e,t,n){"use strict";var r=n(5),o=n(20),i=Object.prototype.toString;function s(e){return"[object Array]"===i.call(e)}function a(e){return null!==e&&"object"==typeof e}function u(e){return"[object Function]"===i.call(e)}function c(e,t){if(null!=e)if("object"!=typeof e&&(e=[e]),s(e))for(var n=0,r=e.length;n0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]=0;n--)if(t.history[n]==e)return t.history[n]},this.reset=function(){t.history=[]},this.waitForEvent=function(e,o){return void 0===o&&(o=7e3),n(t,void 0,void 0,(function(){var t,n=this;return r(this,(function(r){return t=Date.now(),[2,new Promise((function(r){var i=n;!function n(){var s=i.getEventByLastEventByName(e);void 0===s?Date.now()-t>o?r(void 0):setTimeout(n,1e3):r(s)}()}))]}))}))}}return t.getInstance=function(){return null==t.instance&&(t.instance=new t),t.instance},t}();t.ProcessState=o}).call(this,n(3))},function(e,t){var n,r,o=e.exports={};function i(){throw new Error("setTimeout has not been defined")}function s(){throw new Error("clearTimeout has not been defined")}function a(e){if(n===setTimeout)return setTimeout(e,0);if((n===i||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:i}catch(e){n=i}try{r="function"==typeof clearTimeout?clearTimeout:s}catch(e){r=s}}();var u,c=[],f=!1,l=-1;function d(){f&&u&&(f=!1,u.length?c=u.concat(c):l=-1,c.length&&h())}function h(){if(!f){var e=a(d);f=!0;for(var t=c.length;t;){for(u=c,c=[];++l1)for(var n=1;n=200&&e<300}};u.headers={common:{Accept:"application/json, text/plain, */*"}},r.forEach(["delete","get","head"],(function(e){u.headers[e]={}})),r.forEach(["post","put","patch"],(function(e){u.headers[e]=r.merge(i)})),e.exports=u}).call(this,n(3))},function(e,t,n){"use strict";var r=n(0),o=n(26),i=n(6),s=n(28),a=n(29),u=n(10);e.exports=function(e){return new Promise((function(t,c){var f=e.data,l=e.headers;r.isFormData(f)&&delete l["Content-Type"];var d=new XMLHttpRequest;if(e.auth){var h=e.auth.username||"",p=e.auth.password||"";l.Authorization="Basic "+btoa(h+":"+p)}if(d.open(e.method.toUpperCase(),i(e.url,e.params,e.paramsSerializer),!0),d.timeout=e.timeout,d.onreadystatechange=function(){if(d&&4===d.readyState&&(0!==d.status||d.responseURL&&0===d.responseURL.indexOf("file:"))){var n="getAllResponseHeaders"in d?s(d.getAllResponseHeaders()):null,r={data:e.responseType&&"text"!==e.responseType?d.response:d.responseText,status:d.status,statusText:d.statusText,headers:n,config:e,request:d};o(t,c,r),d=null}},d.onabort=function(){d&&(c(u("Request aborted",e,"ECONNABORTED",d)),d=null)},d.onerror=function(){c(u("Network Error",e,null,d)),d=null},d.ontimeout=function(){c(u("timeout of "+e.timeout+"ms exceeded",e,"ECONNABORTED",d)),d=null},r.isStandardBrowserEnv()){var v=n(30),m=(e.withCredentials||a(e.url))&&e.xsrfCookieName?v.read(e.xsrfCookieName):void 0;m&&(l[e.xsrfHeaderName]=m)}if("setRequestHeader"in d&&r.forEach(l,(function(e,t){void 0===f&&"content-type"===t.toLowerCase()?delete l[t]:d.setRequestHeader(t,e)})),e.withCredentials&&(d.withCredentials=!0),e.responseType)try{d.responseType=e.responseType}catch(t){if("json"!==e.responseType)throw t}"function"==typeof e.onDownloadProgress&&d.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&d.upload&&d.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then((function(e){d&&(d.abort(),c(e),d=null)})),void 0===f&&(f=null),d.send(f)}))}},function(e,t,n){"use strict";var r=n(27);e.exports=function(e,t,n,o,i){var s=new Error(e);return r(s,t,n,o,i)}},function(e,t,n){"use strict";var r=n(0);e.exports=function(e,t){t=t||{};var n={};return r.forEach(["url","method","params","data"],(function(e){void 0!==t[e]&&(n[e]=t[e])})),r.forEach(["headers","auth","proxy"],(function(o){r.isObject(t[o])?n[o]=r.deepMerge(e[o],t[o]):void 0!==t[o]?n[o]=t[o]:r.isObject(e[o])?n[o]=r.deepMerge(e[o]):void 0!==e[o]&&(n[o]=e[o])})),r.forEach(["baseURL","transformRequest","transformResponse","paramsSerializer","timeout","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","maxContentLength","validateStatus","maxRedirects","httpAgent","httpsAgent","cancelToken","socketPath"],(function(r){void 0!==t[r]?n[r]=t[r]:void 0!==e[r]&&(n[r]=e[r])})),n}},function(e,t,n){"use strict";function r(e){this.message=e}r.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},r.prototype.__CANCEL__=!0,e.exports=r},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(1),o=n(17),i=r.default;t.fetch=i;var s=o.default;t.axios=s},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function s(e){try{u(r.next(e))}catch(e){i(e)}}function a(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(s,a)}u((r=r.apply(e,t||[])).next())}))},o=this&&this.__generator||function(e,t){var n,r,o,i,s={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function a(i){return function(a){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return s.label++,{value:i[1],done:!1};case 5:s.label++,r=i[1],i=[0];continue;case 7:i=s.ops.pop(),s.trys.pop();continue;default:if(!(o=(o=s.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0?setTimeout(n,t):n()}}window.addEventListener("storage",a),e.addToWaiting(a);var u=setTimeout(a,Math.max(0,t-Date.now()))}))];case 1:return n.sent(),[2]}}))}))},e.addToWaiting=function(t){this.removeFromWaiting(t),void 0!==e.waiters&&e.waiters.push(t)},e.removeFromWaiting=function(t){void 0!==e.waiters&&(e.waiters=e.waiters.filter((function(e){return e!==t})))},e.notifyWaiters=function(){void 0!==e.waiters&&e.waiters.slice().forEach((function(e){return e()}))},e.prototype.releaseLock=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.releaseLock__private__(e)];case 1:return[2,t.sent()]}}))}))},e.prototype.releaseLock__private__=function(t){return r(this,void 0,void 0,(function(){var n,r,s;return o(this,(function(o){switch(o.label){case 0:return n=window.localStorage,r="browser-tabs-lock-key-"+t,null===(s=n.getItem(r))?[2]:(s=JSON.parse(s)).id!==this.id?[3,2]:[4,i.default().lock(s.iat)];case 1:o.sent(),this.acquiredIatSet.delete(s.iat),n.removeItem(r),i.default().unlock(s.iat),e.notifyWaiters(),o.label=2;case 2:return[2]}}))}))},e.lockCorrector=function(){for(var t=Date.now()-5e3,n=window.localStorage,r=Object.keys(n),o=!1,i=0;i0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]=0;n--)if(t.history[n]==e)return t.history[n]},this.reset=function(){t.history=[]},this.waitForEvent=function(e,o){return void 0===o&&(o=7e3),n(t,void 0,void 0,(function(){var t,n=this;return r(this,(function(r){return t=Date.now(),[2,new Promise((function(r){var i=n;!function n(){var s=i.getEventByLastEventByName(e);void 0===s?Date.now()-t>o?r(void 0):setTimeout(n,1e3):r(s)}()}))]}))}))}}return t.getInstance=function(){return null==t.instance&&(t.instance=new t),t.instance},t}();t.ProcessState=o}).call(this,n(3))},function(e,t){var n,r,o=e.exports={};function i(){throw new Error("setTimeout has not been defined")}function s(){throw new Error("clearTimeout has not been defined")}function a(e){if(n===setTimeout)return setTimeout(e,0);if((n===i||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:i}catch(e){n=i}try{r="function"==typeof clearTimeout?clearTimeout:s}catch(e){r=s}}();var u,c=[],f=!1,l=-1;function d(){f&&u&&(f=!1,u.length?c=u.concat(c):l=-1,c.length&&h())}function h(){if(!f){var e=a(d);f=!0;for(var t=c.length;t;){for(u=c,c=[];++l1)for(var n=1;n=200&&e<300}};u.headers={common:{Accept:"application/json, text/plain, */*"}},r.forEach(["delete","get","head"],(function(e){u.headers[e]={}})),r.forEach(["post","put","patch"],(function(e){u.headers[e]=r.merge(i)})),e.exports=u}).call(this,n(3))},function(e,t,n){"use strict";var r=n(0),o=n(26),i=n(6),s=n(28),a=n(29),u=n(10);e.exports=function(e){return new Promise((function(t,c){var f=e.data,l=e.headers;r.isFormData(f)&&delete l["Content-Type"];var d=new XMLHttpRequest;if(e.auth){var h=e.auth.username||"",p=e.auth.password||"";l.Authorization="Basic "+btoa(h+":"+p)}if(d.open(e.method.toUpperCase(),i(e.url,e.params,e.paramsSerializer),!0),d.timeout=e.timeout,d.onreadystatechange=function(){if(d&&4===d.readyState&&(0!==d.status||d.responseURL&&0===d.responseURL.indexOf("file:"))){var n="getAllResponseHeaders"in d?s(d.getAllResponseHeaders()):null,r={data:e.responseType&&"text"!==e.responseType?d.response:d.responseText,status:d.status,statusText:d.statusText,headers:n,config:e,request:d};o(t,c,r),d=null}},d.onabort=function(){d&&(c(u("Request aborted",e,"ECONNABORTED",d)),d=null)},d.onerror=function(){c(u("Network Error",e,null,d)),d=null},d.ontimeout=function(){c(u("timeout of "+e.timeout+"ms exceeded",e,"ECONNABORTED",d)),d=null},r.isStandardBrowserEnv()){var v=n(30),m=(e.withCredentials||a(e.url))&&e.xsrfCookieName?v.read(e.xsrfCookieName):void 0;m&&(l[e.xsrfHeaderName]=m)}if("setRequestHeader"in d&&r.forEach(l,(function(e,t){void 0===f&&"content-type"===t.toLowerCase()?delete l[t]:d.setRequestHeader(t,e)})),e.withCredentials&&(d.withCredentials=!0),e.responseType)try{d.responseType=e.responseType}catch(t){if("json"!==e.responseType)throw t}"function"==typeof e.onDownloadProgress&&d.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&d.upload&&d.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then((function(e){d&&(d.abort(),c(e),d=null)})),void 0===f&&(f=null),d.send(f)}))}},function(e,t,n){"use strict";var r=n(27);e.exports=function(e,t,n,o,i){var s=new Error(e);return r(s,t,n,o,i)}},function(e,t,n){"use strict";var r=n(0);e.exports=function(e,t){t=t||{};var n={};return r.forEach(["url","method","params","data"],(function(e){void 0!==t[e]&&(n[e]=t[e])})),r.forEach(["headers","auth","proxy"],(function(o){r.isObject(t[o])?n[o]=r.deepMerge(e[o],t[o]):void 0!==t[o]?n[o]=t[o]:r.isObject(e[o])?n[o]=r.deepMerge(e[o]):void 0!==e[o]&&(n[o]=e[o])})),r.forEach(["baseURL","transformRequest","transformResponse","paramsSerializer","timeout","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","maxContentLength","validateStatus","maxRedirects","httpAgent","httpsAgent","cancelToken","socketPath"],(function(r){void 0!==t[r]?n[r]=t[r]:void 0!==e[r]&&(n[r]=e[r])})),n}},function(e,t,n){"use strict";function r(e){this.message=e}r.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},r.prototype.__CANCEL__=!0,e.exports=r},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(1),o=n(17),i=r.default;t.fetch=i;var s=o.default;t.axios=s},function(e,t){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(e){"object"==typeof window&&(n=window)}e.exports=n},function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function s(e){try{u(r.next(e))}catch(e){i(e)}}function a(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(s,a)}u((r=r.apply(e,t||[])).next())}))},o=this&&this.__generator||function(e,t){var n,r,o,i,s={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function a(i){return function(a){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;s;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return s.label++,{value:i[1],done:!1};case 5:s.label++,r=i[1],i=[0];continue;case 7:i=s.ops.pop(),s.trys.pop();continue;default:if(!(o=(o=s.trys).length>0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0?setTimeout(n,t):n()}}window.addEventListener("storage",a),e.addToWaiting(a);var u=setTimeout(a,Math.max(0,t-Date.now()))}))];case 1:return n.sent(),[2]}}))}))},e.addToWaiting=function(t){this.removeFromWaiting(t),void 0!==e.waiters&&e.waiters.push(t)},e.removeFromWaiting=function(t){void 0!==e.waiters&&(e.waiters=e.waiters.filter((function(e){return e!==t})))},e.notifyWaiters=function(){void 0!==e.waiters&&e.waiters.slice().forEach((function(e){return e()}))},e.prototype.releaseLock=function(e){return r(this,void 0,void 0,(function(){return o(this,(function(t){switch(t.label){case 0:return[4,this.releaseLock__private__(e)];case 1:return[2,t.sent()]}}))}))},e.prototype.releaseLock__private__=function(t){return r(this,void 0,void 0,(function(){var n,r,s;return o(this,(function(o){switch(o.label){case 0:return n=window.localStorage,r="browser-tabs-lock-key-"+t,null===(s=n.getItem(r))?[2]:(s=JSON.parse(s)).id!==this.id?[3,2]:[4,i.default().lock(s.iat)];case 1:o.sent(),this.acquiredIatSet.delete(s.iat),n.removeItem(r),i.default().unlock(s.iat),e.notifyWaiters(),o.label=2;case 2:return[2]}}))}))},e.lockCorrector=function(){for(var t=Date.now()-5e3,n=window.localStorage,r=Object.keys(n),o=!1,i=0;i0&&o[o.length-1])&&(6===i[0]||2===i[0])){s=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]; /** * @description sends the actual http request and returns a response if successful/ * If not successful due to session expiry reasons, it diff --git a/lib/build/axios.js b/lib/build/axios.js index 9ec8f61b..559d1f61 100644 --- a/lib/build/axios.js +++ b/lib/build/axios.js @@ -225,7 +225,7 @@ function responseInterceptor(axiosInstance) { var _this = this; return function(response) { return __awaiter(_this, void 0, void 0, function() { - var url, idRefreshToken, config, antiCsrfToken; + var url, idRefreshToken, config, antiCsrfToken, frontToken; return __generator(this, function(_a) { try { if (!AuthHttpRequest.initCalled) { @@ -265,11 +265,16 @@ function responseInterceptor(axiosInstance) { if (antiCsrfToken !== undefined) { _1.AntiCsrfToken.setItem(_1.getIDFromCookie(), antiCsrfToken); } + frontToken = response.headers["front-token"]; + if (frontToken !== undefined) { + _1.FrontToken.setItem(frontToken); + } return [2 /*return*/, response]; } } finally { if (_1.getIDFromCookie() === undefined) { _1.AntiCsrfToken.removeToken(); + _1.FrontToken.removeToken(); } } return [2 /*return*/]; @@ -307,6 +312,49 @@ var AuthHttpRequest = /** @class */ (function() { AuthHttpRequest.apiDomain = _1.getDomainFromUrl(refreshTokenUrl); AuthHttpRequest.initCalled = true; }; + AuthHttpRequest.getUserId = function() { + var tokenInfo = _1.FrontToken.getTokenInfo(); + if (tokenInfo === undefined) { + throw new Error("No session exists"); + } + return tokenInfo.uid; + }; + AuthHttpRequest.getJWTPayloadSecurely = function() { + return __awaiter(this, void 0, void 0, function() { + var tokenInfo, preRequestIdToken, retry; + return __generator(this, function(_a) { + switch (_a.label) { + case 0: + tokenInfo = _1.FrontToken.getTokenInfo(); + if (tokenInfo === undefined) { + throw new Error("No session exists"); + } + if (!(tokenInfo.ate < Date.now())) return [3 /*break*/, 4]; + preRequestIdToken = _1.getIDFromCookie(); + return [ + 4 /*yield*/, + _1.handleUnauthorised( + AuthHttpRequest.refreshTokenUrl, + preRequestIdToken, + AuthHttpRequest.websiteRootDomain, + AuthHttpRequest.refreshAPICustomHeaders, + AuthHttpRequest.sessionExpiredStatusCode + ) + ]; + case 1: + retry = _a.sent(); + if (!retry) return [3 /*break*/, 3]; + return [4 /*yield*/, AuthHttpRequest.getJWTPayloadSecurely()]; + case 2: + return [2 /*return*/, _a.sent()]; + case 3: + throw new Error("Could not refresh session"); + case 4: + return [2 /*return*/, tokenInfo.up]; + } + }); + }); + }; AuthHttpRequest.sessionExpiredStatusCode = 401; AuthHttpRequest.initCalled = false; AuthHttpRequest.apiDomain = ""; @@ -334,6 +382,7 @@ var AuthHttpRequest = /** @class */ (function() { idRefreshToken, retry, antiCsrfToken_1, + frontToken, err_1, retry; return __generator(this, function(_b) { @@ -442,6 +491,10 @@ var AuthHttpRequest = /** @class */ (function() { if (antiCsrfToken_1 !== undefined) { _1.AntiCsrfToken.setItem(_1.getIDFromCookie(), antiCsrfToken_1); } + frontToken = response.headers["front-token"]; + if (frontToken !== undefined) { + _1.FrontToken.setItem(frontToken); + } return [2 /*return*/, response]; case 10: return [3 /*break*/, 15]; @@ -489,6 +542,7 @@ var AuthHttpRequest = /** @class */ (function() { case 17: if (_1.getIDFromCookie() === undefined) { _1.AntiCsrfToken.removeToken(); + _1.FrontToken.removeToken(); } return [7 /*endfinally*/]; case 18: @@ -530,6 +584,7 @@ var AuthHttpRequest = /** @class */ (function() { case 3: if (_1.getIDFromCookie() === undefined) { _1.AntiCsrfToken.removeToken(); + _1.FrontToken.removeToken(); } return [7 /*endfinally*/]; case 4: @@ -681,6 +736,7 @@ var AuthHttpRequest = /** @class */ (function() { } finally { if (_1.getIDFromCookie() === undefined) { _1.AntiCsrfToken.removeToken(); + _1.FrontToken.removeToken(); } } return [2 /*return*/]; diff --git a/lib/build/index.d.ts b/lib/build/index.d.ts index 61ca4db8..e6f95f4f 100644 --- a/lib/build/index.d.ts +++ b/lib/build/index.d.ts @@ -5,6 +5,16 @@ export declare class AntiCsrfToken { static removeToken(): void; static setItem(associatedIdRefreshToken: string | undefined, antiCsrf: string): undefined; } +export declare class FrontToken { + private constructor(); + static getTokenInfo(): { + uid: string; + ate: number; + up: any; + } | undefined; + static removeToken(): void; + static setItem(frontToken: string): void; +} /** * @description returns true if retry, else false is session has expired completely. */ @@ -38,6 +48,8 @@ export default class AuthHttpRequest { autoAddCredentials?: boolean; }): void; static getRefreshURLDomain: () => string | undefined; + static getUserId(): string; + static getJWTPayloadSecurely(): Promise; /** * @description sends the actual http request and returns a response if successful/ * If not successful due to session expiry reasons, it @@ -74,3 +86,5 @@ export declare function getIDFromCookie(): string | undefined; export declare function setIDToCookie(idRefreshToken: string, domain: string): void; export declare function getAntiCSRFromCookie(domain: string): string | null; export declare function setAntiCSRFToCookie(antiCSRFToken: string | undefined, domain: string): void; +export declare function getFrontTokenFromCookie(): string | null; +export declare function setFrontTokenToCookie(frontToken: string | undefined, domain: string): void; diff --git a/lib/build/index.js b/lib/build/index.js index 40efbe87..32baad0a 100644 --- a/lib/build/index.js +++ b/lib/build/index.js @@ -201,6 +201,26 @@ var AntiCsrfToken = /** @class */ (function() { return AntiCsrfToken; })(); exports.AntiCsrfToken = AntiCsrfToken; +// Note: We do not store this in memory because another tab may have +// modified this value, and if so, we may not know about it in this tab +var FrontToken = /** @class */ (function() { + function FrontToken() {} + FrontToken.getTokenInfo = function() { + var frontToken = getFrontTokenFromCookie(); + if (frontToken === null) { + return undefined; + } + return JSON.parse(atob(frontToken)); + }; + FrontToken.removeToken = function() { + setFrontTokenToCookie(undefined, AuthHttpRequest.websiteRootDomain); + }; + FrontToken.setItem = function(frontToken) { + setFrontTokenToCookie(frontToken, AuthHttpRequest.websiteRootDomain); + }; + return FrontToken; +})(); +exports.FrontToken = FrontToken; /** * @description returns true if retry, else false is session has expired completely. */ @@ -309,6 +329,49 @@ var AuthHttpRequest = /** @class */ (function() { AuthHttpRequest.apiDomain = getDomainFromUrl(refreshTokenUrl); AuthHttpRequest.initCalled = true; }; + AuthHttpRequest.getUserId = function() { + var tokenInfo = FrontToken.getTokenInfo(); + if (tokenInfo === undefined) { + throw new Error("No session exists"); + } + return tokenInfo.uid; + }; + AuthHttpRequest.getJWTPayloadSecurely = function() { + return __awaiter(this, void 0, void 0, function() { + var tokenInfo, preRequestIdToken, retry; + return __generator(this, function(_a) { + switch (_a.label) { + case 0: + tokenInfo = FrontToken.getTokenInfo(); + if (tokenInfo === undefined) { + throw new Error("No session exists"); + } + if (!(tokenInfo.ate < Date.now())) return [3 /*break*/, 4]; + preRequestIdToken = getIDFromCookie(); + return [ + 4 /*yield*/, + handleUnauthorised( + AuthHttpRequest.refreshTokenUrl, + preRequestIdToken, + AuthHttpRequest.websiteRootDomain, + AuthHttpRequest.refreshAPICustomHeaders, + AuthHttpRequest.sessionExpiredStatusCode + ) + ]; + case 1: + retry = _a.sent(); + if (!retry) return [3 /*break*/, 3]; + return [4 /*yield*/, AuthHttpRequest.getJWTPayloadSecurely()]; + case 2: + return [2 /*return*/, _a.sent()]; + case 3: + throw new Error("Could not refresh session"); + case 4: + return [2 /*return*/, tokenInfo.up]; + } + }); + }); + }; AuthHttpRequest.sessionExpiredStatusCode = 401; AuthHttpRequest.initCalled = false; AuthHttpRequest.apiDomain = ""; @@ -435,6 +498,8 @@ var AuthHttpRequest = /** @class */ (function() { response.headers.forEach(function(value, key) { if (key.toString() === "anti-csrf") { AntiCsrfToken.setItem(getIDFromCookie(), value); + } else if (key.toString() === "front-token") { + FrontToken.setItem(value); } }); return [2 /*return*/, response]; @@ -478,6 +543,7 @@ var AuthHttpRequest = /** @class */ (function() { case 16: if (getIDFromCookie() === undefined) { AntiCsrfToken.removeToken(); + FrontToken.removeToken(); } return [7 /*endfinally*/]; case 17: @@ -519,6 +585,7 @@ var AuthHttpRequest = /** @class */ (function() { case 3: if (getIDFromCookie() === undefined) { AntiCsrfToken.removeToken(); + FrontToken.removeToken(); } return [7 /*endfinally*/]; case 4: @@ -604,6 +671,7 @@ var AuthHttpRequest = /** @class */ (function() { exports.default = AuthHttpRequest; var ID_COOKIE_NAME = "sIRTFrontend"; var ANTI_CSRF_COOKIE_NAME = "sAntiCsrf"; +var FRONT_TOKEN_COOKIE_NAME = "sFrontToken"; /** * @description attempts to call the refresh token API each time we are sure the session has expired, or it throws an error or, * or the ID_COOKIE_NAME has changed value -> which may mean that we have a new set of tokens. @@ -685,6 +753,8 @@ function onUnauthorisedResponse( response.headers.forEach(function(value, key) { if (key.toString() === "anti-csrf") { AntiCsrfToken.setItem(getIDFromCookie(), value); + } else if (key.toString() === "front-token") { + FrontToken.setItem(value); } }); return [2 /*return*/, { value: { result: "RETRY" } }]; @@ -755,7 +825,6 @@ function setIDToCookie(idRefreshToken, domain) { } } exports.setIDToCookie = setIDToCookie; -// NOTE: we do not store this in memory and always read as to synchronize events across tabs function getAntiCSRFromCookie(domain) { var value = "; " + document.cookie; var parts = value.split("; " + ANTI_CSRF_COOKIE_NAME + "="); @@ -808,3 +877,44 @@ function setAntiCSRFToCookie(antiCSRFToken, domain) { } } exports.setAntiCSRFToCookie = setAntiCSRFToCookie; +function getFrontTokenFromCookie() { + var value = "; " + document.cookie; + var parts = value.split("; " + FRONT_TOKEN_COOKIE_NAME + "="); + if (parts.length >= 2) { + var last = parts.pop(); + if (last !== undefined) { + var temp = last.split(";").shift(); + if (temp === undefined) { + return null; + } + return temp; + } + } + return null; +} +exports.getFrontTokenFromCookie = getFrontTokenFromCookie; +// give frontToken as undefined to remove it. +function setFrontTokenToCookie(frontToken, domain) { + var expires = "Thu, 01 Jan 1970 00:00:01 GMT"; + var cookieVal = ""; + if (frontToken !== undefined) { + cookieVal = frontToken; + expires = undefined; // set cookie without expiry + } + if (domain === "localhost") { + // since some browsers ignore cookies with domain set to localhost + if (expires !== undefined) { + document.cookie = FRONT_TOKEN_COOKIE_NAME + "=" + cookieVal + ";expires=" + expires + ";path=/"; + } else { + document.cookie = FRONT_TOKEN_COOKIE_NAME + "=" + cookieVal + ";path=/"; + } + } else { + if (expires !== undefined) { + document.cookie = + FRONT_TOKEN_COOKIE_NAME + "=" + cookieVal + ";expires=" + expires + ";domain=" + domain + ";path=/"; + } else { + document.cookie = FRONT_TOKEN_COOKIE_NAME + "=" + cookieVal + ";domain=" + domain + ";path=/"; + } + } +} +exports.setFrontTokenToCookie = setFrontTokenToCookie; diff --git a/lib/ts/axios.ts b/lib/ts/axios.ts index 85e726b8..e9d4ceda 100644 --- a/lib/ts/axios.ts +++ b/lib/ts/axios.ts @@ -19,7 +19,8 @@ import FetchAuthRequest, { getDomainFromUrl, handleUnauthorised, getIDFromCookie, - setIDToCookie + setIDToCookie, + FrontToken } from "."; import { PROCESS_STATE, ProcessState } from "./processState"; import { package_version } from "./version"; @@ -124,11 +125,16 @@ export function responseInterceptor(axiosInstance: any) { if (antiCsrfToken !== undefined) { AntiCsrfToken.setItem(getIDFromCookie(), antiCsrfToken); } + let frontToken = response.headers["front-token"]; + if (frontToken !== undefined) { + FrontToken.setItem(frontToken); + } return response; } } finally { if (getIDFromCookie() === undefined) { AntiCsrfToken.removeToken(); + FrontToken.removeToken(); } } }; @@ -183,6 +189,38 @@ export default class AuthHttpRequest { AuthHttpRequest.initCalled = true; } + static getUserId(): string { + let tokenInfo = FrontToken.getTokenInfo(); + if (tokenInfo === undefined) { + throw new Error("No session exists"); + } + return tokenInfo.uid; + } + + static async getJWTPayloadSecurely(): Promise { + let tokenInfo = FrontToken.getTokenInfo(); + if (tokenInfo === undefined) { + throw new Error("No session exists"); + } + + if (tokenInfo.ate < Date.now()) { + const preRequestIdToken = getIDFromCookie(); + let retry = await handleUnauthorised( + AuthHttpRequest.refreshTokenUrl, + preRequestIdToken, + AuthHttpRequest.websiteRootDomain, + AuthHttpRequest.refreshAPICustomHeaders, + AuthHttpRequest.sessionExpiredStatusCode + ); + if (retry) { + return await AuthHttpRequest.getJWTPayloadSecurely(); + } else { + throw new Error("Could not refresh session"); + } + } + return tokenInfo.up; + } + /** * @description sends the actual http request and returns a response if successful/ * If not successful due to session expiry reasons, it @@ -285,6 +323,10 @@ export default class AuthHttpRequest { if (antiCsrfToken !== undefined) { AntiCsrfToken.setItem(getIDFromCookie(), antiCsrfToken); } + let frontToken = response.headers["front-token"]; + if (frontToken !== undefined) { + FrontToken.setItem(frontToken); + } return response; } } catch (err) { @@ -318,6 +360,7 @@ export default class AuthHttpRequest { } finally { if (getIDFromCookie() === undefined) { AntiCsrfToken.removeToken(); + FrontToken.removeToken(); } } }; @@ -343,6 +386,7 @@ export default class AuthHttpRequest { } finally { if (getIDFromCookie() === undefined) { AntiCsrfToken.removeToken(); + FrontToken.removeToken(); } } }; @@ -452,6 +496,7 @@ export default class AuthHttpRequest { } finally { if (getIDFromCookie() === undefined) { AntiCsrfToken.removeToken(); + FrontToken.removeToken(); } } }); diff --git a/lib/ts/index.ts b/lib/ts/index.ts index 3e01667e..350c08a7 100644 --- a/lib/ts/index.ts +++ b/lib/ts/index.ts @@ -66,6 +66,34 @@ export class AntiCsrfToken { } } +// Note: We do not store this in memory because another tab may have +// modified this value, and if so, we may not know about it in this tab +export class FrontToken { + private constructor() {} + + static getTokenInfo(): + | { + uid: string; + ate: number; + up: any; + } + | undefined { + let frontToken = getFrontTokenFromCookie(); + if (frontToken === null) { + return undefined; + } + return JSON.parse(atob(frontToken)); + } + + static removeToken() { + setFrontTokenToCookie(undefined, AuthHttpRequest.websiteRootDomain); + } + + static setItem(frontToken: string) { + setFrontTokenToCookie(frontToken, AuthHttpRequest.websiteRootDomain); + } +} + /** * @description returns true if retry, else false is session has expired completely. */ @@ -193,6 +221,38 @@ export default class AuthHttpRequest { return getDomainFromUrl(AuthHttpRequest.refreshTokenUrl); }; + static getUserId(): string { + let tokenInfo = FrontToken.getTokenInfo(); + if (tokenInfo === undefined) { + throw new Error("No session exists"); + } + return tokenInfo.uid; + } + + static async getJWTPayloadSecurely(): Promise { + let tokenInfo = FrontToken.getTokenInfo(); + if (tokenInfo === undefined) { + throw new Error("No session exists"); + } + + if (tokenInfo.ate < Date.now()) { + const preRequestIdToken = getIDFromCookie(); + let retry = await handleUnauthorised( + AuthHttpRequest.refreshTokenUrl, + preRequestIdToken, + AuthHttpRequest.websiteRootDomain, + AuthHttpRequest.refreshAPICustomHeaders, + AuthHttpRequest.sessionExpiredStatusCode + ); + if (retry) { + return await AuthHttpRequest.getJWTPayloadSecurely(); + } else { + throw new Error("Could not refresh session"); + } + } + return tokenInfo.up; + } + /** * @description sends the actual http request and returns a response if successful/ * If not successful due to session expiry reasons, it @@ -286,6 +346,8 @@ export default class AuthHttpRequest { response.headers.forEach((value, key) => { if (key.toString() === "anti-csrf") { AntiCsrfToken.setItem(getIDFromCookie(), value); + } else if (key.toString() === "front-token") { + FrontToken.setItem(value); } }); return response; @@ -318,6 +380,7 @@ export default class AuthHttpRequest { } finally { if (getIDFromCookie() === undefined) { AntiCsrfToken.removeToken(); + FrontToken.removeToken(); } } }; @@ -343,6 +406,7 @@ export default class AuthHttpRequest { } finally { if (getIDFromCookie() === undefined) { AntiCsrfToken.removeToken(); + FrontToken.removeToken(); } } }; @@ -394,6 +458,7 @@ export default class AuthHttpRequest { const ID_COOKIE_NAME = "sIRTFrontend"; const ANTI_CSRF_COOKIE_NAME = "sAntiCsrf"; +const FRONT_TOKEN_COOKIE_NAME = "sFrontToken"; /** * @description attempts to call the refresh token API each time we are sure the session has expired, or it throws an error or, @@ -459,6 +524,8 @@ export async function onUnauthorisedResponse( response.headers.forEach((value: any, key: any) => { if (key.toString() === "anti-csrf") { AntiCsrfToken.setItem(getIDFromCookie(), value); + } else if (key.toString() === "front-token") { + FrontToken.setItem(value); } }); return { result: "RETRY" }; @@ -514,7 +581,6 @@ export function setIDToCookie(idRefreshToken: string, domain: string) { } } -// NOTE: we do not store this in memory and always read as to synchronize events across tabs export function getAntiCSRFromCookie(domain: string): string | null { let value = "; " + document.cookie; let parts = value.split("; " + ANTI_CSRF_COOKIE_NAME + "="); @@ -567,3 +633,43 @@ export function setAntiCSRFToCookie(antiCSRFToken: string | undefined, domain: s window.localStorage.removeItem("anti-csrf-localstorage"); } } + +export function getFrontTokenFromCookie(): string | null { + let value = "; " + document.cookie; + let parts = value.split("; " + FRONT_TOKEN_COOKIE_NAME + "="); + if (parts.length >= 2) { + let last = parts.pop(); + if (last !== undefined) { + let temp = last.split(";").shift(); + if (temp === undefined) { + return null; + } + return temp; + } + } + return null; +} + +// give frontToken as undefined to remove it. +export function setFrontTokenToCookie(frontToken: string | undefined, domain: string) { + let expires: string | undefined = "Thu, 01 Jan 1970 00:00:01 GMT"; + let cookieVal = ""; + if (frontToken !== undefined) { + cookieVal = frontToken; + expires = undefined; // set cookie without expiry + } + if (domain === "localhost") { + // since some browsers ignore cookies with domain set to localhost + if (expires !== undefined) { + document.cookie = `${FRONT_TOKEN_COOKIE_NAME}=${cookieVal};expires=${expires};path=/`; + } else { + document.cookie = `${FRONT_TOKEN_COOKIE_NAME}=${cookieVal};path=/`; + } + } else { + if (expires !== undefined) { + document.cookie = `${FRONT_TOKEN_COOKIE_NAME}=${cookieVal};expires=${expires};domain=${domain};path=/`; + } else { + document.cookie = `${FRONT_TOKEN_COOKIE_NAME}=${cookieVal};domain=${domain};path=/`; + } + } +}