added validation for forms, sensitive text fields have been masked

This commit is contained in:
2026-08-21 17:15:02 +02:00
parent 5aa66ff37a
commit 2215766940
19 changed files with 325 additions and 4 deletions
+21 -1
View File
@@ -29,6 +29,7 @@ export type FormValidationResult =
reason: 'invalid'
}
| { cleaned: Record<string, unknown>; form: FormDoc; ok: true }
| { field: string; ok: false; reason: 'consent' }
| { ok: false; reason: 'not_found' }
/** Field block types that don't carry a submittable value. */
@@ -46,6 +47,16 @@ const CAPTCHA_KEYS = new Set(['cf-turnstile-response', 'g-recaptcha-response'])
/** Hard ceiling on a single field's length, independent of the form config. */
const MAX_FIELD_LENGTH = 5000
/**
* Default name for a GDPR consent field. A checkbox with this name is treated
* as a consent gate: it MUST be checked for the submission to go through,
* enforced here server-side regardless of how the field was configured in the
* panel (so an editor can't weaken it by forgetting `required` or, worse,
* pre-ticking it with defaultValue: true — which GDPR forbids). Configurable
* via FormsOption.consentFieldName.
*/
const DEFAULT_CONSENT_FIELD = 'consent'
/**
* Checks submitted data against the form's own definition, rather than trusting
* whatever arrived.
@@ -63,6 +74,7 @@ export async function validateSubmission(
payload: BasePayload,
formId: string,
data: Record<string, unknown>,
consentFieldName: string = DEFAULT_CONSENT_FIELD,
): Promise<FormValidationResult> {
let form: FormDoc
try {
@@ -88,7 +100,15 @@ export async function validateSubmission(
const isBlank =
value == null || (typeof value === 'string' && value.trim() === '') || value === false
if (field.required && isBlank) {
// GDPR consent gate: a field matching the consent name must be truthy
// (checked). Enforced independently of `required`, so it can't be weakened
// in the panel. This is the one field where server-side enforcement is the
// legal guarantee — the frontend can't bypass it, the editor can't misset it.
if (field.name === consentFieldName) {
if (value !== true) {
return { field: field.name, ok: false, reason: 'consent' }
}
} else if (field.required && isBlank) {
return { field: field.name, kind: 'required', ok: false, reason: 'invalid' }
}