← back to PoppyPetitions

lib/validate.ts

49 lines

// 2026-05-30 (P1-B): defensive input-length caps on write endpoints.
// Prompt-injection / abuse hardening — oversized free-text fields get
// rejected with HTTP 400 BEFORE any DB write or Gemini call, so a caller
// can't smuggle a multi-megabyte payload into the LLM prompt or PG.
//
// These are *guards*, not validators — they only reject when a value is
// present AND a string AND longer than the cap. Absent / non-string values
// pass through untouched so existing required-field checks keep their own
// error messages and valid-input behavior is unchanged.

/** Canonical caps for PoppyPetitions write fields. */
export const LIMITS = {
  TITLE: 200,
  SUMMARY: 2000,
  REASON: 2000,
  COMMENT: 2000,
  BODY: 5000,
  ID: 200,
} as const;

/**
 * Returns an error string if `value` is a string longer than `max`,
 * otherwise null. Non-string / nullish values are not length-checked here.
 */
export function clampLen(
  value: unknown,
  max: number,
  fieldName: string,
): string | null {
  if (typeof value === 'string' && value.length > max) {
    return `${fieldName} exceeds maximum length of ${max} characters`;
  }
  return null;
}

/**
 * Runs clampLen over a list of [value, max, fieldName] tuples and returns
 * the FIRST violation message, or null if all pass.
 */
export function firstLengthViolation(
  checks: Array<[unknown, number, string]>,
): string | null {
  for (const [value, max, fieldName] of checks) {
    const err = clampLen(value, max, fieldName);
    if (err) return err;
  }
  return null;
}