feat: require old password for password reset (#488)

* feat: require old password for password reset
This commit is contained in:
Udit Takkar
2023-10-04 09:53:57 +05:30
committed by GitHub
parent 381a248543
commit 693249916d
4 changed files with 63 additions and 13 deletions

View File

@ -20,6 +20,7 @@ import { FormErrorMessage } from '../form/form-error-message';
export const ZPasswordFormSchema = z export const ZPasswordFormSchema = z
.object({ .object({
currentPassword: z.string().min(6).max(72),
password: z.string().min(6).max(72), password: z.string().min(6).max(72),
repeatedPassword: z.string().min(6).max(72), repeatedPassword: z.string().min(6).max(72),
}) })
@ -40,6 +41,7 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [showCurrentPassword, setShowCurrentPassword] = useState(false);
const { const {
register, register,
@ -48,6 +50,7 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
formState: { errors, isSubmitting }, formState: { errors, isSubmitting },
} = useForm<TPasswordFormSchema>({ } = useForm<TPasswordFormSchema>({
values: { values: {
currentPassword: '',
password: '', password: '',
repeatedPassword: '', repeatedPassword: '',
}, },
@ -56,9 +59,10 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
const { mutateAsync: updatePassword } = trpc.profile.updatePassword.useMutation(); const { mutateAsync: updatePassword } = trpc.profile.updatePassword.useMutation();
const onFormSubmit = async ({ password }: TPasswordFormSchema) => { const onFormSubmit = async ({ currentPassword, password }: TPasswordFormSchema) => {
try { try {
await updatePassword({ await updatePassword({
currentPassword,
password, password,
}); });
@ -92,6 +96,39 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
className={cn('flex w-full flex-col gap-y-4', className)} className={cn('flex w-full flex-col gap-y-4', className)}
onSubmit={handleSubmit(onFormSubmit)} onSubmit={handleSubmit(onFormSubmit)}
> >
<div>
<Label htmlFor="current-password" className="text-muted-foreground">
Current Password
</Label>
<div className="relative">
<Input
id="current-password"
type={showCurrentPassword ? 'text' : 'password'}
minLength={6}
maxLength={72}
autoComplete="current-password"
className="bg-background mt-2 pr-10"
{...register('currentPassword')}
/>
<Button
variant="link"
type="button"
className="absolute right-0 top-0 flex h-full items-center justify-center pr-3"
aria-label={showCurrentPassword ? 'Mask password' : 'Reveal password'}
onClick={() => setShowCurrentPassword((show) => !show)}
>
{showCurrentPassword ? (
<EyeOff className="text-muted-foreground h-5 w-5" />
) : (
<Eye className="text-muted-foreground h-5 w-5" />
)}
</Button>
</div>
<FormErrorMessage className="mt-1.5" error={errors.currentPassword} />
</div>
<div> <div>
<Label htmlFor="password" className="text-muted-foreground"> <Label htmlFor="password" className="text-muted-foreground">
Password Password

View File

@ -7,9 +7,14 @@ import { SALT_ROUNDS } from '../../constants/auth';
export type UpdatePasswordOptions = { export type UpdatePasswordOptions = {
userId: number; userId: number;
password: string; password: string;
currentPassword: string;
}; };
export const updatePassword = async ({ userId, password }: UpdatePasswordOptions) => { export const updatePassword = async ({
userId,
password,
currentPassword,
}: UpdatePasswordOptions) => {
// Existence check // Existence check
const user = await prisma.user.findFirstOrThrow({ const user = await prisma.user.findFirstOrThrow({
where: { where: {
@ -17,23 +22,29 @@ export const updatePassword = async ({ userId, password }: UpdatePasswordOptions
}, },
}); });
const hashedPassword = await hash(password, SALT_ROUNDS); if (!user.password) {
throw new Error('User has no password');
if (user.password) {
// Compare the new password with the old password
const isSamePassword = await compare(password, user.password);
if (isSamePassword) {
throw new Error('Your new password cannot be the same as your old password.');
}
} }
const isCurrentPasswordValid = await compare(currentPassword, user.password);
if (!isCurrentPasswordValid) {
throw new Error('Current password is incorrect.');
}
// Compare the new password with the old password
const isSamePassword = await compare(password, user.password);
if (isSamePassword) {
throw new Error('Your new password cannot be the same as your old password.');
}
const hashedNewPassword = await hash(password, SALT_ROUNDS);
const updatedUser = await prisma.user.update({ const updatedUser = await prisma.user.update({
where: { where: {
id: userId, id: userId,
}, },
data: { data: {
password: hashedPassword, password: hashedNewPassword,
}, },
}); });

View File

@ -40,11 +40,12 @@ export const profileRouter = router({
.input(ZUpdatePasswordMutationSchema) .input(ZUpdatePasswordMutationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
try { try {
const { password } = input; const { password, currentPassword } = input;
return await updatePassword({ return await updatePassword({
userId: ctx.user.id, userId: ctx.user.id,
password, password,
currentPassword,
}); });
} catch (err) { } catch (err) {
let message = let message =

View File

@ -6,6 +6,7 @@ export const ZUpdateProfileMutationSchema = z.object({
}); });
export const ZUpdatePasswordMutationSchema = z.object({ export const ZUpdatePasswordMutationSchema = z.object({
currentPassword: z.string().min(6),
password: z.string().min(6), password: z.string().min(6),
}); });