diff --git a/test/end-to-end/oauth2provider.test.js b/test/end-to-end/oauth2provider.test.js index 0165ce80b..2c3585b94 100644 --- a/test/end-to-end/oauth2provider.test.js +++ b/test/end-to-end/oauth2provider.test.js @@ -26,8 +26,8 @@ import { backendBeforeEach, waitForUrl, createOAuth2Client, - setOAuth2ClientIdInStorage, - removeOAuth2ClientIdFromStorage, + setOAuth2ClientInfo, + removeOAuth2ClientInfo, getOAuth2LoginButton, getOAuth2LogoutButton, getOAuth2TokenData, @@ -36,10 +36,16 @@ import { signUp, getDefaultSignUpFieldValues, getTestEmail, + getOAuth2Error, } from "../helpers"; import fetch from "isomorphic-fetch"; -import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL, SIGN_OUT_API } from "../constants"; +import { + TEST_CLIENT_BASE_URL, + TEST_SERVER_BASE_URL, + SIGN_OUT_API, + TEST_APPLICATION_SERVER_BASE_URL, +} from "../constants"; // We do no thave to use a separate domain for the oauth2 client, since the way we are testing // the lib doesn't interact with the supertokens session handling. @@ -65,6 +71,12 @@ describe("SuperTokens OAuth2Provider", function () { await fetch(`${TEST_SERVER_BASE_URL}/startst`, { method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + coreConfig: { + access_token_validity: 5, // 5 seconds + }, + }), }).catch(console.error); browser = await puppeteer.launch({ @@ -105,13 +117,13 @@ describe("SuperTokens OAuth2Provider", function () { describe("Generic OAuth2 Client Library", function () { afterEach(async function () { - await removeOAuth2ClientIdFromStorage(page); + await removeOAuth2ClientInfo(page); }); it("should successfully complete the OAuth2 flow", async function () { const { client } = await createOAuth2Client({ scope: "offline_access profile openid email", - redirectUris: [`${TEST_CLIENT_BASE_URL}/oauth2/callback`], + redirectUris: [`${TEST_CLIENT_BASE_URL}/oauth/callback`], accessTokenStrategy: "jwt", tokenEndpointAuthMethod: "none", grantTypes: ["authorization_code", "refresh_token"], @@ -119,9 +131,12 @@ describe("SuperTokens OAuth2Provider", function () { skipConsent: true, }); - await setOAuth2ClientIdInStorage(page, client.clientId); + await setOAuth2ClientInfo(page, client.clientId); - await page.goto(`${TEST_CLIENT_BASE_URL}/oauth2/login`); + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(`${TEST_CLIENT_BASE_URL}/oauth/login`), + ]); let loginButton = await getOAuth2LoginButton(page); await loginButton.click(); @@ -132,7 +147,7 @@ describe("SuperTokens OAuth2Provider", function () { const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: getTestEmail() }); await signUp(page, fieldValues, postValues, "emailpassword"); - await waitForUrl(page, "/oauth2/callback"); + await waitForUrl(page, "/oauth/callback"); // Validate token data const tokenData = await getOAuth2TokenData(page); @@ -142,15 +157,320 @@ describe("SuperTokens OAuth2Provider", function () { const logoutButton = await getOAuth2LogoutButton(page); await logoutButton.click(); + await waitFor(1000); + + await page.waitForSelector("#oauth2-token-data", { hidden: true }); + // Ensure the Login Button is visible after logout is clicked loginButton = await getOAuth2LoginButton(page); - assert.ok(loginButton !== null); + await loginButton.click(); + await waitForUrl(page, "/oauth/callback"); + }); + + it("should login without interaction if the user already has a session", async function () { + const { client } = await createOAuth2Client({ + scope: "offline_access profile openid email", + redirectUris: [`${TEST_CLIENT_BASE_URL}/oauth/callback`], + accessTokenStrategy: "jwt", + tokenEndpointAuthMethod: "none", + grantTypes: ["authorization_code", "refresh_token"], + responseTypes: ["code", "id_token"], + skipConsent: true, + }); + + await setOAuth2ClientInfo(page, client.clientId); + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(`${TEST_CLIENT_BASE_URL}/auth`), + ]); + await toggleSignInSignUp(page); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: getTestEmail() }); + await signUp(page, fieldValues, postValues, "emailpassword"); + + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(`${TEST_CLIENT_BASE_URL}/oauth/login`), + ]); + + let loginButton = await getOAuth2LoginButton(page); + await loginButton.click(); + + await waitForUrl(page, "/oauth/callback"); + + // Validate token data + const tokenData = await getOAuth2TokenData(page); + assert.deepStrictEqual(tokenData.aud, [client.clientId]); + }); + + it("should require logging in again with prompt=login", async function () { + const { client } = await createOAuth2Client({ + scope: "offline_access profile openid email", + redirectUris: [`${TEST_CLIENT_BASE_URL}/oauth/callback`], + accessTokenStrategy: "jwt", + tokenEndpointAuthMethod: "none", + grantTypes: ["authorization_code", "refresh_token"], + responseTypes: ["code", "id_token"], + skipConsent: true, + }); + + await setOAuth2ClientInfo(page, client.clientId); + + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(`${TEST_CLIENT_BASE_URL}/oauth/login`), + ]); + + let loginButton = await getOAuth2LoginButton(page, "prompt-login"); + await loginButton.click(); + + await waitForUrl(page, "/auth"); + + await toggleSignInSignUp(page); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: getTestEmail() }); + await signUp(page, fieldValues, postValues, "emailpassword"); + + await waitForUrl(page, "/oauth/callback"); + + // Validate token data + const tokenData = await getOAuth2TokenData(page); + assert.deepStrictEqual(tokenData.aud, [client.clientId]); + + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(`${TEST_CLIENT_BASE_URL}/oauth/login`), + ]); + + loginButton = await getOAuth2LoginButton(page, "prompt-login"); + await loginButton.click(); + await waitForUrl(page, "/auth"); + }); + + it("should require logging in again with max_age=3 after 3 seconds", async function () { + const { client } = await createOAuth2Client({ + scope: "offline_access profile openid email", + redirectUris: [`${TEST_CLIENT_BASE_URL}/oauth/callback`], + accessTokenStrategy: "jwt", + tokenEndpointAuthMethod: "none", + grantTypes: ["authorization_code", "refresh_token"], + responseTypes: ["code", "id_token"], + skipConsent: true, + }); + + await setOAuth2ClientInfo(page, client.clientId); + + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(`${TEST_CLIENT_BASE_URL}/oauth/login`), + ]); + + let loginButton = await getOAuth2LoginButton(page, "max-age-3"); + await loginButton.click(); + + await waitForUrl(page, "/auth"); + + await toggleSignInSignUp(page); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: getTestEmail() }); + await signUp(page, fieldValues, postValues, "emailpassword"); + + await waitForUrl(page, "/oauth/callback"); + + // Validate token data + const tokenData = await getOAuth2TokenData(page); + assert.deepStrictEqual(tokenData.aud, [client.clientId]); + + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(`${TEST_CLIENT_BASE_URL}/oauth/login`), + ]); + + loginButton = await getOAuth2LoginButton(page, "max-age-3"); + await loginButton.click(); + await waitForUrl(page, "/oauth/callback"); + + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(`${TEST_CLIENT_BASE_URL}/oauth/login`), + ]); + + await waitFor(3000); + loginButton = await getOAuth2LoginButton(page, "max-age-3"); + await loginButton.click(); + await waitForUrl(page, "/auth"); }); it("should successfully refresh the tokens after expiry", async function () { const { client } = await createOAuth2Client({ scope: "offline_access profile openid email", - redirectUris: [`${TEST_CLIENT_BASE_URL}/oauth2/callback`], + redirectUris: [`${TEST_CLIENT_BASE_URL}/oauth/callback`], + accessTokenStrategy: "jwt", + tokenEndpointAuthMethod: "none", + grantTypes: ["authorization_code", "refresh_token"], + responseTypes: ["code", "id_token"], + skipConsent: true, + // The library refreshes the token 60 seconds before it expires. + // We set the token lifespan to 63 seconds to force a refresh in 3 seconds. + authorizationCodeGrantAccessTokenLifespan: "63s", + }); + + await setOAuth2ClientInfo(page, client.clientId); + + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(`${TEST_CLIENT_BASE_URL}/oauth/login`), + ]); + + let loginButton = await getOAuth2LoginButton(page); + await loginButton.click(); + + await waitForUrl(page, "/auth"); + + await toggleSignInSignUp(page); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: getTestEmail() }); + await signUp(page, fieldValues, postValues, "emailpassword"); + + await waitForUrl(page, "/oauth/callback"); + + // Validate token data + const tokenDataAfterLogin = await getOAuth2TokenData(page); + assert.deepStrictEqual(tokenDataAfterLogin.aud, [client.clientId]); + + // Although the react-oidc-context library automatically refreshes the + // token, we wait for 4 seconds and reload the page to ensure a refresh. + await waitFor(4000); + await Promise.all([page.reload(), page.waitForNavigation({ waitUntil: "networkidle0" })]); + + const tokenDataAfterRefresh = await getOAuth2TokenData(page); + assert.deepStrictEqual(tokenDataAfterRefresh.aud, [client.clientId]); + + // Validate the token was refreshed + assert(tokenDataAfterLogin.iat !== tokenDataAfterRefresh.iat); + assert(tokenDataAfterLogin.exp < tokenDataAfterRefresh.exp); + }); + + it("should have roles in the id_token if the scopes is requested", async function () { + const { client } = await createOAuth2Client({ + scope: "offline_access profile openid email roles permissions", + redirectUris: [`${TEST_CLIENT_BASE_URL}/oauth/callback`], + accessTokenStrategy: "jwt", + tokenEndpointAuthMethod: "none", + grantTypes: ["authorization_code", "refresh_token"], + responseTypes: ["code", "id_token"], + skipConsent: true, + }); + + await setOAuth2ClientInfo(page, client.clientId, "offline_access profile openid email roles permissions"); + + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(`${TEST_CLIENT_BASE_URL}/oauth/login`), + ]); + + let loginButton = await getOAuth2LoginButton(page); + await loginButton.click(); + + await waitForUrl(page, "/auth"); + + await toggleSignInSignUp(page); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: getTestEmail() }); + await signUp(page, fieldValues, postValues, "emailpassword"); + + await waitForUrl(page, "/oauth/callback"); + + // Validate token data + const tokenDataAfterLogin = await getOAuth2TokenData(page); + assert.deepStrictEqual(tokenDataAfterLogin.aud, [client.clientId]); + + await page.evaluate( + (url) => + window.fetch(url, { + method: "POST", + headers: [["content-type", "application/json"]], + body: JSON.stringify({ + role: "testRole", + permissions: ["testPerm"], + }), + }), + `${TEST_APPLICATION_SERVER_BASE_URL}/setRole` + ); + + await waitFor(2000); + loginButton = await getOAuth2LoginButton(page, "silent"); + await loginButton.click(); + + await waitFor(1000); + + const tokenDataAfterRefresh = await getOAuth2TokenData(page); + assert.deepStrictEqual(tokenDataAfterRefresh.aud, [client.clientId]); + + // Validate the token was refreshed + assert(tokenDataAfterLogin.iat !== tokenDataAfterRefresh.iat); + assert(tokenDataAfterLogin.exp < tokenDataAfterRefresh.exp); + + assert.deepStrictEqual(tokenDataAfterLogin.roles, []); + assert.deepStrictEqual(tokenDataAfterLogin.permissions, []); + + assert.deepStrictEqual(tokenDataAfterRefresh.roles, ["testRole"]); + assert.deepStrictEqual(tokenDataAfterRefresh.permissions, ["testPerm"]); + }); + + it("should not include email info if the scope is not requested", async function () { + const { client } = await createOAuth2Client({ + scope: "offline_access profile openid roles permissions", + redirectUris: [`${TEST_CLIENT_BASE_URL}/oauth/callback`], + accessTokenStrategy: "jwt", + tokenEndpointAuthMethod: "none", + grantTypes: ["authorization_code", "refresh_token"], + responseTypes: ["code", "id_token"], + skipConsent: true, + // The library refreshes the token 60 seconds before it expires. + // We set the token lifespan to 63 seconds to force a refresh in 3 seconds. + authorizationCodeGrantAccessTokenLifespan: "63s", + }); + + await setOAuth2ClientInfo(page, client.clientId, "offline_access profile openid roles permissions"); + + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(`${TEST_CLIENT_BASE_URL}/oauth/login`), + ]); + + let loginButton = await getOAuth2LoginButton(page); + await loginButton.click(); + + await waitForUrl(page, "/auth"); + + await toggleSignInSignUp(page); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: getTestEmail() }); + await signUp(page, fieldValues, postValues, "emailpassword"); + + await waitForUrl(page, "/oauth/callback"); + + // Validate token data + const tokenDataAfterLogin = await getOAuth2TokenData(page); + assert.deepStrictEqual(tokenDataAfterLogin.aud, [client.clientId]); + + // Although the react-oidc-context library automatically refreshes the + // token, we wait for 4 seconds and reload the page to ensure a refresh. + await waitFor(4000); + await Promise.all([page.reload(), page.waitForNavigation({ waitUntil: "networkidle0" })]); + + const tokenDataAfterRefresh = await getOAuth2TokenData(page); + assert.deepStrictEqual(tokenDataAfterRefresh.aud, [client.clientId]); + + // Validate the token was refreshed + assert(tokenDataAfterLogin.iat !== tokenDataAfterRefresh.iat); + assert(tokenDataAfterLogin.exp < tokenDataAfterRefresh.exp); + + assert.strictEqual(tokenDataAfterLogin.email, undefined); + assert.strictEqual(tokenDataAfterLogin.email_verified, undefined); + assert.strictEqual(tokenDataAfterRefresh.email, undefined); + assert.strictEqual(tokenDataAfterRefresh.email_verified, undefined); + }); + + it("should work if the scope phoneNumber is requested for emailpassword user", async function () { + const { client } = await createOAuth2Client({ + scope: "offline_access profile openid phoneNumber", + redirectUris: [`${TEST_CLIENT_BASE_URL}/oauth/callback`], accessTokenStrategy: "jwt", tokenEndpointAuthMethod: "none", grantTypes: ["authorization_code", "refresh_token"], @@ -161,9 +481,12 @@ describe("SuperTokens OAuth2Provider", function () { authorizationCodeGrantAccessTokenLifespan: "63s", }); - await setOAuth2ClientIdInStorage(page, client.clientId); + await setOAuth2ClientInfo(page, client.clientId, "offline_access profile openid phoneNumber"); - await page.goto(`${TEST_CLIENT_BASE_URL}/oauth2/login`); + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(`${TEST_CLIENT_BASE_URL}/oauth/login`), + ]); let loginButton = await getOAuth2LoginButton(page); await loginButton.click(); @@ -174,7 +497,7 @@ describe("SuperTokens OAuth2Provider", function () { const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: getTestEmail() }); await signUp(page, fieldValues, postValues, "emailpassword"); - await waitForUrl(page, "/oauth2/callback"); + await waitForUrl(page, "/oauth/callback"); // Validate token data const tokenDataAfterLogin = await getOAuth2TokenData(page); @@ -183,8 +506,95 @@ describe("SuperTokens OAuth2Provider", function () { // Although the react-oidc-context library automatically refreshes the // token, we wait for 4 seconds and reload the page to ensure a refresh. await waitFor(4000); - await page.reload(); - await page.waitForNavigation({ waitUntil: "networkidle0" }); + await Promise.all([page.reload(), page.waitForNavigation({ waitUntil: "networkidle0" })]); + + const tokenDataAfterRefresh = await getOAuth2TokenData(page); + assert.deepStrictEqual(tokenDataAfterRefresh.aud, [client.clientId]); + + // Validate the token was refreshed + assert(tokenDataAfterLogin.iat !== tokenDataAfterRefresh.iat); + assert(tokenDataAfterLogin.exp < tokenDataAfterRefresh.exp); + + assert.strictEqual(tokenDataAfterLogin.email, undefined); + assert.strictEqual(tokenDataAfterLogin.email_verified, undefined); + assert.strictEqual(tokenDataAfterRefresh.email, undefined); + assert.strictEqual(tokenDataAfterRefresh.email_verified, undefined); + assert.strictEqual(tokenDataAfterLogin.phoneNumber, undefined); + assert.notStrictEqual(tokenDataAfterLogin.phoneNumber_verified, undefined); + assert.strictEqual(tokenDataAfterRefresh.phoneNumber, undefined); + assert.notStrictEqual(tokenDataAfterRefresh.phoneNumber_verified, undefined); + }); + + it("should reject the login if the wrong scope is requested", async function () { + const { client } = await createOAuth2Client({ + scope: "offline_access profile openid", + redirectUris: [`${TEST_CLIENT_BASE_URL}/oauth/callback`], + accessTokenStrategy: "jwt", + tokenEndpointAuthMethod: "none", + grantTypes: ["authorization_code", "refresh_token"], + responseTypes: ["code", "id_token"], + skipConsent: true, + // The library refreshes the token 60 seconds before it expires. + // We set the token lifespan to 63 seconds to force a refresh in 3 seconds. + authorizationCodeGrantAccessTokenLifespan: "63s", + }); + + await setOAuth2ClientInfo(page, client.clientId, "offline_access profile openid roles permissions"); + + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(`${TEST_CLIENT_BASE_URL}/oauth/login`), + ]); + + let loginButton = await getOAuth2LoginButton(page); + await loginButton.click(); + + assert.strictEqual( + await getOAuth2Error(page), + `Error: The requested scope is invalid, unknown, or malformed. The OAuth 2.0 Client is not allowed to request scope 'roles'.` + ); + }); + + it("should work even if the supertokens session is expired", async function () { + const { client } = await createOAuth2Client({ + scope: "offline_access profile openid email", + redirectUris: [`${TEST_CLIENT_BASE_URL}/oauth/callback`], + accessTokenStrategy: "jwt", + tokenEndpointAuthMethod: "none", + grantTypes: ["authorization_code", "refresh_token"], + responseTypes: ["code", "id_token"], + skipConsent: true, + // The library refreshes the token 60 seconds before it expires. + // We set the token lifespan to 63 seconds to force a refresh in 3 seconds. + authorizationCodeGrantAccessTokenLifespan: "63s", + }); + + await setOAuth2ClientInfo(page, client.clientId); + + await toggleSignInSignUp(page); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: getTestEmail() }); + await signUp(page, fieldValues, postValues, "emailpassword"); + + await waitFor(6000); + + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(`${TEST_CLIENT_BASE_URL}/oauth/login`), + ]); + + let loginButton = await getOAuth2LoginButton(page); + await loginButton.click(); + + await waitForUrl(page, "/oauth/callback"); + + // Validate token data + const tokenDataAfterLogin = await getOAuth2TokenData(page); + assert.deepStrictEqual(tokenDataAfterLogin.aud, [client.clientId]); + + // Although the react-oidc-context library automatically refreshes the + // token, we wait for 6 seconds and reload the page to ensure a refresh. + await waitFor(6000); + await Promise.all([page.reload(), page.waitForNavigation({ waitUntil: "networkidle0" })]); const tokenDataAfterRefresh = await getOAuth2TokenData(page); assert.deepStrictEqual(tokenDataAfterRefresh.aud, [client.clientId]); diff --git a/test/end-to-end/signin.test.js b/test/end-to-end/signin.test.js index 876fd83bd..883218ea3 100644 --- a/test/end-to-end/signin.test.js +++ b/test/end-to-end/signin.test.js @@ -482,7 +482,7 @@ describe("SuperTokens SignIn", function () { await assertSignInRedirectTo( page, `${TEST_CLIENT_BASE_URL}/auth?rid=emailpassword&redirectToPath=%2Fredirect-here`, - `${TEST_CLIENT_BASE_URL}/redirect-here` + `/redirect-here` ); // test that if we visit auth again, we end up in redirect-heree again with query params kept @@ -492,15 +492,14 @@ describe("SuperTokens SignIn", function () { ), page.waitForNavigation({ waitUntil: "networkidle0" }), ]); - const { pathname, search } = await page.evaluate(() => window.location); - assert.deepStrictEqual(pathname + search, "/redirect-heree?foo=bar"); + await waitForUrl(page, "/redirect-heree?foo=bar", false); }); it("Successful Sign In with redirect to, redirectToPath directly without trailing slash", async function () { await assertSignInRedirectTo( page, `${TEST_CLIENT_BASE_URL}/auth?rid=emailpassword&redirectToPath=redirect-here`, - `${TEST_CLIENT_BASE_URL}/redirect-here` + `/redirect-here` ); }); @@ -531,7 +530,7 @@ describe("SuperTokens SignIn", function () { await assertSignInRedirectTo( page, `${TEST_CLIENT_BASE_URL}/auth?rid=emailpassword&redirectToPath=https://attacker.com/path`, - `${TEST_CLIENT_BASE_URL}/path` + `/path` ); }); @@ -540,7 +539,7 @@ describe("SuperTokens SignIn", function () { await assertSignInRedirectTo( page, `${TEST_CLIENT_BASE_URL}/auth?rid=emailpassword&redirectToPath=javascript:alert(1)`, - `${TEST_CLIENT_BASE_URL}/javascript:alert(1)` + `/javascript:alert(1)` ); }); @@ -1054,11 +1053,5 @@ async function assertSignInRedirectTo(page, startUrl, finalUrl) { ]); // Submit. - await Promise.all([ - submitFormReturnRequestAndResponse(page, SIGN_IN_API), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - - let href = await page.evaluate(() => window.location.href); - assert.deepStrictEqual(href, finalUrl); + await Promise.all([submitFormReturnRequestAndResponse(page, SIGN_IN_API), waitForUrl(page, finalUrl, false)]); } diff --git a/test/helpers.js b/test/helpers.js index efe97ed4d..672718e9b 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -1117,16 +1117,41 @@ export async function createOAuth2Client(input) { // For the OAuth2 end-to-end test, we need to provide the created clientId to both the OAuth2 login and callback pages. // We use localStorage to store the clientId instead of query params, as it must be available on the callback page as well. -export async function setOAuth2ClientIdInStorage(page, clientId) { - return page.evaluate((clientId) => localStorage.setItem("oauth2-client-id", clientId), clientId); +export async function setOAuth2ClientInfo(page, clientId, scopes, extraConfig, extraSignInParams, extraSignOutParams) { + await page.evaluate((clientId) => localStorage.setItem("oauth2-client-id", clientId), clientId); + if (scopes) { + await page.evaluate((scopes) => localStorage.setItem("oauth2-scopes", scopes), scopes); + } + if (extraConfig) { + await page.evaluate((extraConfig) => localStorage.setItem("oauth2-extra-config", extraConfig), extraConfig); + } + if (extraSignInParams) { + await page.evaluate( + (extraSignInParams) => localStorage.setItem("oauth2-extra-sign-in-params", extraSignInParams), + extraSignInParams + ); + } + if (extraSignOutParams) { + await page.evaluate( + (extraSignOutParams) => localStorage.setItem("oauth2-extra-sign-out-params", extraSignOutParams), + extraSignOutParams + ); + } +} + +export async function removeOAuth2ClientInfo(page) { + await page.evaluate(() => localStorage.removeItem("oauth2-client-id")); + await page.evaluate(() => localStorage.removeItem("oauth2-scopes")); } -export async function removeOAuth2ClientIdFromStorage(page) { - return page.evaluate(() => localStorage.removeItem("oauth2-client-id")); +export async function getOAuth2LoginButton(page, type = "default") { + const id = type === "default" ? "#oauth2-login-button" : `#oauth2-login-button-${type}`; + return page.waitForSelector(id); } -export async function getOAuth2LoginButton(page) { - return page.waitForSelector("#oauth2-login-button"); +export async function getOAuth2Error(page) { + const ele = await page.waitForSelector("#oauth2-error-message"); + return await ele.evaluate((el) => el.textContent); } export async function getOAuth2LogoutButton(page) { diff --git a/test/server/package-lock.json b/test/server/package-lock.json index 80f265cec..6cf95278b 100644 --- a/test/server/package-lock.json +++ b/test/server/package-lock.json @@ -16,7 +16,7 @@ "express": "4.17.1", "morgan": "^1.10.0", "otpauth": "^9.2.0", - "supertokens-node": "18.0" + "supertokens-node": "github:supertokens/supertokens-node#feat/oauth2/base" } }, "node_modules/accepts": { @@ -995,6 +995,11 @@ "node": ">= 0.8.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz", + "integrity": "sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ==" + }, "node_modules/set-function-length": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", @@ -1041,9 +1046,8 @@ "integrity": "sha512-r0JFBjkMIdep3Lbk3JA+MpnpuOtw4RSyrlRAbrzMcxwiYco3GFWl/daimQZ5b1forOiUODpOlXbSOljP/oyurg==" }, "node_modules/supertokens-node": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/supertokens-node/-/supertokens-node-18.0.0.tgz", - "integrity": "sha512-NPFPShZu44EI3jJK0qW8Wm8GfNhM4xUOWFJ/1EvNW1pzVN0EmJkIzqlmzeBExg5oc03fZXKS4YnUTt6PAnlD1w==", + "version": "20.0.2", + "resolved": "git+ssh://git@github.com/supertokens/supertokens-node.git#905b5cd6e9babe50488318e32295a353523b8069", "dependencies": { "content-type": "^1.0.5", "cookie": "0.4.0", @@ -1055,6 +1059,7 @@ "nodemailer": "^6.7.2", "pkce-challenge": "^3.0.0", "psl": "1.8.0", + "set-cookie-parser": "^2.6.0", "supertokens-js-override": "^0.0.4", "twilio": "^4.19.3" } @@ -1976,6 +1981,11 @@ "send": "0.17.1" } }, + "set-cookie-parser": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz", + "integrity": "sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ==" + }, "set-function-length": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", @@ -2013,9 +2023,8 @@ "integrity": "sha512-r0JFBjkMIdep3Lbk3JA+MpnpuOtw4RSyrlRAbrzMcxwiYco3GFWl/daimQZ5b1forOiUODpOlXbSOljP/oyurg==" }, "supertokens-node": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/supertokens-node/-/supertokens-node-18.0.0.tgz", - "integrity": "sha512-NPFPShZu44EI3jJK0qW8Wm8GfNhM4xUOWFJ/1EvNW1pzVN0EmJkIzqlmzeBExg5oc03fZXKS4YnUTt6PAnlD1w==", + "version": "git+ssh://git@github.com/supertokens/supertokens-node.git#905b5cd6e9babe50488318e32295a353523b8069", + "from": "supertokens-node@github:supertokens/supertokens-node#feat/oauth2/base", "requires": { "content-type": "^1.0.5", "cookie": "0.4.0", @@ -2027,6 +2036,7 @@ "nodemailer": "^6.7.2", "pkce-challenge": "^3.0.0", "psl": "1.8.0", + "set-cookie-parser": "^2.6.0", "supertokens-js-override": "^0.0.4", "twilio": "^4.19.3" }, diff --git a/test/server/package.json b/test/server/package.json index 0fdaf93f1..a36e4efd1 100644 --- a/test/server/package.json +++ b/test/server/package.json @@ -16,6 +16,6 @@ "express": "4.17.1", "morgan": "^1.10.0", "otpauth": "^9.2.0", - "supertokens-node": "18.0" + "supertokens-node": "github:supertokens/supertokens-node#feat/oauth2/base" } } diff --git a/test/with-typescript/src/App.tsx b/test/with-typescript/src/App.tsx index 64ae7db8e..6cfc6b85d 100644 --- a/test/with-typescript/src/App.tsx +++ b/test/with-typescript/src/App.tsx @@ -281,6 +281,9 @@ function getRecipeList() { } return context; }, + postAPIHook: async (context) => { + context.userContext; + }, override: { functions: (oI) => { return {