← back to Wine Finder Next

lib/inputValidation.ts

93 lines

// Input Validation and Sanitization

export function validateBottleId(id: string): boolean {
  return /^bottle_\d+_[a-z0-9]{9}$/.test(id);
}

export function validateVoteId(id: string): boolean {
  return /^vote_\d+_[a-z0-9]{9}$/.test(id);
}

export function validateMembershipUnitId(id: string): boolean {
  return /^unit_\d+_[a-z0-9]{9}$/.test(id);
}

export function validateUserId(id: string): boolean {
  return /^user_\d+$/.test(id) || /^[a-z0-9]{40,}$/.test(id);
}

export function validateVoteChoice(choice: string): choice is 'for' | 'against' | 'abstain' {
  return ['for', 'against', 'abstain'].includes(choice);
}

export function validateEmail(email: string): boolean {
  const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
  return emailRegex.test(email) && email.length <= 254;
}

export function validatePrice(price: number): boolean {
  return typeof price === 'number' && price > 0 && price < 1000000 && Number.isFinite(price);
}

export function validateQuantity(quantity: number): boolean {
  return Number.isInteger(quantity) && quantity > 0 && quantity <= 10000;
}

export function sanitizeString(input: string, maxLength: number = 1000): string {
  return input
    .replace(/[<>]/g, '')
    .replace(/javascript:/gi, '')
    .replace(/on\w+=/gi, '')
    .slice(0, maxLength)
    .trim();
}

export function validateShippingAddress(address: any): boolean {
  if (!address || typeof address !== 'object') return false;

  const required = ['name', 'addressLine1', 'city', 'state', 'zipCode', 'country', 'phone'];
  for (const field of required) {
    if (!address[field] || typeof address[field] !== 'string' || address[field].trim() === '') {
      return false;
    }
  }

  // Validate ZIP code (basic US format)
  if (!/^\d{5}(-\d{4})?$/.test(address.zipCode)) {
    return false;
  }

  // Validate phone (basic format)
  if (!/^\+?[\d\s\-()]{10,}$/.test(address.phone)) {
    return false;
  }

  return true;
}

export class ValidationError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'ValidationError';
  }
}

export function validateRequest<T>(
  data: any,
  schema: Record<string, (value: any) => boolean>
): T {
  const errors: string[] = [];

  for (const [key, validator] of Object.entries(schema)) {
    if (!validator(data[key])) {
      errors.push(`Invalid ${key}`);
    }
  }

  if (errors.length > 0) {
    throw new ValidationError(errors.join(', '));
  }

  return data as T;
}