Skip to content

Commit 79f0926

Browse files
MehulZRUdit-takkar
andauthored
fix: signup password hint validation (#10454)
Co-authored-by: Udit Takkar <[email protected]>
1 parent b285f27 commit 79f0926

File tree

6 files changed

+49
-58
lines changed

6 files changed

+49
-58
lines changed

apps/web/components/setup/AdminUser.tsx

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,18 @@ export const AdminUser = (props: { onSubmit: () => void; onError: () => void; on
5757
}),
5858
});
5959

60-
const formMethods = useForm<{
61-
username: string;
62-
email_address: string;
63-
full_name: string;
64-
password: string;
65-
}>({
60+
type formSchemaType = z.infer<typeof formSchema>;
61+
62+
const formMethods = useForm<formSchemaType>({
63+
mode: "onChange",
6664
resolver: zodResolver(formSchema),
6765
});
6866

6967
const onError = () => {
7068
props.onError();
7169
};
7270

73-
const onSubmit = formMethods.handleSubmit(async (data: z.infer<typeof formSchema>) => {
71+
const onSubmit = formMethods.handleSubmit(async (data) => {
7472
props.onSubmit();
7573
const response = await fetch("/api/auth/setup", {
7674
method: "POST",
@@ -130,11 +128,7 @@ export const AdminUser = (props: { onSubmit: () => void; onError: () => void; on
130128
className={classNames("my-0", longWebsiteUrl && "rounded-t-none")}
131129
onBlur={onBlur}
132130
name="username"
133-
onChange={async (e) => {
134-
onChange(e.target.value);
135-
formMethods.setValue("username", e.target.value);
136-
await formMethods.trigger("username");
137-
}}
131+
onChange={(e) => onChange(e.target.value)}
138132
/>
139133
</>
140134
)}
@@ -148,11 +142,7 @@ export const AdminUser = (props: { onSubmit: () => void; onError: () => void; on
148142
<TextField
149143
value={value || ""}
150144
onBlur={onBlur}
151-
onChange={async (e) => {
152-
onChange(e.target.value);
153-
formMethods.setValue("full_name", e.target.value);
154-
await formMethods.trigger("full_name");
155-
}}
145+
onChange={(e) => onChange(e.target.value)}
156146
color={formMethods.formState.errors.full_name ? "warn" : ""}
157147
type="text"
158148
name="full_name"
@@ -172,11 +162,7 @@ export const AdminUser = (props: { onSubmit: () => void; onError: () => void; on
172162
<EmailField
173163
value={value || ""}
174164
onBlur={onBlur}
175-
onChange={async (e) => {
176-
onChange(e.target.value);
177-
formMethods.setValue("email_address", e.target.value);
178-
await formMethods.trigger("email_address");
179-
}}
165+
onChange={(e) => onChange(e.target.value)}
180166
className="my-0"
181167
name="email_address"
182168
/>
@@ -191,11 +177,7 @@ export const AdminUser = (props: { onSubmit: () => void; onError: () => void; on
191177
<PasswordField
192178
value={value || ""}
193179
onBlur={onBlur}
194-
onChange={async (e) => {
195-
onChange(e.target.value);
196-
formMethods.setValue("password", e.target.value);
197-
await formMethods.trigger("password");
198-
}}
180+
onChange={(e) => onChange(e.target.value)}
199181
hintErrors={["caplow", "admin_min", "num"]}
200182
name="password"
201183
className="my-0"

apps/web/pages/api/auth/signup.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type { NextApiRequest, NextApiResponse } from "next";
2-
import { z } from "zod";
32

43
import dayjs from "@calcom/dayjs";
54
import { checkPremiumUsername } from "@calcom/ee/common/lib/checkPremiumUsername";
@@ -11,18 +10,9 @@ import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
1110
import { validateUsernameInTeam, validateUsername } from "@calcom/lib/validateUsername";
1211
import prisma from "@calcom/prisma";
1312
import { IdentityProvider } from "@calcom/prisma/enums";
13+
import { signupSchema } from "@calcom/prisma/zod-utils";
1414
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
1515

16-
const signupSchema = z.object({
17-
username: z.string().refine((value) => !value.includes("+"), {
18-
message: "String should not contain a plus symbol (+).",
19-
}),
20-
email: z.string().email(),
21-
password: z.string().min(7),
22-
language: z.string().optional(),
23-
token: z.string().optional(),
24-
});
25-
2616
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
2717
if (req.method !== "POST") {
2818
return res.status(405).end();

apps/web/pages/signup.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
1717
import slugify from "@calcom/lib/slugify";
1818
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
1919
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
20+
import { signupSchema as apiSignupSchema } from "@calcom/prisma/zod-utils";
2021
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
2122
import { Alert, Button, EmailField, HeadSeo, PasswordField, TextField } from "@calcom/ui";
2223

@@ -25,14 +26,7 @@ import PageWrapper from "@components/PageWrapper";
2526
import { IS_GOOGLE_LOGIN_ENABLED } from "../server/lib/constants";
2627
import { ssrInit } from "../server/lib/ssr";
2728

28-
const signupSchema = z.object({
29-
username: z.string().refine((value) => !value.includes("+"), {
30-
message: "String should not contain a plus symbol (+).",
31-
}),
32-
email: z.string().email(),
33-
password: z.string().min(7),
34-
language: z.string().optional(),
35-
token: z.string().optional(),
29+
const signupSchema = apiSignupSchema.extend({
3630
apiError: z.string().optional(), // Needed to display API errors doesnt get passed to the API
3731
});
3832

@@ -46,6 +40,7 @@ export default function Signup({ prepopulateFormValues, token, orgSlug }: Signup
4640
const { t, i18n } = useLocale();
4741
const flags = useFlagMap();
4842
const methods = useForm<FormValues>({
43+
mode: "onChange",
4944
resolver: zodResolver(signupSchema),
5045
defaultValues: prepopulateFormValues,
5146
});

packages/features/auth/lib/isPasswordValid.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,11 @@ export function isPasswordValid(password: string, breakdown?: boolean, strict?:
1010
num = false, // At least one number
1111
min = false, // Eight characters, or fifteen in strict mode.
1212
admin_min = false;
13-
if (password.length > 7 && (!strict || password.length > 14)) min = true;
13+
if (password.length >= 7 && (!strict || password.length > 14)) min = true;
1414
if (strict && password.length > 14) admin_min = true;
15-
for (let i = 0; i < password.length; i++) {
16-
if (!isNaN(parseInt(password[i]))) num = true;
17-
else {
18-
if (password[i] === password[i].toUpperCase()) cap = true;
19-
if (password[i] === password[i].toLowerCase()) low = true;
20-
}
21-
}
15+
if (password.match(/\d/)) num = true;
16+
if (password.match(/[a-z]/)) low = true;
17+
if (password.match(/[A-Z]/)) cap = true;
2218

2319
if (!breakdown) return cap && low && num && min && (strict ? admin_min : true);
2420

packages/prisma/zod-utils.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {
1414

1515
import { appDataSchemas } from "@calcom/app-store/apps.schemas.generated";
1616
import dayjs from "@calcom/dayjs";
17+
import { isPasswordValid } from "@calcom/features/auth/lib/isPasswordValid";
1718
import type { FieldType as FormBuilderFieldType } from "@calcom/features/form-builder/schema";
1819
import { fieldsSchema as formBuilderFieldsSchema } from "@calcom/features/form-builder/schema";
1920
import { isSupportedTimeZone } from "@calcom/lib/date-fns";
@@ -602,6 +603,28 @@ export const emailSchemaRefinement = (value: string) => {
602603
return emailRegex.test(value);
603604
};
604605

606+
export const signupSchema = z.object({
607+
username: z.string().refine((value) => !value.includes("+"), {
608+
message: "String should not contain a plus symbol (+).",
609+
}),
610+
email: z.string().email(),
611+
password: z.string().superRefine((data, ctx) => {
612+
const isStrict = false;
613+
const result = isPasswordValid(data, true, isStrict);
614+
Object.keys(result).map((key: string) => {
615+
if (!result[key as keyof typeof result]) {
616+
ctx.addIssue({
617+
code: z.ZodIssueCode.custom,
618+
path: [key],
619+
message: key,
620+
});
621+
}
622+
});
623+
}),
624+
language: z.string().optional(),
625+
token: z.string().optional(),
626+
});
627+
605628
export const ZVerifyCodeInputSchema = z.object({
606629
email: z.string().email(),
607630
code: z.string(),
@@ -610,3 +633,4 @@ export const ZVerifyCodeInputSchema = z.object({
610633
export type ZVerifyCodeInputSchema = z.infer<typeof ZVerifyCodeInputSchema>;
611634

612635
export const coerceToDate = z.coerce.date();
636+

packages/ui/components/form/inputs/HintOrErrors.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,21 @@ import { useFormContext } from "react-hook-form";
33

44
import { Check, Circle, Info, X } from "../../icon";
55

6-
export function HintsOrErrors<T extends FieldValues = FieldValues>(props: {
6+
type hintsOrErrorsProps = {
77
hintErrors?: string[];
88
fieldName: string;
99
t: (key: string) => string;
10-
}) {
10+
};
11+
12+
export function HintsOrErrors<T extends FieldValues = FieldValues>({
13+
hintErrors,
14+
fieldName,
15+
t,
16+
}: hintsOrErrorsProps) {
1117
const methods = useFormContext() as ReturnType<typeof useFormContext> | null;
1218
/* If there's no methods it means we're using these components outside a React Hook Form context */
1319
if (!methods) return null;
1420
const { formState } = methods;
15-
const { hintErrors, fieldName, t } = props;
16-
1721
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
1822
// @ts-ignore
1923
const fieldErrors: FieldErrors<T> | undefined = formState.errors[fieldName];

0 commit comments

Comments
 (0)