Skip to content

Commit

Permalink
fix: example tests (#799)
Browse files Browse the repository at this point in the history
* chore: update dependency branch names

* chore: update size limit

* fix: expect successful sign in/ups to not always create a session

* feat: rename validatorId to id

* docs: fix with-phone-password-mfa example and update types

* chore: add validatorId migration guide to changelog

* test: update test for hash redirection change

* chore: re-install deps to update package-lock

---------

Co-authored-by: Rishabh Poddar <rishabh.poddar@gmail.com>
  • Loading branch information
porcellus and rishabhpoddar committed Mar 14, 2024
1 parent 3d0c6b9 commit 640a53d
Show file tree
Hide file tree
Showing 23 changed files with 15,257 additions and 41 deletions.
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Check our [guide](https://supertokens.com/docs/mfa/introduction) for more inform
- Removed an `ErrorBoundary` wrapping all our feature components to make sure all errors are properly catchable by the app
- In `supertokens-web-js` (which you may also be using), we added `firstFactors` into the return type of `getLoginMethods` and removed the enabled flags of different login methods.
- For older FDI versions, the firstFactors array will be calculated based on those enabled flags.
- Renamed `validatorId` in claim validation errors to `id` to match the backend SDKs

#### Migration guide

Expand Down Expand Up @@ -213,6 +214,32 @@ SuperTokens.init({
});
```

#### Renamed validatorId

If you used to use the `validatorId` prop of validationErrors, you should now use `id` instead.

Before:

```ts
async function checkValidators() {
const validationErrors = await Session.validateClaims();
for (const error of validationErrors) {
console.log(error.validatorId, error.reason);
}
}
```

After:

```ts
async function checkValidators() {
const validationErrors = await Session.validateClaims();
for (const error of validationErrors) {
console.log(error.id, error.reason);
}
}
```

### Changes

- Added support for FDI 1.19 (Node SDK>= 17.0.0), but keeping support FDI version 1.17 and 1.18 (node >= 15.0.0, golang>=0.13, python>=0.15.0)
Expand Down
10 changes: 9 additions & 1 deletion examples/with-multifactorauth-recovery-codes/backend/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,21 @@ export const SuperTokensConfig: TypeInput = {
const payload = input.session.getAccessTokenPayload();
const recoveryCodeHash = payload.recoveryCodeHash;
if (recoveryCodeHash) {
await UserMetadata.updateUserMetadata(input.session.getUserId(), {
const userId = input.session.getUserId();
await UserMetadata.updateUserMetadata(userId, {
recoveryCodeHash: null,
});
await input.session.setClaimValue(RecoveryCodeExistsClaim, false);
await input.session.mergeIntoAccessTokenPayload({
recoveryCodeHash: null,
});

const { devices } = await TOTP.listDevices(userId);
for (const dev of devices) {
if (dev.name !== input.deviceName) {
await TOTP.removeDevice(userId, dev.name);
}
}
}
}
return resp;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default function CreateRecoveryCode() {
<div onClick={createRecoveryCode} className="sessionButton createRecoveryCode">
Create recovery code
</div>
<NavLink to="/" className="sessionButton">
<NavLink to="/" className="sessionButton homeButton">
Go to Home page
</NavLink>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,10 @@
.separator-line {
max-width: 200px;
}

.app-container .main-container {
margin-block-end: 60px;
}
}

@media screen and (max-width: 480px) {
Expand All @@ -214,6 +218,6 @@
}

.app-container .main-container {
margin-block-end: 90px;
margin-block-end: 45px;
}
}
26 changes: 21 additions & 5 deletions examples/with-multifactorauth-recovery-codes/test/basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,12 @@ describe("SuperTokens Example Basic tests", function () {
const recoveryCodeDiv = await page.waitForSelector(".recovery-code", { visible: true });
const recoveryCode = await recoveryCodeDiv.evaluate((e) => e.textContent);

// Log out
await page.click("div.bottom-links-container > div:nth-child(3) > div");
await page.click(".homeButton");
await page.waitForSelector("#user-id");

let logoutLink = await page.waitForSelector("div.bottom-links-container > div:nth-child(3) > div");
await logoutLink.click();

await setInputValues(page, [
{ name: "email", value: email },
{ name: "password", value: testPW },
Expand All @@ -140,24 +144,36 @@ describe("SuperTokens Example Basic tests", function () {
const newTOTPSecret = await getTOTPSecret(page);
await completeTOTP(page, newTOTPSecret);
await page.waitForSelector(".createRecoveryCode");
await page.click(".createRecoveryCode");
await page.waitForSelector(".recovery-code", { visible: true });
await page.click(".homeButton");
await page.waitForSelector("#user-id");

logoutLink = await page.waitForSelector("div.bottom-links-container > div:nth-child(3) > div");
await logoutLink.click();

await page.click("div.bottom-links-container > div:nth-child(3) > div");
await setInputValues(page, [
{ name: "email", value: email },
{ name: "password", value: testPW },
]);
await submitForm(page);
await completeTOTP(page, newTOTPSecret, 1);
await page.waitForSelector(".createRecoveryCode");

await page.waitForSelector("#user-id");
await page.click("div.bottom-links-container > div:nth-child(3) > div");

await setInputValues(page, [
{ name: "email", value: email },
{ name: "password", value: testPW },
]);
await submitForm(page);
await completeTOTP(page, origTOTPSecret, 1);
await page.waitForSelector(".createRecoveryCode");
const errorEle = await waitForSTElement(page, "[data-supertokens~=generalError");
const errorText = await errorEle.evaluate((e) => e.innerText);
assert.strictEqual(
errorText,
"Invalid TOTP. Please try again. 5 attempt(s) remaining before account is temporarily locked."
);
});
});
});
Expand Down
1 change: 1 addition & 0 deletions examples/with-phone-password-mfa/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ This demo app uses the EmailPassword and Passwordless recipes to achieve the aut

- Change email validation logic on the backend (in emailpassword recipe) to validate phone number syntax.
- Change how password reset email is sent to instead send an SMS to the phone.
- We override consumeCodePOST to validate the phone number as if it was an email address to facilitate linking

## Future work:

Expand Down
36 changes: 34 additions & 2 deletions examples/with-phone-password-mfa/api-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ supertokens.init({
framework: "express",
supertokens: {
// TODO: This is a core hosted for demo purposes. You can use this, but make sure to change it to your core instance URI eventually.
connectionURI: "https://try.supertokens.com",
connectionURI: "http://localhost:3567",
apiKey: "<REQUIRED FOR MANAGED SERVICE, ELSE YOU CAN REMOVE THIS FIELD>",
},
appInfo: {
Expand Down Expand Up @@ -107,6 +107,38 @@ supertokens.init({
Passwordless.init({
contactMethod: "PHONE",
flowType: "USER_INPUT_CODE",
override: {
apis: (oI) => ({
...oI,
async consumeCodePOST(input) {
// Here we try to verify the phone number as an email address before
if (input.session !== undefined) {
if (!("userInputCode" in input)) {
throw new Error("We only enabled OTP");
}
const checkRes = await Passwordless.checkCode({
deviceId: input.deviceId,
userInputCode: input.userInputCode,
preAuthSessionId: input.preAuthSessionId,
tenantId: input.tenantId,
userContext: input.userContext,
});

if (checkRes.status === "OK" && checkRes.consumedDevice.phoneNumber !== undefined) {
const tokenRes = await EmailVerification.createEmailVerificationToken(
"public",
input.session.getRecipeUserId(),
checkRes.consumedDevice.phoneNumber
);
if (tokenRes.status === "OK") {
await EmailVerification.verifyEmailUsingToken("public", tokenRes.token, false);
}
}
}
return oI.consumeCodePOST!(input);
},
}),
},
}),
Session.init(),
MultiFactorAuth.init({
Expand Down Expand Up @@ -146,7 +178,7 @@ supertokens.init({
}),
}),
EmailVerification.init({
mode: "REQUIRED",
mode: "OPTIONAL",
}),
Dashboard.init(),
],
Expand Down
2 changes: 1 addition & 1 deletion examples/with-phone-password-mfa/test/basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ describe("SuperTokens Example Basic tests", function () {

browser = await puppeteer.launch({
args: ["--no-sandbox", "--disable-setuid-sandbox"],
headless: true,
headless: false,
});
page = await browser.newPage();
});
Expand Down
4 changes: 2 additions & 2 deletions lib/build/emailpassword-shared7.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/build/multifactorauth-shared.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions lib/build/passwordless-shared3.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions lib/build/recipe/totp/index.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/build/thirdparty-shared2.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export function useChildProps(
onFetchError: async (err: Response) => {
if (err.status === Session.getInstanceOrThrow().config.invalidClaimStatusCode) {
const invalidClaims = await getInvalidClaimsFromResponse({ response: err, userContext });
if (invalidClaims.some((i) => i.validatorId === EmailVerificationClaim.id)) {
if (invalidClaims.some((i) => i.id === EmailVerificationClaim.id)) {
try {
// it's OK if this throws,
const evInstance = EmailVerification.getInstanceOrThrow();
Expand Down Expand Up @@ -238,7 +238,7 @@ export function useChildProps(
onFetchError: async (err: Response) => {
if (err.status === Session.getInstanceOrThrow().config.invalidClaimStatusCode) {
const invalidClaims = await getInvalidClaimsFromResponse({ response: err, userContext });
if (invalidClaims.some((i) => i.validatorId === EmailVerificationClaim.id)) {
if (invalidClaims.some((i) => i.id === EmailVerificationClaim.id)) {
try {
// it's OK if this throws,
const evInstance = EmailVerification.getInstanceOrThrow();
Expand Down
4 changes: 2 additions & 2 deletions lib/ts/recipe/passwordless/components/features/mfa/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ export function useChildProps(
onFetchError: async (err: Response) => {
if (err.status === Session.getInstanceOrThrow().config.invalidClaimStatusCode) {
const invalidClaims = await getInvalidClaimsFromResponse({ response: err, userContext });
if (invalidClaims.some((i) => i.validatorId === EmailVerificationClaim.id)) {
if (invalidClaims.some((i) => i.id === EmailVerificationClaim.id)) {
try {
// it's OK if this throws,
const evInstance = EmailVerification.getInstanceOrThrow();
Expand Down Expand Up @@ -362,7 +362,7 @@ function useOnLoad(
err.status === SessionRecipe.getInstanceOrThrow().config.invalidClaimStatusCode
) {
const invalidClaims = await getInvalidClaimsFromResponse({ response: err, userContext });
if (invalidClaims.some((i) => i.validatorId === EmailVerificationClaim.id)) {
if (invalidClaims.some((i) => i.id === EmailVerificationClaim.id)) {
try {
// it's OK if this throws,
const evInstance = EmailVerificationRecipe.getInstanceOrThrow();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ export function useChildProps(
onFetchError: async (err: Response) => {
if (err.status === Session.getInstanceOrThrow().config.invalidClaimStatusCode) {
const invalidClaims = await getInvalidClaimsFromResponse({ response: err, userContext });
if (invalidClaims.some((i) => i.validatorId === EmailVerificationClaim.id)) {
if (invalidClaims.some((i) => i.id === EmailVerificationClaim.id)) {
try {
// it's OK if this throws,
const evInstance = EmailVerification.getInstanceOrThrow();
Expand Down
2 changes: 1 addition & 1 deletion lib/ts/recipe/session/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const getFailureRedirectionInfo = async ({

let failedClaim: ClaimValidationError | undefined = undefined;
for (const validator of globalValidators) {
const claim = invalidClaims.find((c) => c.validatorId === validator.id);
const claim = invalidClaims.find((c) => c.id === validator.id);
if (claim !== undefined) {
const failureCallback = validator.onFailureRedirection;
if (failureCallback) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ const SignInAndUpCallback: React.FC<PropType> = (props) => {
async (err: any) => {
if ("status" in err && err.status === Session.getInstanceOrThrow().config.invalidClaimStatusCode) {
const invalidClaims = await getInvalidClaimsFromResponse({ response: err, userContext });
if (invalidClaims.some((i) => i.validatorId === EmailVerificationClaim.id)) {
if (invalidClaims.some((i) => i.id === EmailVerificationClaim.id)) {
try {
// it's OK if this throws,
const evInstance = EmailVerification.getInstanceOrThrow();
Expand Down

0 comments on commit 640a53d

Please sign in to comment.