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
+17 -1
View File
@@ -7,6 +7,11 @@ import { checkRateLimit } from './rateLimit.js'
import { validateSubmission } from './validateSubmission.js'
export type SubmitFormArgs = {
/**
* Name of the GDPR consent checkbox. A field with this name must be checked
* for the submission to succeed (enforced server-side). Defaults to 'consent'.
*/
consentFieldName?: string
/** Submitted field data — shape matches the form's fields. */
data: Record<string, unknown>
/** Form-builder form ID this submission belongs to. */
@@ -37,6 +42,7 @@ export type SubmitFormArgs = {
* `field` and `kind` narrow it down when a specific field is
* at fault (absent for whole-payload problems like unknown keys)
* - `not_found` — no form with this id
* - `consent` — a required GDPR consent checkbox was left unchecked
* - `error` — persistence failed unexpectedly
*/
export type SubmitFailure =
@@ -48,6 +54,12 @@ export type SubmitFailure =
reason: 'validation'
success: false
}
| {
field?: string
/** A GDPR consent field existed on the form but wasn't checked. */
reason: 'consent'
success: false
}
| { reason: 'error'; success: false }
| { reason: 'not_found'; success: false }
| { reason: 'rate_limited'; success: false }
@@ -69,6 +81,7 @@ export type SubmitFormResult = { submissionId: number | string; success: true }
* server-only: touches the Turnstile secret.
*/
export async function submitForm({
consentFieldName,
data,
formId,
ip,
@@ -93,11 +106,14 @@ export async function submitForm({
// 3. Validate against the form's schema. A public endpoint can't trust the
// shape of `data` — drop unknown keys, enforce required, cap length.
const validation = await validateSubmission(payload, formId, data)
const validation = await validateSubmission(payload, formId, data, consentFieldName)
if (!validation.ok) {
if (validation.reason === 'not_found') {
return { reason: 'not_found', success: false }
}
if (validation.reason === 'consent') {
return { field: validation.field, reason: 'consent', success: false }
}
return {
reason: 'validation',
success: false,