mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
feat(register): password confirmation (#85)
Origin: https://github.com/hyperdxio/hyperdx/pull/81#discussion_r1384379205 Add a password confirmation field to the registration form. - API will require `confirmPassword` field on the registration endpoint - The `confirmPassword` and `password` fields will be validated by zod on API validation - UI on the registration form will have a "Confirm Password" field - Validation errors will be displayed on the UI the same way other validation errors - API test covers mismatch between `confirmPassword` and `password` (simple check, can be improved)
This commit is contained in:
parent
f66200717e
commit
b1a537d88c
5 changed files with 79 additions and 30 deletions
6
.changeset/poor-chefs-glow.md
Normal file
6
.changeset/poor-chefs-glow.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
'@hyperdx/api': minor
|
||||
'@hyperdx/app': minor
|
||||
---
|
||||
|
||||
feat(register): password confirmation
|
||||
|
|
@ -20,7 +20,14 @@ describe('team router', () => {
|
|||
const login = async () => {
|
||||
const agent = getAgent(server);
|
||||
|
||||
await agent.post('/register/password').send(MOCK_USER).expect(200);
|
||||
await agent
|
||||
.post('/register/password')
|
||||
.send({ ...MOCK_USER, confirmPassword: 'wrong-password' })
|
||||
.expect(400);
|
||||
await agent
|
||||
.post('/register/password')
|
||||
.send({ ...MOCK_USER, confirmPassword: MOCK_USER.password })
|
||||
.expect(200);
|
||||
|
||||
const user = await findUserByEmail(MOCK_USER.email);
|
||||
const team = await getTeam(user?.team as any);
|
||||
|
|
|
|||
|
|
@ -15,24 +15,30 @@ import {
|
|||
handleAuthError,
|
||||
} from '../../middleware/auth';
|
||||
|
||||
const registrationSchema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z
|
||||
.string()
|
||||
.min(12, 'Password must have at least 12 characters')
|
||||
.refine(
|
||||
pass => /[a-z]/.test(pass) && /[A-Z]/.test(pass),
|
||||
'Password must include both lower and upper case characters',
|
||||
)
|
||||
.refine(
|
||||
pass => /\d/.test(pass),
|
||||
'Password must include at least one number',
|
||||
)
|
||||
.refine(
|
||||
pass => /[!@#$%^&*(),.?":{}|<>]/.test(pass),
|
||||
'Password must include at least one special character',
|
||||
),
|
||||
});
|
||||
const registrationSchema = z
|
||||
.object({
|
||||
email: z.string().email(),
|
||||
password: z
|
||||
.string()
|
||||
.min(12, 'Password must have at least 12 characters')
|
||||
.refine(
|
||||
pass => /[a-z]/.test(pass) && /[A-Z]/.test(pass),
|
||||
'Password must include both lower and upper case characters',
|
||||
)
|
||||
.refine(
|
||||
pass => /\d/.test(pass),
|
||||
'Password must include at least one number',
|
||||
)
|
||||
.refine(
|
||||
pass => /[!@#$%^&*(),.?":{}|<>]/.test(pass),
|
||||
'Password must include at least one special character',
|
||||
),
|
||||
confirmPassword: z.string(),
|
||||
})
|
||||
.refine(data => data.password === data.confirmPassword, {
|
||||
message: "Passwords don't match",
|
||||
path: ['confirmPassword'],
|
||||
});
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { API_SERVER_URL } from './config';
|
|||
import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import cx from 'classnames';
|
||||
|
||||
import LandingHeader from './LandingHeader';
|
||||
import * as config from './config';
|
||||
|
|
@ -13,6 +14,7 @@ import api from './api';
|
|||
type FormData = {
|
||||
email: string;
|
||||
password: string;
|
||||
confirmPassword: string;
|
||||
};
|
||||
|
||||
export default function AuthPage({ action }: { action: 'register' | 'login' }) {
|
||||
|
|
@ -45,7 +47,11 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) {
|
|||
|
||||
const onSubmit: SubmitHandler<FormData> = data =>
|
||||
registerPassword.mutate(
|
||||
{ email: data.email, password: data.password },
|
||||
{
|
||||
email: data.email,
|
||||
password: data.password,
|
||||
confirmPassword: data.confirmPassword,
|
||||
},
|
||||
{
|
||||
onSuccess: () => router.push('/search'),
|
||||
onError: async error => {
|
||||
|
|
@ -73,6 +79,7 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) {
|
|||
controller: { onSubmit: handleSubmit(onSubmit) },
|
||||
email: register('email', { required: true }),
|
||||
password: register('password', { required: true }),
|
||||
confirmPassword: register('confirmPassword', { required: true }),
|
||||
}
|
||||
: {
|
||||
controller: {
|
||||
|
|
@ -135,9 +142,28 @@ export default function AuthPage({ action }: { action: 'register' | 'login' }) {
|
|||
data-test-id="form-password"
|
||||
id="password"
|
||||
type="password"
|
||||
className="border-0"
|
||||
className={cx('border-0', {
|
||||
'mb-3': isRegister,
|
||||
})}
|
||||
{...form.password}
|
||||
/>
|
||||
{isRegister && (
|
||||
<>
|
||||
<Form.Label
|
||||
htmlFor="confirmPassword"
|
||||
className="text-start text-muted fs-7.5 mb-1"
|
||||
>
|
||||
Confirm Password
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
data-test-id="form-confirm-password"
|
||||
id="confirmPassword"
|
||||
type="password"
|
||||
className="border-0"
|
||||
{...form.confirmPassword}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{isRegister && Object.keys(errors).length > 0 && (
|
||||
<div className="text-danger mt-2">
|
||||
{Object.values(errors).map((error, index) => (
|
||||
|
|
|
|||
|
|
@ -545,15 +545,19 @@ const api = {
|
|||
);
|
||||
},
|
||||
useRegisterPassword() {
|
||||
return useMutation<any, HTTPError, { email: string; password: string }>(
|
||||
async ({ email, password }) =>
|
||||
server(`register/password`, {
|
||||
method: 'POST',
|
||||
json: {
|
||||
email,
|
||||
password,
|
||||
},
|
||||
}).json(),
|
||||
return useMutation<
|
||||
any,
|
||||
HTTPError,
|
||||
{ email: string; password: string; confirmPassword: string }
|
||||
>(async ({ email, password, confirmPassword }) =>
|
||||
server(`register/password`, {
|
||||
method: 'POST',
|
||||
json: {
|
||||
email,
|
||||
password,
|
||||
confirmPassword,
|
||||
},
|
||||
}).json(),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue