diff --git a/CHANGELOG.md b/CHANGELOG.md index 175e36eeb..1cd1ce9dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added OAuth2Provider recipe +### Breaking change + +- Changes type of value in formField object to be `unknown` instead of `string` to add support for accepting any type of value in form fields. + ## [20.1.2] - 2024-09-14 - Fixes formFields to accept non string types as well. diff --git a/lib/build/recipe/emailpassword/api/implementation.js b/lib/build/recipe/emailpassword/api/implementation.js index 0d4ebc815..6619c14cf 100644 --- a/lib/build/recipe/emailpassword/api/implementation.js +++ b/lib/build/recipe/emailpassword/api/implementation.js @@ -40,7 +40,15 @@ 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") + throw new Error( + "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" + ); + 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 +363,15 @@ 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") + throw new Error( + "Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError" + ); + let newPassword = newPasswordAsUnknown; let tokenConsumptionResponse = await options.recipeImplementation.consumePasswordResetToken({ token, tenantId, @@ -371,7 +387,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", @@ -503,8 +519,21 @@ 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") + throw new Error( + "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" + ); + if (typeof passwordAsUnknown !== "string") + throw new Error( + "Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError" + ); + let email = emailAsUnknown; + let password = passwordAsUnknown; const recipeId = "emailpassword"; const checkCredentialsOnTenant = async (tenantId) => { return ( @@ -635,8 +664,21 @@ 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") + throw new Error( + "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" + ); + if (typeof passwordAsUnknown !== "string") + throw new Error( + "Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError" + ); + 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..69da81aba 100644 --- a/lib/build/recipe/emailpassword/api/utils.js +++ b/lib/build/recipe/emailpassword/api/utils.js @@ -27,7 +27,7 @@ async function validateFormFieldsOrThrowError(configFormFields, formFieldsRaw, t } if (curr.id === constants_1.FORM_FIELD_EMAIL_ID || curr.id === constants_1.FORM_FIELD_PASSWORD_ID) { if (typeof curr.value !== "string") { - throw newBadRequestError("The value of formFields with id = " + curr.id + " must be a string"); + throw newBadRequestError(`${curr.id} value must be a string`); } } formFields.push(curr); @@ -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 ee247490e..fa3eb1408 100644 --- a/lib/build/recipe/emailpassword/types.d.ts +++ b/lib/build/recipe/emailpassword/types.d.ts @@ -227,7 +227,7 @@ export declare type APIInterface = { | ((input: { formFields: { id: string; - value: string; + value: unknown; }[]; tenantId: string; options: APIOptions; @@ -247,7 +247,7 @@ export declare type APIInterface = { | ((input: { formFields: { id: string; - value: string; + value: unknown; }[]; token: string; tenantId: string; @@ -273,7 +273,7 @@ export declare type APIInterface = { | ((input: { formFields: { id: string; - value: string; + value: unknown; }[]; tenantId: string; session: SessionContainerInterface | undefined; @@ -300,7 +300,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 e08c0ea09..635ff2bd6 100644 --- a/lib/ts/recipe/emailpassword/api/implementation.ts +++ b/lib/ts/recipe/emailpassword/api/implementation.ts @@ -9,6 +9,7 @@ import RecipeUserId from "../../../recipeUserId"; import { getPasswordResetLink } from "../utils"; import { AuthUtils } from "../../../authUtils"; import { isFakeEmail } from "../../thirdparty/utils"; +import { SessionContainerInterface } from "../../session/types"; export default function getAPIImplementation(): APIInterface { return { @@ -64,7 +65,15 @@ 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") + throw new Error( + "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" + ); + const email: string = emailAsUnknown; // this function will be reused in different parts of the flow below.. async function generateAndSendPasswordResetToken( @@ -321,7 +330,7 @@ export default function getAPIImplementation(): APIInterface { }: { formFields: { id: string; - value: string; + value: unknown; }[]; token: string; tenantId: string; @@ -450,7 +459,15 @@ 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") + throw new Error( + "Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError" + ); + let newPassword: string = newPasswordAsUnknown; let tokenConsumptionResponse = await options.recipeImplementation.consumePasswordResetToken({ token, @@ -471,7 +488,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", @@ -593,7 +610,31 @@ export default function getAPIImplementation(): APIInterface { shouldTryLinkingWithSessionUser, options, userContext, - }) { + }: { + formFields: { + id: string; + value: unknown; + }[]; + tenantId: string; + session?: SessionContainerInterface; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + session: SessionContainerInterface; + user: User; + } + | { + status: "WRONG_CREDENTIALS_ERROR"; + } + | { + status: "SIGN_IN_NOT_ALLOWED"; + reason: string; + } + | GeneralErrorResponse + > { const errorCodeMap = { SIGN_IN_NOT_ALLOWED: "Cannot sign in due to security reasons. Please try resetting your password, use a different login method or contact support. (ERR_CODE_008)", @@ -608,8 +649,24 @@ 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") + throw new Error( + "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" + ); + + if (typeof passwordAsUnknown !== "string") + throw new Error( + "Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError" + ); + + let email: string = emailAsUnknown; + let password: string = passwordAsUnknown; const recipeId = "emailpassword"; @@ -722,7 +779,31 @@ export default function getAPIImplementation(): APIInterface { shouldTryLinkingWithSessionUser, options, userContext, - }) { + }: { + formFields: { + id: string; + value: unknown; + }[]; + tenantId: string; + session?: SessionContainerInterface; + shouldTryLinkingWithSessionUser: boolean | undefined; + options: APIOptions; + userContext: UserContext; + }): Promise< + | { + status: "OK"; + session: SessionContainerInterface; + user: User; + } + | { + status: "SIGN_UP_NOT_ALLOWED"; + reason: string; + } + | { + status: "EMAIL_ALREADY_EXISTS_ERROR"; + } + | GeneralErrorResponse + > { const errorCodeMap = { SIGN_UP_NOT_ALLOWED: "Cannot sign up due to security reasons. Please try logging in, use a different login method or contact support. (ERR_CODE_007)", @@ -737,8 +818,24 @@ 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") + throw new Error( + "Should never come here since we already check that the email value is a string in validateFormFieldsOrThrowError" + ); + + if (typeof passwordAsUnknown !== "string") + throw new Error( + "Should never come here since we already check that the password value is a string in validateFormFieldsOrThrowError" + ); + + 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 2899715cf..899d06bc0 100644 --- a/lib/ts/recipe/emailpassword/api/signin.ts +++ b/lib/ts/recipe/emailpassword/api/signin.ts @@ -38,7 +38,7 @@ export default async function signInAPI( // step 1 let formFields: { id: string; - value: string; + value: unknown; }[] = await validateFormFieldsOrThrowError( options.config.signInFeature.formFields, body.formFields, diff --git a/lib/ts/recipe/emailpassword/api/signup.ts b/lib/ts/recipe/emailpassword/api/signup.ts index 67a4b49e1..bb4c1efa2 100644 --- a/lib/ts/recipe/emailpassword/api/signup.ts +++ b/lib/ts/recipe/emailpassword/api/signup.ts @@ -41,7 +41,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..71204187d 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++) { @@ -52,7 +52,7 @@ export async function validateFormFieldsOrThrowError( } if (curr.id === FORM_FIELD_EMAIL_ID || curr.id === FORM_FIELD_PASSWORD_ID) { if (typeof curr.value !== "string") { - throw newBadRequestError("The value of formFields with id = " + curr.id + " must be a string"); + throw newBadRequestError(`${curr.id} value must be a string`); } } formFields.push(curr); @@ -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 563c95d4f..0fdfbe088 100644 --- a/lib/ts/recipe/emailpassword/types.ts +++ b/lib/ts/recipe/emailpassword/types.ts @@ -230,7 +230,7 @@ export type APIInterface = { | ((input: { formFields: { id: string; - value: string; + value: unknown; }[]; tenantId: string; options: APIOptions; @@ -251,7 +251,7 @@ export type APIInterface = { | ((input: { formFields: { id: string; - value: string; + value: unknown; }[]; token: string; tenantId: string; @@ -275,7 +275,7 @@ export type APIInterface = { | ((input: { formFields: { id: string; - value: string; + value: unknown; }[]; tenantId: string; session: SessionContainerInterface | undefined; @@ -303,7 +303,7 @@ export type APIInterface = { | ((input: { formFields: { id: string; - value: string; + value: unknown; }[]; tenantId: string; session: SessionContainerInterface | undefined; diff --git a/test/emailpassword/passwordreset.test.js b/test/emailpassword/passwordreset.test.js index f4d43b106..e8a579f6f 100644 --- a/test/emailpassword/passwordreset.test.js +++ b/test/emailpassword/passwordreset.test.js @@ -111,6 +111,48 @@ describe(`passwordreset: ${printPath("[test/emailpassword/passwordreset.test.js] assert(response.body.formFields[0].id === "email"); }); + it("test invalid email type in generate token API", async function () { + const connectionURI = await startST(); + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [EmailPassword.init(), Session.init()], + }); + const app = express(); + + app.use(middleware()); + + app.use(errorHandler()); + + let response = await new Promise((resolve) => + request(app) + .post("/auth/user/password/reset/token") + .send({ + formFields: [ + { + id: "email", + value: 123456, + }, + ], + }) + .expect(400) + .end((err, res) => { + if (err) { + resolve(undefined); + } else { + resolve(res); + } + }) + ); + assert(response.body.message === "email value must be a string"); + }); + it("test that generated password link is correct", async function () { const connectionURI = await startST(); @@ -255,6 +297,49 @@ describe(`passwordreset: ${printPath("[test/emailpassword/passwordreset.test.js] assert(response.status !== "FIELD_ERROR"); }); + it("test invalid type of password", async function () { + const connectionURI = await startST(); + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [EmailPassword.init(), Session.init()], + }); + const app = express(); + + app.use(middleware()); + + app.use(errorHandler()); + + let response = await new Promise((resolve) => + request(app) + .post("/auth/user/password/reset") + .send({ + formFields: [ + { + id: "password", + value: 12345, + }, + ], + token: "randomToken", + }) + .expect(400) + .end((err, res) => { + if (err) { + resolve(undefined); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + assert(response.message === "password value must be a string"); + }); + it("test token missing from input", async function () { const connectionURI = await startST(); STExpress.init({ diff --git a/test/emailpassword/signinFeature.test.js b/test/emailpassword/signinFeature.test.js index 21e6e0d5c..15f95f7d6 100644 --- a/test/emailpassword/signinFeature.test.js +++ b/test/emailpassword/signinFeature.test.js @@ -351,7 +351,7 @@ describe(`signinFeature: ${printPath("[test/emailpassword/signinFeature.test.js] } }) ); - assert(JSON.parse(res.text).message === "The value of formFields with id = password must be a string"); + assert(JSON.parse(res.text).message === "password value must be a string"); }); it("test email must be of type string in input", async function () { @@ -404,7 +404,7 @@ describe(`signinFeature: ${printPath("[test/emailpassword/signinFeature.test.js] } }) ); - assert(JSON.parse(res.text).message === "The value of formFields with id = email must be a string"); + assert(JSON.parse(res.text).message === "email value must be a string"); }); /* diff --git a/test/emailpassword/signupFeature.test.js b/test/emailpassword/signupFeature.test.js index 4f337ef8b..c9a5299ab 100644 --- a/test/emailpassword/signupFeature.test.js +++ b/test/emailpassword/signupFeature.test.js @@ -357,6 +357,102 @@ describe(`signupFeature: ${printPath("[test/emailpassword/signupFeature.test.js] assert.strictEqual(badInputResponse.message, "Missing input param: formFields"); }); + it("test bad input, invalid password type in /signup API", async function () { + const connectionURI = await startST(); + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [EmailPassword.init(), Session.init({ getTokenTransferMethod: () => "cookie" })], + }); + + const app = express(); + + app.use(middleware()); + + app.use(errorHandler()); + + let badInputResponse = await new Promise((resolve) => + request(app) + .post("/auth/signup") + .send({ + formFields: [ + { + id: "email", + value: "random@gmail.com", + }, + { + id: "password", + value: 1234, + }, + ], + }) + .expect(400) + .end((err, res) => { + if (err) { + resolve(undefined); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + assert(badInputResponse.message === "password value must be a string"); + }); + + it("test bad input, invalid email type in /signup API", async function () { + const connectionURI = await startST(); + + STExpress.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + websiteDomain: "supertokens.io", + }, + recipeList: [EmailPassword.init(), Session.init({ getTokenTransferMethod: () => "cookie" })], + }); + + const app = express(); + + app.use(middleware()); + + app.use(errorHandler()); + + let badInputResponse = await new Promise((resolve) => + request(app) + .post("/auth/signup") + .send({ + formFields: [ + { + id: "email", + value: 12435, + }, + { + id: "password", + value: "1234", + }, + ], + }) + .expect(400) + .end((err, res) => { + if (err) { + resolve(undefined); + } else { + resolve(JSON.parse(res.text)); + } + }) + ); + assert(badInputResponse.message === "email value must be a string"); + }); + it("test bad input, formFields is not an array in /signup API", async function () { const connectionURI = await startST(); diff --git a/test/with-typescript/index.ts b/test/with-typescript/index.ts index c60e07968..437f15fe0 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,