-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: split up mfa tests into multiple files
- Loading branch information
Showing
9 changed files
with
1,833 additions
and
2,919 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,298 @@ | ||
/* 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. | ||
*/ | ||
|
||
/* | ||
* Imports | ||
*/ | ||
|
||
import assert from "assert"; | ||
import puppeteer from "puppeteer"; | ||
import { | ||
clearBrowserCookiesWithoutAffectingConsole, | ||
setInputValues, | ||
submitForm, | ||
waitForSTElement, | ||
screenshotOnFailure, | ||
backendBeforeEach, | ||
getTestEmail, | ||
getPasswordlessDevice, | ||
waitFor, | ||
getFactorChooserOptions, | ||
} from "../helpers"; | ||
import fetch from "isomorphic-fetch"; | ||
import { CREATE_CODE_API, CREATE_TOTP_DEVICE_API, MFA_INFO_API } from "../constants"; | ||
|
||
import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL } from "../constants"; | ||
import { getTestPhoneNumber } from "../exampleTestHelpers"; | ||
import { | ||
setMFAInfo, | ||
tryEmailPasswordSignUp, | ||
waitForDashboard, | ||
completeOTP, | ||
setupOTP, | ||
logout, | ||
tryEmailPasswordSignIn, | ||
chooseFactor, | ||
tryPasswordlessSignInUp, | ||
setupTOTP, | ||
completeTOTP, | ||
setupUserWithAllFactors, | ||
goToFactorChooser, | ||
waitForAccessDenied, | ||
expectErrorThrown, | ||
waitForLoadingScreen, | ||
waitForBlockedScreen, | ||
} from "./mfa.helpers"; | ||
|
||
/* | ||
* Tests. | ||
*/ | ||
describe("SuperTokens SignIn w/ MFA", function () { | ||
let browser; | ||
let page; | ||
let consoleLogs = []; | ||
|
||
before(async function () { | ||
await backendBeforeEach(); | ||
|
||
await fetch(`${TEST_SERVER_BASE_URL}/startst`, { | ||
method: "POST", | ||
}).catch(console.error); | ||
|
||
browser = await puppeteer.launch({ | ||
args: ["--no-sandbox", "--disable-setuid-sandbox"], | ||
headless: true, | ||
}); | ||
}); | ||
|
||
after(async function () { | ||
await browser.close(); | ||
|
||
await fetch(`${TEST_SERVER_BASE_URL}/after`, { | ||
method: "POST", | ||
}).catch(console.error); | ||
|
||
await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { | ||
method: "POST", | ||
}).catch(console.error); | ||
}); | ||
|
||
afterEach(async function () { | ||
await screenshotOnFailure(this, browser); | ||
if (page) { | ||
await page.close(); | ||
} | ||
}); | ||
|
||
beforeEach(async function () { | ||
page = await browser.newPage(); | ||
page.on("console", (consoleObj) => { | ||
const log = consoleObj.text(); | ||
// console.log(log); | ||
if (log.startsWith("ST_LOGS")) { | ||
consoleLogs.push(log); | ||
} | ||
}); | ||
consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, []); | ||
|
||
await page.evaluate(() => window.localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); | ||
await page.evaluate(() => window.localStorage.removeItem("clientRecipeListForDynamicLogin")); | ||
await page.evaluate(() => window.localStorage.removeItem("mode")); | ||
await page.evaluate(() => window.localStorage.setItem("enableAllRecipes", "true")); | ||
}); | ||
|
||
describe("chooser screen", () => { | ||
let email, phoneNumber; | ||
let totpSecret; | ||
before(async () => { | ||
page = await browser.newPage(); | ||
({ email, phoneNumber, totpSecret } = await setupUserWithAllFactors(page)); | ||
await page.close(); | ||
}); | ||
|
||
it("should redirect to the factor screen during sign in if only one factor is available (limited by FE recipe inits)", async () => { | ||
await page.evaluate(() => { | ||
window.localStorage.setItem("enableAllRecipes", "false"); | ||
window.localStorage.setItem("clientRecipeListForDynamicLogin", JSON.stringify(["emailpassword"])); | ||
}); | ||
|
||
await setMFAInfo({ | ||
requirements: [{ oneOf: ["otp-email", "otp-phone", "totp"] }], | ||
hasTOTP: true, | ||
}); | ||
|
||
await tryEmailPasswordSignIn(page, email); | ||
|
||
await completeTOTP(page, totpSecret); | ||
await waitForDashboard(page); | ||
}); | ||
|
||
it("should redirect to the factor screen during sign in if only one factor is available (limited by isAlreadySetup/isAllowedToSetup)", async () => { | ||
await page.evaluate(() => { | ||
window.localStorage.setItem("enableAllRecipes", "false"); | ||
window.localStorage.setItem("clientRecipeListForDynamicLogin", JSON.stringify(["emailpassword"])); | ||
}); | ||
|
||
await setMFAInfo({ | ||
requirements: [{ oneOf: ["otp-email", "otp-phone", "totp"] }], | ||
hasTOTP: true, | ||
isAlreadySetup: ["totp"], | ||
isAllowedToSetup: [], | ||
}); | ||
|
||
await tryEmailPasswordSignIn(page, email); | ||
|
||
await completeTOTP(page, totpSecret); | ||
await waitForDashboard(page); | ||
}); | ||
it("should redirect to the factor screen during sign in if only one factor is available (limited by next array)", async () => { | ||
await setMFAInfo({ | ||
requirements: [{ oneOf: ["totp"] }], | ||
hasTOTP: true, | ||
}); | ||
|
||
await tryEmailPasswordSignIn(page, email); | ||
|
||
await completeTOTP(page, totpSecret); | ||
await waitForDashboard(page); | ||
}); | ||
|
||
it("should show all factors the user can complete or set up in the next array", async () => { | ||
await setMFAInfo({ | ||
requirements: [{ oneOf: ["totp", "otp-email"] }], | ||
hasTOTP: true, | ||
}); | ||
|
||
await tryEmailPasswordSignIn(page, email); | ||
|
||
const options = await getFactorChooserOptions(page); | ||
assert.deepStrictEqual(new Set(options), new Set(["otp-email", "totp"])); | ||
}); | ||
|
||
it("should show all factors the user can complete or set up if the next array is empty", async () => { | ||
await setMFAInfo({ | ||
requirements: [], | ||
isAlreadySetup: ["otp-phone", "otp-email"], | ||
isAllowedToSetup: ["totp"], | ||
}); | ||
|
||
await tryEmailPasswordSignIn(page, email); | ||
await goToFactorChooser(page); | ||
|
||
const optionsAfter2FA = await getFactorChooserOptions(page); | ||
assert.deepStrictEqual(new Set(optionsAfter2FA), new Set(["otp-phone", "otp-email", "totp"])); | ||
}); | ||
|
||
it("should show access denied if there are no available options during sign in", async () => { | ||
await setMFAInfo({ | ||
requirements: ["otp-phone"], | ||
isAlreadySetup: ["otp-email"], | ||
isAllowedToSetup: [], | ||
}); | ||
|
||
await tryEmailPasswordSignIn(page, email); | ||
await waitForAccessDenied(page); | ||
}); | ||
|
||
it("should show access denied if there are no available options after sign in", async () => { | ||
await setMFAInfo({ | ||
requirements: [], | ||
isAlreadySetup: [], | ||
isAllowedToSetup: [], | ||
}); | ||
|
||
await tryEmailPasswordSignIn(page, email); | ||
await goToFactorChooser(page, false); | ||
|
||
await waitForAccessDenied(page); | ||
}); | ||
|
||
it("should show throw if the only next option is an unknown factor id", async () => { | ||
await setMFAInfo({ | ||
requirements: ["unknown"], | ||
}); | ||
|
||
await expectErrorThrown(page, () => tryEmailPasswordSignIn(page, email)); | ||
}); | ||
|
||
it("should show a back link only if visited after sign in", async () => { | ||
await setMFAInfo({ | ||
requirements: [{ oneOf: ["otp-email", "otp-phone"] }], | ||
}); | ||
|
||
await tryEmailPasswordSignIn(page, email); | ||
await waitForSTElement(page, "[data-supertokens~=factorChooserList]"); | ||
|
||
await waitForSTElement(page, "[data-supertokens~=backButton]", true); | ||
await chooseFactor(page, "otp-phone"); | ||
await completeOTP(page); | ||
|
||
await goToFactorChooser(page); | ||
|
||
await waitForSTElement(page, "[data-supertokens~=backButton]"); | ||
}); | ||
|
||
it("should show a logout link", async () => { | ||
await setMFAInfo({ | ||
requirements: [{ oneOf: ["otp-email", "otp-phone"] }], | ||
}); | ||
|
||
await tryEmailPasswordSignIn(page, email); | ||
await waitForSTElement(page, "[data-supertokens~=factorChooserList]"); | ||
|
||
await waitForSTElement(page, "[data-supertokens~=secondaryLinkWithLeftArrow]"); | ||
await chooseFactor(page, "otp-phone"); | ||
await completeOTP(page); | ||
|
||
await goToFactorChooser(page); | ||
|
||
await waitForSTElement(page, "[data-supertokens~=secondaryLinkWithLeftArrow]"); | ||
}); | ||
|
||
it("should handle MFA info API failures gracefully", async () => { | ||
await setMFAInfo({ | ||
requirements: [], | ||
isAlreadySetup: ["otp-phone", "otp-email"], | ||
isAllowedToSetup: [], | ||
}); | ||
|
||
await page.setRequestInterception(true); | ||
const requestHandler = (request) => { | ||
if (request.url() === MFA_INFO_API && request.method() === "GET") { | ||
return request.respond({ | ||
status: 400, | ||
headers: { | ||
"access-control-allow-origin": TEST_CLIENT_BASE_URL, | ||
"access-control-allow-credentials": "true", | ||
}, | ||
body: JSON.stringify({ | ||
status: "BAD_INPUT", | ||
}), | ||
}); | ||
} | ||
|
||
return request.continue(); | ||
}; | ||
page.on("request", requestHandler); | ||
try { | ||
await tryEmailPasswordSignIn(page, email); | ||
await expectErrorThrown(page, () => goToFactorChooser(page, false)); | ||
} finally { | ||
page.off("request", requestHandler); | ||
await page.setRequestInterception(false); | ||
} | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.