Refactor password validation and enhance localization (#35)
* Refactor password validation and enhance localization A new PasswordSchema is introduced to handle the password validation in a centralized way and is used across all authentication schemas. The password requirements are also altered with additional special character, number, and uppercase letter checks. Error messages now utilize localization to provide dynamic error notifications. * Sign out before impersonating a user This update adds a call to sign out before impersonating a user. This is an additional measure to ensure the security of the system, accentuating the isolation of user sessions. * Refactor password validation and refine password schemas The password validation process has been restructured. The 'PasswordSchema' is now split into two separate schemas - 'PasswordSchema' and 'RefinedPasswordSchema'. The logic for validating repeating passwords has been moved into a separate function named 'refineRepeatPassword'. This streamlines the password validation process and ensures consistency across password checks.
This commit is contained in:
committed by
GitHub
parent
298e70b738
commit
fbe7ca4c9e
@@ -26,7 +26,6 @@
|
||||
"passwordHint": "Ensure it's at least 8 characters",
|
||||
"repeatPasswordHint": "Type your password again",
|
||||
"repeatPassword": "Repeat password",
|
||||
"passwordsDoNotMatch": "The passwords do not match",
|
||||
"passwordForgottenQuestion": "Password forgotten?",
|
||||
"passwordResetLabel": "Reset Password",
|
||||
"passwordResetSubheading": "Enter your email address below. You will receive a link to reset your password.",
|
||||
@@ -69,6 +68,11 @@
|
||||
"default": "We have encountered an error. Please ensure you have a working internet connection and try again",
|
||||
"generic": "Sorry, we weren't able to authenticate you. Please try again.",
|
||||
"link": "Sorry, we encountered an error while sending your link. Please try again.",
|
||||
"codeVerifierMismatch": "It looks like you're trying to sign in using a different browser than the one you used to request the sign in link. Please try again using the same browser."
|
||||
"codeVerifierMismatch": "It looks like you're trying to sign in using a different browser than the one you used to request the sign in link. Please try again using the same browser.",
|
||||
"minPasswordLength": "Password must be at least 8 characters long",
|
||||
"passwordsDoNotMatch": "The passwords do not match",
|
||||
"minPasswordNumbers": "Password must contain at least one number",
|
||||
"minPasswordSpecialChars": "Password must contain at least one special character",
|
||||
"uppercasePassword": "Password must contain at least one uppercase letter"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +142,8 @@ function useSetSession(tokens: { accessToken: string; refreshToken: string }) {
|
||||
queryKey: ['impersonate-user', tokens.accessToken, tokens.refreshToken],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
await supabase.auth.signOut();
|
||||
|
||||
await supabase.auth.setSession({
|
||||
refresh_token: tokens.refreshToken,
|
||||
access_token: tokens.accessToken,
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { RefinedPasswordSchema, refineRepeatPassword } from './password.schema';
|
||||
|
||||
export const PasswordResetSchema = z
|
||||
.object({
|
||||
password: z.string().min(8).max(99),
|
||||
repeatPassword: z.string().min(8).max(99),
|
||||
password: RefinedPasswordSchema,
|
||||
repeatPassword: RefinedPasswordSchema,
|
||||
})
|
||||
.refine((data) => data.password === data.repeatPassword, {
|
||||
message: 'Passwords do not match',
|
||||
path: ['repeatPassword'],
|
||||
});
|
||||
.superRefine(refineRepeatPassword);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { PasswordSchema } from './password.schema';
|
||||
|
||||
export const PasswordSignInSchema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(8).max(99),
|
||||
password: PasswordSchema,
|
||||
});
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { RefinedPasswordSchema, refineRepeatPassword } from './password.schema';
|
||||
|
||||
export const PasswordSignUpSchema = z
|
||||
.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(8).max(99),
|
||||
repeatPassword: z.string().min(8).max(99),
|
||||
password: RefinedPasswordSchema,
|
||||
repeatPassword: RefinedPasswordSchema,
|
||||
})
|
||||
.refine(
|
||||
(schema) => {
|
||||
return schema.password === schema.repeatPassword;
|
||||
},
|
||||
{
|
||||
message: 'Passwords do not match',
|
||||
path: ['repeatPassword'],
|
||||
},
|
||||
);
|
||||
.superRefine(refineRepeatPassword);
|
||||
|
||||
82
packages/features/auth/src/schemas/password.schema.ts
Normal file
82
packages/features/auth/src/schemas/password.schema.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Password requirements
|
||||
* These are the requirements for the password when signing up or changing the password
|
||||
*/
|
||||
const requirements = {
|
||||
minLength: 8,
|
||||
maxLength: 99,
|
||||
specialChars:
|
||||
process.env.NEXT_PUBLIC_PASSWORD_REQUIRE_SPECIAL_CHARS === 'true',
|
||||
numbers: process.env.NEXT_PUBLIC_PASSWORD_REQUIRE_NUMBERS === 'true',
|
||||
uppercase: process.env.NEXT_PUBLIC_PASSWORD_REQUIRE_UPPERCASE === 'true',
|
||||
};
|
||||
|
||||
/**
|
||||
* Password schema
|
||||
* This is used to validate the password on sign in (for existing users when requirements are not enforced)
|
||||
*/
|
||||
export const PasswordSchema = z
|
||||
.string()
|
||||
.min(requirements.minLength)
|
||||
.max(requirements.maxLength);
|
||||
|
||||
/**
|
||||
* Refined password schema with additional requirements
|
||||
* This is required to validate the password requirements on sign up and password change
|
||||
*/
|
||||
export const RefinedPasswordSchema = PasswordSchema.superRefine((val, ctx) =>
|
||||
validatePassword(val, ctx),
|
||||
);
|
||||
|
||||
export function refineRepeatPassword(
|
||||
data: { password: string; repeatPassword: string },
|
||||
ctx: z.RefinementCtx,
|
||||
) {
|
||||
if (data.password !== data.repeatPassword) {
|
||||
ctx.addIssue({
|
||||
message: 'auth:errors.passwordsDoNotMatch',
|
||||
path: ['repeatPassword'],
|
||||
code: 'custom',
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function validatePassword(password: string, ctx: z.RefinementCtx) {
|
||||
if (requirements.specialChars) {
|
||||
const specialCharsCount =
|
||||
password.match(/[!@#$%^&*(),.?":{}|<>]/g)?.length ?? 0;
|
||||
|
||||
if (specialCharsCount < 1) {
|
||||
ctx.addIssue({
|
||||
message: 'auth:errors.minPasswordSpecialChars',
|
||||
code: 'custom',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (requirements.numbers) {
|
||||
const numbersCount = password.match(/\d/g)?.length ?? 0;
|
||||
|
||||
if (numbersCount < 1) {
|
||||
ctx.addIssue({
|
||||
message: 'auth:errors.minPasswordNumbers',
|
||||
code: 'custom',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (requirements.uppercase) {
|
||||
if (!/[A-Z]/.test(password)) {
|
||||
ctx.addIssue({
|
||||
message: 'auth:errors.uppercasePassword',
|
||||
code: 'custom',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { Controller, FormProvider, useFormContext } from 'react-hook-form';
|
||||
|
||||
import { cn } from '../utils';
|
||||
import { Label } from './label';
|
||||
import {Trans} from "../makerkit/trans";
|
||||
|
||||
const Form = FormProvider;
|
||||
|
||||
@@ -156,7 +157,7 @@ const FormMessage = React.forwardRef<
|
||||
className={cn('text-[0.8rem] font-medium text-destructive', className)}
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
{typeof body === 'string' ? <Trans i18nKey={body} defaults={body} /> : body}
|
||||
</p>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user