forked from Mike/freno-dev
194 lines
5.6 KiB
TypeScript
194 lines
5.6 KiB
TypeScript
import { z } from "zod";
|
|
import { validatePassword } from "~/lib/validation";
|
|
import { VALIDATION_CONFIG } from "~/config";
|
|
|
|
/**
|
|
* User API Validation Schemas
|
|
*
|
|
* Zod schemas for user-related operations like authentication,
|
|
* profile updates, and password management
|
|
*/
|
|
|
|
// ============================================================================
|
|
// Custom Password Validator
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Secure password validation with strength requirements
|
|
* Minimum length from config, uppercase, lowercase, number, and special character
|
|
*/
|
|
const securePasswordSchema = z
|
|
.string()
|
|
.min(
|
|
VALIDATION_CONFIG.MIN_PASSWORD_LENGTH,
|
|
`Password must be at least ${VALIDATION_CONFIG.MIN_PASSWORD_LENGTH} characters`
|
|
)
|
|
.refine(
|
|
(password) => {
|
|
const result = validatePassword(password);
|
|
return result.isValid;
|
|
},
|
|
(password) => {
|
|
const result = validatePassword(password);
|
|
return {
|
|
message:
|
|
result.errors.join(", ") || "Password does not meet requirements"
|
|
};
|
|
}
|
|
);
|
|
|
|
// ============================================================================
|
|
// Authentication Schemas
|
|
// ============================================================================
|
|
|
|
/**
|
|
* User registration schema
|
|
*/
|
|
export const registerUserSchema = z
|
|
.object({
|
|
email: z.string().email(),
|
|
password: securePasswordSchema,
|
|
passwordConfirmation: z.string().min(VALIDATION_CONFIG.MIN_PASSWORD_LENGTH)
|
|
})
|
|
.refine((data) => data.password === data.passwordConfirmation, {
|
|
message: "Passwords do not match",
|
|
path: ["passwordConfirmation"]
|
|
});
|
|
|
|
/**
|
|
* User login schema
|
|
*/
|
|
export const loginUserSchema = z.object({
|
|
email: z.string().email(),
|
|
password: z.string().min(1, "Password is required")
|
|
});
|
|
|
|
/**
|
|
* OAuth provider schema
|
|
*/
|
|
export const oauthProviderSchema = z.enum(["google", "github"]);
|
|
|
|
// ============================================================================
|
|
// Profile Management Schemas
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Update email schema
|
|
*/
|
|
export const updateEmailSchema = z.object({
|
|
email: z.string().email()
|
|
});
|
|
|
|
/**
|
|
* Update display name schema
|
|
*/
|
|
export const updateDisplayNameSchema = z.object({
|
|
displayName: z.string().min(1).max(50)
|
|
});
|
|
|
|
/**
|
|
* Update profile image schema
|
|
*/
|
|
export const updateProfileImageSchema = z.object({
|
|
imageUrl: z.string().url()
|
|
});
|
|
|
|
// ============================================================================
|
|
// Password Management Schemas
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Change password schema (requires old password)
|
|
*/
|
|
export const changePasswordSchema = z
|
|
.object({
|
|
oldPassword: z.string().min(1, "Current password is required"),
|
|
newPassword: securePasswordSchema,
|
|
newPasswordConfirmation: z
|
|
.string()
|
|
.min(VALIDATION_CONFIG.MIN_PASSWORD_LENGTH)
|
|
})
|
|
.refine((data) => data.newPassword === data.newPasswordConfirmation, {
|
|
message: "Passwords do not match",
|
|
path: ["newPasswordConfirmation"]
|
|
})
|
|
.refine((data) => data.oldPassword !== data.newPassword, {
|
|
message: "New password must be different from current password",
|
|
path: ["newPassword"]
|
|
});
|
|
|
|
/**
|
|
* Set password schema (for OAuth users adding password)
|
|
*/
|
|
export const setPasswordSchema = z
|
|
.object({
|
|
newPassword: securePasswordSchema,
|
|
newPasswordConfirmation: z
|
|
.string()
|
|
.min(VALIDATION_CONFIG.MIN_PASSWORD_LENGTH)
|
|
})
|
|
.refine((data) => data.newPassword === data.newPasswordConfirmation, {
|
|
message: "Passwords do not match",
|
|
path: ["newPasswordConfirmation"]
|
|
});
|
|
|
|
/**
|
|
* Request password reset schema
|
|
*/
|
|
export const requestPasswordResetSchema = z.object({
|
|
email: z.string().email()
|
|
});
|
|
|
|
/**
|
|
* Reset password schema (with token)
|
|
*/
|
|
export const resetPasswordSchema = z
|
|
.object({
|
|
token: z.string().min(1),
|
|
newPassword: securePasswordSchema,
|
|
newPasswordConfirmation: z
|
|
.string()
|
|
.min(VALIDATION_CONFIG.MIN_PASSWORD_LENGTH)
|
|
})
|
|
.refine((data) => data.newPassword === data.newPasswordConfirmation, {
|
|
message: "Passwords do not match",
|
|
path: ["newPasswordConfirmation"]
|
|
});
|
|
|
|
// ============================================================================
|
|
// Account Management Schemas
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Delete account schema
|
|
*/
|
|
export const deleteAccountSchema = z.object({
|
|
password: z.string().min(1, "Password is required to delete account")
|
|
});
|
|
|
|
/**
|
|
* Email verification schema
|
|
*/
|
|
export const verifyEmailSchema = z.object({
|
|
token: z.string().min(1)
|
|
});
|
|
|
|
// ============================================================================
|
|
// Type Exports
|
|
// ============================================================================
|
|
|
|
export type RegisterUserInput = z.infer<typeof registerUserSchema>;
|
|
export type LoginUserInput = z.infer<typeof loginUserSchema>;
|
|
export type OAuthProvider = z.infer<typeof oauthProviderSchema>;
|
|
export type UpdateEmailInput = z.infer<typeof updateEmailSchema>;
|
|
export type UpdateDisplayNameInput = z.infer<typeof updateDisplayNameSchema>;
|
|
export type UpdateProfileImageInput = z.infer<typeof updateProfileImageSchema>;
|
|
export type ChangePasswordInput = z.infer<typeof changePasswordSchema>;
|
|
export type SetPasswordInput = z.infer<typeof setPasswordSchema>;
|
|
export type RequestPasswordResetInput = z.infer<
|
|
typeof requestPasswordResetSchema
|
|
>;
|
|
export type ResetPasswordInput = z.infer<typeof resetPasswordSchema>;
|
|
export type DeleteAccountInput = z.infer<typeof deleteAccountSchema>;
|
|
export type VerifyEmailInput = z.infer<typeof verifyEmailSchema>;
|