@orion-studios/cms 0.5.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/README.md +96 -0
- package/dist/analytics/react.d.ts +53 -0
- package/dist/analytics/react.js +195 -0
- package/dist/blocks/index.d.ts +222 -0
- package/dist/blocks/index.js +338 -0
- package/dist/chunk-HVJCF2IZ.js +76 -0
- package/dist/chunk-VPUODCNH.js +448 -0
- package/dist/chunk-WQDHEQDE.js +527 -0
- package/dist/content/index.d.ts +51 -0
- package/dist/content/index.js +8 -0
- package/dist/forms/index.d.ts +70 -0
- package/dist/forms/index.js +38 -0
- package/dist/forms/react.d.ts +45 -0
- package/dist/forms/react.js +8 -0
- package/dist/server/index.d.ts +403 -0
- package/dist/server/index.js +2280 -0
- package/dist/studio/index.d.ts +534 -0
- package/dist/studio/index.js +3824 -0
- package/dist/studio/styles.css +444 -0
- package/dist/submission-BKdBedOe.d.ts +61 -0
- package/dist/submission-CzrfXu17.d.ts +30 -0
- package/package.json +97 -0
- package/sql/bootstrap.sql +458 -0
- package/sql/migrations/0001_atomic_scheduled_publish.sql +68 -0
- package/sql/migrations/0002_rate_limits.sql +62 -0
- package/sql/migrations/0003_atomic_global_update.sql +46 -0
- package/sql/migrations/0004_analytics_visitor_tracking.sql +8 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
// src/forms/validation.ts
|
|
2
|
+
var MAJOR_EMAIL_PROVIDERS = [
|
|
3
|
+
"gmail.com",
|
|
4
|
+
"yahoo.com",
|
|
5
|
+
"outlook.com",
|
|
6
|
+
"hotmail.com",
|
|
7
|
+
"icloud.com",
|
|
8
|
+
"live.com",
|
|
9
|
+
"protonmail.com"
|
|
10
|
+
];
|
|
11
|
+
var KNOWN_GOOD_DOMAINS = /* @__PURE__ */ new Set([
|
|
12
|
+
...MAJOR_EMAIL_PROVIDERS,
|
|
13
|
+
"aol.com",
|
|
14
|
+
"gmx.com",
|
|
15
|
+
"googlemail.com",
|
|
16
|
+
"hey.com",
|
|
17
|
+
"mac.com",
|
|
18
|
+
"mail.com",
|
|
19
|
+
"me.com",
|
|
20
|
+
"msn.com",
|
|
21
|
+
"pm.me",
|
|
22
|
+
"proton.me",
|
|
23
|
+
"ymail.com",
|
|
24
|
+
"zoho.com"
|
|
25
|
+
]);
|
|
26
|
+
var EXPLICIT_TYPO_MAP = {
|
|
27
|
+
"gamil.com": "gmail.com",
|
|
28
|
+
"gmial.com": "gmail.com",
|
|
29
|
+
"gmai.com": "gmail.com",
|
|
30
|
+
"gmaill.com": "gmail.com",
|
|
31
|
+
"gmail.co": "gmail.com",
|
|
32
|
+
"gmail.con": "gmail.com",
|
|
33
|
+
"gmail.cmo": "gmail.com",
|
|
34
|
+
"hotmial.com": "hotmail.com",
|
|
35
|
+
"hotmall.com": "hotmail.com",
|
|
36
|
+
"hotmail.co": "hotmail.com",
|
|
37
|
+
"hotmail.con": "hotmail.com",
|
|
38
|
+
"iclod.com": "icloud.com",
|
|
39
|
+
"icloud.co": "icloud.com",
|
|
40
|
+
"icoud.com": "icloud.com",
|
|
41
|
+
"outlok.com": "outlook.com",
|
|
42
|
+
"outloook.com": "outlook.com",
|
|
43
|
+
"outlook.co": "outlook.com",
|
|
44
|
+
"outlook.con": "outlook.com",
|
|
45
|
+
"protonmail.co": "protonmail.com",
|
|
46
|
+
"yaho.com": "yahoo.com",
|
|
47
|
+
"yahooo.com": "yahoo.com",
|
|
48
|
+
"yahoo.co": "yahoo.com",
|
|
49
|
+
"yahoo.con": "yahoo.com"
|
|
50
|
+
};
|
|
51
|
+
var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
52
|
+
function levenshtein(a, b) {
|
|
53
|
+
if (a === b) return 0;
|
|
54
|
+
const rows = a.length + 1;
|
|
55
|
+
const cols = b.length + 1;
|
|
56
|
+
const dist = Array.from({ length: cols }, (_, j) => j);
|
|
57
|
+
for (let i = 1; i < rows; i += 1) {
|
|
58
|
+
let prevDiagonal = dist[0];
|
|
59
|
+
dist[0] = i;
|
|
60
|
+
for (let j = 1; j < cols; j += 1) {
|
|
61
|
+
const temp = dist[j];
|
|
62
|
+
dist[j] = Math.min(
|
|
63
|
+
dist[j] + 1,
|
|
64
|
+
dist[j - 1] + 1,
|
|
65
|
+
prevDiagonal + (a[i - 1] === b[j - 1] ? 0 : 1)
|
|
66
|
+
);
|
|
67
|
+
prevDiagonal = temp;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return dist[cols - 1];
|
|
71
|
+
}
|
|
72
|
+
function normalizeEmail(value) {
|
|
73
|
+
return value.trim().toLowerCase();
|
|
74
|
+
}
|
|
75
|
+
function validateEmail(value, options = {}) {
|
|
76
|
+
const normalized = normalizeEmail(value);
|
|
77
|
+
if (normalized.length === 0) {
|
|
78
|
+
return { valid: false, normalized, message: "Email is required." };
|
|
79
|
+
}
|
|
80
|
+
if (!EMAIL_PATTERN.test(normalized)) {
|
|
81
|
+
return { valid: false, normalized, message: "Enter a valid email address." };
|
|
82
|
+
}
|
|
83
|
+
const [localPart, domain] = normalized.split("@");
|
|
84
|
+
const knownGood = options.knownGoodDomains ? /* @__PURE__ */ new Set([...KNOWN_GOOD_DOMAINS, ...options.knownGoodDomains.map((entry) => entry.toLowerCase())]) : KNOWN_GOOD_DOMAINS;
|
|
85
|
+
if (knownGood.has(domain)) {
|
|
86
|
+
return { valid: true, normalized };
|
|
87
|
+
}
|
|
88
|
+
const explicitCorrection = EXPLICIT_TYPO_MAP[domain];
|
|
89
|
+
if (explicitCorrection) {
|
|
90
|
+
const suggestion = `${localPart}@${explicitCorrection}`;
|
|
91
|
+
return {
|
|
92
|
+
valid: false,
|
|
93
|
+
normalized,
|
|
94
|
+
message: `Did you mean ${suggestion}?`,
|
|
95
|
+
suggestion
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
for (const provider of MAJOR_EMAIL_PROVIDERS) {
|
|
99
|
+
if (levenshtein(domain, provider) === 1) {
|
|
100
|
+
const suggestion = `${localPart}@${provider}`;
|
|
101
|
+
return {
|
|
102
|
+
valid: false,
|
|
103
|
+
normalized,
|
|
104
|
+
message: `Did you mean ${suggestion}?`,
|
|
105
|
+
suggestion
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { valid: true, normalized };
|
|
110
|
+
}
|
|
111
|
+
function normalizePhone(value) {
|
|
112
|
+
const digits = value.replace(/\D+/g, "");
|
|
113
|
+
if (digits.length === 11 && digits.startsWith("1")) {
|
|
114
|
+
return digits.slice(1);
|
|
115
|
+
}
|
|
116
|
+
return digits;
|
|
117
|
+
}
|
|
118
|
+
function formatPhoneUS(value) {
|
|
119
|
+
const digits = normalizePhone(value).slice(0, 10);
|
|
120
|
+
if (digits.length === 0) return "";
|
|
121
|
+
if (digits.length < 4) return `(${digits}`;
|
|
122
|
+
if (digits.length < 7) return `(${digits.slice(0, 3)}) ${digits.slice(3)}`;
|
|
123
|
+
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`;
|
|
124
|
+
}
|
|
125
|
+
function validatePhoneUS(value) {
|
|
126
|
+
const digits = normalizePhone(value);
|
|
127
|
+
if (digits.length === 0) {
|
|
128
|
+
return { valid: false, normalized: "", message: "Phone number is required." };
|
|
129
|
+
}
|
|
130
|
+
if (digits.length !== 10) {
|
|
131
|
+
return { valid: false, normalized: digits, message: "Enter a 10-digit phone number." };
|
|
132
|
+
}
|
|
133
|
+
return { valid: true, normalized: digits };
|
|
134
|
+
}
|
|
135
|
+
function normalizeUrl(value) {
|
|
136
|
+
const trimmed = value.trim();
|
|
137
|
+
if (trimmed.length === 0) return "";
|
|
138
|
+
if (/^https?:\/\//i.test(trimmed)) return trimmed;
|
|
139
|
+
return `https://${trimmed}`;
|
|
140
|
+
}
|
|
141
|
+
function validateUrl(value) {
|
|
142
|
+
const normalized = normalizeUrl(value);
|
|
143
|
+
if (normalized.length === 0) {
|
|
144
|
+
return { valid: false, normalized, message: "URL is required." };
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const parsed = new URL(normalized);
|
|
148
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
149
|
+
return { valid: false, normalized, message: "Enter a valid web address." };
|
|
150
|
+
}
|
|
151
|
+
if (!parsed.hostname.includes(".")) {
|
|
152
|
+
return { valid: false, normalized, message: "Enter a valid web address." };
|
|
153
|
+
}
|
|
154
|
+
return { valid: true, normalized };
|
|
155
|
+
} catch {
|
|
156
|
+
return { valid: false, normalized, message: "Enter a valid web address." };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function validateRequired(value, label = "This field") {
|
|
160
|
+
const normalized = typeof value === "string" ? value.trim() : "";
|
|
161
|
+
const present = typeof value === "string" ? normalized.length > 0 : Array.isArray(value) ? value.length > 0 : value !== null && value !== void 0 && value !== false;
|
|
162
|
+
if (!present) {
|
|
163
|
+
return { valid: false, normalized, message: `${label} is required.` };
|
|
164
|
+
}
|
|
165
|
+
return { valid: true, normalized: typeof value === "string" ? normalized : String(value) };
|
|
166
|
+
}
|
|
167
|
+
var FORM_FIELD_TYPES = [
|
|
168
|
+
"text",
|
|
169
|
+
"textarea",
|
|
170
|
+
"email",
|
|
171
|
+
"phone",
|
|
172
|
+
"url",
|
|
173
|
+
"select",
|
|
174
|
+
"radio",
|
|
175
|
+
"checkbox",
|
|
176
|
+
"date",
|
|
177
|
+
"number",
|
|
178
|
+
"hidden"
|
|
179
|
+
];
|
|
180
|
+
function normalizeFieldValue(value, fieldType) {
|
|
181
|
+
switch (fieldType) {
|
|
182
|
+
case "email":
|
|
183
|
+
return normalizeEmail(value);
|
|
184
|
+
case "phone":
|
|
185
|
+
return normalizePhone(value);
|
|
186
|
+
case "url":
|
|
187
|
+
return normalizeUrl(value);
|
|
188
|
+
default:
|
|
189
|
+
return value.trim();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function validateDate(value) {
|
|
193
|
+
const normalized = value.trim();
|
|
194
|
+
if (normalized.length === 0) {
|
|
195
|
+
return { valid: false, normalized, message: "Date is required." };
|
|
196
|
+
}
|
|
197
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(normalized) || Number.isNaN(Date.parse(normalized))) {
|
|
198
|
+
return { valid: false, normalized, message: "Enter a valid date." };
|
|
199
|
+
}
|
|
200
|
+
return { valid: true, normalized };
|
|
201
|
+
}
|
|
202
|
+
function validateNumber(value) {
|
|
203
|
+
const normalized = value.trim().replace(/,/g, "");
|
|
204
|
+
if (normalized.length === 0) {
|
|
205
|
+
return { valid: false, normalized, message: "A number is required." };
|
|
206
|
+
}
|
|
207
|
+
const parsed = Number(normalized);
|
|
208
|
+
if (!Number.isFinite(parsed)) {
|
|
209
|
+
return { valid: false, normalized, message: "Enter a valid number." };
|
|
210
|
+
}
|
|
211
|
+
return { valid: true, normalized: String(parsed) };
|
|
212
|
+
}
|
|
213
|
+
function inferFieldType(field) {
|
|
214
|
+
const type = typeof field.type === "string" ? field.type.toLowerCase() : "";
|
|
215
|
+
if (type === "tel") return "phone";
|
|
216
|
+
if (FORM_FIELD_TYPES.includes(type)) return type;
|
|
217
|
+
const name = typeof field.name === "string" ? field.name.toLowerCase() : "";
|
|
218
|
+
if (name.includes("email")) return "email";
|
|
219
|
+
if (name.includes("phone") || name.includes("mobile")) return "phone";
|
|
220
|
+
if (name.includes("website") || name.endsWith("url")) return "url";
|
|
221
|
+
return "text";
|
|
222
|
+
}
|
|
223
|
+
function fieldOptions(field) {
|
|
224
|
+
if (!Array.isArray(field.options)) return [];
|
|
225
|
+
return field.options.map((option) => {
|
|
226
|
+
if (typeof option === "string") return { label: option, value: option };
|
|
227
|
+
if (option && typeof option === "object") {
|
|
228
|
+
const record = option;
|
|
229
|
+
const value = typeof record.value === "string" ? record.value : "";
|
|
230
|
+
const label = typeof record.label === "string" && record.label ? record.label : value;
|
|
231
|
+
return value ? { label, value } : null;
|
|
232
|
+
}
|
|
233
|
+
return null;
|
|
234
|
+
}).filter((option) => option !== null);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/forms/submission.ts
|
|
238
|
+
var HONEYPOT_FIELD_NAME = "website_url_confirm";
|
|
239
|
+
function createMemoryRateLimitStore(options) {
|
|
240
|
+
const max = options?.max ?? 5;
|
|
241
|
+
const windowMs = options?.windowMs ?? 6e4;
|
|
242
|
+
const hits = /* @__PURE__ */ new Map();
|
|
243
|
+
return {
|
|
244
|
+
isLimited(key, now) {
|
|
245
|
+
const windowStart = now - windowMs;
|
|
246
|
+
const entries = (hits.get(key) || []).filter((timestamp) => timestamp > windowStart);
|
|
247
|
+
entries.push(now);
|
|
248
|
+
hits.set(key, entries);
|
|
249
|
+
if (hits.size > 1e4) {
|
|
250
|
+
for (const [entryKey, timestamps] of hits) {
|
|
251
|
+
if (timestamps.every((timestamp) => timestamp <= windowStart)) {
|
|
252
|
+
hits.delete(entryKey);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return entries.length > max;
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function collectFieldDefinitions(config) {
|
|
261
|
+
const definitions = [];
|
|
262
|
+
for (const step of config.steps || []) {
|
|
263
|
+
for (const field of step.fields || []) {
|
|
264
|
+
const name = typeof field.name === "string" ? field.name.trim() : "";
|
|
265
|
+
if (!name) continue;
|
|
266
|
+
definitions.push({
|
|
267
|
+
name,
|
|
268
|
+
type: inferFieldType(field),
|
|
269
|
+
required: field.required === true,
|
|
270
|
+
label: typeof field.label === "string" && field.label.length > 0 ? field.label : name,
|
|
271
|
+
options: fieldOptions(field)
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return definitions;
|
|
276
|
+
}
|
|
277
|
+
var MAX_UNKNOWN_FIELDS = 20;
|
|
278
|
+
var MAX_VALUE_LENGTH = 5e3;
|
|
279
|
+
function validateChoice(definition, rawValue) {
|
|
280
|
+
const allowed = new Set(definition.options.map((option) => option.value));
|
|
281
|
+
if (Array.isArray(rawValue)) {
|
|
282
|
+
const values = rawValue.filter((entry) => typeof entry === "string");
|
|
283
|
+
if (allowed.size > 0 && values.some((entry) => !allowed.has(entry))) {
|
|
284
|
+
return { ok: false, message: `Choose a valid option for ${definition.label}.` };
|
|
285
|
+
}
|
|
286
|
+
return { ok: true, value: values };
|
|
287
|
+
}
|
|
288
|
+
if (typeof rawValue === "string") {
|
|
289
|
+
const value = rawValue.trim();
|
|
290
|
+
if (allowed.size > 0 && value.length > 0 && !allowed.has(value)) {
|
|
291
|
+
return { ok: false, message: `Choose a valid option for ${definition.label}.` };
|
|
292
|
+
}
|
|
293
|
+
return { ok: true, value };
|
|
294
|
+
}
|
|
295
|
+
return { ok: true, value: rawValue };
|
|
296
|
+
}
|
|
297
|
+
function processSubmission(args) {
|
|
298
|
+
const {
|
|
299
|
+
config,
|
|
300
|
+
data,
|
|
301
|
+
now = Date.now(),
|
|
302
|
+
minFillTimeMs = 3e3,
|
|
303
|
+
honeypotFieldName = HONEYPOT_FIELD_NAME,
|
|
304
|
+
knownGoodEmailDomains
|
|
305
|
+
} = args;
|
|
306
|
+
const honeypotValue = data[honeypotFieldName];
|
|
307
|
+
if (typeof honeypotValue === "string" && honeypotValue.trim().length > 0) {
|
|
308
|
+
return { outcome: "spam" };
|
|
309
|
+
}
|
|
310
|
+
if (minFillTimeMs > 0) {
|
|
311
|
+
const renderedAt = Number(data._renderedAt);
|
|
312
|
+
if (Number.isFinite(renderedAt) && renderedAt > 0 && now - renderedAt < minFillTimeMs) {
|
|
313
|
+
return { outcome: "spam" };
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const definitions = collectFieldDefinitions(config);
|
|
317
|
+
const normalizedData = {};
|
|
318
|
+
const fieldErrors = {};
|
|
319
|
+
const knownNames = new Set(definitions.map((definition) => definition.name));
|
|
320
|
+
for (const definition of definitions) {
|
|
321
|
+
const rawValue = data[definition.name];
|
|
322
|
+
const isString = typeof rawValue === "string";
|
|
323
|
+
const normalized = isString ? normalizeFieldValue(rawValue.slice(0, MAX_VALUE_LENGTH), definition.type) : "";
|
|
324
|
+
const missing = rawValue === void 0 || rawValue === null || rawValue === false || isString && normalized.length === 0 || Array.isArray(rawValue) && rawValue.length === 0;
|
|
325
|
+
if (definition.required && missing) {
|
|
326
|
+
fieldErrors[definition.name] = `${definition.label} is required.`;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (definition.type === "select" || definition.type === "radio" || definition.type === "checkbox" && definition.options.length > 0) {
|
|
330
|
+
if (missing) continue;
|
|
331
|
+
const choice = validateChoice(definition, rawValue);
|
|
332
|
+
if (!choice.ok) {
|
|
333
|
+
fieldErrors[definition.name] = choice.message || "Choose a valid option.";
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
normalizedData[definition.name] = choice.value;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (definition.type === "checkbox") {
|
|
340
|
+
if (rawValue !== void 0 && rawValue !== null) {
|
|
341
|
+
normalizedData[definition.name] = rawValue === true || rawValue === "true" || rawValue === "on" || rawValue === "yes";
|
|
342
|
+
}
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (normalized.length > 0) {
|
|
346
|
+
if (definition.type === "email") {
|
|
347
|
+
const result = validateEmail(normalized, { knownGoodDomains: knownGoodEmailDomains });
|
|
348
|
+
if (!result.valid) {
|
|
349
|
+
fieldErrors[definition.name] = result.message || "Enter a valid email address.";
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
normalizedData[definition.name] = result.normalized;
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (definition.type === "phone") {
|
|
356
|
+
const result = validatePhoneUS(normalized);
|
|
357
|
+
if (!result.valid) {
|
|
358
|
+
fieldErrors[definition.name] = result.message || "Enter a valid phone number.";
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
normalizedData[definition.name] = result.normalized;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (definition.type === "url") {
|
|
365
|
+
const result = validateUrl(normalized);
|
|
366
|
+
if (!result.valid) {
|
|
367
|
+
fieldErrors[definition.name] = result.message || "Enter a valid web address.";
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
normalizedData[definition.name] = result.normalized;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (definition.type === "date") {
|
|
374
|
+
const result = validateDate(normalized);
|
|
375
|
+
if (!result.valid) {
|
|
376
|
+
fieldErrors[definition.name] = result.message || "Enter a valid date.";
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
normalizedData[definition.name] = result.normalized;
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
if (definition.type === "number") {
|
|
383
|
+
const result = validateNumber(normalized);
|
|
384
|
+
if (!result.valid) {
|
|
385
|
+
fieldErrors[definition.name] = result.message || "Enter a valid number.";
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
normalizedData[definition.name] = result.normalized;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (rawValue !== void 0 && rawValue !== null) {
|
|
393
|
+
normalizedData[definition.name] = isString ? normalized : rawValue;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
let unknownCount = 0;
|
|
397
|
+
for (const [key, value] of Object.entries(data)) {
|
|
398
|
+
if (knownNames.has(key) || key === honeypotFieldName || key.startsWith("_")) continue;
|
|
399
|
+
if (unknownCount >= MAX_UNKNOWN_FIELDS) break;
|
|
400
|
+
unknownCount += 1;
|
|
401
|
+
normalizedData[key] = typeof value === "string" ? value.trim().slice(0, MAX_VALUE_LENGTH) : value;
|
|
402
|
+
}
|
|
403
|
+
if (Object.keys(fieldErrors).length > 0) {
|
|
404
|
+
return { outcome: "invalid", fieldErrors };
|
|
405
|
+
}
|
|
406
|
+
return { outcome: "ok", normalizedData };
|
|
407
|
+
}
|
|
408
|
+
function isOriginAllowed(request, allowedOrigins) {
|
|
409
|
+
const origin = request.headers.get("origin");
|
|
410
|
+
if (!origin) return false;
|
|
411
|
+
if (allowedOrigins && allowedOrigins.length > 0) {
|
|
412
|
+
return allowedOrigins.includes(origin);
|
|
413
|
+
}
|
|
414
|
+
const host = request.headers.get("x-forwarded-host") || request.headers.get("host") || (() => {
|
|
415
|
+
try {
|
|
416
|
+
return new URL(request.url).host;
|
|
417
|
+
} catch {
|
|
418
|
+
return "";
|
|
419
|
+
}
|
|
420
|
+
})();
|
|
421
|
+
if (!host) return false;
|
|
422
|
+
try {
|
|
423
|
+
return new URL(origin).host === host;
|
|
424
|
+
} catch {
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export {
|
|
430
|
+
normalizeEmail,
|
|
431
|
+
validateEmail,
|
|
432
|
+
normalizePhone,
|
|
433
|
+
formatPhoneUS,
|
|
434
|
+
validatePhoneUS,
|
|
435
|
+
normalizeUrl,
|
|
436
|
+
validateUrl,
|
|
437
|
+
validateRequired,
|
|
438
|
+
FORM_FIELD_TYPES,
|
|
439
|
+
normalizeFieldValue,
|
|
440
|
+
validateDate,
|
|
441
|
+
validateNumber,
|
|
442
|
+
inferFieldType,
|
|
443
|
+
fieldOptions,
|
|
444
|
+
HONEYPOT_FIELD_NAME,
|
|
445
|
+
createMemoryRateLimitStore,
|
|
446
|
+
processSubmission,
|
|
447
|
+
isOriginAllowed
|
|
448
|
+
};
|