diff --git a/test/framework/custom.test.js b/test/framework/custom.test.js new file mode 100644 index 000000000..ad842219c --- /dev/null +++ b/test/framework/custom.test.js @@ -0,0 +1,193 @@ +/* Copyright (c) 2021, 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. + */ +const { printPath, setupST, startST, killAllST, cleanST, extractInfoFromResponse } = require("../utils"); +let assert = require("assert"); +let { ProcessState, PROCESS_STATE } = require("../../lib/build/processState"); +let SuperTokens = require("../.."); +let CustomFramework = require("../../framework/custom"); +let Session = require("../../recipe/session"); +let { verifySession } = require("../../recipe/session/framework/custom"); + +describe(`Custom framework: ${printPath("[test/framework/custom.test.js]")}`, function () { + beforeEach(async function () { + await killAllST(); + await setupST(); + ProcessState.getInstance().reset(); + }); + + afterEach(async function () { + try { + await this.server.close(); + } catch (err) {} + }); + after(async function () { + await killAllST(); + await cleanST(); + }); + + // - check if session verify middleware responds with a nice error even without the global error handler + it("test session verify middleware without error handler added", async function () { + const connectionURI = await startST(); + SuperTokens.init({ + framework: "custom", + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [Session.init({ getTokenTransferMethod: () => "cookie", antiCsrf: "VIA_TOKEN" })], + }); + + const req = new CustomFramework.PreParsedRequest({ + method: "get", + url: "/verify", + query: {}, + headers: new Headers(), + cookies: {}, + getFormBody: () => {}, + getJSONBody: () => {}, + }); + const resp = new CustomFramework.CollectingResponse(); + + const verifyResult = await verifySession()(req, resp); + + assert.strictEqual(verifyResult, undefined); + assert.strictEqual(resp.statusCode, 401); + assert.deepStrictEqual(JSON.parse(resp.body), { message: "unauthorised" }); + }); + + // check basic usage of session + it("test basic usage of sessions", async function () { + const connectionURI = await startST(); + SuperTokens.init({ + framework: "custom", + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [Session.init({ getTokenTransferMethod: () => "cookie", antiCsrf: "VIA_TOKEN" })], + }); + + const middleware = CustomFramework.middleware(); + + const req = new CustomFramework.PreParsedRequest({ + method: "get", + url: "/session/create", + query: {}, + headers: new Headers(), + cookies: {}, + getFormBody: () => {}, + getJSONBody: () => {}, + }); + const resp = new CustomFramework.CollectingResponse(); + + await Session.createNewSession(req, resp, "public", SuperTokens.convertToRecipeUserId("testUserId")); + + let res = extractInfoFromResponse(resp); + + assert(res.accessToken !== undefined); + assert(res.antiCsrf !== undefined); + assert(res.refreshToken !== undefined); + + const req2 = new CustomFramework.PreParsedRequest({ + method: "get", + url: "/session/refresh", + query: {}, + headers: new Headers([["anti-csrf", res.antiCsrf]]), + cookies: { + sAccessToken: res.accessToken, + sRefreshToken: res.refreshToken, + }, + getFormBody: () => {}, + getJSONBody: () => {}, + }); + const resp2 = new CustomFramework.CollectingResponse(); + + let verifyState3 = await ProcessState.getInstance().waitForEvent(PROCESS_STATE.CALLING_SERVICE_IN_VERIFY, 1500); + assert(verifyState3 === undefined); + + await Session.refreshSession(req2, resp2); + let res2 = extractInfoFromResponse(resp2); + + assert(res2.accessToken !== undefined); + assert(res2.antiCsrf !== undefined); + assert(res2.refreshToken !== undefined); + + const req3 = new CustomFramework.PreParsedRequest({ + method: "get", + url: "/session/verify", + query: {}, + headers: new Headers(), + cookies: { + sAccessToken: res2.accessToken, + }, + getFormBody: () => {}, + getJSONBody: () => {}, + }); + const resp3 = new CustomFramework.CollectingResponse(); + await verifySession()(req3, resp3); + + let res3 = extractInfoFromResponse(resp3); + let verifyState = await ProcessState.getInstance().waitForEvent(PROCESS_STATE.CALLING_SERVICE_IN_VERIFY); + + assert(verifyState !== undefined); + assert(res3.accessToken !== undefined); + + ProcessState.getInstance().reset(); + + const req4 = new CustomFramework.PreParsedRequest({ + method: "get", + url: "/session/verify", + query: {}, + headers: new Headers(), + cookies: { + sAccessToken: res3.accessToken, + }, + getFormBody: () => {}, + getJSONBody: () => {}, + }); + const resp4 = new CustomFramework.CollectingResponse(); + await verifySession()(req4, resp4); + let verifyState2 = await ProcessState.getInstance().waitForEvent(PROCESS_STATE.CALLING_SERVICE_IN_VERIFY, 1000); + assert(verifyState2 === undefined); + + const req5 = new CustomFramework.PreParsedRequest({ + method: "get", + url: "/session/verify", + query: {}, + headers: new Headers(), + cookies: { + sAccessToken: res3.accessToken, + }, + getFormBody: () => {}, + getJSONBody: () => {}, + }); + const resp5 = new CustomFramework.CollectingResponse(); + await verifySession()(req5, resp5); + await req5.session.revokeSession(); + let sessionRevokedResponseExtracted = extractInfoFromResponse(resp5); + assert.strictEqual(sessionRevokedResponseExtracted.accessTokenExpiry, "Thu, 01 Jan 1970 00:00:00 GMT"); + assert.strictEqual(sessionRevokedResponseExtracted.refreshTokenExpiry, "Thu, 01 Jan 1970 00:00:00 GMT"); + assert.strictEqual(sessionRevokedResponseExtracted.accessToken, ""); + assert.strictEqual(sessionRevokedResponseExtracted.refreshToken, ""); + }); +}); diff --git a/test/utils.js b/test/utils.js index 42ab19a96..39bbf39ed 100644 --- a/test/utils.js +++ b/test/utils.js @@ -41,6 +41,7 @@ const { join } = require("path"); const users = require("./users.json"); let assert = require("assert"); +const { CollectingResponse } = require("../framework/custom"); module.exports.printPath = function (path) { return `${createFormat([consoleOptions.yellow, consoleOptions.italic, consoleOptions.dim])}${path}${createFormat([ @@ -83,7 +84,7 @@ module.exports.setKeyValueInConfig = async function (key, value) { }; module.exports.extractInfoFromResponse = function (res) { - let antiCsrf = res.headers["anti-csrf"]; + let headers; let accessToken = undefined; let refreshToken = undefined; let accessTokenExpiry = undefined; @@ -95,49 +96,69 @@ module.exports.extractInfoFromResponse = function (res) { let accessTokenHttpOnly = false; let idRefreshTokenHttpOnly = false; let refreshTokenHttpOnly = false; - let frontToken = res.headers["front-token"]; - let cookies = res.headers["set-cookie"] || res.headers["Set-Cookie"]; - cookies = cookies === undefined ? [] : cookies; - if (!Array.isArray(cookies)) { - cookies = [cookies]; - } - cookies.forEach((i) => { - if (i.split(";")[0].split("=")[0] === "sAccessToken") { - /** - * if token is sAccessToken=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInZlcnNpb24iOiIyIn0=.eyJzZXNzaW9uSGFuZGxlIjoiMWI4NDBhOTAtMjVmYy00ZjQ4LWE2YWMtMDc0MDIzZjNjZjQwIiwidXNlcklkIjoiIiwicmVmcmVzaFRva2VuSGFzaDEiOiJjYWNhZDNlMGNhMDVkNzRlNWYzNTc4NmFlMGQ2MzJjNDhmMTg1YmZmNmUxNThjN2I2OThkZDYwMzA1NzAyYzI0IiwidXNlckRhdGEiOnt9LCJhbnRpQ3NyZlRva2VuIjoiYTA2MjRjYWItZmIwNy00NTFlLWJmOTYtNWQ3YzU2MjMwZTE4IiwiZXhwaXJ5VGltZSI6MTYyNjUxMjM3NDU4NiwidGltZUNyZWF0ZWQiOjE2MjY1MDg3NzQ1ODYsImxtcnQiOjE2MjY1MDg3NzQ1ODZ9.f1sCkjt0OduS6I6FBQDBLV5zhHXpCU2GXnbe+8OCU6HKG00TX5CM8AyFlOlqzSHABZ7jES/+5k0Ff/rdD34cczlNqICcC4a23AjJg2a097rFrh8/8V7J5fr4UrHLIM4ojZNFz1NyVyDK/ooE6I7soHshEtEVr2XsnJ4q3d+fYs2wwx97PIT82hfHqgbRAzvlv952GYt+OH4bWQE4vTzDqGN7N2OKpn9l2fiCB1Ytzr3ocHRqKuQ8f6xW1n575Q1sSs9F9TtD7lrKfFQH+//6lyKFe2Q1SDc7YU4pE5Cy9Kc/LiqiTU+gsGIJL5qtMzUTG4lX38ugF4QDyNjDBMqCKw==; Max-Age=3599; Expires=Sat, 17 Jul 2021 08:59:34 GMT; Secure; HttpOnly; SameSite=Lax; Path=/' - * i.split(";")[0].split("=")[1] will result in eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInZlcnNpb24iOiIyIn0 - */ - accessToken = decodeURIComponent(i.split(";")[0].split("=").slice(1).join("=")); - if (i.split(";")[2].includes("Expires=")) { - accessTokenExpiry = i.split(";")[2].split("=")[1]; - } else if (i.split(";")[2].includes("expires=")) { - accessTokenExpiry = i.split(";")[2].split("=")[1]; - } else { - accessTokenExpiry = i.split(";")[3].split("=")[1]; - } - if (i.split(";")[1].includes("Domain=")) { - accessTokenDomain = i.split(";")[1].split("=")[1]; - } - accessTokenHttpOnly = i.split(";").findIndex((j) => j.includes("HttpOnly")) !== -1; - } else if (i.split(";")[0].split("=")[0] === "sRefreshToken") { - refreshToken = i.split(";")[0].split("=").slice(1).join("="); - if (i.split(";")[2].includes("Expires=")) { - refreshTokenExpiry = i.split(";")[2].split("=")[1]; - } else if (i.split(";")[2].includes("expires=")) { - refreshTokenExpiry = i.split(";")[2].split("=")[1]; - } else { - refreshTokenExpiry = i.split(";")[3].split("=")[1]; - } - if (i.split(";")[1].includes("Domain=")) { - refreshTokenDomain = i.split(";")[1].split("=").slice(1).join("="); - } - refreshTokenHttpOnly = i.split(";").findIndex((j) => j.includes("HttpOnly")) !== -1; + if (res instanceof CollectingResponse) { + const accessTokenCookie = res.cookies.find((info) => info.key === "sAccessToken"); + if (accessTokenCookie) { + accessToken = accessTokenCookie.value; + accessTokenExpiry = new Date(accessTokenCookie.expires).toUTCString(); + accessTokenDomain = accessTokenCookie.domain; + accessTokenHttpOnly = accessTokenCookie.httpOnly; } - }); + const refreshTokenCookie = res.cookies.find((info) => info.key === "sRefreshToken"); + if (refreshTokenCookie) { + refreshToken = refreshTokenCookie.value; + refreshTokenExpiry = new Date(refreshTokenCookie.expires).toUTCString(); + refreshTokenDomain = refreshTokenCookie.domain; + refreshTokenHttpOnly = refreshTokenCookie.httpOnly; + } + headers = Object.fromEntries(res.headers.entries()); + } else { + let cookies = res.headers["set-cookie"] || res.headers["Set-Cookie"]; + cookies = cookies === undefined ? [] : cookies; + if (!Array.isArray(cookies)) { + cookies = [cookies]; + } + + cookies.forEach((i) => { + if (i.split(";")[0].split("=")[0] === "sAccessToken") { + /** + * if token is sAccessToken=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInZlcnNpb24iOiIyIn0=.eyJzZXNzaW9uSGFuZGxlIjoiMWI4NDBhOTAtMjVmYy00ZjQ4LWE2YWMtMDc0MDIzZjNjZjQwIiwidXNlcklkIjoiIiwicmVmcmVzaFRva2VuSGFzaDEiOiJjYWNhZDNlMGNhMDVkNzRlNWYzNTc4NmFlMGQ2MzJjNDhmMTg1YmZmNmUxNThjN2I2OThkZDYwMzA1NzAyYzI0IiwidXNlckRhdGEiOnt9LCJhbnRpQ3NyZlRva2VuIjoiYTA2MjRjYWItZmIwNy00NTFlLWJmOTYtNWQ3YzU2MjMwZTE4IiwiZXhwaXJ5VGltZSI6MTYyNjUxMjM3NDU4NiwidGltZUNyZWF0ZWQiOjE2MjY1MDg3NzQ1ODYsImxtcnQiOjE2MjY1MDg3NzQ1ODZ9.f1sCkjt0OduS6I6FBQDBLV5zhHXpCU2GXnbe+8OCU6HKG00TX5CM8AyFlOlqzSHABZ7jES/+5k0Ff/rdD34cczlNqICcC4a23AjJg2a097rFrh8/8V7J5fr4UrHLIM4ojZNFz1NyVyDK/ooE6I7soHshEtEVr2XsnJ4q3d+fYs2wwx97PIT82hfHqgbRAzvlv952GYt+OH4bWQE4vTzDqGN7N2OKpn9l2fiCB1Ytzr3ocHRqKuQ8f6xW1n575Q1sSs9F9TtD7lrKfFQH+//6lyKFe2Q1SDc7YU4pE5Cy9Kc/LiqiTU+gsGIJL5qtMzUTG4lX38ugF4QDyNjDBMqCKw==; Max-Age=3599; Expires=Sat, 17 Jul 2021 08:59:34 GMT; Secure; HttpOnly; SameSite=Lax; Path=/' + * i.split(";")[0].split("=")[1] will result in eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInZlcnNpb24iOiIyIn0 + */ + accessToken = decodeURIComponent(i.split(";")[0].split("=").slice(1).join("=")); + if (i.split(";")[2].includes("Expires=")) { + accessTokenExpiry = i.split(";")[2].split("=")[1]; + } else if (i.split(";")[2].includes("expires=")) { + accessTokenExpiry = i.split(";")[2].split("=")[1]; + } else { + accessTokenExpiry = i.split(";")[3].split("=")[1]; + } + if (i.split(";")[1].includes("Domain=")) { + accessTokenDomain = i.split(";")[1].split("=")[1]; + } + accessTokenHttpOnly = i.split(";").findIndex((j) => j.includes("HttpOnly")) !== -1; + } else if (i.split(";")[0].split("=")[0] === "sRefreshToken") { + refreshToken = i.split(";")[0].split("=").slice(1).join("="); + if (i.split(";")[2].includes("Expires=")) { + refreshTokenExpiry = i.split(";")[2].split("=")[1]; + } else if (i.split(";")[2].includes("expires=")) { + refreshTokenExpiry = i.split(";")[2].split("=")[1]; + } else { + refreshTokenExpiry = i.split(";")[3].split("=")[1]; + } + if (i.split(";")[1].includes("Domain=")) { + refreshTokenDomain = i.split(";")[1].split("=").slice(1).join("="); + } + refreshTokenHttpOnly = i.split(";").findIndex((j) => j.includes("HttpOnly")) !== -1; + } + }); + } + let antiCsrf = headers["anti-csrf"]; + let frontToken = headers["front-token"]; - const refreshTokenFromHeader = res.headers["st-refresh-token"]; - const accessTokenFromHeader = res.headers["st-access-token"]; + const refreshTokenFromHeader = headers["st-refresh-token"]; + const accessTokenFromHeader = headers["st-access-token"]; const accessTokenFromAny = accessToken === undefined ? accessTokenFromHeader : accessToken; const refreshTokenFromAny = refreshToken === undefined ? refreshTokenFromHeader : refreshToken;