From b3169f43e4002af5eadc37d19e78587a2ccf3053 Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Sun, 24 Sep 2023 01:57:49 +0200 Subject: [PATCH 1/2] test: split interception.basic1 into multiple smaller test files to help with parallelization --- test/cross.auto_refresh.test.js | 666 ++++++ test/cross.basic_jwt.test.js | 962 +++++++++ test/cross.cross_domain.test.js | 316 +++ test/cross.disable_interception.test.js | 270 +++ test/cross.resp_error.test.js | 283 +++ test/cross.token_migration.test.js | 323 +++ test/cross.unauthorised_event.test.js | 304 +++ test/interception.basic1.test.js | 2496 +---------------------- 8 files changed, 3237 insertions(+), 2383 deletions(-) create mode 100644 test/cross.auto_refresh.test.js create mode 100644 test/cross.basic_jwt.test.js create mode 100644 test/cross.cross_domain.test.js create mode 100644 test/cross.disable_interception.test.js create mode 100644 test/cross.resp_error.test.js create mode 100644 test/cross.token_migration.test.js create mode 100644 test/cross.unauthorised_event.test.js diff --git a/test/cross.auto_refresh.test.js b/test/cross.auto_refresh.test.js new file mode 100644 index 00000000..16db4b6f --- /dev/null +++ b/test/cross.auto_refresh.test.js @@ -0,0 +1,666 @@ +/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +let axios = require("axios"); +let puppeteer = require("puppeteer"); +let decodeJWT = require("jsonwebtoken").decode; +let verifyJWT = require("jsonwebtoken").verify; +let jwksClient = require("jwks-rsa"); +let assert = require("assert"); +let { + delay, + getNumberOfTimesRefreshCalled, + startST, + startSTWithJWTEnabled, + getNumberOfTimesGetSessionCalled, + BASE_URL, + BASE_URL_FOR_ST, + coreTagEqualToOrAfter, + checkIfJWTIsEnabled, + checkIfV3AccessTokenIsSupported +} = require("./utils"); +const { spawn } = require("child_process"); +const { addGenericTestCases: addTestCases } = require("./interception.testgen"); + +/* setupFunc is called through page.evaluate at the start of each test + It should set window.toTest to a function that receives a config object with: + url + method + headers (as object) + body + and should return a response object with: + url + statusCode + headers (as Headers) + responseText (text) +*/ + +addTestCases((name, transferMethod, setupFunc, setupArgs = []) => { + describe(`${name}: automatic session refresh`, function () { + let browser; + let page; + let v3AccessTokenSupported; + + function setup(config = {}) { + // page.on("console", c => console.log(c.text())); + return page.evaluate( + setupFunc, + { + // enableDebugLogs: true, + ...config + }, + ...setupArgs + ); + } + + before(async function () { + spawn( + "./test/startServer", + [process.env.INSTALL_PATH, process.env.NODE_PORT === undefined ? 8080 : process.env.NODE_PORT], + { + // stdio: "inherit", + // env: { + // ...process.env, + // DEBUG: "com.supertokens", + // } + } + ); + await new Promise(r => setTimeout(r, 1000)); + v3AccessTokenSupported = await checkIfV3AccessTokenIsSupported(); + }); + + after(async function () { + let instance = axios.create(); + await instance.post(BASE_URL_FOR_ST + "/after"); + try { + await instance.get(BASE_URL_FOR_ST + "/stop"); + } catch (err) {} + }); + + beforeEach(async function () { + let instance = axios.create(); + await instance.post(BASE_URL_FOR_ST + "/beforeeach"); + await instance.post("http://localhost.org:8082/beforeeach"); // for cross domain + await instance.post(BASE_URL + "/beforeeach"); + + let launchRetries = 0; + while (browser === undefined && launchRetries++ < 3) { + try { + browser = await puppeteer.launch({ + args: ["--no-sandbox", "--disable-setuid-sandbox"], + headless: true + }); + + page = await browser.newPage(); + + await page.goto(BASE_URL + "/index.html", { waitUntil: "load" }); + await page.addScriptTag({ path: `./bundle/bundle.js`, type: "text/javascript" }); + await page.evaluate(BASE_URL => (window.BASE_URL = BASE_URL), BASE_URL); + await new Promise(r => setTimeout(r, 100)); + } catch {} + } + }); + + afterEach(async function () { + if (browser) { + await browser.close(); + browser = undefined; + } + }); + + it("refresh session with invalid tokens should clear all cookies", async function () { + await startST(); + await setup(); + + await page.evaluate(async () => { + let userId = "testing-supertokens-website"; + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + }); + + // we save the cookies.. + let originalCookies = (await page._client.send("Network.getAllCookies")).cookies; + + // we logout + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + await toTest({ url: `${BASE_URL}/logout`, method: "POST" }); + }); + + // we set the old cookies with invalid access token + originalCookies = originalCookies.map(c => + c.name === "sAccessToken" || c.name === "st-access-token" ? { ...c, value: "broken" } : c + ); + await page.setCookie(...originalCookies); + + // now we expect a 401. + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + let resp = await toTest({ url: `${BASE_URL}/`, method: "GET" }); + assert.strictEqual(resp.statusCode, 401); + // assert.strictEqual(resp.url, `${BASE_URL}/auth/session/refresh`); + }); + + // and we assert that the only cookie that exists is the st-last-access-token-update + let newCookies = (await page._client.send("Network.getAllCookies")).cookies; + + assert.strictEqual(newCookies.length, 1); + assert.strictEqual(newCookies[0].name, "st-last-access-token-update"); + }); + + it("refresh session endpoint responding with 500 makes the original call resolve with refresh response", async function () { + await startST(100, true, "0.002"); + await setup(); + + await page.setRequestInterception(true); + let firstGet = true; + let firstPost = true; + page.on("request", req => { + const url = req.url(); + if (url === BASE_URL + "/") { + if (firstGet) { + firstGet = false; + req.respond({ + status: 401, + body: JSON.stringify({ + message: "try refresh token" + }) + }); + } else { + req.respond({ + status: 200, + body: JSON.stringify({ + success: true + }) + }); + } + } else if (url === BASE_URL + "/auth/session/refresh") { + if (firstPost) { + req.respond({ + status: 401, + body: JSON.stringify({ + message: "try refresh token" + }) + }); + firstPost = false; + } else { + req.respond({ + status: 500, + body: JSON.stringify({ + message: "test" + }) + }); + } + } else { + req.continue(); + } + }); + + // page.on("console", l => console.log(l.text())); + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + let userId = "testing-supertokens-website"; + + await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + let response = await toTest({ url: `${BASE_URL}/`, method: "GET" }); + // assert.strictEqual(response.url, `${BASE_URL}/auth/session/refresh`); + assert.strictEqual(response.statusCode, 500); + const data = JSON.parse(response.responseText); + assert.strictEqual(data.message, "test"); + }); + }); + + it("no refresh call after 401 response that removes session", async function () { + await startST(100, true, "0.002"); + await setup(); + await page.setRequestInterception(true); + let refreshCalled = 0; + page.on("request", req => { + const url = req.url(); + if (url === BASE_URL + "/") { + req.respond({ + status: 401, + body: JSON.stringify({ message: "test" }), + headers: { + // Cookies don't actually matter as long as we clear the front-token + // this is because the frontend will still have st-last-access-token-update w/ a removed front-token + // This is interpreted as a logged-out state + "front-token": "remove" + } + }); + } else if (url === BASE_URL + "/auth/session/refresh") { + ++refreshCalled; + req.respond({ + status: 401, + body: JSON.stringify({ message: "nope" }) + }); + } else { + req.continue(); + } + }); + + await page.evaluate(async () => { + let userId = "testing-supertokens-website"; + await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + const resp = await toTest({ + url: `${BASE_URL}/`, + method: "GET", + headers: { "Cache-Control": "no-cache, private" } + }); + + assertNotEqual(resp, undefined); + assert.strictEqual(resp.statusCode, 401); + const data = JSON.parse(resp.responseText); + assertNotEqual(data, undefined); + assert.strictEqual(data.message, "test"); + }); + + // Calls it once before login, but it shouldn't after that + assert.equal(refreshCalled, 1); + }); + + it("original endpoint responding with 500 should not call refresh without cookies", async function () { + await startST(100, true, "0.002"); + await setup(); + await page.setRequestInterception(true); + let refreshCalled = 0; + page.on("request", req => { + const url = req.url(); + if (url === BASE_URL + "/") { + req.respond({ + status: 500, + body: JSON.stringify({ + message: "test" + }) + }); + } else if (url === BASE_URL + "/auth/session/refresh") { + ++refreshCalled; + req.respond({ + status: 500, + body: JSON.stringify({ + message: "nope" + }) + }); + } else { + req.continue(); + } + }); + + await page.evaluate(async () => { + let response = await toTest({ url: `${BASE_URL}/`, method: "GET" }); + assert.strictEqual(response.url, `${BASE_URL}/`); + assert.strictEqual(response.statusCode, 500); + const data = JSON.parse(response.responseText); + assert.strictEqual(data.message, "test"); + }); + // It should call it once before the call - but after that doesn't work it should not try again after the API request + assert.strictEqual(refreshCalled, 1); + }); + + //If via interception, make sure that initially, just an endpoint is just hit twice in case of access token expiry***** + it("test that if via interception, initially an endpoint is hit just twice in case of access token expiary", async () => { + await startST(3); + await setup(); + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + let userId = "testing-supertokens-website"; + + // send api request to login + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.responseText, userId); + + //wait for 3 seconds such that the session expires + await delay(5); + + let getSessionResponse = await toTest({ url: `${BASE_URL}/` }); + assert.strictEqual(getSessionResponse.responseText, userId); + + //check that the number of times getSession was called is 1 + assert.strictEqual(await getNumberOfTimesGetSessionCalled(), 1); + + //check that the number of times refesh session was called is 1 + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); + }); + }); + + //- If you make an api call without cookies(logged out) api throws session expired , then make sure that refresh token api is not getting called , get 401 as the output**** + it("test that an api call without cookies throws session expire, refresh api is not called and 401 is the output", async function () { + await startST(5); + await setup(); + + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + let userId = "testing-supertokens-website"; + + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.responseText, userId); + + let logoutResponse = await toTest({ + url: `${BASE_URL}/logout`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(logoutResponse.responseText, "success"); + + let getSessionResponse = await toTest({ url: `${BASE_URL}/` }); + + //check that the response to getSession without cookies is 401 + assert.strictEqual(getSessionResponse.statusCode, 401); + + assert.strictEqual(getSessionResponse.url, `${BASE_URL}/`); + assert.strictEqual(await getNumberOfTimesRefreshAttempted(), 1); + }); + }); + + // - If via interception, make sure that initially, just an endpoint is just hit once in case of access token NOT expiry***** + it("test that via interception initially an endpoint is just hit once in case of valid access token", async function () { + await startST(5); + await setup(); + + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + let userId = "testing-supertokens-website"; + + // send api request to login + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.responseText, userId); + + let getSessionResponse = await toTest({ url: `${BASE_URL}/` }); + assert.strictEqual(getSessionResponse.responseText, userId); + + //check that the number of times getSession was called is 1 + assert.strictEqual(await getNumberOfTimesGetSessionCalled(), 1); + + //check that the number of times refresh session was called is 0 + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); + }); + }); + + // multiple API calls in parallel when access token is expired (100 of them) and only 1 refresh should be called***** + it("test that multiple API calls in parallel when access token is expired, only 1 refresh should be called", async function () { + await startST(15); + await setup(); + await page.evaluate(async () => { + let userId = "testing-supertokens-website"; + + // send api request to login + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + assert.strictEqual(loginResponse.responseText, userId); + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); + + // wait for 7 seconds so that the accesstoken expires + await delay(17); + + let promises = []; + let n = 100; + + // create an array of 100 get session promises + for (let i = 0; i < n; i++) { + promises.push( + toTest({ + url: `${BASE_URL}/`, + method: "GET", + headers: { "Cache-Control": "no-cache, private" } + }) + ); + } + + // send 100 get session requests + let multipleGetSessionResponse = await Promise.all(promises); + + //check that reponse of all requests are success + let noOfResponeSuccesses = 0; + for (let i = 0; i < multipleGetSessionResponse.length; i++) { + assert.strictEqual(await multipleGetSessionResponse[i].responseText, userId); + noOfResponeSuccesses += 1; + } + + //check that the number of times refresh is called is 1 + + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); + assert.strictEqual(noOfResponeSuccesses, n); + }); + }); + + // multiple API calls in parallel when access token is expired (100 of them) and only 1 refresh should be called***** + it("test that multiple API calls in parallel when access token is expired, only 1 refresh should be called - with delayed calls", async function () { + await startST(15); + await setup(); + await page.evaluate(async () => { + let userId = "testing-supertokens-website"; + + // send api request to login + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + assert.strictEqual(loginResponse.responseText, userId); + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); + + // wait for 7 seconds so that the accesstoken expires + await delay(17); + + let promises = []; + let n = 100; + + // create an array of 100 get session promises + for (let i = 0; i < n; i++) { + // this will make it so that there are calls to the / API during the refresh call. + // these calls should not cause another refresh, cause the tokens would have changed. + await new Promise(r => setTimeout(r, 3 * Math.random())); + promises.push( + toTest({ + url: `${BASE_URL}/`, + method: "GET", + headers: { "Cache-Control": "no-cache, private" } + }) + ); + } + + // send 100 get session requests + let multipleGetSessionResponse = await Promise.all(promises); + + //check that reponse of all requests are success + let noOfResponeSuccesses = 0; + for (let i = 0; i < multipleGetSessionResponse.length; i++) { + assert.strictEqual(await multipleGetSessionResponse[i].responseText, userId); + noOfResponeSuccesses += 1; + } + + //check that the number of times refresh is called is 1 + + try { + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); + } catch (err) { + // this happens sometimes for python testing where the number of refreshes is + // 2. + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 2); + } + assert.strictEqual(noOfResponeSuccesses, n); + }); + }); + + it("test refresh session", async function () { + await startST(3); + await setup(); + await page.evaluate(async () => { + const userId = "testing-supertokens-website"; + const loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.statusCode, 200); + assert.strictEqual(loginResponse.responseText, userId); + //delay for 5 seconds for access token validity expiry + await delay(5); + + //check that the number of times the refreshAPI was called is 0 + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); + + let getResponse = await toTest({ url: `${BASE_URL}/` }); + //check that the response to getSession was success + assert.strictEqual(getResponse.statusCode, 200); + assert.strictEqual(getResponse.responseText, userId); + + //check that the number of time the refreshAPI was called is 1 + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); + }); + }); + + it("test refresh session with multiple 401s", async function () { + await startST(3); + await setup(); + await page.setRequestInterception(true); + let getCount = 0; + page.on("request", req => { + const url = req.url(); + if (url === BASE_URL + "/") { + if (getCount++ < 3) { + req.respond({ + status: 401, + body: JSON.stringify({ + message: "try refresh token" + }) + }); + } else { + req.respond({ + status: 200, + body: JSON.stringify({ + success: true + }) + }); + } + } else { + req.continue(); + } + }); + await page.evaluate(async () => { + const userId = "testing-supertokens-website"; + const loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.statusCode, 200); + assert.strictEqual(loginResponse.responseText, userId); + //delay for 5 seconds for access token validity expiry + await delay(5); + + //check that the number of times the refreshAPI was called is 0 + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); + + let getResponse = await toTest({ url: `${BASE_URL}/` }); + //check that the response to getSession was success + assert.strictEqual(getResponse.statusCode, 200); + assert.deepStrictEqual(JSON.parse(getResponse.responseText), { success: true }); + + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 3); + }); + await page.setRequestInterception(false); + }); + }); +}); diff --git a/test/cross.basic_jwt.test.js b/test/cross.basic_jwt.test.js new file mode 100644 index 00000000..49224513 --- /dev/null +++ b/test/cross.basic_jwt.test.js @@ -0,0 +1,962 @@ +/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +let axios = require("axios"); +let puppeteer = require("puppeteer"); +let decodeJWT = require("jsonwebtoken").decode; +let verifyJWT = require("jsonwebtoken").verify; +let jwksClient = require("jwks-rsa"); +let assert = require("assert"); +let { + delay, + getNumberOfTimesRefreshCalled, + startST, + startSTWithJWTEnabled, + getNumberOfTimesGetSessionCalled, + BASE_URL, + BASE_URL_FOR_ST, + coreTagEqualToOrAfter, + checkIfJWTIsEnabled, + checkIfV3AccessTokenIsSupported +} = require("./utils"); +const { spawn } = require("child_process"); +const { addGenericTestCases: addTestCases } = require("./interception.testgen"); + +/* setupFunc is called through page.evaluate at the start of each test + It should set window.toTest to a function that receives a config object with: + url + method + headers (as object) + body + and should return a response object with: + url + statusCode + headers (as Headers) + responseText (text) +*/ + +addTestCases((name, transferMethod, setupFunc, setupArgs = []) => { + describe(`${name}: basic jwt handling`, function () { + let browser; + let page; + let v3AccessTokenSupported; + + function setup(config = {}) { + // page.on("console", c => console.log(c.text())); + return page.evaluate( + setupFunc, + { + // enableDebugLogs: true, + ...config + }, + ...setupArgs + ); + } + + before(async function () { + spawn( + "./test/startServer", + [process.env.INSTALL_PATH, process.env.NODE_PORT === undefined ? 8080 : process.env.NODE_PORT], + { + // stdio: "inherit", + // env: { + // ...process.env, + // DEBUG: "com.supertokens", + // } + } + ); + await new Promise(r => setTimeout(r, 1000)); + v3AccessTokenSupported = await checkIfV3AccessTokenIsSupported(); + }); + + after(async function () { + let instance = axios.create(); + await instance.post(BASE_URL_FOR_ST + "/after"); + try { + await instance.get(BASE_URL_FOR_ST + "/stop"); + } catch (err) {} + }); + + beforeEach(async function () { + let instance = axios.create(); + await instance.post(BASE_URL_FOR_ST + "/beforeeach"); + await instance.post("http://localhost.org:8082/beforeeach"); // for cross domain + await instance.post(BASE_URL + "/beforeeach"); + + let launchRetries = 0; + while (browser === undefined && launchRetries++ < 3) { + try { + browser = await puppeteer.launch({ + args: ["--no-sandbox", "--disable-setuid-sandbox"], + headless: true + }); + + page = await browser.newPage(); + + await page.goto(BASE_URL + "/index.html", { waitUntil: "load" }); + await page.addScriptTag({ path: `./bundle/bundle.js`, type: "text/javascript" }); + await page.evaluate(BASE_URL => (window.BASE_URL = BASE_URL), BASE_URL); + await new Promise(r => setTimeout(r, 100)); + } catch {} + } + }); + + afterEach(async function () { + if (browser) { + await browser.close(); + browser = undefined; + } + }); + + it("Test that the access token payload and the JWT have all valid claims after creating, refreshing and updating the payload", async function () { + await startSTWithJWTEnabled(); + await setup(); + + let isJwtEnabled = await checkIfJWTIsEnabled(); + + if (!isJwtEnabled) { + return; + } + + await page.setRequestInterception(true); + page.on("request", req => { + const url = req.url(); + if (url === BASE_URL + "/jsondecode") { + let jwt = JSON.parse(req.postData()).jwt; + let decodedJWT = decodeJWT(jwt); + + req.respond({ + status: 200, + body: JSON.stringify(decodedJWT) + }); + } else { + req.continue(); + } + }); + await page.evaluate(async v3AccessTokenSupported => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + + let userId = "testing-supertokens-website"; + + // Create a session + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.responseText, userId); + + // Verify access token payload + let accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); + + assertEqual(accessTokenPayload.customClaim, "customValue"); + let jwt; + + if (v3AccessTokenSupported) { + jwt = await supertokens.getAccessToken(); + assertEqual(accessTokenPayload.jwt, undefined); + assertEqual(accessTokenPayload._jwtPName, undefined); + } else { + assertNotEqual(accessTokenPayload.jwt, undefined); + assertEqual(accessTokenPayload.sub, undefined); + assertEqual(accessTokenPayload._jwtPName, "jwt"); + assertEqual(accessTokenPayload.iss, undefined); + + jwt = accessTokenPayload.jwt; + } + + // Decode the JWT + let decodeResponse = await toTest({ + url: `${BASE_URL}/jsondecode`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ jwt }) + }); + + let decodedJWT = JSON.parse(decodeResponse.responseText); + + // Verify the JWT claims + assert.strictEqual(decodedJWT.sub, userId); + assert.strictEqual(decodedJWT._jwtPName, undefined); + assert.strictEqual(decodedJWT.iss, "http://0.0.0.0:8080/auth"); + assert.strictEqual(decodedJWT.customClaim, "customValue"); + + // Update access token payload + await toTest({ + url: `${BASE_URL}/update-jwt`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ newClaim: "newValue" }) + }); + + // Get access token payload + accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); + assertEqual(accessTokenPayload.customClaim, undefined); + assertEqual(accessTokenPayload.newClaim, "newValue"); + + // Verify new access token payload + if (v3AccessTokenSupported) { + jwt = await supertokens.getAccessToken(); + assertEqual(accessTokenPayload.jwt, undefined); + assertEqual(accessTokenPayload._jwtPName, undefined); + } else { + assertNotEqual(accessTokenPayload.jwt, undefined); + assertEqual(accessTokenPayload.sub, undefined); + assertEqual(accessTokenPayload._jwtPName, "jwt"); + assertEqual(accessTokenPayload.iss, undefined); + + jwt = accessTokenPayload.jwt; + } + + decodeResponse = await toTest({ + url: `${BASE_URL}/jsondecode`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ jwt }) + }); + + decodedJWT = JSON.parse(decodeResponse.responseText); + + // Verify new JWT + assert.strictEqual(decodedJWT.sub, userId); + assert.strictEqual(decodedJWT._jwtPName, undefined); + assert.strictEqual(decodedJWT.customClaim, undefined); + assert.strictEqual(decodedJWT.newClaim, "newValue"); + + let attemptRefresh = await supertokens.attemptRefreshingSession(); + assert.strictEqual(attemptRefresh, true); + + // Verify new access token payload + assertEqual(accessTokenPayload.customClaim, undefined); + assertEqual(accessTokenPayload.newClaim, "newValue"); + + // Verify new access token payload + if (v3AccessTokenSupported) { + jwt = await supertokens.getAccessToken(); + assertEqual(accessTokenPayload.jwt, undefined); + assertEqual(accessTokenPayload._jwtPName, undefined); + } else { + assertNotEqual(accessTokenPayload.jwt, undefined); + assertEqual(accessTokenPayload.sub, undefined); + assertEqual(accessTokenPayload._jwtPName, "jwt"); + assertEqual(accessTokenPayload.iss, undefined); + + jwt = accessTokenPayload.jwt; + } + + decodeResponse = await toTest({ + url: `${BASE_URL}/jsondecode`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ jwt }) + }); + + decodedJWT = JSON.parse(decodeResponse.responseText); + + // Verify new JWT + assert.strictEqual(decodedJWT.sub, userId); + assert.strictEqual(decodedJWT._jwtPName, undefined); + assert.strictEqual(decodedJWT.customClaim, undefined); + assert.strictEqual(decodedJWT.newClaim, "newValue"); + }, v3AccessTokenSupported); + }); + + it("Test that the access token payload and the JWT have all valid claims after updating access token payload", async function () { + await startSTWithJWTEnabled(); + + let isJwtEnabled = await checkIfJWTIsEnabled(); + + if (!isJwtEnabled) { + return; + } + + await setup(); + await page.setRequestInterception(true); + page.on("request", req => { + const url = req.url(); + if (url === BASE_URL + "/jsondecode") { + let jwt = JSON.parse(req.postData()).jwt; + let decodedJWT = decodeJWT(jwt); + + req.respond({ + status: 200, + body: JSON.stringify(decodedJWT) + }); + } else { + req.continue(); + } + }); + await page.evaluate(async v3AccessTokenSupported => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + + let userId = "testing-supertokens-website"; + + // Create a session + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.responseText, userId); + + // Verify access token payload + let accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); + assertEqual(accessTokenPayload.customClaim, "customValue"); + let jwt; + + if (v3AccessTokenSupported) { + jwt = await supertokens.getAccessToken(); + assertEqual(accessTokenPayload.jwt, undefined); + assertEqual(accessTokenPayload._jwtPName, undefined); + } else { + assertNotEqual(accessTokenPayload.jwt, undefined); + assertEqual(accessTokenPayload.sub, undefined); + assertEqual(accessTokenPayload._jwtPName, "jwt"); + assertEqual(accessTokenPayload.iss, undefined); + + jwt = accessTokenPayload.jwt; + } + + // Decode the JWT + let decodeResponse = await toTest({ + url: `${BASE_URL}/jsondecode`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ jwt }) + }); + + let decodedJWT = JSON.parse(decodeResponse.responseText); + + // Verify the JWT claims + assert.strictEqual(decodedJWT.sub, userId); + assert.strictEqual(decodedJWT._jwtPName, undefined); + assert.strictEqual(decodedJWT.iss, "http://0.0.0.0:8080/auth"); + assert.strictEqual(decodedJWT.customClaim, "customValue"); + + // Update access token payload + await toTest({ + url: `${BASE_URL}/update-jwt`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + customClaim: undefined, + newClaim: "newValue" + }) + }); + + // Get access token payload + accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); + assert.strictEqual(accessTokenPayload.customClaim, undefined); + assert.strictEqual(accessTokenPayload.newClaim, "newValue"); + + if (v3AccessTokenSupported) { + jwt = await supertokens.getAccessToken(); + assertEqual(accessTokenPayload.jwt, undefined); + assertEqual(accessTokenPayload._jwtPName, undefined); + } else { + assertNotEqual(accessTokenPayload.jwt, undefined); + assertEqual(accessTokenPayload.sub, undefined); + assertEqual(accessTokenPayload._jwtPName, "jwt"); + assertEqual(accessTokenPayload.iss, undefined); + + jwt = accessTokenPayload.jwt; + } + + decodeResponse = await toTest({ + url: `${BASE_URL}/jsondecode`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ jwt }) + }); + + decodedJWT = JSON.parse(decodeResponse.responseText); + + // Verify new JWT + assert.strictEqual(decodedJWT.sub, userId); + assert.strictEqual(decodedJWT._jwtPName, undefined); + assert.strictEqual(decodedJWT.customClaim, undefined); + assert.strictEqual(decodedJWT.newClaim, "newValue"); + }, v3AccessTokenSupported); + }); + + it("Test that access token payload and JWT are valid after the property name changes and payload is updated", async function () { + await startSTWithJWTEnabled(); + + let isJwtEnabled = await checkIfJWTIsEnabled(); + + if (!isJwtEnabled || v3AccessTokenSupported) { + return; + } + + await setup(); + await page.setRequestInterception(true); + page.on("request", req => { + const url = req.url(); + if (url === BASE_URL + "/jsondecode") { + let jwt = JSON.parse(req.postData()).jwt; + let decodedJWT = decodeJWT(jwt); + + req.respond({ + status: 200, + body: JSON.stringify(decodedJWT) + }); + } else { + req.continue(); + } + }); + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + + let userId = "testing-supertokens-website"; + + // Create a session + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.responseText, userId); + + // Verify access token payload + let accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); + + assertNotEqual(accessTokenPayload.jwt, undefined); + assert.strictEqual(accessTokenPayload.sub, undefined); + assert.strictEqual(accessTokenPayload._jwtPName, "jwt"); + assert.strictEqual(accessTokenPayload.iss, undefined); + assert.strictEqual(accessTokenPayload.customClaim, "customValue"); + + let jwt = accessTokenPayload.jwt; + + // Decode the JWT + let decodeResponse = await toTest({ + url: `${BASE_URL}/jsondecode`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ jwt }) + }); + + let decodedJWT = JSON.parse(decodeResponse.responseText); + + // Verify the JWT claims + assert.strictEqual(decodedJWT.sub, userId); + assert.strictEqual(decodedJWT._jwtPName, undefined); + assert.strictEqual(decodedJWT.iss, "http://0.0.0.0:8080/auth"); + assert.strictEqual(decodedJWT.customClaim, "customValue"); + + await toTest({ + url: `${BASE_URL}/reinitialiseBackendConfig`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + jwtPropertyName: "customJWTProperty" + }) + }); + + // Update access token payload + await toTest({ + url: `${BASE_URL}/update-jwt`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ newClaim: "newValue" }) + }); + + // Get access token payload + accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); + + // Verify new access token payload + assertNotEqual(accessTokenPayload.jwt, undefined); + assert.strictEqual(accessTokenPayload.sub, undefined); + assert.strictEqual(accessTokenPayload._jwtPName, "jwt"); + assert.strictEqual(accessTokenPayload.iss, undefined); + assert.strictEqual(accessTokenPayload.customClaim, undefined); + assert.strictEqual(accessTokenPayload.customJWTProperty, undefined); + assert.strictEqual(accessTokenPayload.newClaim, "newValue"); + + jwt = accessTokenPayload.jwt; + + decodeResponse = await toTest({ + url: `${BASE_URL}/jsondecode`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ jwt }) + }); + + decodedJWT = JSON.parse(decodeResponse.responseText); + + // Verify new JWT + assert.strictEqual(decodedJWT.sub, userId); + assert.strictEqual(decodedJWT._jwtPName, undefined); + assert.strictEqual(decodedJWT.iss, "http://0.0.0.0:8080/auth"); + assert.strictEqual(decodedJWT.customClaim, undefined); + assert.strictEqual(decodedJWT.newClaim, "newValue"); + }); + }); + + it("Test that access token payload and JWT are valid after the property name changes and session is refreshed", async function () { + await startSTWithJWTEnabled(); + + let isJwtEnabled = await checkIfJWTIsEnabled(); + + if (!isJwtEnabled || v3AccessTokenSupported) { + return; + } + + await setup(); + await page.setRequestInterception(true); + page.on("request", req => { + const url = req.url(); + if (url === BASE_URL + "/jsondecode") { + let jwt = JSON.parse(req.postData()).jwt; + let decodedJWT = decodeJWT(jwt); + + req.respond({ + status: 200, + body: JSON.stringify(decodedJWT) + }); + } else { + req.continue(); + } + }); + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + + let userId = "testing-supertokens-website"; + + // Create a session + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.responseText, userId); + + // Verify access token payload + let accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); + + assertNotEqual(accessTokenPayload.jwt, undefined); + assert.strictEqual(accessTokenPayload.sub, undefined); + assert.strictEqual(accessTokenPayload._jwtPName, "jwt"); + assert.strictEqual(accessTokenPayload.iss, undefined); + assert.strictEqual(accessTokenPayload.customClaim, "customValue"); + + let jwt = accessTokenPayload.jwt; + + // Decode the JWT + let decodeResponse = await toTest({ + url: `${BASE_URL}/jsondecode`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ jwt }) + }); + + let decodedJWT = JSON.parse(decodeResponse.responseText); + + // Verify the JWT claims + assert.strictEqual(decodedJWT.sub, userId); + assert.strictEqual(decodedJWT._jwtPName, undefined); + assert.strictEqual(decodedJWT.iss, "http://0.0.0.0:8080/auth"); + assert.strictEqual(decodedJWT.customClaim, "customValue"); + + await toTest({ + url: `${BASE_URL}/reinitialiseBackendConfig`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + jwtPropertyName: "customJWTProperty" + }) + }); + + let attemptRefresh = await supertokens.attemptRefreshingSession(); + assert.strictEqual(attemptRefresh, true); + + // Get access token payload + accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); + + // Verify new access token payload + assert.strictEqual(accessTokenPayload.jwt, undefined); + assertNotEqual(accessTokenPayload.customJWTProperty, undefined); + assert.strictEqual(accessTokenPayload.sub, undefined); + assert.strictEqual(accessTokenPayload._jwtPName, "customJWTProperty"); + assert.strictEqual(accessTokenPayload.iss, undefined); + assert.strictEqual(accessTokenPayload.customClaim, "customValue"); + + jwt = accessTokenPayload.customJWTProperty; + + decodeResponse = await toTest({ + url: `${BASE_URL}/jsondecode`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ jwt }) + }); + + decodedJWT = JSON.parse(decodeResponse.responseText); + + // Verify new JWT + assert.strictEqual(decodedJWT.sub, userId); + assert.strictEqual(decodedJWT._jwtPName, undefined); + assert.strictEqual(decodedJWT.iss, "http://0.0.0.0:8080/auth"); + assert.strictEqual(decodedJWT.customClaim, "customValue"); + }); + }); + + it("Test that access token payload and jwt are valid after the session has expired", async function () { + await startSTWithJWTEnabled(3); + + let isJwtEnabled = await checkIfJWTIsEnabled(); + + if (!isJwtEnabled) { + return; + } + + await setup(); + await page.setRequestInterception(true); + page.on("request", req => { + const url = req.url(); + if (url === BASE_URL + "/jsondecode") { + let jwt = JSON.parse(req.postData()).jwt; + let decodedJWT = decodeJWT(jwt); + + req.respond({ + status: 200, + body: JSON.stringify(decodedJWT) + }); + } else { + req.continue(); + } + }); + await page.evaluate(async v3AccessTokenSupported => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + + let userId = "testing-supertokens-website"; + + // Create a session + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.responseText, userId); + + // Verify access token payload + let accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); + assert.strictEqual(accessTokenPayload.customClaim, "customValue"); + + let jwt; + + if (v3AccessTokenSupported) { + jwt = await supertokens.getAccessToken(); + assertEqual(accessTokenPayload.jwt, undefined); + assertEqual(accessTokenPayload._jwtPName, undefined); + } else { + assertNotEqual(accessTokenPayload.jwt, undefined); + assertEqual(accessTokenPayload.sub, undefined); + assertEqual(accessTokenPayload._jwtPName, "jwt"); + assertEqual(accessTokenPayload.iss, undefined); + + jwt = accessTokenPayload.jwt; + } + + let decodeResponse = await toTest({ + url: `${BASE_URL}/jsondecode`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ jwt }) + }); + + let decodedJWT = JSON.parse(decodeResponse.responseText); + + let jwtExpiry = decodedJWT.exp; + + // Wait for access token to expire + await delay(5); + + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); + + accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); + + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); + + assert.strictEqual(accessTokenPayload.customClaim, "customValue"); + + if (v3AccessTokenSupported) { + jwt = await supertokens.getAccessToken(); + assertEqual(accessTokenPayload.jwt, undefined); + assertEqual(accessTokenPayload._jwtPName, undefined); + } else { + assertNotEqual(accessTokenPayload.jwt, undefined); + assertEqual(accessTokenPayload.sub, undefined); + assertEqual(accessTokenPayload._jwtPName, "jwt"); + assertEqual(accessTokenPayload.iss, undefined); + + jwt = accessTokenPayload.jwt; + } + + decodeResponse = await toTest({ + url: `${BASE_URL}/jsondecode`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ jwt }) + }); + + decodedJWT = JSON.parse(decodeResponse.responseText); + + // Verify new JWT + assert.strictEqual(decodedJWT.sub, userId); + assert.strictEqual(decodedJWT._jwtPName, undefined); + assert.strictEqual(decodedJWT.iss, "http://0.0.0.0:8080/auth"); + assert.strictEqual(decodedJWT.customClaim, "customValue"); + + let newJwtExpiry = decodedJWT.exp; + + assert.strictEqual(newJwtExpiry > Math.ceil(Date.now() / 1000), true); + assertNotEqual(jwtExpiry, newJwtExpiry); + }, v3AccessTokenSupported); + }); + + it("Test full JWT flow with open id discovery", async function () { + await startSTWithJWTEnabled(20); + + let isJwtEnabled = await checkIfJWTIsEnabled(); + + if (!isJwtEnabled) { + return; + } + + await page.setRequestInterception(true); + page.on("request", req => { + const url = req.url(); + if (url === BASE_URL + "/jsondecode") { + let jwt = JSON.parse(req.postData()).jwt; + let decodedJWT = decodeJWT(jwt); + + req.respond({ + status: 200, + body: JSON.stringify(decodedJWT) + }); + } else if (url === BASE_URL + "/jwtVerify") { + let data = JSON.parse(req.postData()); + let jwt = data.jwt; + let jwksURL = data.jwksURL; + let client = jwksClient({ + jwksUri: jwksURL + }); + + function getKey(header, callback) { + client.getSigningKey(header.kid, function (err, key) { + if (err) { + callback(err, null); + return; + } + + var signingKey = key.publicKey || key.rsaPublicKey; + callback(null, signingKey); + }); + } + + verifyJWT(jwt, getKey, (err, decoded) => { + if (err) { + req.respond({ + status: 500, + body: JSON.stringify({ + error: err + }) + }); + return; + } + + req.respond({ + status: 200, + body: JSON.stringify(decoded) + }); + }); + } else { + req.continue(); + } + }); + await setup(); + await page.evaluate(async v3AccessTokenSupported => { + let userId = "testing-supertokens-website"; + + // Create a session + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.responseText, userId); + + // Verify access token payload + let accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); + assert.strictEqual(accessTokenPayload.customClaim, "customValue"); + + let jwt; + + if (v3AccessTokenSupported) { + jwt = await supertokens.getAccessToken(); + assertEqual(accessTokenPayload.jwt, undefined); + assertEqual(accessTokenPayload._jwtPName, undefined); + } else { + assertNotEqual(accessTokenPayload.jwt, undefined); + assertEqual(accessTokenPayload.sub, undefined); + assertEqual(accessTokenPayload._jwtPName, "jwt"); + assertEqual(accessTokenPayload.iss, undefined); + + jwt = accessTokenPayload.jwt; + } + + let decodeResponse = await toTest({ + url: `${BASE_URL}/jsondecode`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ jwt }) + }); + + let decodedJWT = JSON.parse(decodeResponse.responseText); + + // Verify the JWT claims + assert.strictEqual(decodedJWT.sub, userId); + assert.strictEqual(decodedJWT._jwtPName, undefined); + assert.strictEqual(decodedJWT.iss, "http://0.0.0.0:8080/auth"); + assert.strictEqual(decodedJWT.customClaim, "customValue"); + + // Use the jwt issuer to get discovery configuration + + let discoveryEndpoint = decodedJWT.iss + "/.well-known/openid-configuration"; + + let jwksEndpoint = (await (await fetch(discoveryEndpoint)).json()).jwks_uri; + + let verifyResponse = await toTest({ + url: `${BASE_URL}/jwtVerify`, + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + jwt, + jwksURL: jwksEndpoint + }) + }); + + if (verifyResponse.statusCode !== 200) { + throw new Error("JWT Verification failed"); + } + + decodedJWT = JSON.parse(verifyResponse.responseText); + + assert.strictEqual(decodedJWT.sub, userId); + assert.strictEqual(decodedJWT._jwtPName, undefined); + assert.strictEqual(decodedJWT.iss, "http://0.0.0.0:8080/auth"); + assert.strictEqual(decodedJWT.customClaim, "customValue"); + }, v3AccessTokenSupported); + }); + }); +}); diff --git a/test/cross.cross_domain.test.js b/test/cross.cross_domain.test.js new file mode 100644 index 00000000..59b26787 --- /dev/null +++ b/test/cross.cross_domain.test.js @@ -0,0 +1,316 @@ +/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +let axios = require("axios"); +let puppeteer = require("puppeteer"); +let decodeJWT = require("jsonwebtoken").decode; +let verifyJWT = require("jsonwebtoken").verify; +let jwksClient = require("jwks-rsa"); +let assert = require("assert"); +let { + delay, + getNumberOfTimesRefreshCalled, + startST, + startSTWithJWTEnabled, + getNumberOfTimesGetSessionCalled, + BASE_URL, + BASE_URL_FOR_ST, + coreTagEqualToOrAfter, + checkIfJWTIsEnabled, + checkIfV3AccessTokenIsSupported +} = require("./utils"); +const { spawn } = require("child_process"); +const { addGenericTestCases: addTestCases } = require("./interception.testgen"); + +/* setupFunc is called through page.evaluate at the start of each test + It should set window.toTest to a function that receives a config object with: + url + method + headers (as object) + body + and should return a response object with: + url + statusCode + headers (as Headers) + responseText (text) +*/ + +addTestCases((name, transferMethod, setupFunc, setupArgs = []) => { + describe(`${name}: cross domain calls`, function () { + let browser; + let page; + let v3AccessTokenSupported; + + function setup(config = {}) { + // page.on("console", c => console.log(c.text())); + return page.evaluate( + setupFunc, + { + // enableDebugLogs: true, + ...config + }, + ...setupArgs + ); + } + + before(async function () { + spawn( + "./test/startServer", + [process.env.INSTALL_PATH, process.env.NODE_PORT === undefined ? 8080 : process.env.NODE_PORT], + { + // stdio: "inherit", + // env: { + // ...process.env, + // DEBUG: "com.supertokens", + // } + } + ); + await new Promise(r => setTimeout(r, 1000)); + v3AccessTokenSupported = await checkIfV3AccessTokenIsSupported(); + }); + + after(async function () { + let instance = axios.create(); + await instance.post(BASE_URL_FOR_ST + "/after"); + try { + await instance.get(BASE_URL_FOR_ST + "/stop"); + } catch (err) {} + }); + + beforeEach(async function () { + let instance = axios.create(); + await instance.post(BASE_URL_FOR_ST + "/beforeeach"); + await instance.post("http://localhost.org:8082/beforeeach"); // for cross domain + await instance.post(BASE_URL + "/beforeeach"); + + let launchRetries = 0; + while (browser === undefined && launchRetries++ < 3) { + try { + browser = await puppeteer.launch({ + args: ["--no-sandbox", "--disable-setuid-sandbox"], + headless: true + }); + + page = await browser.newPage(); + + await page.goto(BASE_URL + "/index.html", { waitUntil: "load" }); + await page.addScriptTag({ path: `./bundle/bundle.js`, type: "text/javascript" }); + await page.evaluate(BASE_URL => (window.BASE_URL = BASE_URL), BASE_URL); + await new Promise(r => setTimeout(r, 100)); + } catch {} + } + }); + + afterEach(async function () { + if (browser) { + await browser.close(); + browser = undefined; + } + }); + + //cross domain login, userinfo, logout + it("test cross domain", async () => { + await startST(5); + await setup(); + + await page.evaluate(async () => { + let userId = "testing-supertokens-website"; + + // send api request to login + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + credentials: "include", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + //check that the userId which is returned in the response is the same as the one we sent + assert.strictEqual(loginResponse.responseText, userId); + + // check that the session exists + assert.strictEqual(await supertokens.doesSessionExist(), true); + + // check that the number of times session refresh is called is zero + assert.strictEqual(await getNumberOfTimesRefreshCalled(BASE_URL), 0); + + //delay for 5 seconds so that we know accessToken expires + + await delay(5); + // send a get session request , which should do a refresh session request + let getSessionResponse = await toTest({ url: `${BASE_URL}/`, method: "get", credentials: "include" }); + + // check that the getSession was successfull + assert.strictEqual(getSessionResponse.responseText, userId); + + // check that the refresh session was called only once + assert.strictEqual(await getNumberOfTimesRefreshCalled(BASE_URL), 1); + + // do logout + let logoutResponse = await toTest({ + url: `${BASE_URL}/logout`, + method: "post", + credentials: "include", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + assert.strictEqual(logoutResponse.responseText, "success"); + + //check that session does not exist + assert.strictEqual(await supertokens.doesSessionExist(), false); + }); + }); + + //cross domain login, userinfo, logout + it("test cross domain, auto add credentials", async () => { + await startST(5); + await setup(); + await page.evaluate(async () => { + let userId = "testing-supertokens-website"; + + // send api request to login + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + //check that the userId which is returned in the response is the same as the one we sent + assert.strictEqual(loginResponse.responseText, userId); + + // check that the session exists + assert.strictEqual(await supertokens.doesSessionExist(), true); + + // check that the number of times session refresh is called is zero + assert.strictEqual(await getNumberOfTimesRefreshCalled(BASE_URL), 0); + + //delay for 5 seconds so that we know accessToken expires + + await delay(5); + // send a get session request , which should do a refresh session request + let getSessionResponse = await toTest({ url: `${BASE_URL}/`, method: "get" }); + + // check that the getSession was successfull + assert.strictEqual(getSessionResponse.responseText, userId); + + // check that the refresh session was called only once + assert.strictEqual(await getNumberOfTimesRefreshCalled(BASE_URL), 1); + + // do logout + let logoutResponse = await toTest({ + url: `${BASE_URL}/logout`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + assert.strictEqual(logoutResponse.responseText, "success"); + + //check that session does not exist + assert.strictEqual(await supertokens.doesSessionExist(), false); + }); + }); + + //cross domain login, userinfo, logout + it("test cross domain, no auto add credentials, fail", async () => { + await startST(5); + await setup(); + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8082"; + supertokens.init({ + apiDomain: BASE_URL, + autoAddCredentials: false + }); + let userId = "testing-supertokens-website"; + + // send api request to login + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + //check that the userId which is returned in the response is the same as the one we sent + assert.strictEqual(loginResponse.responseText, userId); + + // check that the session exists + assert.strictEqual(await supertokens.doesSessionExist(), true); + + // check that the number of times session refresh is called is zero + assert.strictEqual(await getNumberOfTimesRefreshCalled(BASE_URL), 0); + + //delay for 5 seconds so that we know accessToken expires + + await delay(5); + + let resp = await toTest({ url: `${BASE_URL}/`, method: "get" }); + assert.strictEqual(resp.statusCode, 401); + + assert.strictEqual(await supertokens.doesSessionExist(), false); + + await toTest({ + url: `${BASE_URL}/login`, + method: "post", + credentials: "include", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + // send a get session request , which should do a refresh session request + let getSessionResponse = await toTest({ url: `${BASE_URL}/`, method: "get", credentials: "include" }); + + // check that the getSession was successfull + assert.strictEqual(getSessionResponse.responseText, userId); + + // check that the refresh session was called only once + assert.strictEqual(await getNumberOfTimesRefreshCalled(BASE_URL), 0); + + // do logout + let logoutResponse = await toTest({ + url: `${BASE_URL}/logout`, + method: "post", + credentials: "include", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + assert.strictEqual(logoutResponse.responseText, "success"); + + //check that session does not exist + assert.strictEqual(await supertokens.doesSessionExist(), false); + }); + }); + }); +}); diff --git a/test/cross.disable_interception.test.js b/test/cross.disable_interception.test.js new file mode 100644 index 00000000..9cbb8b33 --- /dev/null +++ b/test/cross.disable_interception.test.js @@ -0,0 +1,270 @@ +/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +let axios = require("axios"); +let puppeteer = require("puppeteer"); +let decodeJWT = require("jsonwebtoken").decode; +let verifyJWT = require("jsonwebtoken").verify; +let jwksClient = require("jwks-rsa"); +let assert = require("assert"); +let { + delay, + getNumberOfTimesRefreshCalled, + startST, + startSTWithJWTEnabled, + getNumberOfTimesGetSessionCalled, + BASE_URL, + BASE_URL_FOR_ST, + coreTagEqualToOrAfter, + checkIfJWTIsEnabled, + checkIfV3AccessTokenIsSupported +} = require("./utils"); +const { spawn } = require("child_process"); +const { addGenericTestCases: addTestCases } = require("./interception.testgen"); + +/* setupFunc is called through page.evaluate at the start of each test + It should set window.toTest to a function that receives a config object with: + url + method + headers (as object) + body + and should return a response object with: + url + statusCode + headers (as Headers) + responseText (text) +*/ + +addTestCases((name, transferMethod, setupFunc, setupArgs = []) => { + describe(`${name}: interception disabling`, function () { + let browser; + let page; + let v3AccessTokenSupported; + + function setup(config = {}) { + // page.on("console", c => console.log(c.text())); + return page.evaluate( + setupFunc, + { + // enableDebugLogs: true, + ...config + }, + ...setupArgs + ); + } + + before(async function () { + spawn( + "./test/startServer", + [process.env.INSTALL_PATH, process.env.NODE_PORT === undefined ? 8080 : process.env.NODE_PORT], + { + // stdio: "inherit", + // env: { + // ...process.env, + // DEBUG: "com.supertokens", + // } + } + ); + await new Promise(r => setTimeout(r, 1000)); + v3AccessTokenSupported = await checkIfV3AccessTokenIsSupported(); + }); + + after(async function () { + let instance = axios.create(); + await instance.post(BASE_URL_FOR_ST + "/after"); + try { + await instance.get(BASE_URL_FOR_ST + "/stop"); + } catch (err) {} + }); + + beforeEach(async function () { + let instance = axios.create(); + await instance.post(BASE_URL_FOR_ST + "/beforeeach"); + await instance.post("http://localhost.org:8082/beforeeach"); // for cross domain + await instance.post(BASE_URL + "/beforeeach"); + + let launchRetries = 0; + while (browser === undefined && launchRetries++ < 3) { + try { + browser = await puppeteer.launch({ + args: ["--no-sandbox", "--disable-setuid-sandbox"], + headless: true + }); + + page = await browser.newPage(); + + await page.goto(BASE_URL + "/index.html", { waitUntil: "load" }); + await page.addScriptTag({ path: `./bundle/bundle.js`, type: "text/javascript" }); + await page.evaluate(BASE_URL => (window.BASE_URL = BASE_URL), BASE_URL); + await new Promise(r => setTimeout(r, 100)); + } catch {} + } + }); + + afterEach(async function () { + if (browser) { + await browser.close(); + browser = undefined; + } + }); + + it("test interception should happen if api domain and website domain are the same and relative path is used", async function () { + await startST(5); + await setup(); + + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + let userId = "testing-supertokens-website"; + + // send api request to login + let loginResponse = await toTest({ + url: `/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.responseText, userId); + + assert.strictEqual(await supertokens.doesSessionExist(), true); + }); + }); + + it("test interception should not happen if api domain and website domain are different and relative path is used", async function () { + await startST(5); + await setup(); + + await page.evaluate(async () => { + let BASE_URL = "https://google.com"; + supertokens.init({ + apiDomain: BASE_URL + }); + let userId = "testing-supertokens-website"; + + // send api request to login + let loginResponse = await toTest({ + url: `/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.responseText, userId); + + assert.strictEqual(await supertokens.doesSessionExist(), false); + }); + }); + + it("should not intercept if url contains superTokensDoNotDoInterception", async function () { + await startST(5); + await setup(); + + await page.evaluate(async () => { + let userId = "testing-supertokens-website"; + + // send api request to login + let loginResponse = await toTest({ + url: `/login#superTokensDoNotDoInterception`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.responseText, userId); + + assert.strictEqual(await supertokens.doesSessionExist(), false); + }); + }); + + it("test disabled interception", async function () { + await startST(); + + await setup(); + await page.evaluate(async () => { + supertokens.init({ + apiDomain: BASE_URL, + postAPIHook: async context => { + assert.strictEqual(context.action === "REFRESH_SESSION" || context.action === "SIGN_OUT", true); + + if (context.action === "REFRESH_SESSION" && context.fetchResponse.statusCode === 200) { + const body = await context.fetchResponse.text(); + assert.strictEqual(body, "refresh success"); + + const frontTokenInHeader = context.fetchResponse.headers.get("front-token"); + assertNotEqual(frontTokenInHeader, ""); + assertNotEqual(frontTokenInHeader, null); + } + + if (context.action === "SIGN_OUT" && context.fetchResponse.statusCode === 200) { + const body = await context.fetchResponse.json(); + assert.strictEqual(body.statusCode, "OK"); + + const frontTokenInHeader = context.fetchResponse.headers.get("front-token"); + assert.strictEqual(frontTokenInHeader, "remove"); + } + } + }); + let userId = "testing-supertokens-website"; + + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.responseText, userId); + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); + + await delay(2); + let attemptRefresh = await supertokens.attemptRefreshingSession(); + assert.strictEqual(attemptRefresh, true); + + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); + await supertokens.signOut(); + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); + assert.strictEqual(await supertokens.doesSessionExist(), false); + }); + }); + + it("test that interception doesn't happen if the shouldDoInterceptionBasedOnUrl override returns false", async function () { + await setup({ + override: ["shouldDoInterceptionBasedOnUrl"] + }); + + await page.evaluate(async () => { + let getResponse = await toTest({ url: `${BASE_URL}/check-rid-no-session` }); + assert.strictEqual(getResponse.responseText, "fail"); + + let getWithOverrideResponse = await toTest({ url: `${BASE_URL}/check-rid-no-session?doOverride` }); + assert.strictEqual(getWithOverrideResponse.responseText, "success"); + }); + }); + }); +}); diff --git a/test/cross.resp_error.test.js b/test/cross.resp_error.test.js new file mode 100644 index 00000000..a32355c5 --- /dev/null +++ b/test/cross.resp_error.test.js @@ -0,0 +1,283 @@ +/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +let axios = require("axios"); +let puppeteer = require("puppeteer"); +let decodeJWT = require("jsonwebtoken").decode; +let verifyJWT = require("jsonwebtoken").verify; +let jwksClient = require("jwks-rsa"); +let assert = require("assert"); +let { + delay, + getNumberOfTimesRefreshCalled, + startST, + startSTWithJWTEnabled, + getNumberOfTimesGetSessionCalled, + BASE_URL, + BASE_URL_FOR_ST, + coreTagEqualToOrAfter, + checkIfJWTIsEnabled, + checkIfV3AccessTokenIsSupported +} = require("./utils"); +const { spawn } = require("child_process"); +const { addGenericTestCases: addTestCases } = require("./interception.testgen"); + +/* setupFunc is called through page.evaluate at the start of each test + It should set window.toTest to a function that receives a config object with: + url + method + headers (as object) + body + and should return a response object with: + url + statusCode + headers (as Headers) + responseText (text) +*/ + +addTestCases((name, transferMethod, setupFunc, setupArgs = []) => { + describe(`${name}: error response handling`, function () { + let browser; + let page; + let v3AccessTokenSupported; + + function setup(config = {}) { + // page.on("console", c => console.log(c.text())); + return page.evaluate( + setupFunc, + { + // enableDebugLogs: true, + ...config + }, + ...setupArgs + ); + } + + before(async function () { + spawn( + "./test/startServer", + [process.env.INSTALL_PATH, process.env.NODE_PORT === undefined ? 8080 : process.env.NODE_PORT], + { + // stdio: "inherit", + // env: { + // ...process.env, + // DEBUG: "com.supertokens", + // } + } + ); + await new Promise(r => setTimeout(r, 1000)); + v3AccessTokenSupported = await checkIfV3AccessTokenIsSupported(); + }); + + after(async function () { + let instance = axios.create(); + await instance.post(BASE_URL_FOR_ST + "/after"); + try { + await instance.get(BASE_URL_FOR_ST + "/stop"); + } catch (err) {} + }); + + beforeEach(async function () { + let instance = axios.create(); + await instance.post(BASE_URL_FOR_ST + "/beforeeach"); + await instance.post("http://localhost.org:8082/beforeeach"); // for cross domain + await instance.post(BASE_URL + "/beforeeach"); + + let launchRetries = 0; + while (browser === undefined && launchRetries++ < 3) { + try { + browser = await puppeteer.launch({ + args: ["--no-sandbox", "--disable-setuid-sandbox"], + headless: true + }); + + page = await browser.newPage(); + + await page.goto(BASE_URL + "/index.html", { waitUntil: "load" }); + await page.addScriptTag({ path: `./bundle/bundle.js`, type: "text/javascript" }); + await page.evaluate(BASE_URL => (window.BASE_URL = BASE_URL), BASE_URL); + await new Promise(r => setTimeout(r, 100)); + } catch {} + } + }); + + afterEach(async function () { + if (browser) { + await browser.close(); + browser = undefined; + } + }); + + it("test that if an api throws an error it gets propagated to the user with interception", async () => { + await startST(); + await setup(); + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + + let val = await toTest({ url: `${BASE_URL}/testError` }); + assert.strictEqual(val.responseText, "test error message"); + assert.strictEqual(val.statusCode, 500); + }); + }); + + it("test that if an api throws a 400 error it gets propagated to the user with interception", async () => { + await startST(); + await setup(); + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + + let val = await toTest({ url: `${BASE_URL}/testError?code=400` }); + assert.strictEqual(val.responseText, "test error message"); + assert.strictEqual(val.statusCode, 400); + }); + }); + + it("test that if an api throws a 405 error it gets propagated to the user with interception", async () => { + await startST(); + await setup(); + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + + let val = await toTest({ url: `${BASE_URL}/testError?code=405` }); + assert.strictEqual(val.responseText, "test error message"); + assert.strictEqual(val.statusCode, 405); + }); + }); + + it("test that if an api throws an error it gets propagated to the user without interception", async () => { + await startST(); + await setup(); + + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + + let val = await toTest({ url: `${BASE_URL}/testError#superTokensDoNotDoInterception`, method: "get" }); + + assert.strictEqual(val.responseText, "test error message"); + assert.strictEqual(val.statusCode, 500); + }); + }); + + it("test that if an api throws a 400 error it gets propagated to the user without interception", async () => { + await startST(); + await setup(); + + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + + let val = await toTest({ + url: `${BASE_URL}/testError?code=400#superTokensDoNotDoInterception`, + method: "get" + }); + + assert.strictEqual(val.responseText, "test error message"); + assert.strictEqual(val.statusCode, 400); + }); + }); + + it("test that if an api throws a 405 error it gets propagated to the user without interception", async () => { + await startST(); + await setup(); + + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + + let val = await toTest({ url: `${BASE_URL}/testError?code=405`, method: "get" }); + + assert.strictEqual(val.responseText, "test error message"); + assert.strictEqual(val.statusCode, 405); + }); + }); + + it("test that network errors are propagated to the user with interception", async () => { + await startST(); + await setup(); + + await page.setRequestInterception(true); + page.on("request", req => { + const url = req.url(); + if (url === BASE_URL + "/testError") { + req.abort(); + } else { + req.continue(); + } + }); + + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + + let caught; + try { + await toTest({ url: `${BASE_URL}/testError`, method: "get" }); + } catch (ex) { + caught = ex; + } + + assert.ok(caught); + }); + }); + + it("test that network errors are propagated to the user without interception", async () => { + await startST(); + await setup(); + + await page.setRequestInterception(true); + page.on("request", req => { + const url = req.url(); + if (url === BASE_URL + "/testError") { + req.abort(); + } else { + req.continue(); + } + }); + + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + + let caught; + try { + await toTest({ url: `${BASE_URL}/testError#superTokensDoNotDoInterception`, method: "get" }); + } catch (ex) { + caught = ex; + } + + assert.ok(caught); + }); + }); + }); +}); diff --git a/test/cross.token_migration.test.js b/test/cross.token_migration.test.js new file mode 100644 index 00000000..14f24772 --- /dev/null +++ b/test/cross.token_migration.test.js @@ -0,0 +1,323 @@ +/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +let axios = require("axios"); +let puppeteer = require("puppeteer"); +let decodeJWT = require("jsonwebtoken").decode; +let verifyJWT = require("jsonwebtoken").verify; +let jwksClient = require("jwks-rsa"); +let assert = require("assert"); +let { + delay, + getNumberOfTimesRefreshCalled, + startST, + startSTWithJWTEnabled, + getNumberOfTimesGetSessionCalled, + BASE_URL, + BASE_URL_FOR_ST, + coreTagEqualToOrAfter, + checkIfJWTIsEnabled, + checkIfV3AccessTokenIsSupported +} = require("./utils"); +const { spawn } = require("child_process"); +const { addGenericTestCases: addTestCases } = require("./interception.testgen"); + +/* setupFunc is called through page.evaluate at the start of each test + It should set window.toTest to a function that receives a config object with: + url + method + headers (as object) + body + and should return a response object with: + url + statusCode + headers (as Headers) + responseText (text) +*/ + +addTestCases((name, transferMethod, setupFunc, setupArgs = []) => { + describe(`${name}: token migration`, function () { + let browser; + let page; + let v3AccessTokenSupported; + + function setup(config = {}) { + // page.on("console", c => console.log(c.text())); + return page.evaluate( + setupFunc, + { + // enableDebugLogs: true, + ...config + }, + ...setupArgs + ); + } + + before(async function () { + spawn( + "./test/startServer", + [process.env.INSTALL_PATH, process.env.NODE_PORT === undefined ? 8080 : process.env.NODE_PORT], + { + // stdio: "inherit", + // env: { + // ...process.env, + // DEBUG: "com.supertokens", + // } + } + ); + await new Promise(r => setTimeout(r, 1000)); + v3AccessTokenSupported = await checkIfV3AccessTokenIsSupported(); + }); + + after(async function () { + let instance = axios.create(); + await instance.post(BASE_URL_FOR_ST + "/after"); + try { + await instance.get(BASE_URL_FOR_ST + "/stop"); + } catch (err) {} + }); + + beforeEach(async function () { + let instance = axios.create(); + await instance.post(BASE_URL_FOR_ST + "/beforeeach"); + await instance.post("http://localhost.org:8082/beforeeach"); // for cross domain + await instance.post(BASE_URL + "/beforeeach"); + + let launchRetries = 0; + while (browser === undefined && launchRetries++ < 3) { + try { + browser = await puppeteer.launch({ + args: ["--no-sandbox", "--disable-setuid-sandbox"], + headless: true + }); + + page = await browser.newPage(); + + await page.goto(BASE_URL + "/index.html", { waitUntil: "load" }); + await page.addScriptTag({ path: `./bundle/bundle.js`, type: "text/javascript" }); + await page.evaluate(BASE_URL => (window.BASE_URL = BASE_URL), BASE_URL); + await new Promise(r => setTimeout(r, 100)); + } catch {} + } + }); + + afterEach(async function () { + if (browser) { + await browser.close(); + browser = undefined; + } + }); + + it("should work after refresh migrating old cookie based sessions", async function () { + if (transferMethod === "header") { + // We skip this in header mode, they can't have legacy sessions + this.skip(); + } + + await startST(); + await setup(); + + await page.evaluate(async () => { + let userId = "testing-supertokens-website"; + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + }); + + await page.setCookie({ name: "sIdRefreshToken", value: "asdf" }); + + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); + let originalCookies = (await page._client.send("Network.getAllCookies")).cookies; + assert.notStrictEqual( + originalCookies.find(cookie => cookie.name === "sIdRefreshToken"), + undefined + ); + + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + let resp = await toTest({ url: `${BASE_URL}/`, method: "GET" }); + assert.strictEqual(resp.statusCode, 200); + }); + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); + let newCookies = (await page._client.send("Network.getAllCookies")).cookies; + assert.strictEqual( + newCookies.find(cookie => cookie.name === "sIdRefreshToken"), + undefined + ); + }); + + it("should work after refresh migrating old cookie based sessions with expired access tokens", async function () { + if (transferMethod === "header") { + // We skip this in header mode, they can't have legacy sessions + this.skip(); + } + + await startST(); + await setup(); + + await page.evaluate(async () => { + let userId = "testing-supertokens-website"; + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + }); + + // This would work even without sIdRefreshToken since we don't actually check the body of the response, just call refresh on all 401s + await page.setCookie({ name: "sIdRefreshToken", value: "asdf" }); + await page.setCookie({ name: "sAccessToken", value: "", expiry: 0 }); + + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); + let originalCookies = (await page._client.send("Network.getAllCookies")).cookies; + assert.notStrictEqual( + originalCookies.find(cookie => cookie.name === "sIdRefreshToken"), + undefined + ); + + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + let resp = await toTest({ url: `${BASE_URL}/`, method: "GET" }); + assert.strictEqual(resp.statusCode, 200); + }); + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); + let newCookies = (await page._client.send("Network.getAllCookies")).cookies; + assert.notStrictEqual( + originalCookies.find(cookie => cookie.name === "sAccessToken"), + undefined + ); + assert.strictEqual( + newCookies.find(cookie => cookie.name === "sIdRefreshToken"), + undefined + ); + }); + + /** + * - Create a session with cookies and add sIdRefreshToken manually to simulate old cookies + * - Change the token method to headers + * - Get session information and make sure the API succeeds, refresh is called and sIdRefreshToken is removed + * - Make sure getAccessToken returns undefined because the backend should have used cookies + * - Sign out + * - Login again and make sure access token is present because backend should now use headers + */ + it("should still work fine work fine if header based auth is enabled after a cookie based session", async function () { + if (transferMethod === "header") { + // We skip this in header mode, they can't have legacy sessions + this.skip(); + } + + await startST(); + await setup(); + + await page.evaluate(async () => { + window.userId = "testing-supertokens"; + window.BASE_URL = "http://localhost.org:8080"; + + // send api request to login + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.responseText, userId); + + // make sure there is no access token + let accessToken = await supertokens.getAccessToken(); + assert.strictEqual(accessToken, undefined); + + let getSessionResponse = await toTest({ + url: `${BASE_URL}/`, + headers: { + Authorization: `Bearer ${accessToken}` + } + }); + + assert.strictEqual(getSessionResponse.statusCode, 200); + assert.strictEqual(getSessionResponse.responseText, userId); + }); + + // This would work even without sIdRefreshToken since we don't actually check the body of the response, just call refresh on all 401s + await page.setCookie({ name: "sIdRefreshToken", value: "asdf" }); + + const originalCookies = (await page._client.send("Network.getAllCookies")).cookies; + assert.notStrictEqual( + originalCookies.find(cookie => cookie.name === "sIdRefreshToken"), + undefined + ); + + await page.evaluate(async () => { + // Switch to header based auth + // Re-initialization doesn't work for everything (i.e., overrides), but it's fine for this + supertokens.init({ + apiDomain: BASE_URL, + tokenTransferMethod: "header" + }); + + let getResponse = await toTest({ url: `${BASE_URL}/`, method: "GET" }); + + //check that the response to getSession was success + assert.strictEqual(getResponse.responseText, userId); + + //check that the number of time the refreshAPI was called is 1 + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); + }); + + const refreshedCookies = (await page._client.send("Network.getAllCookies")).cookies; + assert.strictEqual( + refreshedCookies.find(cookie => cookie.name === "sIdRefreshToken"), + undefined + ); + + await page.evaluate(async () => { + // Make sure this is still undefined because the backend should continue using cookies + accessToken = await supertokens.getAccessToken(); + assert.strictEqual(accessToken, undefined); + + await supertokens.signOut(); + + // send api request to login + loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.responseText, userId); + + // Make sure now access token is present because it should use header based auth + accessToken = await supertokens.getAccessToken(); + assert.notStrictEqual(accessToken, undefined); + }); + }); + }); +}); diff --git a/test/cross.unauthorised_event.test.js b/test/cross.unauthorised_event.test.js new file mode 100644 index 00000000..f04a6ce4 --- /dev/null +++ b/test/cross.unauthorised_event.test.js @@ -0,0 +1,304 @@ +/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +let axios = require("axios"); +let puppeteer = require("puppeteer"); +let decodeJWT = require("jsonwebtoken").decode; +let verifyJWT = require("jsonwebtoken").verify; +let jwksClient = require("jwks-rsa"); +let assert = require("assert"); +let { + delay, + getNumberOfTimesRefreshCalled, + startST, + startSTWithJWTEnabled, + getNumberOfTimesGetSessionCalled, + BASE_URL, + BASE_URL_FOR_ST, + coreTagEqualToOrAfter, + checkIfJWTIsEnabled, + checkIfV3AccessTokenIsSupported +} = require("./utils"); +const { spawn } = require("child_process"); +const { addGenericTestCases: addTestCases } = require("./interception.testgen"); + +/* setupFunc is called through page.evaluate at the start of each test + It should set window.toTest to a function that receives a config object with: + url + method + headers (as object) + body + and should return a response object with: + url + statusCode + headers (as Headers) + responseText (text) +*/ + +addTestCases((name, transferMethod, setupFunc, setupArgs = []) => { + describe(`${name}: unauthorised event`, function () { + let browser; + let page; + let v3AccessTokenSupported; + + function setup(config = {}) { + // page.on("console", c => console.log(c.text())); + return page.evaluate( + setupFunc, + { + // enableDebugLogs: true, + ...config + }, + ...setupArgs + ); + } + + before(async function () { + spawn( + "./test/startServer", + [process.env.INSTALL_PATH, process.env.NODE_PORT === undefined ? 8080 : process.env.NODE_PORT], + { + // stdio: "inherit", + // env: { + // ...process.env, + // DEBUG: "com.supertokens", + // } + } + ); + await new Promise(r => setTimeout(r, 1000)); + v3AccessTokenSupported = await checkIfV3AccessTokenIsSupported(); + }); + + after(async function () { + let instance = axios.create(); + await instance.post(BASE_URL_FOR_ST + "/after"); + try { + await instance.get(BASE_URL_FOR_ST + "/stop"); + } catch (err) {} + }); + + beforeEach(async function () { + let instance = axios.create(); + await instance.post(BASE_URL_FOR_ST + "/beforeeach"); + await instance.post("http://localhost.org:8082/beforeeach"); // for cross domain + await instance.post(BASE_URL + "/beforeeach"); + + let launchRetries = 0; + while (browser === undefined && launchRetries++ < 3) { + try { + browser = await puppeteer.launch({ + args: ["--no-sandbox", "--disable-setuid-sandbox"], + headless: true + }); + + page = await browser.newPage(); + + await page.goto(BASE_URL + "/index.html", { waitUntil: "load" }); + await page.addScriptTag({ path: `./bundle/bundle.js`, type: "text/javascript" }); + await page.evaluate(BASE_URL => (window.BASE_URL = BASE_URL), BASE_URL); + await new Promise(r => setTimeout(r, 100)); + } catch {} + } + }); + + afterEach(async function () { + if (browser) { + await browser.close(); + browser = undefined; + } + }); + + it("test that after login, and clearing all cookies, if we query a protected route, it fires unauthorised event", async function () { + await startST(); + await setup(); + + let consoleLogs = []; + page.on("console", message => { + if (message.text().startsWith("ST_")) { + consoleLogs.push(message.text()); + } + }); + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL, + onHandleEvent: event => { + console.log(`ST_${event.action}:${JSON.stringify(event)}`); + } + }); + let userId = "testing-supertokens-website"; + + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.responseText, userId); + }); + + const client = await page.target().createCDPSession(); + await client.send("Network.clearBrowserCookies"); + await client.send("Network.clearBrowserCache"); + let cookies = await page.cookies(); + assert.strictEqual(cookies.length, 0); + + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + let response = await toTest({ url: `${BASE_URL}/` }); + assert.strictEqual(response.statusCode, 401); + }); + + assert.strictEqual(consoleLogs.length, 2); + + assert.strict(consoleLogs[0].startsWith("ST_SESSION_CREATED")); + + const eventName = "ST_UNAUTHORISED"; + assert.strict(consoleLogs[1].startsWith(eventName)); + const parsedEvent = JSON.parse(consoleLogs[1].substr(eventName.length + 1)); + assert.strictEqual(parsedEvent.sessionExpiredOrRevoked, false); + }); + + it("test that after login, and clearing only httpOnly cookies, if we query a protected route, it fires unauthorised event", async function () { + if (transferMethod === "header") { + // We skip this in header mode: it should work the same without httpOnly cookies + this.skip(); + } + await startST(); + await setup(); + let consoleLogs = []; + page.on("console", message => { + if (message.text().startsWith("ST_")) { + consoleLogs.push(message.text()); + } + }); + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL, + onHandleEvent: event => { + console.log(`ST_${event.action}:${JSON.stringify(event)}`); + } + }); + let userId = "testing-supertokens-website"; + + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.responseText, userId); + }); + + let originalCookies = (await page.cookies()).filter(c => !c.httpOnly); + + const client = await page.target().createCDPSession(); + await client.send("Network.clearBrowserCookies"); + await client.send("Network.clearBrowserCache"); + + await page.setCookie(...originalCookies); + let cookies = await page.cookies(); + assert.strictEqual(cookies.length, 3); + + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + let response = await toTest({ url: `${BASE_URL}/` }); + assert.strictEqual(response.statusCode, 401); + }); + + assert.strictEqual(consoleLogs.length, 2); + + assert.strict(consoleLogs[0].startsWith("ST_SESSION_CREATED")); + + const eventName = "ST_UNAUTHORISED"; + assert.strict(consoleLogs[1].startsWith(eventName)); + const parsedEvent = JSON.parse(consoleLogs[1].substr(eventName.length + 1)); + assert.strict(parsedEvent.sessionExpiredOrRevoked); + }); + + it("test that unauthorised event is not fired on initial page load", async function () { + await startST(); + await setup(); + let consoleLogs = []; + page.on("console", message => { + if (message.text().startsWith("ST_")) { + consoleLogs.push(message.text()); + } + }); + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL, + onHandleEvent: event => { + console.log("ST_" + event.action); + } + }); + let userId = "testing-supertokens-website"; + + let loginResponse = await toTest({ + url: `${BASE_URL}/login`, + method: "post", + headers: { + Accept: "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ userId }) + }); + + assert.strictEqual(loginResponse.responseText, userId); + }); + assert.strictEqual(consoleLogs.length, 1); + assert.strictEqual(consoleLogs[0], "ST_SESSION_CREATED"); + }); + + it("test that unauthorised event is fired when calling protected route without a session", async function () { + await startST(); + await setup(); + let consoleLogs = []; + page.on("console", message => { + if (message.text().startsWith("ST_")) { + consoleLogs.push(message.text()); + } + }); + await page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL, + onHandleEvent: event => { + console.log(`ST_${event.action}:${JSON.stringify(event)}`); + } + }); + let response = await toTest({ url: `${BASE_URL}/` }); + assert.strictEqual(response.statusCode, 401); + }); + + assert.strictEqual(consoleLogs.length, 1); + + const eventName = "ST_UNAUTHORISED"; + + assert.strict(consoleLogs[0].startsWith(eventName)); + const parsedEvent = JSON.parse(consoleLogs[0].substr(eventName.length + 1)); + assert.strictEqual(parsedEvent.sessionExpiredOrRevoked, false); + }); + }); +}); diff --git a/test/interception.basic1.test.js b/test/interception.basic1.test.js index 1b7c8093..23c18d68 100644 --- a/test/interception.basic1.test.js +++ b/test/interception.basic1.test.js @@ -122,7 +122,8 @@ addTestCases((name, transferMethod, setupFunc, setupArgs = []) => { await page.goto(BASE_URL + "/index.html", { waitUntil: "load" }); await page.addScriptTag({ path: `./bundle/bundle.js`, type: "text/javascript" }); - page.evaluate(BASE_URL => (window.BASE_URL = BASE_URL), BASE_URL); + await page.evaluate(BASE_URL => (window.BASE_URL = BASE_URL), BASE_URL); + await new Promise(r => setTimeout(r, 100)); } catch {} } }); @@ -170,96 +171,6 @@ addTestCases((name, transferMethod, setupFunc, setupArgs = []) => { }); }); - it("test refresh session", async function () { - await startST(3); - await setup(); - await page.evaluate(async () => { - const userId = "testing-supertokens-website"; - const loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - assert.strictEqual(loginResponse.statusCode, 200); - assert.strictEqual(loginResponse.responseText, userId); - //delay for 5 seconds for access token validity expiry - await delay(5); - - //check that the number of times the refreshAPI was called is 0 - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); - - let getResponse = await toTest({ url: `${BASE_URL}/` }); - //check that the response to getSession was success - assert.strictEqual(getResponse.statusCode, 200); - assert.strictEqual(getResponse.responseText, userId); - - //check that the number of time the refreshAPI was called is 1 - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); - }); - }); - - it("test refresh session with multiple 401s", async function () { - await startST(3); - await setup(); - await page.setRequestInterception(true); - let getCount = 0; - page.on("request", req => { - const url = req.url(); - if (url === BASE_URL + "/") { - if (getCount++ < 3) { - req.respond({ - status: 401, - body: JSON.stringify({ - message: "try refresh token" - }) - }); - } else { - req.respond({ - status: 200, - body: JSON.stringify({ - success: true - }) - }); - } - } else { - req.continue(); - } - }); - await page.evaluate(async () => { - const userId = "testing-supertokens-website"; - const loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - assert.strictEqual(loginResponse.statusCode, 200); - assert.strictEqual(loginResponse.responseText, userId); - //delay for 5 seconds for access token validity expiry - await delay(5); - - //check that the number of times the refreshAPI was called is 0 - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); - - let getResponse = await toTest({ url: `${BASE_URL}/` }); - //check that the response to getSession was success - assert.strictEqual(getResponse.statusCode, 200); - assert.deepStrictEqual(JSON.parse(getResponse.responseText), { success: true }); - - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 3); - }); - await page.setRequestInterception(false); - }); - it("test session after signing key change", async function () { // We can have access tokens valid for longer than the signing key update interval await startST(100, true, "0.002"); @@ -732,123 +643,6 @@ addTestCases((name, transferMethod, setupFunc, setupArgs = []) => { }); }); - // multiple API calls in parallel when access token is expired (100 of them) and only 1 refresh should be called***** - it("test that multiple API calls in parallel when access token is expired, only 1 refresh should be called", async function () { - await startST(15); - await setup(); - await page.evaluate(async () => { - let userId = "testing-supertokens-website"; - - // send api request to login - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - assert.strictEqual(loginResponse.responseText, userId); - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); - - // wait for 7 seconds so that the accesstoken expires - await delay(17); - - let promises = []; - let n = 100; - - // create an array of 100 get session promises - for (let i = 0; i < n; i++) { - promises.push( - toTest({ - url: `${BASE_URL}/`, - method: "GET", - headers: { "Cache-Control": "no-cache, private" } - }) - ); - } - - // send 100 get session requests - let multipleGetSessionResponse = await Promise.all(promises); - - //check that reponse of all requests are success - let noOfResponeSuccesses = 0; - for (let i = 0; i < multipleGetSessionResponse.length; i++) { - assert.strictEqual(await multipleGetSessionResponse[i].responseText, userId); - noOfResponeSuccesses += 1; - } - - //check that the number of times refresh is called is 1 - - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); - assert.strictEqual(noOfResponeSuccesses, n); - }); - }); - - // multiple API calls in parallel when access token is expired (100 of them) and only 1 refresh should be called***** - it("test that multiple API calls in parallel when access token is expired, only 1 refresh should be called - with delayed calls", async function () { - await startST(15); - await setup(); - await page.evaluate(async () => { - let userId = "testing-supertokens-website"; - - // send api request to login - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - assert.strictEqual(loginResponse.responseText, userId); - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); - - // wait for 7 seconds so that the accesstoken expires - await delay(17); - - let promises = []; - let n = 100; - - // create an array of 100 get session promises - for (let i = 0; i < n; i++) { - // this will make it so that there are calls to the / API during the refresh call. - // these calls should not cause another refresh, cause the tokens would have changed. - await new Promise(r => setTimeout(r, 3 * Math.random())); - promises.push( - toTest({ - url: `${BASE_URL}/`, - method: "GET", - headers: { "Cache-Control": "no-cache, private" } - }) - ); - } - - // send 100 get session requests - let multipleGetSessionResponse = await Promise.all(promises); - - //check that reponse of all requests are success - let noOfResponeSuccesses = 0; - for (let i = 0; i < multipleGetSessionResponse.length; i++) { - assert.strictEqual(await multipleGetSessionResponse[i].responseText, userId); - noOfResponeSuccesses += 1; - } - - //check that the number of times refresh is called is 1 - - try { - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); - } catch (err) { - // this happens sometimes for python testing where the number of refreshes is - // 2. - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 2); - } - assert.strictEqual(noOfResponeSuccesses, n); - }); - }); - // - Things should work if anti-csrf is disabled.****** it("test that things should work correctly if anti-csrf is disabled", async function () { await startST(3, false); @@ -893,168 +687,6 @@ addTestCases((name, transferMethod, setupFunc, setupArgs = []) => { }); }); - // if any API throws error, it gets propagated to the user properly (with and without interception)****** - it("test that if an api throws an error it gets propagated to the user with interception", async () => { - await startST(); - await setup(); - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); - - let val = await toTest({ url: `${BASE_URL}/testError` }); - assert.strictEqual(val.responseText, "test error message"); - assert.strictEqual(val.statusCode, 500); - }); - }); - - it("test that if an api throws a 400 error it gets propagated to the user with interception", async () => { - await startST(); - await setup(); - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); - - let val = await toTest({ url: `${BASE_URL}/testError?code=400` }); - assert.strictEqual(val.responseText, "test error message"); - assert.strictEqual(val.statusCode, 400); - }); - }); - - it("test that if an api throws a 405 error it gets propagated to the user with interception", async () => { - await startST(); - await setup(); - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); - - let val = await toTest({ url: `${BASE_URL}/testError?code=405` }); - assert.strictEqual(val.responseText, "test error message"); - assert.strictEqual(val.statusCode, 405); - }); - }); - - it("test that if an api throws an error it gets propagated to the user without interception", async () => { - await startST(); - await setup(); - - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); - - let val = await toTest({ url: `${BASE_URL}/testError#superTokensDoNotDoInterception`, method: "get" }); - - assert.strictEqual(val.responseText, "test error message"); - assert.strictEqual(val.statusCode, 500); - }); - }); - - it("test that if an api throws a 400 error it gets propagated to the user without interception", async () => { - await startST(); - await setup(); - - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); - - let val = await toTest({ - url: `${BASE_URL}/testError?code=400#superTokensDoNotDoInterception`, - method: "get" - }); - - assert.strictEqual(val.responseText, "test error message"); - assert.strictEqual(val.statusCode, 400); - }); - }); - - it("test that if an api throws a 405 error it gets propagated to the user without interception", async () => { - await startST(); - await setup(); - - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); - - let val = await toTest({ url: `${BASE_URL}/testError?code=405`, method: "get" }); - - assert.strictEqual(val.responseText, "test error message"); - assert.strictEqual(val.statusCode, 405); - }); - }); - - it("test that network errors are propagated to the user with interception", async () => { - await startST(); - await setup(); - - await page.setRequestInterception(true); - page.on("request", req => { - const url = req.url(); - if (url === BASE_URL + "/testError") { - req.abort(); - } else { - req.continue(); - } - }); - - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); - - let caught; - try { - await toTest({ url: `${BASE_URL}/testError`, method: "get" }); - } catch (ex) { - caught = ex; - } - - assert.ok(caught); - }); - }); - - it("test that network errors are propagated to the user without interception", async () => { - await startST(); - await setup(); - - await page.setRequestInterception(true); - page.on("request", req => { - const url = req.url(); - if (url === BASE_URL + "/testError") { - req.abort(); - } else { - req.continue(); - } - }); - - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); - - let caught; - try { - await toTest({ url: `${BASE_URL}/testError#superTokensDoNotDoInterception`, method: "get" }); - } catch (ex) { - caught = ex; - } - - assert.ok(caught); - }); - }); - // - Calling SuperTokens.init more than once works!******* it("test that calling SuperTokens.init more than once works", async () => { await startST(); @@ -1110,70 +742,33 @@ addTestCases((name, transferMethod, setupFunc, setupArgs = []) => { }); }); - //If via interception, make sure that initially, just an endpoint is just hit twice in case of access token expiry***** - it("test that if via interception, initially an endpoint is hit just twice in case of access token expiary", async () => { - await startST(3); + it("check sessionDoes exist calls refresh API just once", async function () { + await startST(); await setup(); + await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); let userId = "testing-supertokens-website"; - // send api request to login - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - assert.strictEqual(loginResponse.responseText, userId); - - //wait for 3 seconds such that the session expires - await delay(5); - - let getSessionResponse = await toTest({ url: `${BASE_URL}/` }); - assert.strictEqual(getSessionResponse.responseText, userId); - - //check that the number of times getSession was called is 1 - assert.strictEqual(await getNumberOfTimesGetSessionCalled(), 1); - - //check that the number of times refesh session was called is 1 - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); - }); - }); - - //- If you make an api call without cookies(logged out) api throws session expired , then make sure that refresh token api is not getting called , get 401 as the output**** - it("test that an api call without cookies throws session expire, refresh api is not called and 401 is the output", async function () { - await startST(5); - await setup(); + // check document cookie = "" + assert.strictEqual(document.cookie, ""); - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); - let userId = "testing-supertokens-website"; + // call sessionDoesExist + assert.strictEqual(await supertokens.doesSessionExist(), false); - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); + // check refresh API was called once + document.cookie has removed + assert.strictEqual(await getNumberOfTimesRefreshAttempted(), 1); + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); + // assert.strictEqual(document.cookie, "sIRTFrontend=remove"); - assert.strictEqual(loginResponse.responseText, userId); + // call sessionDoesExist + assert.strictEqual(await supertokens.doesSessionExist(), false); + // check refresh API not called + assert.strictEqual(await getNumberOfTimesRefreshAttempted(), 1); + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); + // assert.strictEqual(document.cookie, "sIRTFrontend=remove"); - let logoutResponse = await toTest({ - url: `${BASE_URL}/logout`, + await toTest({ + url: `/login`, method: "post", headers: { Accept: "application/json", @@ -1182,95 +777,41 @@ addTestCases((name, transferMethod, setupFunc, setupArgs = []) => { body: JSON.stringify({ userId }) }); - assert.strictEqual(logoutResponse.responseText, "success"); - - let getSessionResponse = await toTest({ url: `${BASE_URL}/` }); - - //check that the response to getSession without cookies is 401 - assert.strictEqual(getSessionResponse.statusCode, 401); - - assert.strictEqual(getSessionResponse.url, `${BASE_URL}/`); + // call sessionDoesExist + assert.strictEqual(await supertokens.doesSessionExist(), true); + // check refresh API not called assert.strictEqual(await getNumberOfTimesRefreshAttempted(), 1); + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); + // assert.notEqual(document.cookie, "sIRTFrontend=remove"); }); }); - // - If via interception, make sure that initially, just an endpoint is just hit once in case of access token NOT expiry***** - it("test that via interception initially an endpoint is just hit once in case of valid access token", async function () { - await startST(5); - await setup(); + it("check clearing all frontend set cookies still works (without anti-csrf)", async function () { + await startST(3, false); + await setup(); await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); - let userId = "testing-supertokens-website"; - - // send api request to login - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - assert.strictEqual(loginResponse.responseText, userId); - - let getSessionResponse = await toTest({ url: `${BASE_URL}/` }); - assert.strictEqual(getSessionResponse.responseText, userId); - - //check that the number of times getSession was called is 1 - assert.strictEqual(await getNumberOfTimesGetSessionCalled(), 1); - - //check that the number of times refresh session was called is 0 - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); - }); - }); + function deleteAllCookies() { + var cookies = document.cookie.split(";"); - it("test interception should happen if api domain and website domain are the same and relative path is used", async function () { - await startST(5); - await setup(); + for (var i = 0; i < cookies.length; i++) { + var cookie = cookies[i]; + var eqPos = cookie.indexOf("="); + var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie; + document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT"; + } + } - await page.evaluate(async () => { let BASE_URL = "http://localhost.org:8080"; supertokens.init({ apiDomain: BASE_URL }); let userId = "testing-supertokens-website"; - // send api request to login - let loginResponse = await toTest({ - url: `/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - assert.strictEqual(loginResponse.responseText, userId); - - assert.strictEqual(await supertokens.doesSessionExist(), true); - }); - }); - - it("test interception should not happen if api domain and website domain are different and relative path is used", async function () { - await startST(5); - await setup(); - - await page.evaluate(async () => { - let BASE_URL = "https://google.com"; - supertokens.init({ - apiDomain: BASE_URL - }); - let userId = "testing-supertokens-website"; + // check document cookie = "" + assert.strictEqual(document.cookie, ""); - // send api request to login - let loginResponse = await toTest({ + await toTest({ url: `/login`, method: "post", headers: { @@ -1280,1645 +821,103 @@ addTestCases((name, transferMethod, setupFunc, setupArgs = []) => { body: JSON.stringify({ userId }) }); - assert.strictEqual(loginResponse.responseText, userId); - - assert.strictEqual(await supertokens.doesSessionExist(), false); - }); - }); - - it("should not intercept if url contains superTokensDoNotDoInterception", async function () { - await startST(5); - await setup(); - - await page.evaluate(async () => { - let userId = "testing-supertokens-website"; - - // send api request to login - let loginResponse = await toTest({ - url: `/login#superTokensDoNotDoInterception`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - assert.strictEqual(loginResponse.responseText, userId); - - assert.strictEqual(await supertokens.doesSessionExist(), false); - }); - }); - - //cross domain login, userinfo, logout - it("test cross domain", async () => { - await startST(5); - await setup(); - - await page.evaluate(async () => { - let userId = "testing-supertokens-website"; - - // send api request to login - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - credentials: "include", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - //check that the userId which is returned in the response is the same as the one we sent - assert.strictEqual(loginResponse.responseText, userId); - - // check that the session exists + // call sessionDoesExist assert.strictEqual(await supertokens.doesSessionExist(), true); + // check refresh API not called + assert.strictEqual(await getNumberOfTimesRefreshAttempted(), 1); // it's one here since it gets called during login.. + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); + // assert.notEqual(document.cookie, "sIRTFrontend=remove"); - // check that the number of times session refresh is called is zero - assert.strictEqual(await getNumberOfTimesRefreshCalled(BASE_URL), 0); - - //delay for 5 seconds so that we know accessToken expires - - await delay(5); - // send a get session request , which should do a refresh session request - let getSessionResponse = await toTest({ url: `${BASE_URL}/`, method: "get", credentials: "include" }); - - // check that the getSession was successfull - assert.strictEqual(getSessionResponse.responseText, userId); - - // check that the refresh session was called only once - assert.strictEqual(await getNumberOfTimesRefreshCalled(BASE_URL), 1); - - // do logout - let logoutResponse = await toTest({ - url: `${BASE_URL}/logout`, - method: "post", - credentials: "include", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - assert.strictEqual(logoutResponse.responseText, "success"); - - //check that session does not exist - assert.strictEqual(await supertokens.doesSessionExist(), false); - }); - }); - - //cross domain login, userinfo, logout - it("test cross domain, auto add credentials", async () => { - await startST(5); - await setup(); - await page.evaluate(async () => { - let userId = "testing-supertokens-website"; - - // send api request to login - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - //check that the userId which is returned in the response is the same as the one we sent - assert.strictEqual(loginResponse.responseText, userId); - - // check that the session exists + // clear all cookies + deleteAllCookies(); + // call sessionDoesExist (returns true) + call to refresh assert.strictEqual(await supertokens.doesSessionExist(), true); - - // check that the number of times session refresh is called is zero - assert.strictEqual(await getNumberOfTimesRefreshCalled(BASE_URL), 0); - - //delay for 5 seconds so that we know accessToken expires - - await delay(5); - // send a get session request , which should do a refresh session request - let getSessionResponse = await toTest({ url: `${BASE_URL}/`, method: "get" }); - - // check that the getSession was successfull - assert.strictEqual(getSessionResponse.responseText, userId); - - // check that the refresh session was called only once - assert.strictEqual(await getNumberOfTimesRefreshCalled(BASE_URL), 1); - - // do logout - let logoutResponse = await toTest({ - url: `${BASE_URL}/logout`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - assert.strictEqual(logoutResponse.responseText, "success"); - - //check that session does not exist - assert.strictEqual(await supertokens.doesSessionExist(), false); - }); - }); - - //cross domain login, userinfo, logout - it("test cross domain, no auto add credentials, fail", async () => { - await startST(5); - await setup(); - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8082"; - supertokens.init({ - apiDomain: BASE_URL, - autoAddCredentials: false - }); - let userId = "testing-supertokens-website"; - - // send api request to login - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - //check that the userId which is returned in the response is the same as the one we sent - assert.strictEqual(loginResponse.responseText, userId); - - // check that the session exists - assert.strictEqual(await supertokens.doesSessionExist(), true); - - // check that the number of times session refresh is called is zero - assert.strictEqual(await getNumberOfTimesRefreshCalled(BASE_URL), 0); - - //delay for 5 seconds so that we know accessToken expires - - await delay(5); - - let resp = await toTest({ url: `${BASE_URL}/`, method: "get" }); - assert.strictEqual(resp.statusCode, 401); - - assert.strictEqual(await supertokens.doesSessionExist(), false); - - await toTest({ - url: `${BASE_URL}/login`, - method: "post", - credentials: "include", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - // send a get session request , which should do a refresh session request - let getSessionResponse = await toTest({ url: `${BASE_URL}/`, method: "get", credentials: "include" }); - - // check that the getSession was successfull - assert.strictEqual(getSessionResponse.responseText, userId); - - // check that the refresh session was called only once - assert.strictEqual(await getNumberOfTimesRefreshCalled(BASE_URL), 0); - - // do logout - let logoutResponse = await toTest({ - url: `${BASE_URL}/logout`, - method: "post", - credentials: "include", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - assert.strictEqual(logoutResponse.responseText, "success"); - - //check that session does not exist - assert.strictEqual(await supertokens.doesSessionExist(), false); - }); - }); - - it("check sessionDoes exist calls refresh API just once", async function () { - await startST(); - await setup(); - - await page.evaluate(async () => { - let userId = "testing-supertokens-website"; - - // check document cookie = "" - assert.strictEqual(document.cookie, ""); - - // call sessionDoesExist - assert.strictEqual(await supertokens.doesSessionExist(), false); - - // check refresh API was called once + document.cookie has removed - assert.strictEqual(await getNumberOfTimesRefreshAttempted(), 1); - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); - // assert.strictEqual(document.cookie, "sIRTFrontend=remove"); - - // call sessionDoesExist - assert.strictEqual(await supertokens.doesSessionExist(), false); - // check refresh API not called - assert.strictEqual(await getNumberOfTimesRefreshAttempted(), 1); - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); - // assert.strictEqual(document.cookie, "sIRTFrontend=remove"); - - await toTest({ - url: `/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - // call sessionDoesExist - assert.strictEqual(await supertokens.doesSessionExist(), true); - // check refresh API not called - assert.strictEqual(await getNumberOfTimesRefreshAttempted(), 1); - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); - // assert.notEqual(document.cookie, "sIRTFrontend=remove"); - }); - }); - - it("check clearing all frontend set cookies still works (without anti-csrf)", async function () { - await startST(3, false); - - await setup(); - await page.evaluate(async () => { - function deleteAllCookies() { - var cookies = document.cookie.split(";"); - - for (var i = 0; i < cookies.length; i++) { - var cookie = cookies[i]; - var eqPos = cookie.indexOf("="); - var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie; - document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT"; - } - } - - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); - let userId = "testing-supertokens-website"; - - // check document cookie = "" - assert.strictEqual(document.cookie, ""); - - await toTest({ - url: `/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - // call sessionDoesExist - assert.strictEqual(await supertokens.doesSessionExist(), true); - // check refresh API not called - assert.strictEqual(await getNumberOfTimesRefreshAttempted(), 1); // it's one here since it gets called during login.. - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); - // assert.notEqual(document.cookie, "sIRTFrontend=remove"); - - // clear all cookies - deleteAllCookies(); - // call sessionDoesExist (returns true) + call to refresh - assert.strictEqual(await supertokens.doesSessionExist(), true); - assert.strictEqual(await getNumberOfTimesRefreshAttempted(), 2); - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); + assert.strictEqual(await getNumberOfTimesRefreshAttempted(), 2); + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); // call sessionDoesExist (returns true) + no call to refresh assert.strictEqual(await supertokens.doesSessionExist(), true); assert.strictEqual(await getNumberOfTimesRefreshAttempted(), 2); - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); - }); - }); - - it("check clearing all frontend set cookies logs our user (with anti-csrf)", async function () { - await startST(); - - await setup(); - await page.evaluate(async () => { - function deleteAllCookies() { - var cookies = document.cookie.split(";"); - - for (var i = 0; i < cookies.length; i++) { - var cookie = cookies[i]; - var eqPos = cookie.indexOf("="); - var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie; - document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT"; - } - } - - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); - let userId = "testing-supertokens-website"; - - // check document cookie = "" - assert.strictEqual(document.cookie, ""); - - await toTest({ - url: `/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - // call sessionDoesExist - assert.strictEqual(await supertokens.doesSessionExist(), true); - // check refresh API not called - assert.strictEqual(await getNumberOfTimesRefreshAttempted(), 1); // it's one here since it gets called during login.. - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); - // assert.notEqual(document.cookie, "sIRTFrontend=remove"); - - // clear all cookies - deleteAllCookies(); - // call sessionDoesExist (returns false) + call to refresh - assert.strictEqual(await supertokens.doesSessionExist(), false); - assert.strictEqual(await getNumberOfTimesRefreshAttempted(), 2); - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); - - // call sessionDoesExist (returns false) + no call to refresh - assert.strictEqual(await supertokens.doesSessionExist(), false); - assert.strictEqual(await getNumberOfTimesRefreshAttempted(), 2); - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); - }); - }); - - it("test that unauthorised event is not fired on initial page load", async function () { - await startST(); - await setup(); - let consoleLogs = []; - page.on("console", message => { - if (message.text().startsWith("ST_")) { - consoleLogs.push(message.text()); - } - }); - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL, - onHandleEvent: event => { - console.log("ST_" + event.action); - } - }); - let userId = "testing-supertokens-website"; - - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - assert.strictEqual(loginResponse.responseText, userId); - }); - assert.strictEqual(consoleLogs.length, 1); - assert.strictEqual(consoleLogs[0], "ST_SESSION_CREATED"); - }); - - it("test that unauthorised event is fired when calling protected route without a session", async function () { - await startST(); - await setup(); - let consoleLogs = []; - page.on("console", message => { - if (message.text().startsWith("ST_")) { - consoleLogs.push(message.text()); - } - }); - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL, - onHandleEvent: event => { - console.log(`ST_${event.action}:${JSON.stringify(event)}`); - } - }); - let response = await toTest({ url: `${BASE_URL}/` }); - assert.strictEqual(response.statusCode, 401); - }); - - assert.strictEqual(consoleLogs.length, 1); - - const eventName = "ST_UNAUTHORISED"; - - assert.strict(consoleLogs[0].startsWith(eventName)); - const parsedEvent = JSON.parse(consoleLogs[0].substr(eventName.length + 1)); - assert.strictEqual(parsedEvent.sessionExpiredOrRevoked, false); - }); - - it("test that setting headers works", async function () { - await setup(); - const [_, req2, req3] = await Promise.all([ - page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); - await toTest({ url: `${BASE_URL}/test2`, headers: { asdf2: "123" } }); - await toTest({ url: `${BASE_URL}/test3` }); - }), - page.waitForRequest(`${BASE_URL}/test2`), - page.waitForRequest(`${BASE_URL}/test3`) - ]); - - assert.equal(req2.headers()["rid"], "anti-csrf"); - assert.equal(req2.headers()["asdf2"], "123"); - - assert.equal(req3.headers()["rid"], "anti-csrf"); - assert.equal(req3.headers()["asdf"], undefined); - }); - - it("test that after login, and clearing all cookies, if we query a protected route, it fires unauthorised event", async function () { - await startST(); - await setup(); - - let consoleLogs = []; - page.on("console", message => { - if (message.text().startsWith("ST_")) { - consoleLogs.push(message.text()); - } - }); - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL, - onHandleEvent: event => { - console.log(`ST_${event.action}:${JSON.stringify(event)}`); - } - }); - let userId = "testing-supertokens-website"; - - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - assert.strictEqual(loginResponse.responseText, userId); - }); - - const client = await page.target().createCDPSession(); - await client.send("Network.clearBrowserCookies"); - await client.send("Network.clearBrowserCache"); - let cookies = await page.cookies(); - assert.strictEqual(cookies.length, 0); - - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - let response = await toTest({ url: `${BASE_URL}/` }); - assert.strictEqual(response.statusCode, 401); - }); - - assert.strictEqual(consoleLogs.length, 2); - - assert.strict(consoleLogs[0].startsWith("ST_SESSION_CREATED")); - - const eventName = "ST_UNAUTHORISED"; - assert.strict(consoleLogs[1].startsWith(eventName)); - const parsedEvent = JSON.parse(consoleLogs[1].substr(eventName.length + 1)); - assert.strictEqual(parsedEvent.sessionExpiredOrRevoked, false); - }); - - it("test that after login, and clearing only httpOnly cookies, if we query a protected route, it fires unauthorised event", async function () { - if (transferMethod === "header") { - // We skip this in header mode: it should work the same without httpOnly cookies - this.skip(); - } - await startST(); - await setup(); - let consoleLogs = []; - page.on("console", message => { - if (message.text().startsWith("ST_")) { - consoleLogs.push(message.text()); - } - }); - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL, - onHandleEvent: event => { - console.log(`ST_${event.action}:${JSON.stringify(event)}`); - } - }); - let userId = "testing-supertokens-website"; - - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - assert.strictEqual(loginResponse.responseText, userId); - }); - - let originalCookies = (await page.cookies()).filter(c => !c.httpOnly); - - const client = await page.target().createCDPSession(); - await client.send("Network.clearBrowserCookies"); - await client.send("Network.clearBrowserCache"); - - await page.setCookie(...originalCookies); - let cookies = await page.cookies(); - assert.strictEqual(cookies.length, 3); - - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - let response = await toTest({ url: `${BASE_URL}/` }); - assert.strictEqual(response.statusCode, 401); - }); - - assert.strictEqual(consoleLogs.length, 2); - - assert.strict(consoleLogs[0].startsWith("ST_SESSION_CREATED")); - - const eventName = "ST_UNAUTHORISED"; - assert.strict(consoleLogs[1].startsWith(eventName)); - const parsedEvent = JSON.parse(consoleLogs[1].substr(eventName.length + 1)); - assert.strict(parsedEvent.sessionExpiredOrRevoked); - }); - - it("refresh session with invalid tokens should clear all cookies", async function () { - await startST(); - await setup(); - - await page.evaluate(async () => { - let userId = "testing-supertokens-website"; - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - }); - - // we save the cookies.. - let originalCookies = (await page._client.send("Network.getAllCookies")).cookies; - - // we logout - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - await toTest({ url: `${BASE_URL}/logout`, method: "POST" }); - }); - - // we set the old cookies with invalid access token - originalCookies = originalCookies.map(c => - c.name === "sAccessToken" || c.name === "st-access-token" ? { ...c, value: "broken" } : c - ); - await page.setCookie(...originalCookies); - - // now we expect a 401. - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - let resp = await toTest({ url: `${BASE_URL}/`, method: "GET" }); - assert.strictEqual(resp.statusCode, 401); - // assert.strictEqual(resp.url, `${BASE_URL}/auth/session/refresh`); - }); - - // and we assert that the only cookie that exists is the st-last-access-token-update - let newCookies = (await page._client.send("Network.getAllCookies")).cookies; - - assert.strictEqual(newCookies.length, 1); - assert.strictEqual(newCookies[0].name, "st-last-access-token-update"); - }); - - it("refresh session endpoint responding with 500 makes the original call resolve with refresh response", async function () { - await startST(100, true, "0.002"); - await setup(); - - await page.setRequestInterception(true); - let firstGet = true; - let firstPost = true; - page.on("request", req => { - const url = req.url(); - if (url === BASE_URL + "/") { - if (firstGet) { - firstGet = false; - req.respond({ - status: 401, - body: JSON.stringify({ - message: "try refresh token" - }) - }); - } else { - req.respond({ - status: 200, - body: JSON.stringify({ - success: true - }) - }); - } - } else if (url === BASE_URL + "/auth/session/refresh") { - if (firstPost) { - req.respond({ - status: 401, - body: JSON.stringify({ - message: "try refresh token" - }) - }); - firstPost = false; - } else { - req.respond({ - status: 500, - body: JSON.stringify({ - message: "test" - }) - }); - } - } else { - req.continue(); - } - }); - - // page.on("console", l => console.log(l.text())); - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); - let userId = "testing-supertokens-website"; - - await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - let response = await toTest({ url: `${BASE_URL}/`, method: "GET" }); - // assert.strictEqual(response.url, `${BASE_URL}/auth/session/refresh`); - assert.strictEqual(response.statusCode, 500); - const data = JSON.parse(response.responseText); - assert.strictEqual(data.message, "test"); - }); - }); - - it("no refresh call after 401 response that removes session", async function () { - await startST(100, true, "0.002"); - await setup(); - await page.setRequestInterception(true); - let refreshCalled = 0; - page.on("request", req => { - const url = req.url(); - if (url === BASE_URL + "/") { - req.respond({ - status: 401, - body: JSON.stringify({ message: "test" }), - headers: { - // Cookies don't actually matter as long as we clear the front-token - // this is because the frontend will still have st-last-access-token-update w/ a removed front-token - // This is interpreted as a logged-out state - "front-token": "remove" - } - }); - } else if (url === BASE_URL + "/auth/session/refresh") { - ++refreshCalled; - req.respond({ - status: 401, - body: JSON.stringify({ message: "nope" }) - }); - } else { - req.continue(); - } - }); - - await page.evaluate(async () => { - let userId = "testing-supertokens-website"; - await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - const resp = await toTest({ - url: `${BASE_URL}/`, - method: "GET", - headers: { "Cache-Control": "no-cache, private" } - }); - - assertNotEqual(resp, undefined); - assert.strictEqual(resp.statusCode, 401); - const data = JSON.parse(resp.responseText); - assertNotEqual(data, undefined); - assert.strictEqual(data.message, "test"); - }); - - // Calls it once before login, but it shouldn't after that - assert.equal(refreshCalled, 1); - }); - - it("original endpoint responding with 500 should not call refresh without cookies", async function () { - await startST(100, true, "0.002"); - await setup(); - await page.setRequestInterception(true); - let refreshCalled = 0; - page.on("request", req => { - const url = req.url(); - if (url === BASE_URL + "/") { - req.respond({ - status: 500, - body: JSON.stringify({ - message: "test" - }) - }); - } else if (url === BASE_URL + "/auth/session/refresh") { - ++refreshCalled; - req.respond({ - status: 500, - body: JSON.stringify({ - message: "nope" - }) - }); - } else { - req.continue(); - } - }); - - await page.evaluate(async () => { - let response = await toTest({ url: `${BASE_URL}/`, method: "GET" }); - assert.strictEqual(response.url, `${BASE_URL}/`); - assert.strictEqual(response.statusCode, 500); - const data = JSON.parse(response.responseText); - assert.strictEqual(data.message, "test"); - }); - // It should call it once before the call - but after that doesn't work it should not try again after the API request - assert.strictEqual(refreshCalled, 1); - }); - - it("Test that the access token payload and the JWT have all valid claims after creating, refreshing and updating the payload", async function () { - await startSTWithJWTEnabled(); - await setup(); - - let isJwtEnabled = await checkIfJWTIsEnabled(); - - if (!isJwtEnabled) { - return; - } - - await page.setRequestInterception(true); - page.on("request", req => { - const url = req.url(); - if (url === BASE_URL + "/jsondecode") { - let jwt = JSON.parse(req.postData()).jwt; - let decodedJWT = decodeJWT(jwt); - - req.respond({ - status: 200, - body: JSON.stringify(decodedJWT) - }); - } else { - req.continue(); - } - }); - await page.evaluate(async v3AccessTokenSupported => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); - - let userId = "testing-supertokens-website"; - - // Create a session - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - assert.strictEqual(loginResponse.responseText, userId); - - // Verify access token payload - let accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); - - assertEqual(accessTokenPayload.customClaim, "customValue"); - let jwt; - - if (v3AccessTokenSupported) { - jwt = await supertokens.getAccessToken(); - assertEqual(accessTokenPayload.jwt, undefined); - assertEqual(accessTokenPayload._jwtPName, undefined); - } else { - assertNotEqual(accessTokenPayload.jwt, undefined); - assertEqual(accessTokenPayload.sub, undefined); - assertEqual(accessTokenPayload._jwtPName, "jwt"); - assertEqual(accessTokenPayload.iss, undefined); - - jwt = accessTokenPayload.jwt; - } - - // Decode the JWT - let decodeResponse = await toTest({ - url: `${BASE_URL}/jsondecode`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ jwt }) - }); - - let decodedJWT = JSON.parse(decodeResponse.responseText); - - // Verify the JWT claims - assert.strictEqual(decodedJWT.sub, userId); - assert.strictEqual(decodedJWT._jwtPName, undefined); - assert.strictEqual(decodedJWT.iss, "http://0.0.0.0:8080/auth"); - assert.strictEqual(decodedJWT.customClaim, "customValue"); - - // Update access token payload - await toTest({ - url: `${BASE_URL}/update-jwt`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ newClaim: "newValue" }) - }); - - // Get access token payload - accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); - assertEqual(accessTokenPayload.customClaim, undefined); - assertEqual(accessTokenPayload.newClaim, "newValue"); - - // Verify new access token payload - if (v3AccessTokenSupported) { - jwt = await supertokens.getAccessToken(); - assertEqual(accessTokenPayload.jwt, undefined); - assertEqual(accessTokenPayload._jwtPName, undefined); - } else { - assertNotEqual(accessTokenPayload.jwt, undefined); - assertEqual(accessTokenPayload.sub, undefined); - assertEqual(accessTokenPayload._jwtPName, "jwt"); - assertEqual(accessTokenPayload.iss, undefined); - - jwt = accessTokenPayload.jwt; - } - - decodeResponse = await toTest({ - url: `${BASE_URL}/jsondecode`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ jwt }) - }); - - decodedJWT = JSON.parse(decodeResponse.responseText); - - // Verify new JWT - assert.strictEqual(decodedJWT.sub, userId); - assert.strictEqual(decodedJWT._jwtPName, undefined); - assert.strictEqual(decodedJWT.customClaim, undefined); - assert.strictEqual(decodedJWT.newClaim, "newValue"); - - let attemptRefresh = await supertokens.attemptRefreshingSession(); - assert.strictEqual(attemptRefresh, true); - - // Verify new access token payload - assertEqual(accessTokenPayload.customClaim, undefined); - assertEqual(accessTokenPayload.newClaim, "newValue"); - - // Verify new access token payload - if (v3AccessTokenSupported) { - jwt = await supertokens.getAccessToken(); - assertEqual(accessTokenPayload.jwt, undefined); - assertEqual(accessTokenPayload._jwtPName, undefined); - } else { - assertNotEqual(accessTokenPayload.jwt, undefined); - assertEqual(accessTokenPayload.sub, undefined); - assertEqual(accessTokenPayload._jwtPName, "jwt"); - assertEqual(accessTokenPayload.iss, undefined); - - jwt = accessTokenPayload.jwt; - } - - decodeResponse = await toTest({ - url: `${BASE_URL}/jsondecode`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ jwt }) - }); - - decodedJWT = JSON.parse(decodeResponse.responseText); - - // Verify new JWT - assert.strictEqual(decodedJWT.sub, userId); - assert.strictEqual(decodedJWT._jwtPName, undefined); - assert.strictEqual(decodedJWT.customClaim, undefined); - assert.strictEqual(decodedJWT.newClaim, "newValue"); - }, v3AccessTokenSupported); - }); - - it("Test that the access token payload and the JWT have all valid claims after updating access token payload", async function () { - await startSTWithJWTEnabled(); - - let isJwtEnabled = await checkIfJWTIsEnabled(); - - if (!isJwtEnabled) { - return; - } - - await setup(); - await page.setRequestInterception(true); - page.on("request", req => { - const url = req.url(); - if (url === BASE_URL + "/jsondecode") { - let jwt = JSON.parse(req.postData()).jwt; - let decodedJWT = decodeJWT(jwt); - - req.respond({ - status: 200, - body: JSON.stringify(decodedJWT) - }); - } else { - req.continue(); - } - }); - await page.evaluate(async v3AccessTokenSupported => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); - - let userId = "testing-supertokens-website"; - - // Create a session - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - assert.strictEqual(loginResponse.responseText, userId); - - // Verify access token payload - let accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); - assertEqual(accessTokenPayload.customClaim, "customValue"); - let jwt; - - if (v3AccessTokenSupported) { - jwt = await supertokens.getAccessToken(); - assertEqual(accessTokenPayload.jwt, undefined); - assertEqual(accessTokenPayload._jwtPName, undefined); - } else { - assertNotEqual(accessTokenPayload.jwt, undefined); - assertEqual(accessTokenPayload.sub, undefined); - assertEqual(accessTokenPayload._jwtPName, "jwt"); - assertEqual(accessTokenPayload.iss, undefined); - - jwt = accessTokenPayload.jwt; - } - - // Decode the JWT - let decodeResponse = await toTest({ - url: `${BASE_URL}/jsondecode`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ jwt }) - }); - - let decodedJWT = JSON.parse(decodeResponse.responseText); - - // Verify the JWT claims - assert.strictEqual(decodedJWT.sub, userId); - assert.strictEqual(decodedJWT._jwtPName, undefined); - assert.strictEqual(decodedJWT.iss, "http://0.0.0.0:8080/auth"); - assert.strictEqual(decodedJWT.customClaim, "customValue"); - - // Update access token payload - await toTest({ - url: `${BASE_URL}/update-jwt`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ - customClaim: undefined, - newClaim: "newValue" - }) - }); - - // Get access token payload - accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); - assert.strictEqual(accessTokenPayload.customClaim, undefined); - assert.strictEqual(accessTokenPayload.newClaim, "newValue"); - - if (v3AccessTokenSupported) { - jwt = await supertokens.getAccessToken(); - assertEqual(accessTokenPayload.jwt, undefined); - assertEqual(accessTokenPayload._jwtPName, undefined); - } else { - assertNotEqual(accessTokenPayload.jwt, undefined); - assertEqual(accessTokenPayload.sub, undefined); - assertEqual(accessTokenPayload._jwtPName, "jwt"); - assertEqual(accessTokenPayload.iss, undefined); - - jwt = accessTokenPayload.jwt; - } - - decodeResponse = await toTest({ - url: `${BASE_URL}/jsondecode`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ jwt }) - }); - - decodedJWT = JSON.parse(decodeResponse.responseText); - - // Verify new JWT - assert.strictEqual(decodedJWT.sub, userId); - assert.strictEqual(decodedJWT._jwtPName, undefined); - assert.strictEqual(decodedJWT.customClaim, undefined); - assert.strictEqual(decodedJWT.newClaim, "newValue"); - }, v3AccessTokenSupported); - }); - - it("Test that access token payload and JWT are valid after the property name changes and payload is updated", async function () { - await startSTWithJWTEnabled(); - - let isJwtEnabled = await checkIfJWTIsEnabled(); - - if (!isJwtEnabled || v3AccessTokenSupported) { - return; - } - - await setup(); - await page.setRequestInterception(true); - page.on("request", req => { - const url = req.url(); - if (url === BASE_URL + "/jsondecode") { - let jwt = JSON.parse(req.postData()).jwt; - let decodedJWT = decodeJWT(jwt); - - req.respond({ - status: 200, - body: JSON.stringify(decodedJWT) - }); - } else { - req.continue(); - } - }); - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); - - let userId = "testing-supertokens-website"; - - // Create a session - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - assert.strictEqual(loginResponse.responseText, userId); - - // Verify access token payload - let accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); - - assertNotEqual(accessTokenPayload.jwt, undefined); - assert.strictEqual(accessTokenPayload.sub, undefined); - assert.strictEqual(accessTokenPayload._jwtPName, "jwt"); - assert.strictEqual(accessTokenPayload.iss, undefined); - assert.strictEqual(accessTokenPayload.customClaim, "customValue"); - - let jwt = accessTokenPayload.jwt; - - // Decode the JWT - let decodeResponse = await toTest({ - url: `${BASE_URL}/jsondecode`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ jwt }) - }); - - let decodedJWT = JSON.parse(decodeResponse.responseText); - - // Verify the JWT claims - assert.strictEqual(decodedJWT.sub, userId); - assert.strictEqual(decodedJWT._jwtPName, undefined); - assert.strictEqual(decodedJWT.iss, "http://0.0.0.0:8080/auth"); - assert.strictEqual(decodedJWT.customClaim, "customValue"); - - await toTest({ - url: `${BASE_URL}/reinitialiseBackendConfig`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ - jwtPropertyName: "customJWTProperty" - }) - }); - - // Update access token payload - await toTest({ - url: `${BASE_URL}/update-jwt`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ newClaim: "newValue" }) - }); - - // Get access token payload - accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); - - // Verify new access token payload - assertNotEqual(accessTokenPayload.jwt, undefined); - assert.strictEqual(accessTokenPayload.sub, undefined); - assert.strictEqual(accessTokenPayload._jwtPName, "jwt"); - assert.strictEqual(accessTokenPayload.iss, undefined); - assert.strictEqual(accessTokenPayload.customClaim, undefined); - assert.strictEqual(accessTokenPayload.customJWTProperty, undefined); - assert.strictEqual(accessTokenPayload.newClaim, "newValue"); - - jwt = accessTokenPayload.jwt; - - decodeResponse = await toTest({ - url: `${BASE_URL}/jsondecode`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ jwt }) - }); - - decodedJWT = JSON.parse(decodeResponse.responseText); - - // Verify new JWT - assert.strictEqual(decodedJWT.sub, userId); - assert.strictEqual(decodedJWT._jwtPName, undefined); - assert.strictEqual(decodedJWT.iss, "http://0.0.0.0:8080/auth"); - assert.strictEqual(decodedJWT.customClaim, undefined); - assert.strictEqual(decodedJWT.newClaim, "newValue"); - }); - }); - - it("Test that access token payload and JWT are valid after the property name changes and session is refreshed", async function () { - await startSTWithJWTEnabled(); - - let isJwtEnabled = await checkIfJWTIsEnabled(); - - if (!isJwtEnabled || v3AccessTokenSupported) { - return; - } - - await setup(); - await page.setRequestInterception(true); - page.on("request", req => { - const url = req.url(); - if (url === BASE_URL + "/jsondecode") { - let jwt = JSON.parse(req.postData()).jwt; - let decodedJWT = decodeJWT(jwt); - - req.respond({ - status: 200, - body: JSON.stringify(decodedJWT) - }); - } else { - req.continue(); - } - }); - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); - - let userId = "testing-supertokens-website"; - - // Create a session - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - assert.strictEqual(loginResponse.responseText, userId); - - // Verify access token payload - let accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); - - assertNotEqual(accessTokenPayload.jwt, undefined); - assert.strictEqual(accessTokenPayload.sub, undefined); - assert.strictEqual(accessTokenPayload._jwtPName, "jwt"); - assert.strictEqual(accessTokenPayload.iss, undefined); - assert.strictEqual(accessTokenPayload.customClaim, "customValue"); - - let jwt = accessTokenPayload.jwt; - - // Decode the JWT - let decodeResponse = await toTest({ - url: `${BASE_URL}/jsondecode`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ jwt }) - }); - - let decodedJWT = JSON.parse(decodeResponse.responseText); - - // Verify the JWT claims - assert.strictEqual(decodedJWT.sub, userId); - assert.strictEqual(decodedJWT._jwtPName, undefined); - assert.strictEqual(decodedJWT.iss, "http://0.0.0.0:8080/auth"); - assert.strictEqual(decodedJWT.customClaim, "customValue"); - - await toTest({ - url: `${BASE_URL}/reinitialiseBackendConfig`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ - jwtPropertyName: "customJWTProperty" - }) - }); - - let attemptRefresh = await supertokens.attemptRefreshingSession(); - assert.strictEqual(attemptRefresh, true); - - // Get access token payload - accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); - - // Verify new access token payload - assert.strictEqual(accessTokenPayload.jwt, undefined); - assertNotEqual(accessTokenPayload.customJWTProperty, undefined); - assert.strictEqual(accessTokenPayload.sub, undefined); - assert.strictEqual(accessTokenPayload._jwtPName, "customJWTProperty"); - assert.strictEqual(accessTokenPayload.iss, undefined); - assert.strictEqual(accessTokenPayload.customClaim, "customValue"); - - jwt = accessTokenPayload.customJWTProperty; - - decodeResponse = await toTest({ - url: `${BASE_URL}/jsondecode`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ jwt }) - }); - - decodedJWT = JSON.parse(decodeResponse.responseText); - - // Verify new JWT - assert.strictEqual(decodedJWT.sub, userId); - assert.strictEqual(decodedJWT._jwtPName, undefined); - assert.strictEqual(decodedJWT.iss, "http://0.0.0.0:8080/auth"); - assert.strictEqual(decodedJWT.customClaim, "customValue"); - }); - }); - - it("Test that access token payload and jwt are valid after the session has expired", async function () { - await startSTWithJWTEnabled(3); - - let isJwtEnabled = await checkIfJWTIsEnabled(); - - if (!isJwtEnabled) { - return; - } - - await setup(); - await page.setRequestInterception(true); - page.on("request", req => { - const url = req.url(); - if (url === BASE_URL + "/jsondecode") { - let jwt = JSON.parse(req.postData()).jwt; - let decodedJWT = decodeJWT(jwt); - - req.respond({ - status: 200, - body: JSON.stringify(decodedJWT) - }); - } else { - req.continue(); - } - }); - await page.evaluate(async v3AccessTokenSupported => { - let BASE_URL = "http://localhost.org:8080"; - supertokens.init({ - apiDomain: BASE_URL - }); - - let userId = "testing-supertokens-website"; - - // Create a session - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - assert.strictEqual(loginResponse.responseText, userId); - - // Verify access token payload - let accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); - assert.strictEqual(accessTokenPayload.customClaim, "customValue"); - - let jwt; - - if (v3AccessTokenSupported) { - jwt = await supertokens.getAccessToken(); - assertEqual(accessTokenPayload.jwt, undefined); - assertEqual(accessTokenPayload._jwtPName, undefined); - } else { - assertNotEqual(accessTokenPayload.jwt, undefined); - assertEqual(accessTokenPayload.sub, undefined); - assertEqual(accessTokenPayload._jwtPName, "jwt"); - assertEqual(accessTokenPayload.iss, undefined); - - jwt = accessTokenPayload.jwt; - } - - let decodeResponse = await toTest({ - url: `${BASE_URL}/jsondecode`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ jwt }) - }); - - let decodedJWT = JSON.parse(decodeResponse.responseText); - - let jwtExpiry = decodedJWT.exp; - - // Wait for access token to expire - await delay(5); - - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); - - accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); - - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); - - assert.strictEqual(accessTokenPayload.customClaim, "customValue"); - - if (v3AccessTokenSupported) { - jwt = await supertokens.getAccessToken(); - assertEqual(accessTokenPayload.jwt, undefined); - assertEqual(accessTokenPayload._jwtPName, undefined); - } else { - assertNotEqual(accessTokenPayload.jwt, undefined); - assertEqual(accessTokenPayload.sub, undefined); - assertEqual(accessTokenPayload._jwtPName, "jwt"); - assertEqual(accessTokenPayload.iss, undefined); - - jwt = accessTokenPayload.jwt; - } - - decodeResponse = await toTest({ - url: `${BASE_URL}/jsondecode`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ jwt }) - }); - - decodedJWT = JSON.parse(decodeResponse.responseText); - - // Verify new JWT - assert.strictEqual(decodedJWT.sub, userId); - assert.strictEqual(decodedJWT._jwtPName, undefined); - assert.strictEqual(decodedJWT.iss, "http://0.0.0.0:8080/auth"); - assert.strictEqual(decodedJWT.customClaim, "customValue"); - - let newJwtExpiry = decodedJWT.exp; - - assert.strictEqual(newJwtExpiry > Math.ceil(Date.now() / 1000), true); - assertNotEqual(jwtExpiry, newJwtExpiry); - }, v3AccessTokenSupported); - }); - - it("Test full JWT flow with open id discovery", async function () { - await startSTWithJWTEnabled(20); - - let isJwtEnabled = await checkIfJWTIsEnabled(); - - if (!isJwtEnabled) { - return; - } - - await page.setRequestInterception(true); - page.on("request", req => { - const url = req.url(); - if (url === BASE_URL + "/jsondecode") { - let jwt = JSON.parse(req.postData()).jwt; - let decodedJWT = decodeJWT(jwt); - - req.respond({ - status: 200, - body: JSON.stringify(decodedJWT) - }); - } else if (url === BASE_URL + "/jwtVerify") { - let data = JSON.parse(req.postData()); - let jwt = data.jwt; - let jwksURL = data.jwksURL; - let client = jwksClient({ - jwksUri: jwksURL - }); - - function getKey(header, callback) { - client.getSigningKey(header.kid, function (err, key) { - if (err) { - callback(err, null); - return; - } - - var signingKey = key.publicKey || key.rsaPublicKey; - callback(null, signingKey); - }); - } - - verifyJWT(jwt, getKey, (err, decoded) => { - if (err) { - req.respond({ - status: 500, - body: JSON.stringify({ - error: err - }) - }); - return; - } - - req.respond({ - status: 200, - body: JSON.stringify(decoded) - }); - }); - } else { - req.continue(); - } - }); - await setup(); - await page.evaluate(async v3AccessTokenSupported => { - let userId = "testing-supertokens-website"; - - // Create a session - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); + }); + }); - assert.strictEqual(loginResponse.responseText, userId); + it("check clearing all frontend set cookies logs our user (with anti-csrf)", async function () { + await startST(); - // Verify access token payload - let accessTokenPayload = await supertokens.getAccessTokenPayloadSecurely(); - assert.strictEqual(accessTokenPayload.customClaim, "customValue"); + await setup(); + await page.evaluate(async () => { + function deleteAllCookies() { + var cookies = document.cookie.split(";"); - let jwt; + for (var i = 0; i < cookies.length; i++) { + var cookie = cookies[i]; + var eqPos = cookie.indexOf("="); + var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie; + document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT"; + } + } - if (v3AccessTokenSupported) { - jwt = await supertokens.getAccessToken(); - assertEqual(accessTokenPayload.jwt, undefined); - assertEqual(accessTokenPayload._jwtPName, undefined); - } else { - assertNotEqual(accessTokenPayload.jwt, undefined); - assertEqual(accessTokenPayload.sub, undefined); - assertEqual(accessTokenPayload._jwtPName, "jwt"); - assertEqual(accessTokenPayload.iss, undefined); + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + let userId = "testing-supertokens-website"; - jwt = accessTokenPayload.jwt; - } + // check document cookie = "" + assert.strictEqual(document.cookie, ""); - let decodeResponse = await toTest({ - url: `${BASE_URL}/jsondecode`, + await toTest({ + url: `/login`, method: "post", headers: { Accept: "application/json", "Content-Type": "application/json" }, - body: JSON.stringify({ jwt }) + body: JSON.stringify({ userId }) }); - let decodedJWT = JSON.parse(decodeResponse.responseText); - - // Verify the JWT claims - assert.strictEqual(decodedJWT.sub, userId); - assert.strictEqual(decodedJWT._jwtPName, undefined); - assert.strictEqual(decodedJWT.iss, "http://0.0.0.0:8080/auth"); - assert.strictEqual(decodedJWT.customClaim, "customValue"); - - // Use the jwt issuer to get discovery configuration - - let discoveryEndpoint = decodedJWT.iss + "/.well-known/openid-configuration"; + // call sessionDoesExist + assert.strictEqual(await supertokens.doesSessionExist(), true); + // check refresh API not called + assert.strictEqual(await getNumberOfTimesRefreshAttempted(), 1); // it's one here since it gets called during login.. + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); + // assert.notEqual(document.cookie, "sIRTFrontend=remove"); - let jwksEndpoint = (await (await fetch(discoveryEndpoint)).json()).jwks_uri; + // clear all cookies + deleteAllCookies(); + // call sessionDoesExist (returns false) + call to refresh + assert.strictEqual(await supertokens.doesSessionExist(), false); + assert.strictEqual(await getNumberOfTimesRefreshAttempted(), 2); + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); - let verifyResponse = await toTest({ - url: `${BASE_URL}/jwtVerify`, - method: "POST", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ - jwt, - jwksURL: jwksEndpoint - }) - }); + // call sessionDoesExist (returns false) + no call to refresh + assert.strictEqual(await supertokens.doesSessionExist(), false); + assert.strictEqual(await getNumberOfTimesRefreshAttempted(), 2); + assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); + }); + }); - if (verifyResponse.statusCode !== 200) { - throw new Error("JWT Verification failed"); - } + it("test that setting headers works", async function () { + await setup(); + const [_, req2, req3] = await Promise.all([ + page.evaluate(async () => { + let BASE_URL = "http://localhost.org:8080"; + supertokens.init({ + apiDomain: BASE_URL + }); + await toTest({ url: `${BASE_URL}/test2`, headers: { asdf2: "123" } }); + await toTest({ url: `${BASE_URL}/test3` }); + }), + page.waitForRequest(`${BASE_URL}/test2`), + page.waitForRequest(`${BASE_URL}/test3`) + ]); - decodedJWT = JSON.parse(verifyResponse.responseText); + assert.equal(req2.headers()["rid"], "anti-csrf"); + assert.equal(req2.headers()["asdf2"], "123"); - assert.strictEqual(decodedJWT.sub, userId); - assert.strictEqual(decodedJWT._jwtPName, undefined); - assert.strictEqual(decodedJWT.iss, "http://0.0.0.0:8080/auth"); - assert.strictEqual(decodedJWT.customClaim, "customValue"); - }, v3AccessTokenSupported); + assert.equal(req3.headers()["rid"], "anti-csrf"); + assert.equal(req3.headers()["asdf"], undefined); }); it("test when ACCESS_TOKEN_PAYLOAD_UPDATED is fired", async function () { @@ -3192,261 +1191,6 @@ addTestCases((name, transferMethod, setupFunc, setupArgs = []) => { }); }); - it("test disabled interception", async function () { - await startST(); - - await setup(); - await page.evaluate(async () => { - supertokens.init({ - apiDomain: BASE_URL, - postAPIHook: async context => { - assert.strictEqual(context.action === "REFRESH_SESSION" || context.action === "SIGN_OUT", true); - - if (context.action === "REFRESH_SESSION" && context.fetchResponse.statusCode === 200) { - const body = await context.fetchResponse.text(); - assert.strictEqual(body, "refresh success"); - - const frontTokenInHeader = context.fetchResponse.headers.get("front-token"); - assertNotEqual(frontTokenInHeader, ""); - assertNotEqual(frontTokenInHeader, null); - } - - if (context.action === "SIGN_OUT" && context.fetchResponse.statusCode === 200) { - const body = await context.fetchResponse.json(); - assert.strictEqual(body.statusCode, "OK"); - - const frontTokenInHeader = context.fetchResponse.headers.get("front-token"); - assert.strictEqual(frontTokenInHeader, "remove"); - } - } - }); - let userId = "testing-supertokens-website"; - - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - assert.strictEqual(loginResponse.responseText, userId); - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); - - await delay(2); - let attemptRefresh = await supertokens.attemptRefreshingSession(); - assert.strictEqual(attemptRefresh, true); - - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); - await supertokens.signOut(); - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); - assert.strictEqual(await supertokens.doesSessionExist(), false); - }); - }); - - it("should work after refresh migrating old cookie based sessions", async function () { - if (transferMethod === "header") { - // We skip this in header mode, they can't have legacy sessions - this.skip(); - } - - await startST(); - await setup(); - - await page.evaluate(async () => { - let userId = "testing-supertokens-website"; - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - }); - - await page.setCookie({ name: "sIdRefreshToken", value: "asdf" }); - - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); - let originalCookies = (await page._client.send("Network.getAllCookies")).cookies; - assert.notStrictEqual( - originalCookies.find(cookie => cookie.name === "sIdRefreshToken"), - undefined - ); - - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - let resp = await toTest({ url: `${BASE_URL}/`, method: "GET" }); - assert.strictEqual(resp.statusCode, 200); - }); - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); - let newCookies = (await page._client.send("Network.getAllCookies")).cookies; - assert.strictEqual( - newCookies.find(cookie => cookie.name === "sIdRefreshToken"), - undefined - ); - }); - - it("should work after refresh migrating old cookie based sessions with expired access tokens", async function () { - if (transferMethod === "header") { - // We skip this in header mode, they can't have legacy sessions - this.skip(); - } - - await startST(); - await setup(); - - await page.evaluate(async () => { - let userId = "testing-supertokens-website"; - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - }); - - // This would work even without sIdRefreshToken since we don't actually check the body of the response, just call refresh on all 401s - await page.setCookie({ name: "sIdRefreshToken", value: "asdf" }); - await page.setCookie({ name: "sAccessToken", value: "", expiry: 0 }); - - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 0); - let originalCookies = (await page._client.send("Network.getAllCookies")).cookies; - assert.notStrictEqual( - originalCookies.find(cookie => cookie.name === "sIdRefreshToken"), - undefined - ); - - await page.evaluate(async () => { - let BASE_URL = "http://localhost.org:8080"; - let resp = await toTest({ url: `${BASE_URL}/`, method: "GET" }); - assert.strictEqual(resp.statusCode, 200); - }); - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); - let newCookies = (await page._client.send("Network.getAllCookies")).cookies; - assert.notStrictEqual( - originalCookies.find(cookie => cookie.name === "sAccessToken"), - undefined - ); - assert.strictEqual( - newCookies.find(cookie => cookie.name === "sIdRefreshToken"), - undefined - ); - }); - - /** - * - Create a session with cookies and add sIdRefreshToken manually to simulate old cookies - * - Change the token method to headers - * - Get session information and make sure the API succeeds, refresh is called and sIdRefreshToken is removed - * - Make sure getAccessToken returns undefined because the backend should have used cookies - * - Sign out - * - Login again and make sure access token is present because backend should now use headers - */ - it("should still work fine work fine if header based auth is enabled after a cookie based session", async function () { - if (transferMethod === "header") { - // We skip this in header mode, they can't have legacy sessions - this.skip(); - } - - await startST(); - await setup(); - - await page.evaluate(async () => { - window.userId = "testing-supertokens"; - window.BASE_URL = "http://localhost.org:8080"; - - // send api request to login - let loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - assert.strictEqual(loginResponse.responseText, userId); - - // make sure there is no access token - let accessToken = await supertokens.getAccessToken(); - assert.strictEqual(accessToken, undefined); - - let getSessionResponse = await toTest({ - url: `${BASE_URL}/`, - headers: { - Authorization: `Bearer ${accessToken}` - } - }); - - assert.strictEqual(getSessionResponse.statusCode, 200); - assert.strictEqual(getSessionResponse.responseText, userId); - }); - - // This would work even without sIdRefreshToken since we don't actually check the body of the response, just call refresh on all 401s - await page.setCookie({ name: "sIdRefreshToken", value: "asdf" }); - - const originalCookies = (await page._client.send("Network.getAllCookies")).cookies; - assert.notStrictEqual( - originalCookies.find(cookie => cookie.name === "sIdRefreshToken"), - undefined - ); - - await page.evaluate(async () => { - // Switch to header based auth - // Re-initialization doesn't work for everything (i.e., overrides), but it's fine for this - supertokens.init({ - apiDomain: BASE_URL, - tokenTransferMethod: "header" - }); - - let getResponse = await toTest({ url: `${BASE_URL}/`, method: "GET" }); - - //check that the response to getSession was success - assert.strictEqual(getResponse.responseText, userId); - - //check that the number of time the refreshAPI was called is 1 - assert.strictEqual(await getNumberOfTimesRefreshCalled(), 1); - }); - - const refreshedCookies = (await page._client.send("Network.getAllCookies")).cookies; - assert.strictEqual( - refreshedCookies.find(cookie => cookie.name === "sIdRefreshToken"), - undefined - ); - - await page.evaluate(async () => { - // Make sure this is still undefined because the backend should continue using cookies - accessToken = await supertokens.getAccessToken(); - assert.strictEqual(accessToken, undefined); - - await supertokens.signOut(); - - // send api request to login - loginResponse = await toTest({ - url: `${BASE_URL}/login`, - method: "post", - headers: { - Accept: "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ userId }) - }); - - assert.strictEqual(loginResponse.responseText, userId); - - // Make sure now access token is present because it should use header based auth - accessToken = await supertokens.getAccessToken(); - assert.notStrictEqual(accessToken, undefined); - }); - }); - it("should work fine if the last header is empty", async () => { await startST(); await setup(); @@ -3532,19 +1276,5 @@ addTestCases((name, transferMethod, setupFunc, setupArgs = []) => { }); await page.setRequestInterception(false); }); - - it("test that interception doesn't happen if the shouldDoInterceptionBasedOnUrl override returns false", async function () { - await setup({ - override: ["shouldDoInterceptionBasedOnUrl"] - }); - - await page.evaluate(async () => { - let getResponse = await toTest({ url: `${BASE_URL}/check-rid-no-session` }); - assert.strictEqual(getResponse.responseText, "fail"); - - let getWithOverrideResponse = await toTest({ url: `${BASE_URL}/check-rid-no-session?doOverride` }); - assert.strictEqual(getWithOverrideResponse.responseText, "success"); - }); - }); }); }); From 8ec7098243ce5d07b84dbf6358cd3fbe9570868c Mon Sep 17 00:00:00 2001 From: Mihaly Lengyel Date: Sun, 24 Sep 2023 11:35:26 +0200 Subject: [PATCH 2/2] chore: bump patch version --- CHANGELOG.md | 6 ++++++ bundle/bundle.js | 2 +- lib/build/version.d.ts | 2 +- lib/build/version.js | 2 +- lib/ts/version.ts | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 7 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f21a2df..d9522b88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +## [17.0.4] - 2023-09-24 + +### Test changes + +- Split some test suites into multiple files to help with test parallelization + ## [17.0.3] - 2023-09-XX ### Changes diff --git a/bundle/bundle.js b/bundle/bundle.js index 36d18250..840e5aca 100644 --- a/bundle/bundle.js +++ b/bundle/bundle.js @@ -1 +1 @@ -var supertokens;(()=>{"use strict";var e={759:function(e,t,n){var r=this&&this.__assign||function(){return r=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]o?[2,{isValid:!1,reason:{message:"expired",ageInSeconds:n,maxAgeInSeconds:o}}]:t.includes(e)?[2,{isValid:!0}]:[2,{isValid:!1,reason:{message:"wrong value",expectedToInclude:e,actualValue:t}}])}))}))}}},excludes:function(e,o,s){return void 0===o&&(o=t.defaultMaxAgeInSeconds),{id:void 0!==s?s:t.id,refresh:function(e){return t.refresh(e)},shouldRefresh:function(e,n){return void 0===t.getValueFromPayload(e,n)||void 0!==o&&e[t.id].to?[2,{isValid:!1,reason:{message:"expired",ageInSeconds:n,maxAgeInSeconds:o}}]:t.includes(e)?[2,{isValid:!1,reason:{message:"wrong value",expectedToNotInclude:e,actualValue:t}}]:[2,{isValid:!0}])}))}))}}},includesAll:function(e,o,s){return void 0===o&&(o=t.defaultMaxAgeInSeconds),{id:void 0!==s?s:t.id,refresh:function(e){return t.refresh(e)},shouldRefresh:function(e,n){return void 0===t.getValueFromPayload(e,n)||void 0!==o&&e[t.id].to?[2,{isValid:!1,reason:{message:"expired",ageInSeconds:n,maxAgeInSeconds:o}}]:(a=new Set(t),[2,(u=e.every((function(e){return a.has(e)})))?{isValid:u}:{isValid:u,reason:{message:"wrong value",expectedToInclude:e,actualValue:t}}]))}))}))}}},includesAny:function(e,o,s){return void 0===o&&(o=t.defaultMaxAgeInSeconds),{id:void 0!==s?s:t.id,refresh:function(e){return t.refresh(e)},shouldRefresh:function(e,n){return void 0===t.getValueFromPayload(e,n)||void 0!==o&&e[t.id].to?[2,{isValid:!1,reason:{message:"expired",ageInSeconds:n,maxAgeInSeconds:o}}]:(a=new Set(t),[2,(u=e.some((function(e){return a.has(e)})))?{isValid:u}:{isValid:u,reason:{message:"wrong value",expectedToIncludeAtLeastOneOf:e,actualValue:t}}]))}))}))}}},excludesAll:function(e,o,s){return void 0===o&&(o=t.defaultMaxAgeInSeconds),{id:void 0!==s?s:t.id,refresh:function(e){return t.refresh(e)},shouldRefresh:function(e,n){return void 0===t.getValueFromPayload(e,n)||void 0!==o&&e[t.id].to?[2,{isValid:!1,reason:{message:"expired",ageInSeconds:n,maxAgeInSeconds:o}}]:(a=new Set(t),[2,(u=e.every((function(e){return!a.has(e)})))?{isValid:u}:{isValid:u,reason:{message:"wrong value",expectedToNotInclude:e,actualValue:t}}]))}))}))}}}},this.id=e.id,this.refresh=e.refresh,this.defaultMaxAgeInSeconds=e.defaultMaxAgeInSeconds}return e.prototype.getValueFromPayload=function(e,t){return void 0!==e[this.id]?e[this.id].v:void 0},e.prototype.getLastFetchedTime=function(e,t){return void 0!==e[this.id]?e[this.id].t:void 0},e}();t.PrimitiveArrayClaim=o},911:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.PrimitiveClaim=void 0;var n=function(){function e(e){var t=this;this.validators={hasValue:function(e,n,r){return void 0===n&&(n=t.defaultMaxAgeInSeconds),{id:void 0!==r?r:t.id,refresh:function(e){return t.refresh(e)},shouldRefresh:function(e,r){return void 0===t.getValueFromPayload(e,r)||void 0!==n&&e[t.id].tn?{isValid:!1,reason:{message:"expired",ageInSeconds:i,maxAgeInSeconds:n}}:s!==e?{isValid:!1,reason:{message:"wrong value",expectedValue:e,actualValue:s}}:{isValid:!0}}}}},this.id=e.id,this.refresh=e.refresh,this.defaultMaxAgeInSeconds=e.defaultMaxAgeInSeconds}return e.prototype.getValueFromPayload=function(e,t){return void 0!==e[this.id]?e[this.id].v:void 0},e.prototype.getLastFetchedTime=function(e,t){return void 0!==e[this.id]?e[this.id].t:void 0},e}();t.PrimitiveClaim=n},173:function(e,t){var n,r=this&&this.__extends||(n=function(e,t){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},n(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function r(){this.constructor=e}n(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)});Object.defineProperty(t,"__esModule",{value:!0}),t.STGeneralError=void 0;var o=function(e){function t(t){var n=e.call(this,t)||this;return n.isSuperTokensGeneralError=!0,n}return r(t,e),t.isThisError=function(e){return!0===e.isSuperTokensGeneralError},t}(Error);t.STGeneralError=o},379:function(e,t,n){var r=this&&this.__assign||function(){return r=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]=300)throw c;return[4,h.config.postAPIHook({action:"REFRESH_SESSION",fetchResponse:c.clone(),requestInit:u.requestInit,url:u.url,userContext:{}})];case 12:return s.sent(),[4,k(!1)];case 13:return"NOT_EXISTS"===s.sent().status?((0,d.logDebugMessage)("onUnauthorisedResponse: local session doesn't exist, so returning session expired"),[2,{result:"SESSION_EXPIRED"}]):(h.config.onHandleEvent({action:"REFRESH_SESSION",userContext:{}}),(0,d.logDebugMessage)("onUnauthorisedResponse: Sending RETRY signal"),[2,{result:"RETRY"}]);case 14:return v=s.sent(),[4,k(!1)];case 15:return"NOT_EXISTS"===s.sent().status?((0,d.logDebugMessage)("onUnauthorisedResponse: local session doesn't exist, so returning session expired"),[2,{result:"SESSION_EXPIRED",error:v}]):((0,d.logDebugMessage)("onUnauthorisedResponse: sending API_ERROR"),[2,{result:"API_ERROR",error:v}]);case 16:return[4,t.releaseLock("REFRESH_TOKEN_USE")];case 17:return s.sent(),(0,d.logDebugMessage)("onUnauthorisedResponse: Released lock"),[4,k(!1)];case 18:return"NOT_EXISTS"!==s.sent().status?[3,21]:((0,d.logDebugMessage)("onUnauthorisedResponse: local session doesn't exist, so removing anti-csrf and sFrontToken"),[4,f.removeToken()]);case 19:return s.sent(),[4,g.removeToken()];case 20:s.sent(),s.label=21;case 21:return[7];case 22:return[4,k(!1)];case 23:return"NOT_EXISTS"===(b=s.sent()).status?((0,d.logDebugMessage)("onUnauthorisedResponse: lock acquired failed and local session doesn't exist, so sending SESSION_EXPIRED"),[2,{result:"SESSION_EXPIRED"}]):b.status!==e.status||"EXISTS"===b.status&&"EXISTS"===e.status&&b.lastAccessTokenUpdate!==e.lastAccessTokenUpdate?((0,d.logDebugMessage)("onUnauthorisedResponse: lock acquired failed and retrying early because pre and post id refresh tokens don't match"),[2,{result:"RETRY"}]):[3,2];case 24:return[2]}}))}))}function y(){(0,d.logDebugMessage)("onTokenUpdate: firing ACCESS_TOKEN_PAYLOAD_UPDATED event"),h.config.onHandleEvent({action:"ACCESS_TOKEN_PAYLOAD_UPDATED",userContext:{}})}function w(e){return o(this,void 0,void 0,(function(){var t;return s(this,(function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),[4,h.recipeImpl.getInvalidClaimsFromResponse({response:e,userContext:{}})];case 1:return(t=n.sent())&&h.config.onHandleEvent({action:"API_INVALID_CLAIM",claimValidationErrors:t,userContext:{}}),[3,3];case 2:return n.sent(),[3,3];case 3:return[2]}}))}))}function k(e){return o(this,void 0,void 0,(function(){var t,n,r;return s(this,(function(o){switch(o.label){case 0:return(0,d.logDebugMessage)("getLocalSessionState: called"),[4,D(p)];case 1:return t=o.sent(),[4,g.doesTokenExists()];case 2:return o.sent()&&void 0!==t?((0,d.logDebugMessage)("getLocalSessionState: returning EXISTS since both frontToken and lastAccessTokenUpdate exists"),[2,{status:"EXISTS",lastAccessTokenUpdate:t}]):[3,3];case 3:return t?((0,d.logDebugMessage)("getLocalSessionState: returning NOT_EXISTS since frontToken was cleared but lastAccessTokenUpdate exists"),[2,{status:"NOT_EXISTS"}]):[3,4];case 4:return n={status:"MAY_EXIST"},e?((0,d.logDebugMessage)("getLocalSessionState: trying to refresh"),[4,m(n)]):[3,7];case 5:return"RETRY"!==(r=o.sent()).result?((0,d.logDebugMessage)("getLocalSessionState: return NOT_EXISTS in case error from backend"+r.result),[2,{status:"NOT_EXISTS"}]):((0,d.logDebugMessage)("getLocalSessionState: Retrying post refresh"),[4,k(e)]);case 6:return[2,o.sent()];case 7:return(0,d.logDebugMessage)("getLocalSessionState: returning: "+n.status),[2,n]}}))}))}function S(e){switch(e){case"access":return"st-access-token";case"refresh":return"st-refresh-token"}}function I(e,t){var n=S(e);return""!==t?((0,d.logDebugMessage)("setToken: saved ".concat(e," token into cookies")),T(n,t,Date.now()+31536e5)):((0,d.logDebugMessage)("setToken: cleared ".concat(e," token from cookies")),T(n,t,0))}function T(e,t,n){var r="Fri, 31 Dec 9999 23:59:59 GMT";n!==Number.MAX_SAFE_INTEGER&&(r=new Date(n).toUTCString());var o=h.config.sessionTokenFrontendDomain;return"localhost"===o||o===c.default.getReferenceOrThrow().windowHandler.location.getHostName()?u.default.getReferenceOrThrow().cookieHandler.setCookie("".concat(e,"=").concat(t,";expires=").concat(r,";path=/;samesite=").concat(h.config.isInIframe?"none;secure":"lax")):u.default.getReferenceOrThrow().cookieHandler.setCookie("".concat(e,"=").concat(t,";expires=").concat(r,";domain=").concat(o,";path=/;samesite=").concat(h.config.isInIframe?"none;secure":"lax"))}function R(e){return o(this,void 0,void 0,(function(){return s(this,(function(t){return[2,D(S(e))]}))}))}function D(e){return o(this,void 0,void 0,(function(){var t,n,r,o;return s(this,(function(s){switch(s.label){case 0:return n="; ",[4,u.default.getReferenceOrThrow().cookieHandler.getCookie()];case 1:return t=n+s.sent(),(r=t.split("; "+e+"=")).length>=2&&void 0!==(o=r.pop())?[2,o.split(";").shift()]:[2,void 0]}}))}))}function E(e,t){return void 0===t&&(t=!1),o(this,void 0,void 0,(function(){var n,r;return s(this,(function(o){switch(o.label){case 0:return(0,d.logDebugMessage)("setTokenHeaders: adding existing tokens as header"),[4,R("access")];case 1:return n=o.sent(),[4,R("refresh")];case 2:return r=o.sent(),!t&&void 0===n||void 0===r?(0,d.logDebugMessage)("setAuthorizationHeaderIfRequired: token for header based auth not found"):e.has("Authorization")?(0,d.logDebugMessage)("setAuthorizationHeaderIfRequired: Authorization header defined by the user, not adding"):((0,d.logDebugMessage)("setAuthorizationHeaderIfRequired: added authorization header"),e.set("Authorization","Bearer ".concat(t?r:n))),[2]}}))}))}function x(e){return o(this,void 0,void 0,(function(){var t,n,r,o,i;return s(this,(function(s){switch(s.label){case 0:return(0,d.logDebugMessage)("saveTokensFromHeaders: Saving updated tokens from the response headers"),null===(t=e.headers.get("st-refresh-token"))?[3,2]:((0,d.logDebugMessage)("saveTokensFromHeaders: saving new refresh token"),[4,I("refresh",t)]);case 1:s.sent(),s.label=2;case 2:return null===(n=e.headers.get("st-access-token"))?[3,4]:((0,d.logDebugMessage)("saveTokensFromHeaders: saving new access token"),[4,I("access",n)]);case 3:s.sent(),s.label=4;case 4:return null===(r=e.headers.get("front-token"))?[3,6]:((0,d.logDebugMessage)("saveTokensFromHeaders: Setting sFrontToken: "+r),[4,g.setItem(r)]);case 5:s.sent(),s.label=6;case 6:return null===(o=e.headers.get("anti-csrf"))?[3,9]:[4,k(!0)];case 7:return"EXISTS"!==(i=s.sent()).status?[3,9]:((0,d.logDebugMessage)("saveTokensFromHeaders: Setting anti-csrf token"),[4,f.setItem(i.lastAccessTokenUpdate,o)]);case 8:s.sent(),s.label=9;case 9:return[2]}}))}))}function M(){return o(this,void 0,void 0,(function(){var e;return s(this,(function(t){switch(t.label){case 0:return(0,d.logDebugMessage)("saveLastAccessTokenUpdate: called"),e=Date.now().toString(),(0,d.logDebugMessage)("saveLastAccessTokenUpdate: setting "+e),[4,T(p,e,Number.MAX_SAFE_INTEGER)];case 1:return t.sent(),[4,T("sIRTFrontend","",0)];case 2:return t.sent(),[2]}}))}))}function A(){return o(this,void 0,void 0,(function(){function e(){return o(this,void 0,void 0,(function(){var e,t,n,r,o;return s(this,(function(s){switch(s.label){case 0:return t="; ",[4,u.default.getReferenceOrThrow().cookieHandler.getCookie()];case 1:return e=t+s.sent(),(n=e.split("; sAntiCsrf=")).length>=2&&void 0!==(r=n.pop())?void 0===(o=r.split(";").shift())?[2,null]:[2,o]:[2,null]}}))}))}var t;return s(this,(function(n){switch(n.label){case 0:return(0,d.logDebugMessage)("getAntiCSRFToken: called"),[4,k(!0)];case 1:return"EXISTS"!==n.sent().status?((0,d.logDebugMessage)("getAntiCSRFToken: Returning because local session state != EXISTS"),[2,null]):[4,e()];case 2:return t=n.sent(),(0,d.logDebugMessage)("getAntiCSRFToken: returning: "+t),[2,t]}}))}))}function _(e){return o(this,void 0,void 0,(function(){return s(this,(function(t){switch(t.label){case 0:return(0,d.logDebugMessage)("setAntiCSRF: called: "+e),void 0===e?[3,2]:[4,T(v,e,Number.MAX_SAFE_INTEGER)];case 1:return t.sent(),[3,4];case 2:return[4,T(v,"",0)];case 3:t.sent(),t.label=4;case 4:return[2]}}))}))}function C(){return o(this,void 0,void 0,(function(){var e;return s(this,(function(t){switch(t.label){case 0:return(0,d.logDebugMessage)("getFrontTokenFromCookie: called"),[4,D(b)];case 1:return[2,void 0===(e=t.sent())?null:e]}}))}))}function O(e){return JSON.parse(decodeURIComponent(escape(atob(e))))}function P(){return o(this,void 0,void 0,(function(){var e;return s(this,(function(t){switch(t.label){case 0:return(0,d.logDebugMessage)("getFrontToken: called"),[4,k(!0)];case 1:return"EXISTS"!==t.sent().status?((0,d.logDebugMessage)("getFrontToken: Returning because sIRTFrontend != EXISTS"),[2,null]):[4,C()];case 2:return e=t.sent(),(0,d.logDebugMessage)("getFrontToken: returning: "+e),[2,e]}}))}))}function H(e){return o(this,void 0,void 0,(function(){var t,n,r;return s(this,(function(o){switch(o.label){case 0:return(0,d.logDebugMessage)("setFrontToken: called"),[4,C()];case 1:return null!==(t=o.sent())&&void 0!==e&&(n=O(t).up,r=O(e).up,JSON.stringify(n)!==JSON.stringify(r)&&y()),void 0!==e?[3,3]:[4,T(b,"",0)];case 2:return o.sent(),[3,5];case 3:return[4,T(b,e,Number.MAX_SAFE_INTEGER)];case 4:o.sent(),o.label=5;case 5:return[2]}}))}))}function F(e,t,n){if(null!=n){var r="remove"!==n;(0,d.logDebugMessage)("fireSessionUpdateEventsIfNecessary wasLoggedIn: ".concat(e," frontTokenExistsAfter: ").concat(r," status: ").concat(t)),e?r||(t===h.config.sessionExpiredStatusCode?((0,d.logDebugMessage)("onUnauthorisedResponse: firing UNAUTHORISED event"),h.config.onHandleEvent({action:"UNAUTHORISED",sessionExpiredOrRevoked:!0,userContext:{}})):((0,d.logDebugMessage)("onUnauthorisedResponse: firing SIGN_OUT event"),h.config.onHandleEvent({action:"SIGN_OUT",userContext:{}}))):r&&((0,d.logDebugMessage)("onUnauthorisedResponse: firing SESSION_CREATED event"),h.config.onHandleEvent({action:"SESSION_CREATED",userContext:{}}))}else(0,d.logDebugMessage)("fireSessionUpdateEventsIfNecessary returning early because the front token was not updated")}t.onUnauthorisedResponse=m,t.onTokenUpdate=y,t.onInvalidClaimResponse=w,t.getLocalSessionState=k,t.getStorageNameForToken=S,t.setToken=I,t.getTokenForHeaderAuth=R,t.saveLastAccessTokenUpdate=M,t.setAntiCSRF=_,t.getFrontToken=P,t.setFrontToken=H,t.fireSessionUpdateEventsIfNecessary=F},569:function(e,t,n){var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,s){function i(e){try{u(r.next(e))}catch(e){s(e)}}function a(e){try{u(r.throw(e))}catch(e){s(e)}}function u(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(i,a)}u((r=r.apply(e,t||[])).next())}))},o=this&&this.__generator||function(e,t){var n,r,o,s,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return s={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function a(s){return function(a){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;i;)try{if(n=1,r&&(o=2&s[0]?r.return:s[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,s[1])).done)return o;switch(r=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return i.label++,{value:s[1],done:!1};case 5:i.label++,r=s[1],s=[0];continue;case 7:s=i.ops.pop(),i.trys.pop();continue;default:if(!((o=(o=i.trys).length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]{Object.defineProperty(t,"__esModule",{value:!0}),t.logDebugMessage=t.disableLogging=t.enableLogging=void 0;var r=n(255),o=!1;t.enableLogging=function(){o=!0},t.disableLogging=function(){o=!1},t.logDebugMessage=function(e){o&&console.log("".concat("com.supertokens",' {t: "').concat((new Date).toISOString(),'", message: "').concat(e,'", supertokens-website-ver: "').concat(r.package_version,'"}'))}},992:(e,t)=>{function n(e){return/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.isAnIpAddress=void 0,t.isAnIpAddress=n;function r(e,t){void 0===t&&(t=!1),e=e.trim();try{if(!e.startsWith("http://")&&!e.startsWith("https://"))throw new Error("converting to proper URL");var o=new URL(e);return t?o.hostname.startsWith("localhost")||n(o.hostname)?"http://"+o.host:"https://"+o.host:o.protocol+"//"+o.host}catch(e){}if(e.startsWith("/"))throw new Error("Please provide a valid domain name");if(0===e.indexOf(".")&&(e=e.substr(1)),(-1!==e.indexOf(".")||e.startsWith("localhost"))&&!e.startsWith("http://")&&!e.startsWith("https://")){e="https://"+e;try{return new URL(e),r(e,!0)}catch(e){}}throw new Error("Please provide a valid domain name")}t.default=function(e){var t=this;this.getAsStringDangerous=function(){return t.value},this.value=r(e)}},260:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0});function n(e){e=e.trim();try{if(!e.startsWith("http://")&&!e.startsWith("https://"))throw new Error("converting to proper URL");return"/"===(e=new URL(e).pathname).charAt(e.length-1)?e.substr(0,e.length-1):e}catch(e){}if((function(e){if(-1===e.indexOf(".")||e.startsWith("/"))return!1;try{return-1!==new URL(e).hostname.indexOf(".")}catch(e){}try{return-1!==new URL("http://"+e).hostname.indexOf(".")}catch(e){}return!1}(e)||e.startsWith("localhost"))&&!e.startsWith("http://")&&!e.startsWith("https://"))return n(e="http://"+e);"/"!==e.charAt(0)&&(e="/"+e);try{return new URL("http://example.com"+e),n("http://example.com"+e)}catch(e){throw new Error("Please provide a valid URL path")}}t.default=function e(t){var r=this;this.startsWith=function(e){return r.value.startsWith(e.value)},this.appendPath=function(t){return new e(r.value+t.value)},this.getAsStringDangerous=function(){return r.value},this.value=n(t)}},743:function(e,t){var n,r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,s){function i(e){try{u(r.next(e))}catch(e){s(e)}}function a(e){try{u(r.throw(e))}catch(e){s(e)}}function u(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(i,a)}u((r=r.apply(e,t||[])).next())}))},o=this&&this.__generator||function(e,t){var n,r,o,s,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return s={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function a(s){return function(a){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;i;)try{if(n=1,r&&(o=2&s[0]?r.return:s[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,s[1])).done)return o;switch(r=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return i.label++,{value:s[1],done:!1};case 5:i.label++,r=s[1],s=[0];continue;case 7:s=i.ops.pop(),i.trys.pop();continue;default:if(!((o=(o=i.trys).length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]=0;n--)if(e.history[n]==t)return e.history[n]},this.reset=function(){e.history=[]},this.waitForEvent=function(t,n){return void 0===n&&(n=7e3),r(e,void 0,void 0,(function(){var e,r=this;return o(this,(function(o){return e=Date.now(),[2,new Promise((function(o){var s=r;!function r(){var i=s.getEventByLastEventByName(t);void 0===i?Date.now()-e>n?o(void 0):setTimeout(r,1e3):o(i)}()}))]}))}))}}return e.getInstance=function(){return null==e.instance&&(e.instance=new e),e.instance},e}();t.ProcessState=s},994:function(e,t,n){var r=this&&this.__assign||function(){return r=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]=300)throw r;return[4,e.postAPIHook({action:"SIGN_OUT",requestInit:n.requestInit,url:n.url,fetchResponse:r.clone(),userContext:t.userContext})];case 4:return s.sent(),[4,r.clone().json()];case 5:if("GENERAL_ERROR"===(o=s.sent()).status)throw(0,c.logDebugMessage)("doRequest: Throwing general error"),a=void 0===o.message?"No Error Message Provided":o.message,new l.STGeneralError(a);return[2]}}))}))},getInvalidClaimsFromResponse:function(e){return o(this,void 0,void 0,(function(){var t;return s(this,(function(n){switch(n.label){case 0:return"body"in e.response?[4,e.response.clone().json()]:[3,2];case 1:return t=n.sent(),[3,3];case 2:t="string"==typeof e.response.data?JSON.parse(e.response.data):e.response.data,n.label=3;case 3:return[2,t.claimValidationErrors]}}))}))},getGlobalClaimValidators:function(e){return e.claimValidatorsAddedByOtherRecipes},validateClaims:function(e){return o(this,void 0,void 0,(function(){var t,n,r,o,i,a,u,c,l;return s(this,(function(s){switch(s.label){case 0:return[4,this.getAccessTokenPayloadSecurely({userContext:e.userContext})];case 1:t=s.sent(),n=0,r=e.claimValidators,s.label=2;case 2:return n1){var u=n.split(":")[n.split(":").length-1];"string"!=typeof(i=u)||isNaN(i)||isNaN(parseFloat(i))||(a+=":"+u,o=""===r.port?o:o+":"+r.port)}return n.startsWith(".")?("."+o).endsWith(a):o===a}}}},272:function(e,t,n){var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,s){function i(e){try{u(r.next(e))}catch(e){s(e)}}function a(e){try{u(r.throw(e))}catch(e){s(e)}}function u(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(i,a)}u((r=r.apply(e,t||[])).next())}))},o=this&&this.__generator||function(e,t){var n,r,o,s,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return s={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function a(s){return function(a){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;i;)try{if(n=1,r&&(o=2&s[0]?r.return:s[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,s[1])).done)return o;switch(r=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return i.label++,{value:s[1],done:!1};case 5:i.label++,r=s[1],s=[0];continue;case 7:s=i.ops.pop(),i.trys.pop();continue;default:if(!((o=(o=i.trys).length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]{Object.defineProperty(t,"__esModule",{value:!0}),t.CookieHandlerReference=void 0;var r=n(272),o=function(){function e(e){var t=function(e){return e};void 0!==e&&(t=e),this.cookieHandler=t(r.defaultCookieHandlerImplementation)}return e.init=function(t){void 0===e.instance&&(e.instance=new e(t))},e.getReferenceOrThrow=function(){if(void 0===e.instance)throw new Error("SuperTokensCookieHandler must be initialized before calling this method.");return e.instance},e}();t.CookieHandlerReference=o,t.default=o},318:function(e,t,n){var r=this&&this.__assign||function(){return r=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]{Object.defineProperty(t,"__esModule",{value:!0}),t.LockFactoryReference=void 0;var r=n(895),o=function(){function e(e){this.lockFactory=e}return e.init=function(t,n){void 0===this.instance&&(this.instance=new e(null!=t?t:function(e){return function(){return Promise.resolve(new r.default(e))}}(n)))},e.getReferenceOrThrow=function(){if(void 0===e.instance)throw new Error("SuperTokensLockReference must be initialized before calling this method.");return e.instance},e}();t.LockFactoryReference=o,t.default=o},153:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.SessionClaimValidatorStore=void 0;var n=function(){function e(){}return e.claimValidatorsAddedByOtherRecipes=[],e.addClaimValidatorFromOtherRecipe=function(t){e.claimValidatorsAddedByOtherRecipes.push(t)},e.getClaimValidatorsAddedByOtherRecipes=function(){return e.claimValidatorsAddedByOtherRecipes},e}();t.SessionClaimValidatorStore=n,t.default=n},586:function(e,t){var n=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,s){function i(e){try{u(r.next(e))}catch(e){s(e)}}function a(e){try{u(r.throw(e))}catch(e){s(e)}}function u(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(i,a)}u((r=r.apply(e,t||[])).next())}))},r=this&&this.__generator||function(e,t){var n,r,o,s,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return s={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function a(s){return function(a){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;i;)try{if(n=1,r&&(o=2&s[0]?r.return:s[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,s[1])).done)return o;switch(r=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return i.label++,{value:s[1],done:!1};case 5:i.label++,r=s[1],s=[0];continue;case 7:s=i.ops.pop(),i.trys.pop();continue;default:if(!((o=(o=i.trys).length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]{Object.defineProperty(t,"__esModule",{value:!0}),t.WindowHandlerReference=void 0;var r=n(586),o=function(){function e(e){var t=function(e){return e};void 0!==e&&(t=e),this.windowHandler=t(r.defaultWindowHandlerImplementation)}return e.init=function(t){void 0===e.instance&&(e.instance=new e(t))},e.getReferenceOrThrow=function(){if(void 0===e.instance)throw new Error("SuperTokensWindowHandler must be initialized before calling this method.");return e.instance},e}();t.WindowHandlerReference=o,t.default=o},255:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.supported_fdi=t.package_version=void 0,t.package_version="17.0.3",t.supported_fdi=["1.16","1.17","1.18"]},648:function(e,t,n){var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,s){function i(e){try{u(r.next(e))}catch(e){s(e)}}function a(e){try{u(r.throw(e))}catch(e){s(e)}}function u(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(i,a)}u((r=r.apply(e,t||[])).next())}))},o=this&&this.__generator||function(e,t){var n,r,o,s,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return s={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function a(s){return function(a){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;i;)try{if(n=1,r&&(o=2&s[0]?r.return:s[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,s[1])).done)return o;switch(r=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return i.label++,{value:s[1],done:!1};case 5:i.label++,r=s[1],s=[0];continue;case 7:s=i.ops.pop(),i.trys.pop();continue;default:if(!((o=(o=i.trys).length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]0?setTimeout(n,t):n(null)}}window.addEventListener("storage",i),e.addToWaiting(i);var a=setTimeout(i,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,c;return o(this,(function(o){switch(o.label){case 0:return n=void 0===this.storageHandler?u:this.storageHandler,r=a+"-"+t,null===(s=n.getItemSync(r))?[2]:(c=JSON.parse(s)).id!==this.id?[3,2]:[4,i.default().lock(c.iat)];case 1:o.sent(),this.acquiredIatSet.delete(c.iat),n.removeItemSync(r),i.default().unlock(c.iat),e.notifyWaiters(),o.label=2;case 2:return[2]}}))}))},e.lockCorrector=function(t){for(var n=Date.now()-5e3,r=t,o=[],s=0;;){var i=r.keySync(s);if(null===i)break;o.push(i),s++}for(var u=!1,c=0;c{Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function e(){var e=this;this.locked=new Map,this.addToLocked=function(t,n){var r=e.locked.get(t);void 0===r?void 0===n?e.locked.set(t,[]):e.locked.set(t,[n]):void 0!==n&&(r.unshift(n),e.locked.set(t,r))},this.isLocked=function(t){return e.locked.has(t)},this.lock=function(t){return new Promise((function(n,r){e.isLocked(t)?e.addToLocked(t,n):(e.addToLocked(t),n())}))},this.unlock=function(t){var n=e.locked.get(t);if(void 0!==n&&0!==n.length){var r=n.pop();e.locked.set(t,n),void 0!==r&&setTimeout(r,0)}else e.locked.delete(t)}}return e.getInstance=function(){return void 0===e.instance&&(e.instance=new e),e.instance},e}();t.default=function(){return n.getInstance()}},225:function(e,t){var n=this&&this.__assign||function(){return n=Object.assign||function(e){for(var t,n=1,r=arguments.length;n{Object.defineProperty(t,"__esModule",{value:!0}),t.OverrideableBuilder=void 0;var r=n(225),o=function(){function e(e){this.layers=[e],this.proxies=[]}return e.prototype.override=function(e){for(var t=(0,r.getProxyObject)(this.layers[0]),n=e(t,this),o=0,s=Object.keys(this.layers[0]);o=0;--o){var s=e.layers[o][n];if(null!=s)return s.bind(e.result).apply(void 0,r)}}},c=this,l=0;l{"use strict";var e={759:function(e,t,n){var r=this&&this.__assign||function(){return r=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]o?[2,{isValid:!1,reason:{message:"expired",ageInSeconds:n,maxAgeInSeconds:o}}]:t.includes(e)?[2,{isValid:!0}]:[2,{isValid:!1,reason:{message:"wrong value",expectedToInclude:e,actualValue:t}}])}))}))}}},excludes:function(e,o,s){return void 0===o&&(o=t.defaultMaxAgeInSeconds),{id:void 0!==s?s:t.id,refresh:function(e){return t.refresh(e)},shouldRefresh:function(e,n){return void 0===t.getValueFromPayload(e,n)||void 0!==o&&e[t.id].to?[2,{isValid:!1,reason:{message:"expired",ageInSeconds:n,maxAgeInSeconds:o}}]:t.includes(e)?[2,{isValid:!1,reason:{message:"wrong value",expectedToNotInclude:e,actualValue:t}}]:[2,{isValid:!0}])}))}))}}},includesAll:function(e,o,s){return void 0===o&&(o=t.defaultMaxAgeInSeconds),{id:void 0!==s?s:t.id,refresh:function(e){return t.refresh(e)},shouldRefresh:function(e,n){return void 0===t.getValueFromPayload(e,n)||void 0!==o&&e[t.id].to?[2,{isValid:!1,reason:{message:"expired",ageInSeconds:n,maxAgeInSeconds:o}}]:(a=new Set(t),[2,(u=e.every((function(e){return a.has(e)})))?{isValid:u}:{isValid:u,reason:{message:"wrong value",expectedToInclude:e,actualValue:t}}]))}))}))}}},includesAny:function(e,o,s){return void 0===o&&(o=t.defaultMaxAgeInSeconds),{id:void 0!==s?s:t.id,refresh:function(e){return t.refresh(e)},shouldRefresh:function(e,n){return void 0===t.getValueFromPayload(e,n)||void 0!==o&&e[t.id].to?[2,{isValid:!1,reason:{message:"expired",ageInSeconds:n,maxAgeInSeconds:o}}]:(a=new Set(t),[2,(u=e.some((function(e){return a.has(e)})))?{isValid:u}:{isValid:u,reason:{message:"wrong value",expectedToIncludeAtLeastOneOf:e,actualValue:t}}]))}))}))}}},excludesAll:function(e,o,s){return void 0===o&&(o=t.defaultMaxAgeInSeconds),{id:void 0!==s?s:t.id,refresh:function(e){return t.refresh(e)},shouldRefresh:function(e,n){return void 0===t.getValueFromPayload(e,n)||void 0!==o&&e[t.id].to?[2,{isValid:!1,reason:{message:"expired",ageInSeconds:n,maxAgeInSeconds:o}}]:(a=new Set(t),[2,(u=e.every((function(e){return!a.has(e)})))?{isValid:u}:{isValid:u,reason:{message:"wrong value",expectedToNotInclude:e,actualValue:t}}]))}))}))}}}},this.id=e.id,this.refresh=e.refresh,this.defaultMaxAgeInSeconds=e.defaultMaxAgeInSeconds}return e.prototype.getValueFromPayload=function(e,t){return void 0!==e[this.id]?e[this.id].v:void 0},e.prototype.getLastFetchedTime=function(e,t){return void 0!==e[this.id]?e[this.id].t:void 0},e}();t.PrimitiveArrayClaim=o},911:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.PrimitiveClaim=void 0;var n=function(){function e(e){var t=this;this.validators={hasValue:function(e,n,r){return void 0===n&&(n=t.defaultMaxAgeInSeconds),{id:void 0!==r?r:t.id,refresh:function(e){return t.refresh(e)},shouldRefresh:function(e,r){return void 0===t.getValueFromPayload(e,r)||void 0!==n&&e[t.id].tn?{isValid:!1,reason:{message:"expired",ageInSeconds:i,maxAgeInSeconds:n}}:s!==e?{isValid:!1,reason:{message:"wrong value",expectedValue:e,actualValue:s}}:{isValid:!0}}}}},this.id=e.id,this.refresh=e.refresh,this.defaultMaxAgeInSeconds=e.defaultMaxAgeInSeconds}return e.prototype.getValueFromPayload=function(e,t){return void 0!==e[this.id]?e[this.id].v:void 0},e.prototype.getLastFetchedTime=function(e,t){return void 0!==e[this.id]?e[this.id].t:void 0},e}();t.PrimitiveClaim=n},173:function(e,t){var n,r=this&&this.__extends||(n=function(e,t){return n=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])},n(e,t)},function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function r(){this.constructor=e}n(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)});Object.defineProperty(t,"__esModule",{value:!0}),t.STGeneralError=void 0;var o=function(e){function t(t){var n=e.call(this,t)||this;return n.isSuperTokensGeneralError=!0,n}return r(t,e),t.isThisError=function(e){return!0===e.isSuperTokensGeneralError},t}(Error);t.STGeneralError=o},379:function(e,t,n){var r=this&&this.__assign||function(){return r=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]=300)throw c;return[4,h.config.postAPIHook({action:"REFRESH_SESSION",fetchResponse:c.clone(),requestInit:u.requestInit,url:u.url,userContext:{}})];case 12:return s.sent(),[4,k(!1)];case 13:return"NOT_EXISTS"===s.sent().status?((0,d.logDebugMessage)("onUnauthorisedResponse: local session doesn't exist, so returning session expired"),[2,{result:"SESSION_EXPIRED"}]):(h.config.onHandleEvent({action:"REFRESH_SESSION",userContext:{}}),(0,d.logDebugMessage)("onUnauthorisedResponse: Sending RETRY signal"),[2,{result:"RETRY"}]);case 14:return v=s.sent(),[4,k(!1)];case 15:return"NOT_EXISTS"===s.sent().status?((0,d.logDebugMessage)("onUnauthorisedResponse: local session doesn't exist, so returning session expired"),[2,{result:"SESSION_EXPIRED",error:v}]):((0,d.logDebugMessage)("onUnauthorisedResponse: sending API_ERROR"),[2,{result:"API_ERROR",error:v}]);case 16:return[4,t.releaseLock("REFRESH_TOKEN_USE")];case 17:return s.sent(),(0,d.logDebugMessage)("onUnauthorisedResponse: Released lock"),[4,k(!1)];case 18:return"NOT_EXISTS"!==s.sent().status?[3,21]:((0,d.logDebugMessage)("onUnauthorisedResponse: local session doesn't exist, so removing anti-csrf and sFrontToken"),[4,f.removeToken()]);case 19:return s.sent(),[4,g.removeToken()];case 20:s.sent(),s.label=21;case 21:return[7];case 22:return[4,k(!1)];case 23:return"NOT_EXISTS"===(b=s.sent()).status?((0,d.logDebugMessage)("onUnauthorisedResponse: lock acquired failed and local session doesn't exist, so sending SESSION_EXPIRED"),[2,{result:"SESSION_EXPIRED"}]):b.status!==e.status||"EXISTS"===b.status&&"EXISTS"===e.status&&b.lastAccessTokenUpdate!==e.lastAccessTokenUpdate?((0,d.logDebugMessage)("onUnauthorisedResponse: lock acquired failed and retrying early because pre and post id refresh tokens don't match"),[2,{result:"RETRY"}]):[3,2];case 24:return[2]}}))}))}function y(){(0,d.logDebugMessage)("onTokenUpdate: firing ACCESS_TOKEN_PAYLOAD_UPDATED event"),h.config.onHandleEvent({action:"ACCESS_TOKEN_PAYLOAD_UPDATED",userContext:{}})}function w(e){return o(this,void 0,void 0,(function(){var t;return s(this,(function(n){switch(n.label){case 0:return n.trys.push([0,2,,3]),[4,h.recipeImpl.getInvalidClaimsFromResponse({response:e,userContext:{}})];case 1:return(t=n.sent())&&h.config.onHandleEvent({action:"API_INVALID_CLAIM",claimValidationErrors:t,userContext:{}}),[3,3];case 2:return n.sent(),[3,3];case 3:return[2]}}))}))}function k(e){return o(this,void 0,void 0,(function(){var t,n,r;return s(this,(function(o){switch(o.label){case 0:return(0,d.logDebugMessage)("getLocalSessionState: called"),[4,D(p)];case 1:return t=o.sent(),[4,g.doesTokenExists()];case 2:return o.sent()&&void 0!==t?((0,d.logDebugMessage)("getLocalSessionState: returning EXISTS since both frontToken and lastAccessTokenUpdate exists"),[2,{status:"EXISTS",lastAccessTokenUpdate:t}]):[3,3];case 3:return t?((0,d.logDebugMessage)("getLocalSessionState: returning NOT_EXISTS since frontToken was cleared but lastAccessTokenUpdate exists"),[2,{status:"NOT_EXISTS"}]):[3,4];case 4:return n={status:"MAY_EXIST"},e?((0,d.logDebugMessage)("getLocalSessionState: trying to refresh"),[4,m(n)]):[3,7];case 5:return"RETRY"!==(r=o.sent()).result?((0,d.logDebugMessage)("getLocalSessionState: return NOT_EXISTS in case error from backend"+r.result),[2,{status:"NOT_EXISTS"}]):((0,d.logDebugMessage)("getLocalSessionState: Retrying post refresh"),[4,k(e)]);case 6:return[2,o.sent()];case 7:return(0,d.logDebugMessage)("getLocalSessionState: returning: "+n.status),[2,n]}}))}))}function S(e){switch(e){case"access":return"st-access-token";case"refresh":return"st-refresh-token"}}function I(e,t){var n=S(e);return""!==t?((0,d.logDebugMessage)("setToken: saved ".concat(e," token into cookies")),T(n,t,Date.now()+31536e5)):((0,d.logDebugMessage)("setToken: cleared ".concat(e," token from cookies")),T(n,t,0))}function T(e,t,n){var r="Fri, 31 Dec 9999 23:59:59 GMT";n!==Number.MAX_SAFE_INTEGER&&(r=new Date(n).toUTCString());var o=h.config.sessionTokenFrontendDomain;return"localhost"===o||o===c.default.getReferenceOrThrow().windowHandler.location.getHostName()?u.default.getReferenceOrThrow().cookieHandler.setCookie("".concat(e,"=").concat(t,";expires=").concat(r,";path=/;samesite=").concat(h.config.isInIframe?"none;secure":"lax")):u.default.getReferenceOrThrow().cookieHandler.setCookie("".concat(e,"=").concat(t,";expires=").concat(r,";domain=").concat(o,";path=/;samesite=").concat(h.config.isInIframe?"none;secure":"lax"))}function R(e){return o(this,void 0,void 0,(function(){return s(this,(function(t){return[2,D(S(e))]}))}))}function D(e){return o(this,void 0,void 0,(function(){var t,n,r,o;return s(this,(function(s){switch(s.label){case 0:return n="; ",[4,u.default.getReferenceOrThrow().cookieHandler.getCookie()];case 1:return t=n+s.sent(),(r=t.split("; "+e+"=")).length>=2&&void 0!==(o=r.pop())?[2,o.split(";").shift()]:[2,void 0]}}))}))}function E(e,t){return void 0===t&&(t=!1),o(this,void 0,void 0,(function(){var n,r;return s(this,(function(o){switch(o.label){case 0:return(0,d.logDebugMessage)("setTokenHeaders: adding existing tokens as header"),[4,R("access")];case 1:return n=o.sent(),[4,R("refresh")];case 2:return r=o.sent(),!t&&void 0===n||void 0===r?(0,d.logDebugMessage)("setAuthorizationHeaderIfRequired: token for header based auth not found"):e.has("Authorization")?(0,d.logDebugMessage)("setAuthorizationHeaderIfRequired: Authorization header defined by the user, not adding"):((0,d.logDebugMessage)("setAuthorizationHeaderIfRequired: added authorization header"),e.set("Authorization","Bearer ".concat(t?r:n))),[2]}}))}))}function x(e){return o(this,void 0,void 0,(function(){var t,n,r,o,i;return s(this,(function(s){switch(s.label){case 0:return(0,d.logDebugMessage)("saveTokensFromHeaders: Saving updated tokens from the response headers"),null===(t=e.headers.get("st-refresh-token"))?[3,2]:((0,d.logDebugMessage)("saveTokensFromHeaders: saving new refresh token"),[4,I("refresh",t)]);case 1:s.sent(),s.label=2;case 2:return null===(n=e.headers.get("st-access-token"))?[3,4]:((0,d.logDebugMessage)("saveTokensFromHeaders: saving new access token"),[4,I("access",n)]);case 3:s.sent(),s.label=4;case 4:return null===(r=e.headers.get("front-token"))?[3,6]:((0,d.logDebugMessage)("saveTokensFromHeaders: Setting sFrontToken: "+r),[4,g.setItem(r)]);case 5:s.sent(),s.label=6;case 6:return null===(o=e.headers.get("anti-csrf"))?[3,9]:[4,k(!0)];case 7:return"EXISTS"!==(i=s.sent()).status?[3,9]:((0,d.logDebugMessage)("saveTokensFromHeaders: Setting anti-csrf token"),[4,f.setItem(i.lastAccessTokenUpdate,o)]);case 8:s.sent(),s.label=9;case 9:return[2]}}))}))}function M(){return o(this,void 0,void 0,(function(){var e;return s(this,(function(t){switch(t.label){case 0:return(0,d.logDebugMessage)("saveLastAccessTokenUpdate: called"),e=Date.now().toString(),(0,d.logDebugMessage)("saveLastAccessTokenUpdate: setting "+e),[4,T(p,e,Number.MAX_SAFE_INTEGER)];case 1:return t.sent(),[4,T("sIRTFrontend","",0)];case 2:return t.sent(),[2]}}))}))}function A(){return o(this,void 0,void 0,(function(){function e(){return o(this,void 0,void 0,(function(){var e,t,n,r,o;return s(this,(function(s){switch(s.label){case 0:return t="; ",[4,u.default.getReferenceOrThrow().cookieHandler.getCookie()];case 1:return e=t+s.sent(),(n=e.split("; sAntiCsrf=")).length>=2&&void 0!==(r=n.pop())?void 0===(o=r.split(";").shift())?[2,null]:[2,o]:[2,null]}}))}))}var t;return s(this,(function(n){switch(n.label){case 0:return(0,d.logDebugMessage)("getAntiCSRFToken: called"),[4,k(!0)];case 1:return"EXISTS"!==n.sent().status?((0,d.logDebugMessage)("getAntiCSRFToken: Returning because local session state != EXISTS"),[2,null]):[4,e()];case 2:return t=n.sent(),(0,d.logDebugMessage)("getAntiCSRFToken: returning: "+t),[2,t]}}))}))}function _(e){return o(this,void 0,void 0,(function(){return s(this,(function(t){switch(t.label){case 0:return(0,d.logDebugMessage)("setAntiCSRF: called: "+e),void 0===e?[3,2]:[4,T(v,e,Number.MAX_SAFE_INTEGER)];case 1:return t.sent(),[3,4];case 2:return[4,T(v,"",0)];case 3:t.sent(),t.label=4;case 4:return[2]}}))}))}function C(){return o(this,void 0,void 0,(function(){var e;return s(this,(function(t){switch(t.label){case 0:return(0,d.logDebugMessage)("getFrontTokenFromCookie: called"),[4,D(b)];case 1:return[2,void 0===(e=t.sent())?null:e]}}))}))}function O(e){return JSON.parse(decodeURIComponent(escape(atob(e))))}function P(){return o(this,void 0,void 0,(function(){var e;return s(this,(function(t){switch(t.label){case 0:return(0,d.logDebugMessage)("getFrontToken: called"),[4,k(!0)];case 1:return"EXISTS"!==t.sent().status?((0,d.logDebugMessage)("getFrontToken: Returning because sIRTFrontend != EXISTS"),[2,null]):[4,C()];case 2:return e=t.sent(),(0,d.logDebugMessage)("getFrontToken: returning: "+e),[2,e]}}))}))}function H(e){return o(this,void 0,void 0,(function(){var t,n,r;return s(this,(function(o){switch(o.label){case 0:return(0,d.logDebugMessage)("setFrontToken: called"),[4,C()];case 1:return null!==(t=o.sent())&&void 0!==e&&(n=O(t).up,r=O(e).up,JSON.stringify(n)!==JSON.stringify(r)&&y()),void 0!==e?[3,3]:[4,T(b,"",0)];case 2:return o.sent(),[3,5];case 3:return[4,T(b,e,Number.MAX_SAFE_INTEGER)];case 4:o.sent(),o.label=5;case 5:return[2]}}))}))}function F(e,t,n){if(null!=n){var r="remove"!==n;(0,d.logDebugMessage)("fireSessionUpdateEventsIfNecessary wasLoggedIn: ".concat(e," frontTokenExistsAfter: ").concat(r," status: ").concat(t)),e?r||(t===h.config.sessionExpiredStatusCode?((0,d.logDebugMessage)("onUnauthorisedResponse: firing UNAUTHORISED event"),h.config.onHandleEvent({action:"UNAUTHORISED",sessionExpiredOrRevoked:!0,userContext:{}})):((0,d.logDebugMessage)("onUnauthorisedResponse: firing SIGN_OUT event"),h.config.onHandleEvent({action:"SIGN_OUT",userContext:{}}))):r&&((0,d.logDebugMessage)("onUnauthorisedResponse: firing SESSION_CREATED event"),h.config.onHandleEvent({action:"SESSION_CREATED",userContext:{}}))}else(0,d.logDebugMessage)("fireSessionUpdateEventsIfNecessary returning early because the front token was not updated")}t.onUnauthorisedResponse=m,t.onTokenUpdate=y,t.onInvalidClaimResponse=w,t.getLocalSessionState=k,t.getStorageNameForToken=S,t.setToken=I,t.getTokenForHeaderAuth=R,t.saveLastAccessTokenUpdate=M,t.setAntiCSRF=_,t.getFrontToken=P,t.setFrontToken=H,t.fireSessionUpdateEventsIfNecessary=F},569:function(e,t,n){var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,s){function i(e){try{u(r.next(e))}catch(e){s(e)}}function a(e){try{u(r.throw(e))}catch(e){s(e)}}function u(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(i,a)}u((r=r.apply(e,t||[])).next())}))},o=this&&this.__generator||function(e,t){var n,r,o,s,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return s={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function a(s){return function(a){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;i;)try{if(n=1,r&&(o=2&s[0]?r.return:s[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,s[1])).done)return o;switch(r=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return i.label++,{value:s[1],done:!1};case 5:i.label++,r=s[1],s=[0];continue;case 7:s=i.ops.pop(),i.trys.pop();continue;default:if(!((o=(o=i.trys).length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]{Object.defineProperty(t,"__esModule",{value:!0}),t.logDebugMessage=t.disableLogging=t.enableLogging=void 0;var r=n(255),o=!1;t.enableLogging=function(){o=!0},t.disableLogging=function(){o=!1},t.logDebugMessage=function(e){o&&console.log("".concat("com.supertokens",' {t: "').concat((new Date).toISOString(),'", message: "').concat(e,'", supertokens-website-ver: "').concat(r.package_version,'"}'))}},992:(e,t)=>{function n(e){return/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.isAnIpAddress=void 0,t.isAnIpAddress=n;function r(e,t){void 0===t&&(t=!1),e=e.trim();try{if(!e.startsWith("http://")&&!e.startsWith("https://"))throw new Error("converting to proper URL");var o=new URL(e);return t?o.hostname.startsWith("localhost")||n(o.hostname)?"http://"+o.host:"https://"+o.host:o.protocol+"//"+o.host}catch(e){}if(e.startsWith("/"))throw new Error("Please provide a valid domain name");if(0===e.indexOf(".")&&(e=e.substr(1)),(-1!==e.indexOf(".")||e.startsWith("localhost"))&&!e.startsWith("http://")&&!e.startsWith("https://")){e="https://"+e;try{return new URL(e),r(e,!0)}catch(e){}}throw new Error("Please provide a valid domain name")}t.default=function(e){var t=this;this.getAsStringDangerous=function(){return t.value},this.value=r(e)}},260:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0});function n(e){e=e.trim();try{if(!e.startsWith("http://")&&!e.startsWith("https://"))throw new Error("converting to proper URL");return"/"===(e=new URL(e).pathname).charAt(e.length-1)?e.substr(0,e.length-1):e}catch(e){}if((function(e){if(-1===e.indexOf(".")||e.startsWith("/"))return!1;try{return-1!==new URL(e).hostname.indexOf(".")}catch(e){}try{return-1!==new URL("http://"+e).hostname.indexOf(".")}catch(e){}return!1}(e)||e.startsWith("localhost"))&&!e.startsWith("http://")&&!e.startsWith("https://"))return n(e="http://"+e);"/"!==e.charAt(0)&&(e="/"+e);try{return new URL("http://example.com"+e),n("http://example.com"+e)}catch(e){throw new Error("Please provide a valid URL path")}}t.default=function e(t){var r=this;this.startsWith=function(e){return r.value.startsWith(e.value)},this.appendPath=function(t){return new e(r.value+t.value)},this.getAsStringDangerous=function(){return r.value},this.value=n(t)}},743:function(e,t){var n,r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,s){function i(e){try{u(r.next(e))}catch(e){s(e)}}function a(e){try{u(r.throw(e))}catch(e){s(e)}}function u(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(i,a)}u((r=r.apply(e,t||[])).next())}))},o=this&&this.__generator||function(e,t){var n,r,o,s,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return s={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function a(s){return function(a){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;i;)try{if(n=1,r&&(o=2&s[0]?r.return:s[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,s[1])).done)return o;switch(r=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return i.label++,{value:s[1],done:!1};case 5:i.label++,r=s[1],s=[0];continue;case 7:s=i.ops.pop(),i.trys.pop();continue;default:if(!((o=(o=i.trys).length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]=0;n--)if(e.history[n]==t)return e.history[n]},this.reset=function(){e.history=[]},this.waitForEvent=function(t,n){return void 0===n&&(n=7e3),r(e,void 0,void 0,(function(){var e,r=this;return o(this,(function(o){return e=Date.now(),[2,new Promise((function(o){var s=r;!function r(){var i=s.getEventByLastEventByName(t);void 0===i?Date.now()-e>n?o(void 0):setTimeout(r,1e3):o(i)}()}))]}))}))}}return e.getInstance=function(){return null==e.instance&&(e.instance=new e),e.instance},e}();t.ProcessState=s},994:function(e,t,n){var r=this&&this.__assign||function(){return r=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]=300)throw r;return[4,e.postAPIHook({action:"SIGN_OUT",requestInit:n.requestInit,url:n.url,fetchResponse:r.clone(),userContext:t.userContext})];case 4:return s.sent(),[4,r.clone().json()];case 5:if("GENERAL_ERROR"===(o=s.sent()).status)throw(0,c.logDebugMessage)("doRequest: Throwing general error"),a=void 0===o.message?"No Error Message Provided":o.message,new l.STGeneralError(a);return[2]}}))}))},getInvalidClaimsFromResponse:function(e){return o(this,void 0,void 0,(function(){var t;return s(this,(function(n){switch(n.label){case 0:return"body"in e.response?[4,e.response.clone().json()]:[3,2];case 1:return t=n.sent(),[3,3];case 2:t="string"==typeof e.response.data?JSON.parse(e.response.data):e.response.data,n.label=3;case 3:return[2,t.claimValidationErrors]}}))}))},getGlobalClaimValidators:function(e){return e.claimValidatorsAddedByOtherRecipes},validateClaims:function(e){return o(this,void 0,void 0,(function(){var t,n,r,o,i,a,u,c,l;return s(this,(function(s){switch(s.label){case 0:return[4,this.getAccessTokenPayloadSecurely({userContext:e.userContext})];case 1:t=s.sent(),n=0,r=e.claimValidators,s.label=2;case 2:return n1){var u=n.split(":")[n.split(":").length-1];"string"!=typeof(i=u)||isNaN(i)||isNaN(parseFloat(i))||(a+=":"+u,o=""===r.port?o:o+":"+r.port)}return n.startsWith(".")?("."+o).endsWith(a):o===a}}}},272:function(e,t,n){var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,s){function i(e){try{u(r.next(e))}catch(e){s(e)}}function a(e){try{u(r.throw(e))}catch(e){s(e)}}function u(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(i,a)}u((r=r.apply(e,t||[])).next())}))},o=this&&this.__generator||function(e,t){var n,r,o,s,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return s={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function a(s){return function(a){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;i;)try{if(n=1,r&&(o=2&s[0]?r.return:s[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,s[1])).done)return o;switch(r=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return i.label++,{value:s[1],done:!1};case 5:i.label++,r=s[1],s=[0];continue;case 7:s=i.ops.pop(),i.trys.pop();continue;default:if(!((o=(o=i.trys).length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]{Object.defineProperty(t,"__esModule",{value:!0}),t.CookieHandlerReference=void 0;var r=n(272),o=function(){function e(e){var t=function(e){return e};void 0!==e&&(t=e),this.cookieHandler=t(r.defaultCookieHandlerImplementation)}return e.init=function(t){void 0===e.instance&&(e.instance=new e(t))},e.getReferenceOrThrow=function(){if(void 0===e.instance)throw new Error("SuperTokensCookieHandler must be initialized before calling this method.");return e.instance},e}();t.CookieHandlerReference=o,t.default=o},318:function(e,t,n){var r=this&&this.__assign||function(){return r=Object.assign||function(e){for(var t,n=1,r=arguments.length;n0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]{Object.defineProperty(t,"__esModule",{value:!0}),t.LockFactoryReference=void 0;var r=n(895),o=function(){function e(e){this.lockFactory=e}return e.init=function(t,n){void 0===this.instance&&(this.instance=new e(null!=t?t:function(e){return function(){return Promise.resolve(new r.default(e))}}(n)))},e.getReferenceOrThrow=function(){if(void 0===e.instance)throw new Error("SuperTokensLockReference must be initialized before calling this method.");return e.instance},e}();t.LockFactoryReference=o,t.default=o},153:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.SessionClaimValidatorStore=void 0;var n=function(){function e(){}return e.claimValidatorsAddedByOtherRecipes=[],e.addClaimValidatorFromOtherRecipe=function(t){e.claimValidatorsAddedByOtherRecipes.push(t)},e.getClaimValidatorsAddedByOtherRecipes=function(){return e.claimValidatorsAddedByOtherRecipes},e}();t.SessionClaimValidatorStore=n,t.default=n},586:function(e,t){var n=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,s){function i(e){try{u(r.next(e))}catch(e){s(e)}}function a(e){try{u(r.throw(e))}catch(e){s(e)}}function u(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(i,a)}u((r=r.apply(e,t||[])).next())}))},r=this&&this.__generator||function(e,t){var n,r,o,s,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return s={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function a(s){return function(a){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;i;)try{if(n=1,r&&(o=2&s[0]?r.return:s[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,s[1])).done)return o;switch(r=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return i.label++,{value:s[1],done:!1};case 5:i.label++,r=s[1],s=[0];continue;case 7:s=i.ops.pop(),i.trys.pop();continue;default:if(!((o=(o=i.trys).length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]{Object.defineProperty(t,"__esModule",{value:!0}),t.WindowHandlerReference=void 0;var r=n(586),o=function(){function e(e){var t=function(e){return e};void 0!==e&&(t=e),this.windowHandler=t(r.defaultWindowHandlerImplementation)}return e.init=function(t){void 0===e.instance&&(e.instance=new e(t))},e.getReferenceOrThrow=function(){if(void 0===e.instance)throw new Error("SuperTokensWindowHandler must be initialized before calling this method.");return e.instance},e}();t.WindowHandlerReference=o,t.default=o},255:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.supported_fdi=t.package_version=void 0,t.package_version="17.0.4",t.supported_fdi=["1.16","1.17","1.18"]},648:function(e,t,n){var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(o,s){function i(e){try{u(r.next(e))}catch(e){s(e)}}function a(e){try{u(r.throw(e))}catch(e){s(e)}}function u(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(i,a)}u((r=r.apply(e,t||[])).next())}))},o=this&&this.__generator||function(e,t){var n,r,o,s,i={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return s={next:a(0),throw:a(1),return:a(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function a(s){return function(a){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;i;)try{if(n=1,r&&(o=2&s[0]?r.return:s[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,s[1])).done)return o;switch(r=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return i.label++,{value:s[1],done:!1};case 5:i.label++,r=s[1],s=[0];continue;case 7:s=i.ops.pop(),i.trys.pop();continue;default:if(!((o=(o=i.trys).length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]0&&o[o.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]0?setTimeout(n,t):n(null)}}window.addEventListener("storage",i),e.addToWaiting(i);var a=setTimeout(i,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,c;return o(this,(function(o){switch(o.label){case 0:return n=void 0===this.storageHandler?u:this.storageHandler,r=a+"-"+t,null===(s=n.getItemSync(r))?[2]:(c=JSON.parse(s)).id!==this.id?[3,2]:[4,i.default().lock(c.iat)];case 1:o.sent(),this.acquiredIatSet.delete(c.iat),n.removeItemSync(r),i.default().unlock(c.iat),e.notifyWaiters(),o.label=2;case 2:return[2]}}))}))},e.lockCorrector=function(t){for(var n=Date.now()-5e3,r=t,o=[],s=0;;){var i=r.keySync(s);if(null===i)break;o.push(i),s++}for(var u=!1,c=0;c{Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function e(){var e=this;this.locked=new Map,this.addToLocked=function(t,n){var r=e.locked.get(t);void 0===r?void 0===n?e.locked.set(t,[]):e.locked.set(t,[n]):void 0!==n&&(r.unshift(n),e.locked.set(t,r))},this.isLocked=function(t){return e.locked.has(t)},this.lock=function(t){return new Promise((function(n,r){e.isLocked(t)?e.addToLocked(t,n):(e.addToLocked(t),n())}))},this.unlock=function(t){var n=e.locked.get(t);if(void 0!==n&&0!==n.length){var r=n.pop();e.locked.set(t,n),void 0!==r&&setTimeout(r,0)}else e.locked.delete(t)}}return e.getInstance=function(){return void 0===e.instance&&(e.instance=new e),e.instance},e}();t.default=function(){return n.getInstance()}},225:function(e,t){var n=this&&this.__assign||function(){return n=Object.assign||function(e){for(var t,n=1,r=arguments.length;n{Object.defineProperty(t,"__esModule",{value:!0}),t.OverrideableBuilder=void 0;var r=n(225),o=function(){function e(e){this.layers=[e],this.proxies=[]}return e.prototype.override=function(e){for(var t=(0,r.getProxyObject)(this.layers[0]),n=e(t,this),o=0,s=Object.keys(this.layers[0]);o=0;--o){var s=e.layers[o][n];if(null!=s)return s.bind(e.result).apply(void 0,r)}}},c=this,l=0;l