@actuate-media/cms-core 0.30.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/api/admin-contracts.test.js +10 -2
- package/dist/__tests__/api/admin-contracts.test.js.map +1 -1
- package/dist/__tests__/api/public-forms.test.d.ts +2 -0
- package/dist/__tests__/api/public-forms.test.d.ts.map +1 -0
- package/dist/__tests__/api/public-forms.test.js +125 -0
- package/dist/__tests__/api/public-forms.test.js.map +1 -0
- package/dist/__tests__/api/public-seo.test.js +2 -0
- package/dist/__tests__/api/public-seo.test.js.map +1 -1
- package/dist/__tests__/forms/forms-service.test.d.ts +2 -0
- package/dist/__tests__/forms/forms-service.test.d.ts.map +1 -0
- package/dist/__tests__/forms/forms-service.test.js +193 -0
- package/dist/__tests__/forms/forms-service.test.js.map +1 -0
- package/dist/__tests__/forms/spam.test.d.ts +2 -0
- package/dist/__tests__/forms/spam.test.d.ts.map +1 -0
- package/dist/__tests__/forms/spam.test.js +115 -0
- package/dist/__tests__/forms/spam.test.js.map +1 -0
- package/dist/__tests__/forms/submission-files.test.d.ts +2 -0
- package/dist/__tests__/forms/submission-files.test.d.ts.map +1 -0
- package/dist/__tests__/forms/submission-files.test.js +65 -0
- package/dist/__tests__/forms/submission-files.test.js.map +1 -0
- package/dist/__tests__/forms/submission-pipeline.test.d.ts +2 -0
- package/dist/__tests__/forms/submission-pipeline.test.d.ts.map +1 -0
- package/dist/__tests__/forms/submission-pipeline.test.js +131 -0
- package/dist/__tests__/forms/submission-pipeline.test.js.map +1 -0
- package/dist/__tests__/forms/submission-validation.test.d.ts +2 -0
- package/dist/__tests__/forms/submission-validation.test.d.ts.map +1 -0
- package/dist/__tests__/forms/submission-validation.test.js +108 -0
- package/dist/__tests__/forms/submission-validation.test.js.map +1 -0
- package/dist/__tests__/seo/audit-runner.test.js +54 -1
- package/dist/__tests__/seo/audit-runner.test.js.map +1 -1
- package/dist/api/handler-factory.d.ts.map +1 -1
- package/dist/api/handler-factory.js +2 -2
- package/dist/api/handler-factory.js.map +1 -1
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +724 -155
- package/dist/api/handlers.js.map +1 -1
- package/dist/api/router.d.ts +2 -1
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +3 -0
- package/dist/api/router.js.map +1 -1
- package/dist/config/types.d.ts +42 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/cron/index.d.ts +1 -0
- package/dist/cron/index.d.ts.map +1 -1
- package/dist/cron/index.js +10 -1
- package/dist/cron/index.js.map +1 -1
- package/dist/forms/entries.d.ts +45 -0
- package/dist/forms/entries.d.ts.map +1 -0
- package/dist/forms/entries.js +348 -0
- package/dist/forms/entries.js.map +1 -0
- package/dist/forms/files.d.ts +38 -0
- package/dist/forms/files.d.ts.map +1 -0
- package/dist/forms/files.js +134 -0
- package/dist/forms/files.js.map +1 -0
- package/dist/forms/index.d.ts +8 -0
- package/dist/forms/index.d.ts.map +1 -1
- package/dist/forms/index.js +7 -0
- package/dist/forms/index.js.map +1 -1
- package/dist/forms/service.d.ts +92 -0
- package/dist/forms/service.d.ts.map +1 -0
- package/dist/forms/service.js +579 -0
- package/dist/forms/service.js.map +1 -0
- package/dist/forms/spam.d.ts +33 -0
- package/dist/forms/spam.d.ts.map +1 -0
- package/dist/forms/spam.js +93 -0
- package/dist/forms/spam.js.map +1 -0
- package/dist/forms/submission.d.ts +95 -0
- package/dist/forms/submission.d.ts.map +1 -0
- package/dist/forms/submission.js +282 -0
- package/dist/forms/submission.js.map +1 -0
- package/dist/forms/types.d.ts +244 -0
- package/dist/forms/types.d.ts.map +1 -0
- package/dist/forms/types.js +36 -0
- package/dist/forms/types.js.map +1 -0
- package/dist/forms/validation.d.ts +19 -0
- package/dist/forms/validation.d.ts.map +1 -0
- package/dist/forms/validation.js +235 -0
- package/dist/forms/validation.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/security/client-ip.d.ts +11 -21
- package/dist/security/client-ip.d.ts.map +1 -1
- package/dist/security/client-ip.js +38 -0
- package/dist/security/client-ip.js.map +1 -1
- package/dist/seo/audit-runner.d.ts +37 -2
- package/dist/seo/audit-runner.d.ts.map +1 -1
- package/dist/seo/audit-runner.js +56 -6
- package/dist/seo/audit-runner.js.map +1 -1
- package/dist/seo/index.d.ts +2 -1
- package/dist/seo/index.d.ts.map +1 -1
- package/dist/seo/index.js +1 -1
- package/dist/seo/index.js.map +1 -1
- package/package.json +1 -1
- package/prisma/cms-schema.prisma +81 -7
- package/prisma/migrations/0010_forms/migration.sql +130 -0
- package/prisma/schema.prisma +130 -35
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
function hostOf(value) {
|
|
2
|
+
if (!value)
|
|
3
|
+
return null;
|
|
4
|
+
try {
|
|
5
|
+
return new URL(value).hostname.toLowerCase();
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function normalizeHost(domain) {
|
|
12
|
+
return domain
|
|
13
|
+
.trim()
|
|
14
|
+
.toLowerCase()
|
|
15
|
+
.replace(/^https?:\/\//, '')
|
|
16
|
+
.replace(/\/.*$/, '')
|
|
17
|
+
.replace(/^\*\./, '');
|
|
18
|
+
}
|
|
19
|
+
/** Concatenate user-supplied string values for keyword scanning. */
|
|
20
|
+
function textBlob(fields, rawValues) {
|
|
21
|
+
const parts = [];
|
|
22
|
+
for (const field of fields) {
|
|
23
|
+
if (field.type === 'honeypot' || field.type === 'divider')
|
|
24
|
+
continue;
|
|
25
|
+
const v = rawValues[field.key];
|
|
26
|
+
if (typeof v === 'string')
|
|
27
|
+
parts.push(v);
|
|
28
|
+
else if (Array.isArray(v))
|
|
29
|
+
parts.push(v.map((x) => String(x)).join(' '));
|
|
30
|
+
}
|
|
31
|
+
return parts.join(' \n ').toLowerCase();
|
|
32
|
+
}
|
|
33
|
+
export function scoreSubmissionSpam(input) {
|
|
34
|
+
const reasons = [];
|
|
35
|
+
const settings = input.settings ?? {};
|
|
36
|
+
const strategies = new Set([
|
|
37
|
+
...(input.defaultStrategies ?? []),
|
|
38
|
+
...(settings.strategies ?? []),
|
|
39
|
+
]);
|
|
40
|
+
// Honeypot — always evaluated when a honeypot field/key exists. A populated
|
|
41
|
+
// honeypot means an automated client filled a field that is hidden from
|
|
42
|
+
// humans via CSS.
|
|
43
|
+
const honeypotKey = settings.honeypotFieldKey ?? input.fields.find((f) => f.type === 'honeypot')?.key;
|
|
44
|
+
if (honeypotKey) {
|
|
45
|
+
const v = input.rawValues[honeypotKey];
|
|
46
|
+
const filled = typeof v === 'string' ? v.trim() !== '' : v != null && v !== false;
|
|
47
|
+
if (filled)
|
|
48
|
+
reasons.push('honeypot');
|
|
49
|
+
}
|
|
50
|
+
// Time-to-submit — humans take a few seconds; instant submits are bots.
|
|
51
|
+
if (strategies.has('timeToSubmit') &&
|
|
52
|
+
typeof settings.minSubmitSeconds === 'number' &&
|
|
53
|
+
settings.minSubmitSeconds > 0 &&
|
|
54
|
+
typeof input.elapsedMs === 'number' &&
|
|
55
|
+
input.elapsedMs >= 0) {
|
|
56
|
+
if (input.elapsedMs < settings.minSubmitSeconds * 1000)
|
|
57
|
+
reasons.push('too_fast');
|
|
58
|
+
}
|
|
59
|
+
// Email / domain blocklist.
|
|
60
|
+
if (strategies.has('blocklist')) {
|
|
61
|
+
const email = (input.senderEmail ?? '').trim().toLowerCase();
|
|
62
|
+
if (email) {
|
|
63
|
+
const blockedEmails = (settings.blockedEmails ?? []).map((e) => e.trim().toLowerCase());
|
|
64
|
+
if (blockedEmails.includes(email))
|
|
65
|
+
reasons.push('blocked_email');
|
|
66
|
+
const domain = email.split('@')[1] ?? '';
|
|
67
|
+
const blockedDomains = (settings.blockedDomains ?? []).map((d) => normalizeHost(d));
|
|
68
|
+
if (domain && blockedDomains.includes(domain))
|
|
69
|
+
reasons.push('blocked_domain');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Keyword blocklist.
|
|
73
|
+
if (strategies.has('keyword') && (settings.blockedKeywords?.length ?? 0) > 0) {
|
|
74
|
+
const blob = textBlob(input.fields, input.rawValues);
|
|
75
|
+
for (const keyword of settings.blockedKeywords ?? []) {
|
|
76
|
+
const k = keyword.trim().toLowerCase();
|
|
77
|
+
if (k && blob.includes(k)) {
|
|
78
|
+
reasons.push('blocked_keyword');
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Domain allowlist — the submitting origin must be on the allowlist.
|
|
84
|
+
if (strategies.has('domainAllowlist') && (settings.allowedDomains?.length ?? 0) > 0) {
|
|
85
|
+
const host = hostOf(input.origin) ?? hostOf(input.referrer);
|
|
86
|
+
const allowed = (settings.allowedDomains ?? []).map((d) => normalizeHost(d));
|
|
87
|
+
const ok = host != null && allowed.some((a) => host === a || host.endsWith(`.${a}`));
|
|
88
|
+
if (!ok)
|
|
89
|
+
reasons.push('origin_not_allowed');
|
|
90
|
+
}
|
|
91
|
+
return { status: reasons.length > 0 ? 'spam' : 'clean', reasons };
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=spam.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spam.js","sourceRoot":"","sources":["../../src/forms/spam.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { type FieldError } from './validation.js';
|
|
2
|
+
import { type IncomingFile } from './files.js';
|
|
3
|
+
import type { FormDefinition, FormField, SpamStatus } from './types.js';
|
|
4
|
+
export interface SubmissionConfirmation {
|
|
5
|
+
type: 'message' | 'redirect';
|
|
6
|
+
message?: string;
|
|
7
|
+
redirectUrl?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SubmissionContext {
|
|
10
|
+
/** Raw field values from the request (object keyed by field key). */
|
|
11
|
+
values: Record<string, unknown>;
|
|
12
|
+
/** Already-validated file uploads to attach after the row is created. */
|
|
13
|
+
files?: IncomingFile[];
|
|
14
|
+
ip?: string;
|
|
15
|
+
userAgent?: string | null;
|
|
16
|
+
referrer?: string | null;
|
|
17
|
+
origin?: string | null;
|
|
18
|
+
pageUrl?: string | null;
|
|
19
|
+
/** Opaque attribution blob captured client-side; stored as-is. */
|
|
20
|
+
attribution?: Record<string, unknown> | null;
|
|
21
|
+
/** Milliseconds between render and submit (for the time-trap spam check). */
|
|
22
|
+
elapsedMs?: number | null;
|
|
23
|
+
}
|
|
24
|
+
export interface SubmissionResult {
|
|
25
|
+
submissionId: string;
|
|
26
|
+
entryNumber: number | null;
|
|
27
|
+
spamStatus: SpamStatus;
|
|
28
|
+
confirmation: SubmissionConfirmation;
|
|
29
|
+
}
|
|
30
|
+
/** Thrown when field validation fails; mapped to HTTP 422 by the API layer. */
|
|
31
|
+
export declare class SubmissionValidationError extends Error {
|
|
32
|
+
fieldErrors: FieldError[];
|
|
33
|
+
constructor(fieldErrors: FieldError[]);
|
|
34
|
+
}
|
|
35
|
+
/** Resolve the field schema the submission should be validated against. */
|
|
36
|
+
declare function resolveFields(form: FormDefinition): Promise<{
|
|
37
|
+
fields: FormField[];
|
|
38
|
+
versionId: string | null;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Process a validated request into a persisted submission. Throws
|
|
42
|
+
* `SubmissionValidationError` when field validation fails so the caller can
|
|
43
|
+
* return a structured 422.
|
|
44
|
+
*/
|
|
45
|
+
export declare function processSubmission(form: FormDefinition, ctx: SubmissionContext): Promise<SubmissionResult>;
|
|
46
|
+
export interface PublicFormField {
|
|
47
|
+
id: string;
|
|
48
|
+
key: string;
|
|
49
|
+
label: string;
|
|
50
|
+
type: string;
|
|
51
|
+
required: boolean;
|
|
52
|
+
placeholder?: string;
|
|
53
|
+
helpText?: string;
|
|
54
|
+
defaultValue?: unknown;
|
|
55
|
+
options?: {
|
|
56
|
+
label: string;
|
|
57
|
+
value: string;
|
|
58
|
+
}[];
|
|
59
|
+
width?: string;
|
|
60
|
+
sortOrder: number;
|
|
61
|
+
validationRules?: Record<string, unknown>;
|
|
62
|
+
conditionalLogic?: unknown;
|
|
63
|
+
}
|
|
64
|
+
export interface PublicForm {
|
|
65
|
+
id: string;
|
|
66
|
+
name: string;
|
|
67
|
+
slug: string;
|
|
68
|
+
description?: string;
|
|
69
|
+
fields: PublicFormField[];
|
|
70
|
+
submitPath: string;
|
|
71
|
+
successMessage?: string;
|
|
72
|
+
redirectUrl?: string;
|
|
73
|
+
spam: {
|
|
74
|
+
honeypotFieldKey?: string;
|
|
75
|
+
minSubmitSeconds?: number;
|
|
76
|
+
};
|
|
77
|
+
captcha?: {
|
|
78
|
+
provider: string;
|
|
79
|
+
siteKey: string;
|
|
80
|
+
};
|
|
81
|
+
schemaVersionId?: string | null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Project a form definition into the public, embeddable schema. Strips every
|
|
85
|
+
* server-only concern (access rules, notification recipients, webhook secrets,
|
|
86
|
+
* blocklists, encryption flags) so nothing sensitive leaks to the browser.
|
|
87
|
+
*/
|
|
88
|
+
export declare function buildPublicForm(form: FormDefinition, fields: FormField[], schemaVersionId: string | null, options?: {
|
|
89
|
+
captcha?: {
|
|
90
|
+
provider: string;
|
|
91
|
+
siteKey: string;
|
|
92
|
+
};
|
|
93
|
+
}): PublicForm;
|
|
94
|
+
export { resolveFields as resolveSubmissionFields };
|
|
95
|
+
//# sourceMappingURL=submission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submission.d.ts","sourceRoot":"","sources":["../../src/forms/submission.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public submission pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates the full lifecycle for an incoming public submission:
|
|
5
|
+
* resolve schema → validate + normalize → sanitize → score spam →
|
|
6
|
+
* derive sender/preview → assign entry number (transaction) → persist →
|
|
7
|
+
* attach files → fire plugin hooks → audit log → build confirmation.
|
|
8
|
+
*
|
|
9
|
+
* The HTTP layer (`api/handlers.ts`) is responsible only for parsing the
|
|
10
|
+
* request (JSON or multipart), resolving the form, rate-limiting, CORS, and
|
|
11
|
+
* optional CAPTCHA verification. All trust decisions live here so the same
|
|
12
|
+
* pipeline backs every entry point (public slug route, legacy id route, and
|
|
13
|
+
* the future page-builder block).
|
|
14
|
+
*/
|
|
15
|
+
import { getDB } from '../db.js';
|
|
16
|
+
import { logEvent } from '../security/audit.js';
|
|
17
|
+
import { hashClientIp } from '../security/client-ip.js';
|
|
18
|
+
import { getActuateConfig } from '../config/runtime.js';
|
|
19
|
+
import { getSchemaVersion } from './service.js';
|
|
20
|
+
import { validateAndNormalizeSubmission } from './validation.js';
|
|
21
|
+
import { scoreSubmissionSpam } from './spam.js';
|
|
22
|
+
import { persistSubmissionFiles } from './files.js';
|
|
23
|
+
/** Thrown when field validation fails; mapped to HTTP 422 by the API layer. */
|
|
24
|
+
export class SubmissionValidationError extends Error {
|
|
25
|
+
fieldErrors;
|
|
26
|
+
constructor(fieldErrors) {
|
|
27
|
+
super('Submission validation failed');
|
|
28
|
+
this.name = 'SubmissionValidationError';
|
|
29
|
+
this.fieldErrors = fieldErrors;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const DEFAULT_SUCCESS_MESSAGE = 'Thank you! Your submission has been received.';
|
|
33
|
+
function asString(value) {
|
|
34
|
+
if (typeof value === 'string')
|
|
35
|
+
return value;
|
|
36
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
37
|
+
return String(value);
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
/** Resolve the field schema the submission should be validated against. */
|
|
41
|
+
async function resolveFields(form) {
|
|
42
|
+
if (form.activeSchemaVersionId) {
|
|
43
|
+
const version = await getSchemaVersion(form.activeSchemaVersionId);
|
|
44
|
+
if (version && version.fields.length > 0) {
|
|
45
|
+
return { fields: version.fields, versionId: version.id };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { fields: form.fields ?? [], versionId: form.activeSchemaVersionId ?? null };
|
|
49
|
+
}
|
|
50
|
+
function deriveSender(fields, data) {
|
|
51
|
+
let name = null;
|
|
52
|
+
let email = null;
|
|
53
|
+
let phone = null;
|
|
54
|
+
for (const field of fields) {
|
|
55
|
+
const v = asString(data[field.key]).trim();
|
|
56
|
+
if (!v)
|
|
57
|
+
continue;
|
|
58
|
+
if (!email && field.type === 'email')
|
|
59
|
+
email = v;
|
|
60
|
+
if (!phone && field.type === 'phone')
|
|
61
|
+
phone = v;
|
|
62
|
+
if (!name && /name/i.test(field.key))
|
|
63
|
+
name = v;
|
|
64
|
+
}
|
|
65
|
+
// Fallbacks by common key naming when the schema didn't use typed fields.
|
|
66
|
+
if (!email)
|
|
67
|
+
email = firstByKeys(data, ['email', 'emailaddress', 'email_address']);
|
|
68
|
+
if (!name)
|
|
69
|
+
name = firstByKeys(data, ['name', 'fullname', 'full_name', 'firstname', 'first_name']);
|
|
70
|
+
if (!phone)
|
|
71
|
+
phone = firstByKeys(data, ['phone', 'phonenumber', 'phone_number', 'tel']);
|
|
72
|
+
return { name, email, phone };
|
|
73
|
+
}
|
|
74
|
+
function firstByKeys(data, candidates) {
|
|
75
|
+
const lower = new Map(Object.entries(data).map(([k, v]) => [k.toLowerCase(), v]));
|
|
76
|
+
for (const c of candidates) {
|
|
77
|
+
const v = asString(lower.get(c)).trim();
|
|
78
|
+
if (v)
|
|
79
|
+
return v;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
function derivePreview(fields, data) {
|
|
84
|
+
// Prefer long-form fields, then any non-empty string value.
|
|
85
|
+
for (const field of fields) {
|
|
86
|
+
if (field.type === 'textarea') {
|
|
87
|
+
const v = asString(data[field.key]).trim();
|
|
88
|
+
if (v)
|
|
89
|
+
return v.slice(0, 200);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const messageKeys = ['message', 'body', 'comments', 'notes', 'description'];
|
|
93
|
+
const byKey = firstByKeys(data, messageKeys);
|
|
94
|
+
if (byKey)
|
|
95
|
+
return byKey.slice(0, 200);
|
|
96
|
+
for (const v of Object.values(data)) {
|
|
97
|
+
const s = asString(v).trim();
|
|
98
|
+
if (s)
|
|
99
|
+
return s.slice(0, 200);
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
/** Site-wide default spam strategies; per-form strategies are merged in `scoreSubmissionSpam`. */
|
|
104
|
+
function defaultSpamStrategies() {
|
|
105
|
+
return (getActuateConfig()?.forms?.defaultSpamProtection ?? []);
|
|
106
|
+
}
|
|
107
|
+
async function assignAndCreate(db, row, formId) {
|
|
108
|
+
// Sequential per-form entry number assigned inside a transaction so two
|
|
109
|
+
// concurrent submissions don't read the same max. Not globally unique by DB
|
|
110
|
+
// constraint, but the transaction makes collisions vanishingly unlikely.
|
|
111
|
+
if (typeof db.$transaction === 'function') {
|
|
112
|
+
return db.$transaction(async (tx) => {
|
|
113
|
+
const agg = await tx.formSubmission.aggregate({
|
|
114
|
+
where: { formId },
|
|
115
|
+
_max: { entryNumber: true },
|
|
116
|
+
});
|
|
117
|
+
const entryNumber = (agg?._max?.entryNumber ?? 0) + 1;
|
|
118
|
+
const created = await tx.formSubmission.create({
|
|
119
|
+
data: { ...row, entryNumber },
|
|
120
|
+
select: { id: true, entryNumber: true },
|
|
121
|
+
});
|
|
122
|
+
return { id: String(created.id), entryNumber: created.entryNumber ?? entryNumber };
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const created = await db.formSubmission.create({
|
|
126
|
+
data: row,
|
|
127
|
+
select: { id: true, entryNumber: true },
|
|
128
|
+
});
|
|
129
|
+
return { id: String(created.id), entryNumber: created.entryNumber ?? null };
|
|
130
|
+
}
|
|
131
|
+
function buildConfirmation(form) {
|
|
132
|
+
if (form.redirectUrl && form.redirectUrl.trim()) {
|
|
133
|
+
return { type: 'redirect', redirectUrl: form.redirectUrl.trim() };
|
|
134
|
+
}
|
|
135
|
+
const message = form.successMessage?.trim() ||
|
|
136
|
+
getActuateConfig()?.forms?.defaultSuccessMessage ||
|
|
137
|
+
DEFAULT_SUCCESS_MESSAGE;
|
|
138
|
+
return { type: 'message', message };
|
|
139
|
+
}
|
|
140
|
+
async function fireSubmissionHooks(formId, data) {
|
|
141
|
+
try {
|
|
142
|
+
const config = getActuateConfig();
|
|
143
|
+
const hooks = (config?._pluginHooks ?? []);
|
|
144
|
+
for (const hook of hooks.filter((h) => h?.event === 'afterCreate:form-submissions')) {
|
|
145
|
+
await hook.handler({ formId, data });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
150
|
+
console.error('[Actuate CMS] Form submission hook error:', err);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Process a validated request into a persisted submission. Throws
|
|
156
|
+
* `SubmissionValidationError` when field validation fails so the caller can
|
|
157
|
+
* return a structured 422.
|
|
158
|
+
*/
|
|
159
|
+
export async function processSubmission(form, ctx) {
|
|
160
|
+
const db = getDB();
|
|
161
|
+
const { fields, versionId } = await resolveFields(form);
|
|
162
|
+
const fileKeys = new Set((ctx.files ?? []).map((f) => f.fieldKey));
|
|
163
|
+
const validation = validateAndNormalizeSubmission(fields, ctx.values, { fileKeys });
|
|
164
|
+
if (!validation.ok) {
|
|
165
|
+
throw new SubmissionValidationError(validation.errors);
|
|
166
|
+
}
|
|
167
|
+
const sender = deriveSender(fields, validation.data);
|
|
168
|
+
const spam = scoreSubmissionSpam({
|
|
169
|
+
fields,
|
|
170
|
+
rawValues: ctx.values,
|
|
171
|
+
settings: form.spamSettings,
|
|
172
|
+
defaultStrategies: defaultSpamStrategies(),
|
|
173
|
+
senderEmail: sender.email,
|
|
174
|
+
elapsedMs: ctx.elapsedMs ?? null,
|
|
175
|
+
origin: ctx.origin ?? null,
|
|
176
|
+
referrer: ctx.referrer ?? null,
|
|
177
|
+
});
|
|
178
|
+
const attribution = ctx.attribution && typeof ctx.attribution === 'object' ? ctx.attribution : null;
|
|
179
|
+
const row = {
|
|
180
|
+
formId: form.id,
|
|
181
|
+
schemaVersionId: versionId,
|
|
182
|
+
data: validation.data,
|
|
183
|
+
normalizedData: validation.normalized,
|
|
184
|
+
attribution,
|
|
185
|
+
senderName: sender.name,
|
|
186
|
+
senderEmail: sender.email,
|
|
187
|
+
senderPhone: sender.phone,
|
|
188
|
+
preview: derivePreview(fields, validation.data),
|
|
189
|
+
spamStatus: spam.status,
|
|
190
|
+
unread: true,
|
|
191
|
+
ipAddressHash: hashClientIp(ctx.ip ?? 'unknown'),
|
|
192
|
+
userAgent: ctx.userAgent ?? null,
|
|
193
|
+
referrer: ctx.referrer ?? null,
|
|
194
|
+
pageUrl: ctx.pageUrl ?? null,
|
|
195
|
+
utmSource: asString(attribution?.source) || null,
|
|
196
|
+
utmMedium: asString(attribution?.medium) || null,
|
|
197
|
+
utmCampaign: asString(attribution?.campaign) || null,
|
|
198
|
+
submittedAt: new Date(),
|
|
199
|
+
};
|
|
200
|
+
const { id, entryNumber } = await assignAndCreate(db, row, form.id);
|
|
201
|
+
if ((ctx.files?.length ?? 0) > 0) {
|
|
202
|
+
try {
|
|
203
|
+
await persistSubmissionFiles(form.id, id, ctx.files ?? []);
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
207
|
+
console.error('[Actuate CMS] Failed to persist submission files:', err);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Notification/webhook side-effects (PR6) run via plugin hooks; never block
|
|
212
|
+
// or fail the submission on them. Spam is not notified.
|
|
213
|
+
if (spam.status !== 'spam') {
|
|
214
|
+
void fireSubmissionHooks(form.id, validation.data);
|
|
215
|
+
}
|
|
216
|
+
await logEvent({
|
|
217
|
+
event: spam.status === 'spam' ? 'form.spam_detected' : 'form.submitted',
|
|
218
|
+
details: {
|
|
219
|
+
formId: form.id,
|
|
220
|
+
submissionId: id,
|
|
221
|
+
entryNumber,
|
|
222
|
+
spamStatus: spam.status,
|
|
223
|
+
...(spam.reasons.length > 0 ? { spamReasons: spam.reasons } : {}),
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
return {
|
|
227
|
+
submissionId: id,
|
|
228
|
+
entryNumber,
|
|
229
|
+
spamStatus: spam.status,
|
|
230
|
+
confirmation: buildConfirmation(form),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Project a form definition into the public, embeddable schema. Strips every
|
|
235
|
+
* server-only concern (access rules, notification recipients, webhook secrets,
|
|
236
|
+
* blocklists, encryption flags) so nothing sensitive leaks to the browser.
|
|
237
|
+
*/
|
|
238
|
+
export function buildPublicForm(form, fields, schemaVersionId, options = {}) {
|
|
239
|
+
const publicFields = fields
|
|
240
|
+
.filter((f) => f.type !== 'honeypot')
|
|
241
|
+
.map((f) => ({
|
|
242
|
+
id: f.id,
|
|
243
|
+
key: f.key,
|
|
244
|
+
label: f.label,
|
|
245
|
+
type: f.type,
|
|
246
|
+
required: Boolean(f.required),
|
|
247
|
+
placeholder: f.placeholder,
|
|
248
|
+
helpText: f.helpText,
|
|
249
|
+
defaultValue: f.defaultValue ?? undefined,
|
|
250
|
+
options: f.options,
|
|
251
|
+
width: f.width,
|
|
252
|
+
sortOrder: f.sortOrder,
|
|
253
|
+
validationRules: f.validationRules
|
|
254
|
+
? sanitizePublicRules(f.validationRules)
|
|
255
|
+
: undefined,
|
|
256
|
+
conditionalLogic: f.conditionalLogic,
|
|
257
|
+
}));
|
|
258
|
+
const basePath = getActuateConfig()?.forms?.publicSubmissionPath ?? '/api/cms/public/forms';
|
|
259
|
+
return {
|
|
260
|
+
id: form.id,
|
|
261
|
+
name: form.name,
|
|
262
|
+
slug: form.slug,
|
|
263
|
+
description: form.description || undefined,
|
|
264
|
+
fields: publicFields,
|
|
265
|
+
submitPath: `${basePath.replace(/\/$/, '')}/${form.slug}/submit`,
|
|
266
|
+
successMessage: form.successMessage,
|
|
267
|
+
redirectUrl: form.redirectUrl,
|
|
268
|
+
spam: {
|
|
269
|
+
honeypotFieldKey: form.spamSettings?.honeypotFieldKey ?? fields.find((f) => f.type === 'honeypot')?.key,
|
|
270
|
+
minSubmitSeconds: form.spamSettings?.minSubmitSeconds,
|
|
271
|
+
},
|
|
272
|
+
captcha: options.captcha && options.captcha.provider !== 'none' ? options.captcha : undefined,
|
|
273
|
+
schemaVersionId,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/** Strip server-side-only validation hints (custom validator names) before exposing rules. */
|
|
277
|
+
function sanitizePublicRules(rules) {
|
|
278
|
+
const { customValidator: _drop, ...rest } = rules;
|
|
279
|
+
return rest;
|
|
280
|
+
}
|
|
281
|
+
export { resolveFields as resolveSubmissionFields };
|
|
282
|
+
//# sourceMappingURL=submission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submission.js","sourceRoot":"","sources":["../../src/forms/submission.ts"],"names":[],"mappings":"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"}
|