Initial commit
This commit is contained in:
Vendored
-91
@@ -1,91 +0,0 @@
|
||||
import 'server-only';
|
||||
import { verifyTurnstile } from '../turnstile/index.js';
|
||||
import { checkRateLimit } from './rateLimit.js';
|
||||
import { validateSubmission } from './validateSubmission.js';
|
||||
/**
|
||||
* Handles a form submission end to end: rate limit, verify Turnstile, validate
|
||||
* against the form's own schema, then store.
|
||||
*
|
||||
* The order is cost-ascending on purpose — the cheapest checks reject first, so
|
||||
* a flood never reaches Turnstile's network call or the database.
|
||||
*
|
||||
* Emails aren't sent here. The form-builder sends whatever an editor configured
|
||||
* under the form's "Emails" tab (form-submissions hook → payload.sendEmail),
|
||||
* which goes out over panelSmtpAdapter. Storing the submission is enough.
|
||||
*
|
||||
* server-only: touches the Turnstile secret.
|
||||
*/ export async function submitForm({ data, formId, ip, maxPerMinute = 5, payload, turnstileToken }) {
|
||||
// 1. Rate limit — cheapest gate, drops a flood before any real work.
|
||||
if (maxPerMinute > 0 && ip) {
|
||||
if (!checkRateLimit({
|
||||
key: ip,
|
||||
max: maxPerMinute
|
||||
})) {
|
||||
return {
|
||||
reason: 'rate_limited',
|
||||
success: false
|
||||
};
|
||||
}
|
||||
}
|
||||
// 2. Turnstile — verify when a token is supplied; reject on failure.
|
||||
if (turnstileToken !== undefined) {
|
||||
const ok = await verifyTurnstile({
|
||||
ip,
|
||||
payload,
|
||||
token: turnstileToken
|
||||
});
|
||||
if (!ok) {
|
||||
return {
|
||||
reason: 'turnstile',
|
||||
success: false
|
||||
};
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
if (!validation.ok) {
|
||||
if (validation.reason === 'not_found') {
|
||||
return {
|
||||
reason: 'not_found',
|
||||
success: false
|
||||
};
|
||||
}
|
||||
return {
|
||||
reason: 'validation',
|
||||
success: false,
|
||||
...validation.field ? {
|
||||
field: validation.field
|
||||
} : {},
|
||||
...validation.kind ? {
|
||||
kind: validation.kind
|
||||
} : {}
|
||||
};
|
||||
}
|
||||
// 4. Persist (form-builder shape: submissionData array). Triggers the email
|
||||
// hook. Only validated, known fields are stored.
|
||||
try {
|
||||
const submission = await payload.create({
|
||||
collection: 'form-submissions',
|
||||
data: {
|
||||
form: formId,
|
||||
submissionData: Object.entries(validation.cleaned).map(([field, value])=>({
|
||||
field,
|
||||
value: value == null ? '' : String(value)
|
||||
}))
|
||||
}
|
||||
});
|
||||
return {
|
||||
submissionId: submission.id,
|
||||
success: true
|
||||
};
|
||||
} catch (err) {
|
||||
payload.logger.error(`[ipal] Form submission failed: ${err.message}`);
|
||||
return {
|
||||
reason: 'error',
|
||||
success: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//# sourceMappingURL=submitForm.js.map
|
||||
Reference in New Issue
Block a user