Skip to content

Commit c5b94c1

Browse files
authored
chore: password reg (cgoinglove#310)
1 parent d19a2c4 commit c5b94c1

File tree

4 files changed

+72
-13
lines changed

4 files changed

+72
-13
lines changed

package.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,7 @@
153153
"vitest": "^3.2.4"
154154
},
155155
"lint-staged": {
156-
"*.{js,json,mjs,ts,yaml,tsx,css}": [
157-
"pnpm format",
158-
"pnpm lint:fix"
159-
]
156+
"*.{js,json,mjs,ts,yaml,tsx,css}": ["pnpm format", "pnpm lint:fix"]
160157
},
161158
"packageManager": "[email protected]",
162159
"engines": {

src/components/auth/email-sign-up.tsx

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useState } from "react";
3+
import { useState, useMemo } from "react";
44
import { Button } from "@/components/ui/button";
55
import { Input } from "@/components/ui/input";
66
import { Label } from "@/components/ui/label";
@@ -13,7 +13,7 @@ import {
1313
} from "@/components/ui/card";
1414
import { useObjectState } from "@/hooks/use-object-state";
1515
import { cn } from "lib/utils";
16-
import { ChevronLeft, Loader } from "lucide-react";
16+
import { ChevronLeft, Loader, Check, X } from "lucide-react";
1717
import { toast } from "sonner";
1818
import { safe } from "ts-safe";
1919
import { UserZodSchema } from "app-types/user";
@@ -42,6 +42,16 @@ export default function EmailSignUp({
4242
t("Auth.SignUp.step3"),
4343
];
4444

45+
// Password validation checklist
46+
const passwordValidation = useMemo(() => {
47+
const password = formData.password;
48+
return {
49+
hasMinLength: password.length >= 8 && password.length <= 20,
50+
hasLetter: /[a-zA-Z]/.test(password),
51+
hasNumber: /\d/.test(password),
52+
};
53+
}, [formData.password]);
54+
4555
const safeProcessWithLoading = function <T>(fn: () => Promise<T>) {
4656
setIsLoading(true);
4757
return safe(() => fn()).watch(() => setIsLoading(false));
@@ -195,6 +205,58 @@ export default function EmailSignUp({
195205
onChange={(e) => setFormData({ password: e.target.value })}
196206
required
197207
/>
208+
{formData.password && (
209+
<div className="space-y-1 mt-2">
210+
<div className="flex items-center gap-2 text-xs">
211+
{passwordValidation.hasMinLength ? (
212+
<Check className="size-3 text-primary" />
213+
) : (
214+
<X className="size-3 text-destructive" />
215+
)}
216+
<span
217+
className={
218+
passwordValidation.hasMinLength
219+
? "text-primary"
220+
: "text-muted-foreground"
221+
}
222+
>
223+
8-20 characters
224+
</span>
225+
</div>
226+
<div className="flex items-center gap-2 text-xs">
227+
{passwordValidation.hasLetter ? (
228+
<Check className="size-3 text-primary" />
229+
) : (
230+
<X className="size-3 text-destructive" />
231+
)}
232+
<span
233+
className={
234+
passwordValidation.hasLetter
235+
? "text-primary"
236+
: "text-muted-foreground"
237+
}
238+
>
239+
At least one letter
240+
</span>
241+
</div>
242+
<div className="flex items-center gap-2 text-xs">
243+
{passwordValidation.hasNumber ? (
244+
<Check className="size-3 text-primary" />
245+
) : (
246+
<X className="size-3 text-destructive" />
247+
)}
248+
<span
249+
className={
250+
passwordValidation.hasNumber
251+
? "text-primary"
252+
: "text-muted-foreground"
253+
}
254+
>
255+
At least one number
256+
</span>
257+
</div>
258+
</div>
259+
)}
198260
</div>
199261
)}
200262
<p className="text-muted-foreground text-xs mb-6">

src/lib/validations/password.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { z } from "zod";
22

33
export const passwordRegexPattern =
44
process.env.NEXT_PUBLIC_PASSWORD_REGEX_PATTERN ||
5-
"^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?]).{8,20}$";
5+
"^(?=.*[a-zA-Z])(?=.*\\d)[a-zA-Z\\d!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?]{8,20}$";
66

77
export const passwordRequirementsText =
88
process.env.NEXT_PUBLIC_PASSWORD_REQUIREMENTS_TEXT ||
9-
"Password must be 8-20 characters and contain at least one uppercase letter, one lowercase letter, one number, and one special character.";
9+
"Password must be 8-20 characters and contain at least one letter and one number.";
1010

1111
// Shared password validation schema
1212
export const passwordSchema = z

tests/auth/signup.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ test.describe("User Signup", () => {
88
Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
99
const testEmail = `playwright.signup.${uniqueSuffix}@example.com`;
1010
const testName = `Signup Test User ${uniqueSuffix}`;
11-
const testPassword = "SignupTest123!";
11+
const testPassword = "SignupTest123";
1212

1313
// Navigate to sign-up page
1414
await page.goto("/sign-up");
@@ -206,8 +206,8 @@ test.describe("User Signup", () => {
206206
await expect(passwordInput).toBeVisible();
207207
await expect(page.getByText(/Step 3 of 3/i)).toBeVisible();
208208

209-
// Try weak password
210-
await passwordInput.fill("weak");
209+
// Try weak password (no number)
210+
await passwordInput.fill("abcdefgh");
211211

212212
const createButton = page.getByRole("button", {
213213
name: "Create account",
@@ -219,9 +219,9 @@ test.describe("User Signup", () => {
219219
await expect(passwordInput).toBeVisible();
220220
await expect(page.getByText(/Step 3 of 3/i)).toBeVisible();
221221

222-
// Try with strong password
222+
// Try with strong password (letter + number, no special char required)
223223
await passwordInput.clear();
224-
await passwordInput.fill("Strong123!");
224+
await passwordInput.fill("Test1234");
225225
await createButton.click();
226226

227227
// Should either succeed or show server validation error

0 commit comments

Comments
 (0)