diff --git a/examples/cloudflare-workers/with-email-password-hono-be-only/index.ts b/examples/cloudflare-workers/with-email-password-hono-be-only/index.ts index c72163352..1c5ad844f 100644 --- a/examples/cloudflare-workers/with-email-password-hono-be-only/index.ts +++ b/examples/cloudflare-workers/with-email-password-hono-be-only/index.ts @@ -41,8 +41,8 @@ app.get("/sessioninfo", (c) => { app.get("/", (c) => { return c.json({ - "message": "Hello from Supertokens" - }) -}) + message: "Hello from Supertokens", + }); +}); export default app; diff --git a/lib/build/recipe/emailpassword/api/implementation.js b/lib/build/recipe/emailpassword/api/implementation.js index 703c9462a..ec7904e1a 100644 --- a/lib/build/recipe/emailpassword/api/implementation.js +++ b/lib/build/recipe/emailpassword/api/implementation.js @@ -40,7 +40,16 @@ function getAPIImplementation() { }; }, generatePasswordResetTokenPOST: async function ({ formFields, tenantId, options, userContext }) { - const email = formFields.filter((f) => f.id === "email")[0].value; + // NOTE: Check for email being a non-string value. This check will likely + // never evaluate to `true` as there is an upper-level check for the type + // in validation but kept here to be safe. + const emailAsUnknown = formFields.filter((f) => f.id === "email")[0].value; + if (typeof emailAsUnknown !== "string") + return { + status: "GENERAL_ERROR", + message: "email value needs to be a string", + }; + const email = emailAsUnknown; // this function will be reused in different parts of the flow below.. async function generateAndSendPasswordResetToken(primaryUserId, recipeUserId) { // the user ID here can be primary or recipe level. @@ -355,7 +364,16 @@ function getAPIImplementation() { }; } } - let newPassword = formFields.filter((f) => f.id === "password")[0].value; + // NOTE: Check for password being a non-string value. This check will likely + // never evaluate to `true` as there is an upper-level check for the type + // in validation but kept here to be safe. + const newPasswordAsUnknown = formFields.filter((f) => f.id === "password")[0].value; + if (typeof newPasswordAsUnknown !== "string") + return { + status: "GENERAL_ERROR", + message: "password value needs to be a string", + }; + let newPassword = newPasswordAsUnknown; let tokenConsumptionResponse = await options.recipeImplementation.consumePasswordResetToken({ token, tenantId, @@ -371,7 +389,7 @@ function getAPIImplementation() { // This should happen only cause of a race condition where the user // might be deleted before token creation and consumption. // Also note that this being undefined doesn't mean that the email password - // user does not exist, but it means that there is no reicpe or primary user + // user does not exist, but it means that there is no recipe or primary user // for whom the token was generated. return { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR", @@ -496,8 +514,23 @@ function getAPIImplementation() { "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_012)", }, }; - let email = formFields.filter((f) => f.id === "email")[0].value; - let password = formFields.filter((f) => f.id === "password")[0].value; + const emailAsUnknown = formFields.filter((f) => f.id === "email")[0].value; + const passwordAsUnknown = formFields.filter((f) => f.id === "password")[0].value; + // NOTE: Following checks will likely never throw an error as the + // check for type is done in a parent function but they are kept + // here to be on the safe side. + if (typeof emailAsUnknown !== "string") + return { + status: "GENERAL_ERROR", + message: "email value needs to be a string", + }; + if (typeof passwordAsUnknown !== "string") + return { + status: "GENERAL_ERROR", + message: "password value needs to be a string", + }; + let email = emailAsUnknown; + let password = passwordAsUnknown; const recipeId = "emailpassword"; const checkCredentialsOnTenant = async (tenantId) => { return ( @@ -619,8 +652,23 @@ function getAPIImplementation() { "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_016)", }, }; - let email = formFields.filter((f) => f.id === "email")[0].value; - let password = formFields.filter((f) => f.id === "password")[0].value; + const emailAsUnknown = formFields.filter((f) => f.id === "email")[0].value; + const passwordAsUnknown = formFields.filter((f) => f.id === "password")[0].value; + // NOTE: Following checks will likely never throw an error as the + // check for type is done in a parent function but they are kept + // here to be on the safe side. + if (typeof emailAsUnknown !== "string") + return { + status: "GENERAL_ERROR", + message: "email value needs to be a string", + }; + if (typeof passwordAsUnknown !== "string") + return { + status: "GENERAL_ERROR", + message: "password value needs to be a string", + }; + let email = emailAsUnknown; + let password = passwordAsUnknown; const preAuthCheckRes = await authUtils_1.AuthUtils.preAuthChecks({ authenticatingAccountInfo: { recipeId: "emailpassword", diff --git a/lib/build/recipe/emailpassword/api/utils.d.ts b/lib/build/recipe/emailpassword/api/utils.d.ts index d797f42e3..0f67df755 100644 --- a/lib/build/recipe/emailpassword/api/utils.d.ts +++ b/lib/build/recipe/emailpassword/api/utils.d.ts @@ -9,6 +9,6 @@ export declare function validateFormFieldsOrThrowError( ): Promise< { id: string; - value: string; + value: unknown; }[] >; diff --git a/lib/build/recipe/emailpassword/api/utils.js b/lib/build/recipe/emailpassword/api/utils.js index afbca42de..4e612889e 100644 --- a/lib/build/recipe/emailpassword/api/utils.js +++ b/lib/build/recipe/emailpassword/api/utils.js @@ -35,7 +35,9 @@ async function validateFormFieldsOrThrowError(configFormFields, formFieldsRaw, t // we trim the email: https://github.com/supertokens/supertokens-core/issues/99 formFields = formFields.map((field) => { if (field.id === constants_1.FORM_FIELD_EMAIL_ID) { - return Object.assign(Object.assign({}, field), { value: field.value.trim() }); + return Object.assign(Object.assign({}, field), { + value: typeof field.value === "string" ? field.value.trim() : field.value, + }); } return field; }); diff --git a/lib/build/recipe/emailpassword/types.d.ts b/lib/build/recipe/emailpassword/types.d.ts index 58be90676..53be20e82 100644 --- a/lib/build/recipe/emailpassword/types.d.ts +++ b/lib/build/recipe/emailpassword/types.d.ts @@ -225,7 +225,7 @@ export declare type APIInterface = { | ((input: { formFields: { id: string; - value: string; + value: unknown; }[]; tenantId: string; options: APIOptions; @@ -245,7 +245,7 @@ export declare type APIInterface = { | ((input: { formFields: { id: string; - value: string; + value: unknown; }[]; token: string; tenantId: string; @@ -271,7 +271,7 @@ export declare type APIInterface = { | ((input: { formFields: { id: string; - value: string; + value: unknown; }[]; tenantId: string; session: SessionContainerInterface | undefined; @@ -297,7 +297,7 @@ export declare type APIInterface = { | ((input: { formFields: { id: string; - value: string; + value: unknown; }[]; tenantId: string; session: SessionContainerInterface | undefined; diff --git a/lib/ts/recipe/emailpassword/api/generatePasswordResetToken.ts b/lib/ts/recipe/emailpassword/api/generatePasswordResetToken.ts index fb600bb05..85da1aca2 100644 --- a/lib/ts/recipe/emailpassword/api/generatePasswordResetToken.ts +++ b/lib/ts/recipe/emailpassword/api/generatePasswordResetToken.ts @@ -35,7 +35,7 @@ export default async function generatePasswordResetToken( // step 1 let formFields: { id: string; - value: string; + value: unknown; }[] = await validateFormFieldsOrThrowError( options.config.resetPasswordUsingTokenFeature.formFieldsForGenerateTokenForm, requestBody.formFields, diff --git a/lib/ts/recipe/emailpassword/api/implementation.ts b/lib/ts/recipe/emailpassword/api/implementation.ts index 7467c2957..76a96e531 100644 --- a/lib/ts/recipe/emailpassword/api/implementation.ts +++ b/lib/ts/recipe/emailpassword/api/implementation.ts @@ -65,7 +65,16 @@ export default function getAPIImplementation(): APIInterface { | { status: "PASSWORD_RESET_NOT_ALLOWED"; reason: string } | GeneralErrorResponse > { - const email = formFields.filter((f) => f.id === "email")[0].value; + // NOTE: Check for email being a non-string value. This check will likely + // never evaluate to `true` as there is an upper-level check for the type + // in validation but kept here to be safe. + const emailAsUnknown = formFields.filter((f) => f.id === "email")[0].value; + if (typeof emailAsUnknown !== "string") + return { + status: "GENERAL_ERROR", + message: "email value needs to be a string", + }; + const email: string = emailAsUnknown; // this function will be reused in different parts of the flow below.. async function generateAndSendPasswordResetToken( @@ -322,7 +331,7 @@ export default function getAPIImplementation(): APIInterface { }: { formFields: { id: string; - value: string; + value: unknown; }[]; token: string; tenantId: string; @@ -451,7 +460,16 @@ export default function getAPIImplementation(): APIInterface { } } - let newPassword = formFields.filter((f) => f.id === "password")[0].value; + // NOTE: Check for password being a non-string value. This check will likely + // never evaluate to `true` as there is an upper-level check for the type + // in validation but kept here to be safe. + const newPasswordAsUnknown = formFields.filter((f) => f.id === "password")[0].value; + if (typeof newPasswordAsUnknown !== "string") + return { + status: "GENERAL_ERROR", + message: "password value needs to be a string", + }; + let newPassword: string = newPasswordAsUnknown; let tokenConsumptionResponse = await options.recipeImplementation.consumePasswordResetToken({ token, @@ -472,7 +490,7 @@ export default function getAPIImplementation(): APIInterface { // This should happen only cause of a race condition where the user // might be deleted before token creation and consumption. // Also note that this being undefined doesn't mean that the email password - // user does not exist, but it means that there is no reicpe or primary user + // user does not exist, but it means that there is no recipe or primary user // for whom the token was generated. return { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR", @@ -596,7 +614,7 @@ export default function getAPIImplementation(): APIInterface { }: { formFields: { id: string; - value: string; + value: unknown; }[]; tenantId: string; session?: SessionContainerInterface; @@ -631,8 +649,26 @@ export default function getAPIImplementation(): APIInterface { "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_012)", }, }; - let email = formFields.filter((f) => f.id === "email")[0].value; - let password = formFields.filter((f) => f.id === "password")[0].value; + const emailAsUnknown = formFields.filter((f) => f.id === "email")[0].value; + const passwordAsUnknown = formFields.filter((f) => f.id === "password")[0].value; + + // NOTE: Following checks will likely never throw an error as the + // check for type is done in a parent function but they are kept + // here to be on the safe side. + if (typeof emailAsUnknown !== "string") + return { + status: "GENERAL_ERROR", + message: "email value needs to be a string", + }; + + if (typeof passwordAsUnknown !== "string") + return { + status: "GENERAL_ERROR", + message: "password value needs to be a string", + }; + + let email: string = emailAsUnknown; + let password: string = passwordAsUnknown; const recipeId = "emailpassword"; @@ -745,7 +781,7 @@ export default function getAPIImplementation(): APIInterface { }: { formFields: { id: string; - value: string; + value: unknown; }[]; tenantId: string; session?: SessionContainerInterface; @@ -780,8 +816,26 @@ export default function getAPIImplementation(): APIInterface { "Cannot sign in / up due to security reasons. Please contact support. (ERR_CODE_016)", }, }; - let email = formFields.filter((f) => f.id === "email")[0].value; - let password = formFields.filter((f) => f.id === "password")[0].value; + const emailAsUnknown = formFields.filter((f) => f.id === "email")[0].value; + const passwordAsUnknown = formFields.filter((f) => f.id === "password")[0].value; + + // NOTE: Following checks will likely never throw an error as the + // check for type is done in a parent function but they are kept + // here to be on the safe side. + if (typeof emailAsUnknown !== "string") + return { + status: "GENERAL_ERROR", + message: "email value needs to be a string", + }; + + if (typeof passwordAsUnknown !== "string") + return { + status: "GENERAL_ERROR", + message: "password value needs to be a string", + }; + + let email: string = emailAsUnknown; + let password: string = passwordAsUnknown; const preAuthCheckRes = await AuthUtils.preAuthChecks({ authenticatingAccountInfo: { diff --git a/lib/ts/recipe/emailpassword/api/passwordReset.ts b/lib/ts/recipe/emailpassword/api/passwordReset.ts index c6a26c010..9ab19df6b 100644 --- a/lib/ts/recipe/emailpassword/api/passwordReset.ts +++ b/lib/ts/recipe/emailpassword/api/passwordReset.ts @@ -39,7 +39,7 @@ export default async function passwordReset( // a password that meets the password policy. let formFields: { id: string; - value: string; + value: unknown; }[] = await validateFormFieldsOrThrowError( options.config.resetPasswordUsingTokenFeature.formFieldsForPasswordResetForm, requestBody.formFields, diff --git a/lib/ts/recipe/emailpassword/api/signin.ts b/lib/ts/recipe/emailpassword/api/signin.ts index 77c1161ff..8822cd421 100644 --- a/lib/ts/recipe/emailpassword/api/signin.ts +++ b/lib/ts/recipe/emailpassword/api/signin.ts @@ -33,7 +33,7 @@ export default async function signInAPI( // step 1 let formFields: { id: string; - value: string; + value: unknown; }[] = await validateFormFieldsOrThrowError( options.config.signInFeature.formFields, (await options.req.getJSONBody()).formFields, diff --git a/lib/ts/recipe/emailpassword/api/signup.ts b/lib/ts/recipe/emailpassword/api/signup.ts index ec9a2781d..aa7c3e863 100644 --- a/lib/ts/recipe/emailpassword/api/signup.ts +++ b/lib/ts/recipe/emailpassword/api/signup.ts @@ -37,7 +37,7 @@ export default async function signUpAPI( // step 1 let formFields: { id: string; - value: string; + value: unknown; }[] = await validateFormFieldsOrThrowError( options.config.signUpFeature.formFields, requestBody.formFields, diff --git a/lib/ts/recipe/emailpassword/api/utils.ts b/lib/ts/recipe/emailpassword/api/utils.ts index 23023ae1f..fa7959539 100644 --- a/lib/ts/recipe/emailpassword/api/utils.ts +++ b/lib/ts/recipe/emailpassword/api/utils.ts @@ -25,7 +25,7 @@ export async function validateFormFieldsOrThrowError( ): Promise< { id: string; - value: string; + value: unknown; }[] > { // first we check syntax ---------------------------- @@ -39,7 +39,7 @@ export async function validateFormFieldsOrThrowError( let formFields: { id: string; - value: string; + value: unknown; }[] = []; for (let i = 0; i < formFieldsRaw.length; i++) { @@ -63,7 +63,7 @@ export async function validateFormFieldsOrThrowError( if (field.id === FORM_FIELD_EMAIL_ID) { return { ...field, - value: field.value.trim(), + value: typeof field.value === "string" ? field.value.trim() : field.value, }; } return field; diff --git a/lib/ts/recipe/emailpassword/types.ts b/lib/ts/recipe/emailpassword/types.ts index ee8b481df..b02f5dfa3 100644 --- a/lib/ts/recipe/emailpassword/types.ts +++ b/lib/ts/recipe/emailpassword/types.ts @@ -228,7 +228,7 @@ export type APIInterface = { | ((input: { formFields: { id: string; - value: string; + value: unknown; }[]; tenantId: string; options: APIOptions; @@ -249,7 +249,7 @@ export type APIInterface = { | ((input: { formFields: { id: string; - value: string; + value: unknown; }[]; token: string; tenantId: string; @@ -273,7 +273,7 @@ export type APIInterface = { | ((input: { formFields: { id: string; - value: string; + value: unknown; }[]; tenantId: string; session: SessionContainerInterface | undefined; @@ -300,7 +300,7 @@ export type APIInterface = { | ((input: { formFields: { id: string; - value: string; + value: unknown; }[]; tenantId: string; session: SessionContainerInterface | undefined; diff --git a/test/with-typescript/index.ts b/test/with-typescript/index.ts index f16582249..517d79961 100644 --- a/test/with-typescript/index.ts +++ b/test/with-typescript/index.ts @@ -1348,8 +1348,26 @@ EmailPassword.init({ signInPOST: async (input) => { let formFields = input.formFields; let options = input.options; - let email = formFields.filter((f) => f.id === "email")[0].value; - let password = formFields.filter((f) => f.id === "password")[0].value; + const emailAsUnknown = formFields.filter((f) => f.id === "email")[0].value; + const passwordAsUnknown = formFields.filter((f) => f.id === "password")[0].value; + + // NOTE: Following checks will likely never throw an error as the + // check for type is done in a parent function but they are kept + // here to be on the safe side. + if (typeof emailAsUnknown !== "string") + return { + status: "GENERAL_ERROR", + message: "email value needs to be a string", + }; + + if (typeof passwordAsUnknown !== "string") + return { + status: "GENERAL_ERROR", + message: "password value needs to be a string", + }; + + let email: string = emailAsUnknown; + let password: string = passwordAsUnknown; let response = await options.recipeImplementation.signIn({ email,