From 068b5d2ee02920e1e7240cba7d410931e6ad0b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lehoczky=20Zolt=C3=A1n?= Date: Mon, 13 May 2024 09:07:20 +0200 Subject: [PATCH] refactor: use ENUM values for MFA factors --- v2/mfa/backend-setup.mdx | 65 +++++++++++++------ v2/mfa/email-sms-otp/otp-for-all-users.mdx | 18 +++-- v2/mfa/email-sms-otp/otp-for-opt-in-users.mdx | 38 +++++++---- v2/mfa/frontend-setup.mdx | 5 +- v2/mfa/step-up-auth.mdx | 54 +++++++-------- v2/mfa/totp/totp-for-all-users.mdx | 22 +++++-- v2/mfa/totp/totp-for-opt-in-users.mdx | 40 ++++++++---- 7 files changed, 160 insertions(+), 82 deletions(-) diff --git a/v2/mfa/backend-setup.mdx b/v2/mfa/backend-setup.mdx index 2db15c01f..79cee71d5 100644 --- a/v2/mfa/backend-setup.mdx +++ b/v2/mfa/backend-setup.mdx @@ -123,7 +123,10 @@ supertokens.init({ flowType: "USER_INPUT_CODE" }), MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"] + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ] }) // highlight-end ] @@ -164,7 +167,7 @@ async function createNewTenant() { let resp = await Multitenancy.createOrUpdateTenant("customer1", { emailPasswordEnabled: true, passwordlessEnabled: true, - firstFactors: ["emailpassword"] + firstFactors: [MultiFactorAuth.FactorIds.EMAILPASSWORD] }); if (resp.createdNew) { @@ -262,14 +265,17 @@ SuperTokens.init({ }), totp.init(), MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"], + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ], // highlight-start override: { functions: (originalImplementation) => { return { ...originalImplementation, getMFARequirementsForAuth: async function (input) { - return ["totp"] + return [MultiFactorAuth.FactorIds.TOTP] } } } @@ -324,7 +330,10 @@ supertokens.init({ recipeList: [ // ... MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"], + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ], override: { functions: (originalImplementation) => { return { @@ -332,7 +341,10 @@ supertokens.init({ getMFARequirementsForAuth: async function (input) { // highlight-start return [{ - oneOf: ["totp", "otp-email"] + oneOf: [ + MultiFactorAuth.FactorIds.TOTP, + MultiFactorAuth.FactorIds.OTP_EMAIL + ] }] // highlight-end } @@ -382,7 +394,10 @@ supertokens.init({ recipeList: [ // ... MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"], + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ], override: { functions: (originalImplementation) => { return { @@ -390,7 +405,10 @@ supertokens.init({ getMFARequirementsForAuth: async function (input) { // highlight-start return [{ - allOfInAnyOrder: ["totp", "otp-email"] + allOfInAnyOrder: [ + MultiFactorAuth.FactorIds.TOTP, + MultiFactorAuth.FactorIds.OTP_EMAIL + ] }] // highlight-end } @@ -442,7 +460,10 @@ supertokens.init({ recipeList: [ // ... MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"], + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ], override: { functions: (originalImplementation) => { return { @@ -450,13 +471,13 @@ supertokens.init({ getMFARequirementsForAuth: async function (input) { // highlight-start let currentCompletedFactors = MultiFactorAuth.MultiFactorAuthClaim.getValueFromPayload(input.accessTokenPayload) - if ("totp" in currentCompletedFactors) { + if (MultiFactorAuth.FactorIds.TOTP in currentCompletedFactors) { // this means the totp factor is completed - return ["otp-email"] + return [MultiFactorAuth.FactorIds.OTP_EMAIL] } else { // this means we have not finished totp yet, and we want // to do that right after first factor login - return ["totp"] + return [MultiFactorAuth.FactorIds.TOTP] } // highlight-end } @@ -516,8 +537,8 @@ async function createNewTenant() { let resp = await Multitenancy.createOrUpdateTenant("customer1", { emailPasswordEnabled: true, passwordlessEnabled: true, - firstFactors: ["emailpassword"], - requiredSecondaryFactors: ["otp-email"] + firstFactors: [MultiFactorAuth.FactorIds.EMAILPASSWORD], + requiredSecondaryFactors: [MultiFactorAuth.FactorIds.OTP_EMAIL] }); if (resp.createdNew) { @@ -592,7 +613,10 @@ supertokens.init({ recipeList: [ // ... MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"], + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ], override: { functions: (originalImplementation) => { return { @@ -650,7 +674,7 @@ You want to start by creating an API that does [session verification](/docs/sess import MultiFactorAuth from "supertokens-node/recipe/multifactorauth"; async function enableMFAForUser(userId: string) { - await MultiFactorAuth.addToRequiredSecondaryFactorsForUser(userId, "totp") + await MultiFactorAuth.addToRequiredSecondaryFactorsForUser(userId, MultiFactorAuth.FactorIds.TOTP) } ``` @@ -697,7 +721,10 @@ supertokens.init({ recipeList: [ // ... MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"], + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ], override: { functions: (originalImplementation) => { return { @@ -760,7 +787,7 @@ import MultiFactorAuth from "supertokens-node/recipe/multifactorauth"; async function isTotpEnabledForUser(userId: string) { let factors = await MultiFactorAuth.getRequiredSecondaryFactorsForUser(userId) - return factors.includes("totp") + return factors.includes(MultiFactorAuth.FactorIds.TOTP) } ``` @@ -795,7 +822,7 @@ import MultiFactorAuth from "supertokens-node/recipe/multifactorauth"; async function isTotpSetupForUser(userId: string) { let factors = await MultiFactorAuth.getFactorsSetupForUser(userId) - return factors.includes("totp") + return factors.includes(MultiFactorAuth.FactorIds.TOTP) } ``` diff --git a/v2/mfa/email-sms-otp/otp-for-all-users.mdx b/v2/mfa/email-sms-otp/otp-for-all-users.mdx index cb4fdeb21..d0d0c4cd5 100644 --- a/v2/mfa/email-sms-otp/otp-for-all-users.mdx +++ b/v2/mfa/email-sms-otp/otp-for-all-users.mdx @@ -83,7 +83,10 @@ supertokens.init({ }), // highlight-end MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"], + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ] // highlight-start override: { functions: (originalImplementation) => { @@ -193,7 +196,10 @@ supertokens.init({ contactMethod: "EMAIL" }), MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"] + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ] }) // highlight-end ] @@ -482,14 +488,18 @@ To configure otp-email requirement for a tenant, we can call the following API: ```tsx import Multitenancy from "supertokens-node/recipe/multitenancy"; +import MultiFactorAuth from "supertokens-node/recipe/multifactorauth" async function createNewTenant() { let resp = await Multitenancy.createOrUpdateTenant("customer1", { emailPasswordEnabled: true, thirdPartyEnabled: true, passwordlessEnabled: true, - firstFactors: ["emailpassword", "thirdparty"], - requiredSecondaryFactors: ["otp-email"] + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ] + requiredSecondaryFactors: [MultiFactorAuth.FactorIds.OTP_EMAIL] }); if (resp.createdNew) { diff --git a/v2/mfa/email-sms-otp/otp-for-opt-in-users.mdx b/v2/mfa/email-sms-otp/otp-for-opt-in-users.mdx index 709735f0d..4d5e6b856 100644 --- a/v2/mfa/email-sms-otp/otp-for-opt-in-users.mdx +++ b/v2/mfa/email-sms-otp/otp-for-opt-in-users.mdx @@ -92,7 +92,10 @@ supertokens.init({ }), // highlight-end MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"], + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ] // highlight-start override: { functions: (originalImplementation) => { @@ -102,7 +105,7 @@ supertokens.init({ let roles = await UserRoles.getRolesForUser(input.tenantId, (await input.user).id) if (roles.roles.includes("admin")) { // we only want otp-email for admins - return ["otp-email"] + return [MultiFactorAuth.FactorIds.OTP_EMAIL] } else { // no MFA for non admin users. return [] @@ -210,7 +213,10 @@ supertokens.init({ } }), MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"], + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ] }) // highlight-end ] @@ -297,7 +303,7 @@ import MultiFactorAuth from "supertokens-node/recipe/multifactorauth"; async function isOTPEmailEnabledForUser(userId: string) { let factors = await MultiFactorAuth.getRequiredSecondaryFactorsForUser(userId) - return factors.includes("otp-email") + return factors.includes(MultiFactorAuth.FactorIds.OTP_EMAIL) } ``` @@ -327,11 +333,11 @@ If the user wants to enable or disable otp-email for them, you can make an API o import MultiFactorAuth from "supertokens-node/recipe/multifactorauth"; async function enableMFAForUser(userId: string) { - await MultiFactorAuth.addToRequiredSecondaryFactorsForUser(userId, "otp-email") + await MultiFactorAuth.addToRequiredSecondaryFactorsForUser(userId, MultiFactorAuth.FactorIds.OTP_EMAIL) } async function disableMFAForUser(userId: string) { - await MultiFactorAuth.removeFromRequiredSecondaryFactorsForUser(userId, "otp-email") + await MultiFactorAuth.removeFromRequiredSecondaryFactorsForUser(userId, MultiFactorAuth.FactorIds.OTP_EMAIL) } ``` @@ -416,7 +422,10 @@ supertokens.init({ } }), MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"], + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ] override: { functions: (originalImplementation) => { return { @@ -424,9 +433,9 @@ supertokens.init({ getMFARequirementsForAuth: async function (input) { let roles = await UserRoles.getRolesForUser(input.tenantId, (await input.user).id) // highlight-next-line - if (roles.roles.includes("admin") && (await input.requiredSecondaryFactorsForTenant).includes("otp-email")) { + if (roles.roles.includes("admin") && (await input.requiredSecondaryFactorsForTenant).includes(MultiFactorAuth.FactorIds.OTP_EMAIL)) { // we only want otp-email for admins - return ["otp-email"] + return [MultiFactorAuth.FactorIds.OTP_EMAIL] } else { // no MFA for non admin users. return [] @@ -529,16 +538,19 @@ supertokens.init({ } }), MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"], + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ] // highlight-start override: { functions: (originalImplementation) => { return { ...originalImplementation, getMFARequirementsForAuth: async function (input) { - if ((await input.requiredSecondaryFactorsForUser).includes("otp-email")) { - if ((await input.requiredSecondaryFactorsForTenant).includes("otp-email")) { - return ["otp-email"] + if ((await input.requiredSecondaryFactorsForUser).includes(MultiFactorAuth.FactorIds.OTP_EMAIL)) { + if ((await input.requiredSecondaryFactorsForTenant).includes(MultiFactorAuth.FactorIds.OTP_EMAIL)) { + return [MultiFactorAuth.FactorIds.OTP_EMAIL] } } // no otp-email required for input.user, with the input.tenant. diff --git a/v2/mfa/frontend-setup.mdx b/v2/mfa/frontend-setup.mdx index 7f617bb76..453fb97ca 100644 --- a/v2/mfa/frontend-setup.mdx +++ b/v2/mfa/frontend-setup.mdx @@ -57,7 +57,10 @@ supertokens.init({ }), // highlight-start MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"] + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ] }) // highlight-end ] diff --git a/v2/mfa/step-up-auth.mdx b/v2/mfa/step-up-auth.mdx index 6b8034c70..471acfc28 100644 --- a/v2/mfa/step-up-auth.mdx +++ b/v2/mfa/step-up-auth.mdx @@ -53,7 +53,7 @@ app.post( verifySession(), async (req: SessionRequest, res) => { let mfaClaim = await req.session!.getClaimValue(MultiFactorAuth.MultiFactorAuthClaim); - const totpCompletedTime = mfaClaim!.c["totp"]; + const totpCompletedTime = mfaClaim!.c[MultiFactorAuth.FactorIds.TOTP]; if (totpCompletedTime === undefined || totpCompletedTime < (Date.now() - 1000*60*5)) { // this means that the user had completed the TOTP challenge more than 5 minutes ago // so we should ask them to complete it again @@ -64,7 +64,7 @@ app.post( id: MultiFactorAuth.MultiFactorAuthClaim.key, reason: { message: "Factor validation failed: totp not completed", - factorId: "totp", + factorId: MultiFactorAuth.FactorIds.TOTP, }, }] }) @@ -97,7 +97,7 @@ server.route({ }, handler: async (req: SessionRequest, res) => { let mfaClaim = await req.session!.getClaimValue(MultiFactorAuth.MultiFactorAuthClaim); - const totpCompletedTime = mfaClaim!.c["totp"]; + const totpCompletedTime = mfaClaim!.c[MultiFactorAuth.FactorIds.TOTP]; if (totpCompletedTime === undefined || totpCompletedTime < (Date.now() - 1000*60*5)) { // this means that the user had completed the TOTP challenge more than 5 minutes ago // so we should ask them to complete it again @@ -108,7 +108,7 @@ server.route({ id: MultiFactorAuth.MultiFactorAuthClaim.key, reason: { message: "Factor validation failed: totp not completed", - factorId: "totp", + factorId: MultiFactorAuth.FactorIds.TOTP, }, }] }) @@ -134,7 +134,7 @@ fastify.post("/update-blog", { preHandler: verifySession(), }, async (req: SessionRequest, res) => { let mfaClaim = await req.session!.getClaimValue(MultiFactorAuth.MultiFactorAuthClaim); - const totpCompletedTime = mfaClaim!.c["totp"]; + const totpCompletedTime = mfaClaim!.c[MultiFactorAuth.FactorIds.TOTP]; if (totpCompletedTime === undefined || totpCompletedTime < (Date.now() - 1000*60*5)) { // this means that the user had completed the TOTP challenge more than 5 minutes ago // so we should ask them to complete it again @@ -145,7 +145,7 @@ fastify.post("/update-blog", { id: MultiFactorAuth.MultiFactorAuthClaim.key, reason: { message: "Factor validation failed: totp not completed", - factorId: "totp", + factorId: MultiFactorAuth.FactorIds.TOTP, }, }] }) @@ -165,7 +165,7 @@ import { Error as STError } from "supertokens-node/recipe/session" async function updateBlog(awsEvent: SessionEvent) { let mfaClaim = await awsEvent.session!.getClaimValue(MultiFactorAuth.MultiFactorAuthClaim); - const totpCompletedTime = mfaClaim!.c["totp"]; + const totpCompletedTime = mfaClaim!.c[MultiFactorAuth.FactorIds.TOTP]; if (totpCompletedTime === undefined || totpCompletedTime < (Date.now() - 1000*60*5)) { // this means that the user had completed the TOTP challenge more than 5 minutes ago // so we should ask them to complete it again @@ -176,7 +176,7 @@ async function updateBlog(awsEvent: SessionEvent) { id: MultiFactorAuth.MultiFactorAuthClaim.key, reason: { message: "Factor validation failed: totp not completed", - factorId: "totp", + factorId: MultiFactorAuth.FactorIds.TOTP, }, }] }) @@ -201,7 +201,7 @@ let router = new KoaRouter(); router.post("/update-blog", verifySession(), async (ctx: SessionContext, next) => { let mfaClaim = await ctx.session!.getClaimValue(MultiFactorAuth.MultiFactorAuthClaim); - const totpCompletedTime = mfaClaim!.c["totp"]; + const totpCompletedTime = mfaClaim!.c[MultiFactorAuth.FactorIds.TOTP]; if (totpCompletedTime === undefined || totpCompletedTime < (Date.now() - 1000*60*5)) { // this means that the user had completed the TOTP challenge more than 5 minutes ago // so we should ask them to complete it again @@ -212,7 +212,7 @@ router.post("/update-blog", verifySession(), async (ctx: SessionContext, next) = id: MultiFactorAuth.MultiFactorAuthClaim.key, reason: { message: "Factor validation failed: totp not completed", - factorId: "totp", + factorId: MultiFactorAuth.FactorIds.TOTP, }, }] }) @@ -239,7 +239,7 @@ class Example { @response(200) async handler() { let mfaClaim = await (this.ctx as any).session!.getClaimValue(MultiFactorAuth.MultiFactorAuthClaim); - const totpCompletedTime = mfaClaim!.c["totp"]; + const totpCompletedTime = mfaClaim!.c[MultiFactorAuth.FactorIds.TOTP]; if (totpCompletedTime === undefined || totpCompletedTime < (Date.now() - 1000*60*5)) { // this means that the user had completed the TOTP challenge more than 5 minutes ago // so we should ask them to complete it again @@ -250,7 +250,7 @@ class Example { id: MultiFactorAuth.MultiFactorAuthClaim.key, reason: { message: "Factor validation failed: totp not completed", - factorId: "totp", + factorId: MultiFactorAuth.FactorIds.TOTP, }, }] }) @@ -280,7 +280,7 @@ export default async function example(req: SessionRequest, res: any) { res ) let mfaClaim = await req.session!.getClaimValue(MultiFactorAuth.MultiFactorAuthClaim); - const totpCompletedTime = mfaClaim!.c["totp"]; + const totpCompletedTime = mfaClaim!.c[MultiFactorAuth.FactorIds.TOTP]; if (totpCompletedTime === undefined || totpCompletedTime < (Date.now() - 1000*60*5)) { // this means that the user had completed the TOTP challenge more than 5 minutes ago // so we should ask them to complete it again @@ -293,7 +293,7 @@ export default async function example(req: SessionRequest, res: any) { id: MultiFactorAuth.MultiFactorAuthClaim.key, reason: { message: "Factor validation failed: totp not completed", - factorId: "totp", + factorId: MultiFactorAuth.FactorIds.TOTP, }, }] }) @@ -326,7 +326,7 @@ export function POST(request: NextRequest) { return NextResponse.json(err, { status: 500 }); } let mfaClaim = await session!.getClaimValue(MultiFactorAuth.MultiFactorAuthClaim); - const totpCompletedTime = mfaClaim!.c["totp"]; + const totpCompletedTime = mfaClaim!.c[MultiFactorAuth.FactorIds.TOTP]; if (totpCompletedTime === undefined || totpCompletedTime < (Date.now() - 1000*60*5)) { // this means that the user had completed the TOTP challenge more than 5 minutes ago // so we should ask them to complete it again @@ -337,7 +337,7 @@ export function POST(request: NextRequest) { id: MultiFactorAuth.MultiFactorAuthClaim.key, reason: { message: "Factor validation failed: totp not completed", - factorId: "totp", + factorId: MultiFactorAuth.FactorIds.TOTP, }, }] }) @@ -366,7 +366,7 @@ export class ExampleController { @UseGuards(new AuthGuard()) async postExample(@Session() session: SessionContainer): Promise { let mfaClaim = await session!.getClaimValue(MultiFactorAuth.MultiFactorAuthClaim); - const totpCompletedTime = mfaClaim!.c["totp"]; + const totpCompletedTime = mfaClaim!.c[MultiFactorAuth.FactorIds.TOTP]; if (totpCompletedTime === undefined || totpCompletedTime < (Date.now() - 1000*60*5)) { // this means that the user had completed the TOTP challenge more than 5 minutes ago // so we should ask them to complete it again @@ -377,7 +377,7 @@ export class ExampleController { id: MultiFactorAuth.MultiFactorAuthClaim.key, reason: { message: "Factor validation failed: totp not completed", - factorId: "totp", + factorId: MultiFactorAuth.FactorIds.TOTP, }, }] }) @@ -456,9 +456,9 @@ supertokens.init({ // if the above did not throw, it means that the user has logged in and has completed all the required // factors for login. So now we check specifically for the step up auth case: - if (input.factorId === "totp" && (await input.factorsSetUpForUser).includes("totp")) { + if (input.factorId === MultiFactorAuth.FactorIds.TOTP && (await input.factorsSetUpForUser).includes(MultiFactorAuth.FactorIds.TOTP)) { // this is an example of checking for totp, but you can also use other factor IDs. - const totpCompletedTime = claimValue.c["totp"]; + const totpCompletedTime = claimValue.c[MultiFactorAuth.FactorIds.TOTP]; if (totpCompletedTime === undefined || totpCompletedTime < (Date.now() - 1000 * 60 * 5)) { // this means that the user had completed the TOTP challenge more than 5 minutes ago // so we should ask them to complete it again @@ -469,7 +469,7 @@ supertokens.init({ id: MultiFactorAuth.MultiFactorAuthClaim.key, reason: { message: "Factor validation failed: totp not completed", - factorId: "totp", + factorId: MultiFactorAuth.FactorIds.TOTP, }, }] }) @@ -542,7 +542,7 @@ To redirect the user to as factor challenge page and then navigate them back to import MultiFactorAuth from 'supertokens-auth-react/recipe/multifactorauth'; async function redirectToTotpSetupScreen() { - MultiFactorAuth.redirectToFactor("totp", false, true) + MultiFactorAuth.redirectToFactor(MultiFactorAuth.FactorIds.TOTP, false, true) } ``` @@ -611,7 +611,7 @@ function InvalidClaimHandler(props: React.PropsWithChildren) { return null; } - let totpCompletedTime = claimValue.value?.c["totp"] + let totpCompletedTime = claimValue.value?.c[MultiFactorAuth.FactorIds.TOTP] if (totpCompletedTime === undefined || totpCompletedTime < (DateProviderReference.getReferenceOrThrow().dateProvider.now() - 1000 * 60 * 5)) { return
You need to complete TOTP before seeing this page. Please click here to finish to proceed.
} @@ -640,7 +640,7 @@ async function shouldLoadRoute(): Promise { // since all default claim validators have passed, we now check for if the user has finished TOTP // within the last 5 mins let mfaClaimValue = await Session.getClaimValue({ claim: MultiFactorAuthClaim }); - let totpCompletedTime = mfaClaimValue?.c["totp"]; + let totpCompletedTime = mfaClaimValue?.c[MultiFactorAuth.FactorIds.TOTP]; if (totpCompletedTime === undefined || totpCompletedTime < (DateProviderReference.getReferenceOrThrow().dateProvider.now() - 1000 * 60 * 5)) { // ths user needs to complete TOTP since it's been more than 5 mins since they completed it. return false; @@ -693,7 +693,7 @@ async function shouldLoadRoute(): Promise { // since all default claim validators have passed, we now check for if the user has finished TOTP // within the last 5 mins let mfaClaimValue = await Session.getClaimValue({ claim: MultiFactorAuthClaim }); - let totpCompletedTime = mfaClaimValue?.c["totp"]; + let totpCompletedTime = mfaClaimValue?.c[MultiFactorAuth.FactorIds.TOTP]; if (totpCompletedTime === undefined || totpCompletedTime < (DateProviderReference.getReferenceOrThrow().dateProvider.now() - 1000 * 60 * 5)) { // ths user needs to complete TOTP since it's been more than 5 mins since they completed it. return false; @@ -723,7 +723,7 @@ async function shouldLoadRoute(): Promise { // since all default claim validators have passed, we now check for if the user has finished TOTP // within the last 5 mins let mfaClaimValue = await supertokensSession.getClaimValue({ claim: supertokensMultiFactorAuth.MultiFactorAuthClaim }); - let totpCompletedTime = mfaClaimValue?.c["totp"]; + let totpCompletedTime = mfaClaimValue?.c[MultiFactorAuth.FactorIds.TOTP]; if (totpCompletedTime === undefined || totpCompletedTime < (supertokensDateProviderReference.DateProviderReference.getReferenceOrThrow().dateProvider.now() - 1000 * 60 * 5)) { // ths user needs to complete TOTP since it's been more than 5 mins since they completed it. return false; @@ -763,7 +763,7 @@ async function checkIfMFAIsCompleted() { let isMFACompleted: boolean = (await SuperTokens.getAccessTokenPayloadSecurely())["st-mfa"].v; if (isMFACompleted) { let completedFactors = (await SuperTokens.getAccessTokenPayloadSecurely())["st-mfa"].c; - if (completedFactors["totp"] === undefined || completedFactors["totp"] < (Date.now() - 1000*60*5)) { + if (completedFactors[MultiFactorAuth.FactorIds.TOTP] === undefined || completedFactors[MultiFactorAuth.FactorIds.TOTP] < (Date.now() - 1000*60*5)) { // user has not finished TOTP MFA in the last 5 minutes } } else { diff --git a/v2/mfa/totp/totp-for-all-users.mdx b/v2/mfa/totp/totp-for-all-users.mdx index a67eb6e3f..86ad62929 100644 --- a/v2/mfa/totp/totp-for-all-users.mdx +++ b/v2/mfa/totp/totp-for-all-users.mdx @@ -61,14 +61,17 @@ supertokens.init({ // highlight-next-line totp.init(), MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"], + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ], // highlight-start override: { functions: (originalImplementation) => { return { ...originalImplementation, getMFARequirementsForAuth: async function (input) { - return ["totp"] + return [MultiFactorAuth.FactorIds.OTP_EMAIL] } } } @@ -160,7 +163,10 @@ supertokens.init({ // highlight-start totp.init(), MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"] + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ] }) // highlight-end ] @@ -514,6 +520,7 @@ async function verifyTotpDevice(deviceName: string, userInputTotp: string) { ```tsx import supertokensTotp from "supertokens-web-js-script/recipe/totp" import supertokensSession from "supertokens-web-js-script/recipe/session" + async function verifyTotpDevice(deviceName: string, userInputTotp: string) { if (await supertokensSession.doesSessionExist()) { try { @@ -655,6 +662,7 @@ async function verifyTotpCode(userInputTotp: string) { ```tsx import supertokensTotp from "supertokens-web-js-script/recipe/totp" import supertokensSession from "supertokens-web-js-script/recipe/session" + async function verifyTotpCode(userInputTotp: string) { if (await supertokensSession.doesSessionExist()) { try { @@ -809,13 +817,17 @@ To configure TOTP requirement for a tenant, we can call the following API: ```tsx import Multitenancy from "supertokens-node/recipe/multitenancy"; +import MultiFactorAuth from "supertokens-node/recipe/multifactorauth" async function createNewTenant() { let resp = await Multitenancy.createOrUpdateTenant("customer1", { emailPasswordEnabled: true, thirdPartyEnabled: true, - firstFactors: ["emailpassword", "thirdparty"], - requiredSecondaryFactors: ["totp"] + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ] + requiredSecondaryFactors: [MultiFactorAuth.FactorIds.OTP_EMAIL] }); if (resp.createdNew) { diff --git a/v2/mfa/totp/totp-for-opt-in-users.mdx b/v2/mfa/totp/totp-for-opt-in-users.mdx index 7bf3b3f68..52de29b17 100644 --- a/v2/mfa/totp/totp-for-opt-in-users.mdx +++ b/v2/mfa/totp/totp-for-opt-in-users.mdx @@ -71,7 +71,10 @@ supertokens.init({ // highlight-next-line totp.init(), MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"], + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ], // highlight-start override: { functions: (originalImplementation) => { @@ -81,7 +84,7 @@ supertokens.init({ let roles = await UserRoles.getRolesForUser(input.tenantId, (await input.user).id) if (roles.roles.includes("admin")) { // we only want totp for admins - return ["totp"] + return [MultiFactorAuth.FactorIds.TOTP] } else { // no MFA for non admin users. return [] @@ -167,7 +170,10 @@ supertokens.init({ } }), MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"], + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ], }) // highlight-end ] @@ -251,7 +257,7 @@ import MultiFactorAuth from "supertokens-node/recipe/multifactorauth"; async function isTotpEnabledForUser(userId: string) { let factors = await MultiFactorAuth.getRequiredSecondaryFactorsForUser(userId) - return factors.includes("totp") + return factors.includes(MultiFactorAuth.FactorIds.TOTP) } ``` @@ -281,11 +287,11 @@ If the user wants to enable or disable TOTP for them, you can make an API on you import MultiFactorAuth from "supertokens-node/recipe/multifactorauth"; async function enableMFAForUser(userId: string) { - await MultiFactorAuth.addToRequiredSecondaryFactorsForUser(userId, "totp") + await MultiFactorAuth.addToRequiredSecondaryFactorsForUser(userId, MultiFactorAuth.FactorIds.TOTP) } async function disableMFAForUser(userId: string) { - await MultiFactorAuth.removeFromRequiredSecondaryFactorsForUser(userId, "totp") + await MultiFactorAuth.removeFromRequiredSecondaryFactorsForUser(userId, MultiFactorAuth.FactorIds.TOTP) } ``` @@ -350,6 +356,7 @@ async function fetchTOTPDevices() { ```tsx import supertokensSession from "supertokens-web-js-script/recipe/session" import supertokensTotp from "supertokens-web-js-script/recipe/totp" + async function fetchTOTPDevices() { if (await supertokensSession.doesSessionExist()) { try { @@ -458,6 +465,7 @@ async function removeTOTPDevices(deviceName: string) { ```tsx import supertokensSession from "supertokens-web-js-script/recipe/session" import supertokensTotp from "supertokens-web-js-script/recipe/totp" + async function removeTOTPDevices(deviceName: string) { if (await supertokensSession.doesSessionExist()) { try { @@ -603,7 +611,10 @@ supertokens.init({ // highlight-next-line totp.init(), MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"], + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ], override: { functions: (originalImplementation) => { return { @@ -611,9 +622,9 @@ supertokens.init({ getMFARequirementsForAuth: async function (input) { let roles = await UserRoles.getRolesForUser(input.tenantId, (await input.user).id) // highlight-next-line - if (roles.roles.includes("admin") && (await input.requiredSecondaryFactorsForTenant).includes("totp")) { + if (roles.roles.includes("admin") && (await input.requiredSecondaryFactorsForTenant).includes(MultiFactorAuth.FactorIds.TOTP)) { // we only want totp for admins - return ["totp"] + return [MultiFactorAuth.FactorIds.TOTP] } else { // no MFA for non admin users. return [] @@ -694,17 +705,20 @@ supertokens.init({ } }), MultiFactorAuth.init({ - firstFactors: ["emailpassword", "thirdparty"], + firstFactors: [ + MultiFactorAuth.FactorIds.EMAILPASSWORD, + MultiFactorAuth.FactorIds.THIRDPARTY + ], // highlight-start override: { functions: (originalImplementation) => { return { ...originalImplementation, getMFARequirementsForAuth: async function (input) { - if ((await input.requiredSecondaryFactorsForUser).includes("totp")) { + if ((await input.requiredSecondaryFactorsForUser).includes(MultiFactorAuth.FactorIds.TOTP)) { // this means that the user has finished setting up a device from their settings page. - if ((await input.requiredSecondaryFactorsForTenant).includes("totp")) { - return ["totp"] + if ((await input.requiredSecondaryFactorsForTenant).includes(MultiFactorAuth.FactorIds.TOTP)) { + return [MultiFactorAuth.FactorIds.TOTP] } } // no totp required for input.user, with the input.tenant.