Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Add changes to support unknown type in formField values #928

Merged
merged 7 commits into from
Sep 25, 2024
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
56 changes: 49 additions & 7 deletions lib/build/recipe/emailpassword/api/implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,15 @@ function getAPIImplementation() {
};
},
deepjyoti30-st marked this conversation as resolved.
Show resolved Hide resolved
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.
Expand Down Expand Up @@ -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,
Expand All @@ -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",
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion lib/build/recipe/emailpassword/api/utils.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ export declare function validateFormFieldsOrThrowError(
): Promise<
{
id: string;
value: string;
value: unknown;
}[]
>;
6 changes: 4 additions & 2 deletions lib/build/recipe/emailpassword/api/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@ 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);
}
// 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;
});
Expand Down
8 changes: 4 additions & 4 deletions lib/build/recipe/emailpassword/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ export declare type APIInterface = {
| ((input: {
formFields: {
id: string;
value: string;
value: unknown;
}[];
tenantId: string;
options: APIOptions;
Expand All @@ -247,7 +247,7 @@ export declare type APIInterface = {
| ((input: {
formFields: {
id: string;
value: string;
value: unknown;
}[];
token: string;
tenantId: string;
Expand All @@ -273,7 +273,7 @@ export declare type APIInterface = {
| ((input: {
formFields: {
id: string;
value: string;
value: unknown;
}[];
tenantId: string;
session: SessionContainerInterface | undefined;
Expand All @@ -300,7 +300,7 @@ export declare type APIInterface = {
| ((input: {
formFields: {
id: string;
value: string;
value: unknown;
}[];
tenantId: string;
session: SessionContainerInterface | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
117 changes: 107 additions & 10 deletions lib/ts/recipe/emailpassword/api/implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
rishabhpoddar marked this conversation as resolved.
Show resolved Hide resolved
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(
Expand Down Expand Up @@ -321,7 +330,7 @@ export default function getAPIImplementation(): APIInterface {
}: {
formFields: {
id: string;
value: string;
value: unknown;
}[];
token: string;
tenantId: string;
Expand Down Expand Up @@ -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,
Expand All @@ -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",
Expand Down Expand Up @@ -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)",
Expand All @@ -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";

Expand Down Expand Up @@ -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)",
Expand All @@ -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: {
Expand Down
2 changes: 1 addition & 1 deletion lib/ts/recipe/emailpassword/api/passwordReset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion lib/ts/recipe/emailpassword/api/signin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion lib/ts/recipe/emailpassword/api/signup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading
Loading