@frigear-nu/forms 0.0.6 → 0.0.8
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 +16 -1
- package/dist/module.d.mts +148 -8
- package/dist/module.json +1 -1
- package/dist/module.mjs +65 -35
- package/dist/runtime/app/components/FormBuilder.d.vue.ts +26 -0
- package/dist/runtime/app/components/FormBuilder.vue +220 -0
- package/dist/runtime/app/components/FormBuilder.vue.d.ts +26 -0
- package/dist/runtime/app/components/FormBuilderAccess.d.vue.ts +16 -0
- package/dist/runtime/app/components/FormBuilderAccess.vue +404 -0
- package/dist/runtime/app/components/FormBuilderAccess.vue.d.ts +16 -0
- package/dist/runtime/app/components/FormBuilderCaptchaSettings.d.vue.ts +15 -0
- package/dist/runtime/app/components/FormBuilderCaptchaSettings.vue +126 -0
- package/dist/runtime/app/components/FormBuilderCaptchaSettings.vue.d.ts +15 -0
- package/dist/runtime/app/components/FormBuilderCondition.d.vue.ts +16 -0
- package/dist/runtime/app/components/FormBuilderCondition.vue +62 -0
- package/dist/runtime/app/components/FormBuilderCondition.vue.d.ts +16 -0
- package/dist/runtime/app/components/FormBuilderFieldRow.d.vue.ts +27 -0
- package/dist/runtime/app/components/FormBuilderFieldRow.vue +87 -0
- package/dist/runtime/app/components/FormBuilderFieldRow.vue.d.ts +27 -0
- package/dist/runtime/app/components/FormBuilderFieldSettings.d.vue.ts +25 -0
- package/dist/runtime/app/components/FormBuilderFieldSettings.vue +170 -0
- package/dist/runtime/app/components/FormBuilderFieldSettings.vue.d.ts +25 -0
- package/dist/runtime/app/components/FormBuilderInsertRow.d.vue.ts +20 -0
- package/dist/runtime/app/components/FormBuilderInsertRow.vue +51 -0
- package/dist/runtime/app/components/FormBuilderInsertRow.vue.d.ts +20 -0
- package/dist/runtime/app/components/FormBuilderOptions.d.vue.ts +11 -0
- package/dist/runtime/app/components/FormBuilderOptions.vue +81 -0
- package/dist/runtime/app/components/FormBuilderOptions.vue.d.ts +11 -0
- package/dist/runtime/app/components/FormBuilderPhoneSettings.d.vue.ts +11 -0
- package/dist/runtime/app/components/FormBuilderPhoneSettings.vue +63 -0
- package/dist/runtime/app/components/FormBuilderPhoneSettings.vue.d.ts +11 -0
- package/dist/runtime/app/components/FormBuilderSettings.d.vue.ts +11 -0
- package/dist/runtime/app/components/FormBuilderSettings.vue +233 -0
- package/dist/runtime/app/components/FormBuilderSettings.vue.d.ts +11 -0
- package/dist/runtime/app/components/FormBuilderStepHeader.d.vue.ts +27 -0
- package/dist/runtime/app/components/FormBuilderStepHeader.vue +71 -0
- package/dist/runtime/app/components/FormBuilderStepHeader.vue.d.ts +27 -0
- package/dist/runtime/app/components/FormBuilderStepSettings.d.vue.ts +13 -0
- package/dist/runtime/app/components/FormBuilderStepSettings.vue +33 -0
- package/dist/runtime/app/components/FormBuilderStepSettings.vue.d.ts +13 -0
- package/dist/runtime/app/components/FormBuilderText.d.vue.ts +19 -0
- package/dist/runtime/app/components/FormBuilderText.vue +127 -0
- package/dist/runtime/app/components/FormBuilderText.vue.d.ts +19 -0
- package/dist/runtime/app/components/FormCaptcha.d.vue.ts +19 -0
- package/dist/runtime/app/components/FormCaptcha.vue +95 -0
- package/dist/runtime/app/components/FormCaptcha.vue.d.ts +19 -0
- package/dist/runtime/app/components/FormEmbed.d.vue.ts +17 -0
- package/dist/runtime/app/components/FormEmbed.vue +113 -0
- package/dist/runtime/app/components/FormEmbed.vue.d.ts +17 -0
- package/dist/runtime/app/components/FormField.d.vue.ts +4 -0
- package/dist/runtime/app/components/FormField.vue +35 -3
- package/dist/runtime/app/components/FormField.vue.d.ts +4 -0
- package/dist/runtime/app/components/FormPhoneInput.d.vue.ts +20 -0
- package/dist/runtime/app/components/FormPhoneInput.vue +56 -0
- package/dist/runtime/app/components/FormPhoneInput.vue.d.ts +20 -0
- package/dist/runtime/app/components/FormPreview.d.vue.ts +12 -0
- package/dist/runtime/app/components/FormPreview.vue +244 -0
- package/dist/runtime/app/components/FormPreview.vue.d.ts +12 -0
- package/dist/runtime/app/components/FormRenderer.d.vue.ts +6 -0
- package/dist/runtime/app/components/FormRenderer.vue +16 -7
- package/dist/runtime/app/components/FormRenderer.vue.d.ts +6 -0
- package/dist/runtime/app/components/FormSubmissionView.vue +2 -2
- package/dist/runtime/app/composables/index.d.ts +4 -1
- package/dist/runtime/app/composables/index.js +4 -1
- package/dist/runtime/app/composables/useCaptcha.d.ts +22 -0
- package/dist/runtime/app/composables/useCaptcha.js +39 -0
- package/dist/runtime/app/composables/useCaptchaSecrets.d.ts +7 -0
- package/dist/runtime/app/composables/useCaptchaSecrets.js +69 -0
- package/dist/runtime/app/composables/useFormBuilderEditor.d.ts +49 -0
- package/dist/runtime/app/composables/useFormBuilderEditor.js +210 -0
- package/dist/runtime/app/composables/useFormBuilderLabels.d.ts +8 -0
- package/dist/runtime/app/composables/useFormBuilderLabels.js +41 -0
- package/dist/runtime/app/composables/useForms.d.ts +18 -3
- package/dist/runtime/app/composables/useForms.js +38 -5
- package/dist/runtime/app/pages/admin-form.vue +112 -69
- package/dist/runtime/app/pages/admin-submission.vue +7 -16
- package/dist/runtime/app/pages/admin-submissions.vue +41 -46
- package/dist/runtime/app/pages/admin.vue +128 -44
- package/dist/runtime/app/pages/form.vue +71 -5
- package/dist/runtime/i18n/lang/da.json +231 -3
- package/dist/runtime/i18n/lang/en.json +231 -3
- package/dist/runtime/menus.js +10 -0
- package/dist/runtime/server/db/schema.sqlite.d.ts +283 -0
- package/dist/runtime/server/db/schema.sqlite.js +33 -1
- package/dist/runtime/server/mcp/schemas.d.ts +9 -0
- package/dist/runtime/server/mcp/schemas.js +8 -0
- package/dist/runtime/server/mcp/tools/admin/create-form.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/admin/create-form.js +21 -0
- package/dist/runtime/server/mcp/tools/admin/get-form-definition.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/admin/get-form-definition.js +14 -0
- package/dist/runtime/server/mcp/tools/admin/get-submission.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/admin/get-submission.js +26 -0
- package/dist/runtime/server/mcp/tools/admin/list-all-forms.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/admin/list-all-forms.js +21 -0
- package/dist/runtime/server/mcp/tools/admin/list-submissions.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/admin/list-submissions.js +22 -0
- package/dist/runtime/server/mcp/tools/admin/update-form.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/admin/update-form.js +24 -0
- package/dist/runtime/server/mcp/tools/get-form.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/get-form.js +12 -0
- package/dist/runtime/server/mcp/tools/list-forms.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/list-forms.js +11 -0
- package/dist/runtime/server/mcp/tools/submit-form.d.ts +2 -0
- package/dist/runtime/server/mcp/tools/submit-form.js +17 -0
- package/dist/runtime/server/plugins/email-delivery.js +16 -11
- package/dist/runtime/server/plugins/encryption.d.ts +2 -0
- package/dist/runtime/server/plugins/encryption.js +4 -0
- package/dist/runtime/server/plugins/staff-scopes.d.ts +2 -0
- package/dist/runtime/server/plugins/staff-scopes.js +1 -0
- package/dist/runtime/server/routes/admin/form-clone.post.d.ts +2 -0
- package/dist/runtime/server/routes/admin/form-clone.post.js +17 -0
- package/dist/runtime/server/routes/admin/form-password.delete.d.ts +2 -0
- package/dist/runtime/server/routes/admin/form-password.delete.js +8 -0
- package/dist/runtime/server/routes/admin/form-password.get.d.ts +2 -0
- package/dist/runtime/server/routes/admin/form-password.get.js +8 -0
- package/dist/runtime/server/routes/admin/form-password.put.d.ts +2 -0
- package/dist/runtime/server/routes/admin/form-password.put.js +14 -0
- package/dist/runtime/server/routes/admin/form-secret.delete.d.ts +2 -0
- package/dist/runtime/server/routes/admin/form-secret.delete.js +8 -0
- package/dist/runtime/server/routes/admin/form-secrets.get.d.ts +2 -0
- package/dist/runtime/server/routes/admin/form-secrets.get.js +5 -0
- package/dist/runtime/server/routes/admin/form-secrets.put.d.ts +2 -0
- package/dist/runtime/server/routes/admin/form-secrets.put.js +12 -0
- package/dist/runtime/server/routes/admin/form-token.delete.d.ts +2 -0
- package/dist/runtime/server/routes/admin/form-token.delete.js +12 -0
- package/dist/runtime/server/routes/admin/form-tokens.get.d.ts +2 -0
- package/dist/runtime/server/routes/admin/form-tokens.get.js +8 -0
- package/dist/runtime/server/routes/admin/form-tokens.post.d.ts +2 -0
- package/dist/runtime/server/routes/admin/form-tokens.post.js +12 -0
- package/dist/runtime/server/routes/admin/form.get.js +5 -1
- package/dist/runtime/server/routes/admin/form.patch.js +3 -12
- package/dist/runtime/server/routes/admin/forms.post.js +1 -6
- package/dist/runtime/server/routes/public/access.get.d.ts +2 -0
- package/dist/runtime/server/routes/public/access.get.js +3 -0
- package/dist/runtime/server/routes/public/form.get.js +3 -1
- package/dist/runtime/server/routes/public/submit.post.js +2 -2
- package/dist/runtime/server/utils/access.d.ts +27 -0
- package/dist/runtime/server/utils/access.js +63 -0
- package/dist/runtime/server/utils/admin-schemas.d.ts +219 -15
- package/dist/runtime/server/utils/auth.d.ts +7 -7
- package/dist/runtime/server/utils/auth.js +4 -4
- package/dist/runtime/server/utils/captcha.d.ts +10 -0
- package/dist/runtime/server/utils/captcha.js +25 -0
- package/dist/runtime/server/utils/crypto.d.ts +18 -0
- package/dist/runtime/server/utils/crypto.js +48 -0
- package/dist/runtime/server/utils/forms.d.ts +6 -1
- package/dist/runtime/server/utils/forms.js +28 -0
- package/dist/runtime/server/utils/mcp-access.d.ts +9 -0
- package/dist/runtime/server/utils/mcp-access.js +8 -0
- package/dist/runtime/server/utils/rate-limit.d.ts +3 -0
- package/dist/runtime/server/utils/rate-limit.js +19 -0
- package/dist/runtime/server/utils/render.d.ts +2 -11
- package/dist/runtime/server/utils/render.js +1 -26
- package/dist/runtime/server/utils/secrets.d.ts +28 -0
- package/dist/runtime/server/utils/secrets.js +60 -0
- package/dist/runtime/server/utils/submit.d.ts +12 -1
- package/dist/runtime/server/utils/submit.js +18 -4
- package/dist/runtime/utils/builder.d.ts +73 -0
- package/dist/runtime/utils/builder.js +220 -0
- package/dist/runtime/utils/captcha.d.ts +9 -0
- package/dist/runtime/utils/captcha.js +7 -0
- package/dist/runtime/utils/conditions.d.ts +3 -1
- package/dist/runtime/utils/conditions.js +4 -0
- package/dist/runtime/utils/definition.d.ts +116 -6
- package/dist/runtime/utils/definition.js +53 -4
- package/dist/runtime/utils/embed.d.ts +8 -0
- package/dist/runtime/utils/embed.js +7 -0
- package/dist/runtime/utils/index.d.ts +16 -3
- package/dist/runtime/utils/index.js +66 -3
- package/dist/runtime/utils/markdown.d.ts +1 -5
- package/dist/runtime/utils/markdown.js +1 -5
- package/dist/runtime/utils/phone.d.ts +29 -0
- package/dist/runtime/utils/phone.js +294 -0
- package/dist/runtime/utils/preview.d.ts +12 -0
- package/dist/runtime/utils/preview.js +17 -0
- package/dist/runtime/utils/schedule.d.ts +1 -0
- package/dist/runtime/utils/schedule.js +1 -0
- package/dist/runtime/utils/schema.d.ts +2 -0
- package/dist/runtime/utils/schema.js +12 -4
- package/dist/runtime/utils/template.d.ts +57 -0
- package/dist/runtime/utils/template.js +147 -0
- package/dist/runtime/utils/text.d.ts +0 -3
- package/dist/runtime/utils/text.js +0 -5
- package/dist/types.d.mts +5 -1
- package/package.json +13 -5
|
@@ -23,9 +23,11 @@ export declare const formInput: z.ZodObject<{
|
|
|
23
23
|
showIf: z.ZodOptional<z.ZodObject<{
|
|
24
24
|
field: z.ZodString;
|
|
25
25
|
op: z.ZodEnum<{
|
|
26
|
+
in: "in";
|
|
26
27
|
eq: "eq";
|
|
27
28
|
neq: "neq";
|
|
28
|
-
|
|
29
|
+
empty: "empty";
|
|
30
|
+
notEmpty: "notEmpty";
|
|
29
31
|
truthy: "truthy";
|
|
30
32
|
}>;
|
|
31
33
|
value: z.ZodOptional<z.ZodUnknown>;
|
|
@@ -34,16 +36,17 @@ export declare const formInput: z.ZodObject<{
|
|
|
34
36
|
key: z.ZodString;
|
|
35
37
|
type: z.ZodEnum<{
|
|
36
38
|
number: "number";
|
|
37
|
-
|
|
39
|
+
date: "date";
|
|
40
|
+
file: "file";
|
|
38
41
|
email: "email";
|
|
42
|
+
text: "text";
|
|
39
43
|
tel: "tel";
|
|
40
44
|
textarea: "textarea";
|
|
41
45
|
select: "select";
|
|
42
46
|
radio: "radio";
|
|
43
47
|
checkbox: "checkbox";
|
|
44
|
-
date: "date";
|
|
45
|
-
file: "file";
|
|
46
48
|
markdown: "markdown";
|
|
49
|
+
captcha: "captcha";
|
|
47
50
|
}>;
|
|
48
51
|
content: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
49
52
|
label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
@@ -67,12 +70,39 @@ export declare const formInput: z.ZodObject<{
|
|
|
67
70
|
accept: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
68
71
|
message: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
69
72
|
}, z.core.$strip>>;
|
|
73
|
+
phone: z.ZodOptional<z.ZodObject<{
|
|
74
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
75
|
+
simple: "simple";
|
|
76
|
+
country: "country";
|
|
77
|
+
}>>;
|
|
78
|
+
countries: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
79
|
+
defaultCountry: z.ZodOptional<z.ZodString>;
|
|
80
|
+
}, z.core.$strip>>;
|
|
81
|
+
captcha: z.ZodOptional<z.ZodObject<{
|
|
82
|
+
provider: z.ZodOptional<z.ZodEnum<{
|
|
83
|
+
turnstile: "turnstile";
|
|
84
|
+
recaptcha: "recaptcha";
|
|
85
|
+
hcaptcha: "hcaptcha";
|
|
86
|
+
}>>;
|
|
87
|
+
siteKey: z.ZodOptional<z.ZodString>;
|
|
88
|
+
theme: z.ZodOptional<z.ZodEnum<{
|
|
89
|
+
auto: "auto";
|
|
90
|
+
light: "light";
|
|
91
|
+
dark: "dark";
|
|
92
|
+
}>>;
|
|
93
|
+
size: z.ZodOptional<z.ZodEnum<{
|
|
94
|
+
normal: "normal";
|
|
95
|
+
compact: "compact";
|
|
96
|
+
}>>;
|
|
97
|
+
}, z.core.$strip>>;
|
|
70
98
|
showIf: z.ZodOptional<z.ZodObject<{
|
|
71
99
|
field: z.ZodString;
|
|
72
100
|
op: z.ZodEnum<{
|
|
101
|
+
in: "in";
|
|
73
102
|
eq: "eq";
|
|
74
103
|
neq: "neq";
|
|
75
|
-
|
|
104
|
+
empty: "empty";
|
|
105
|
+
notEmpty: "notEmpty";
|
|
76
106
|
truthy: "truthy";
|
|
77
107
|
}>;
|
|
78
108
|
value: z.ZodOptional<z.ZodUnknown>;
|
|
@@ -87,10 +117,48 @@ export declare const formInput: z.ZodObject<{
|
|
|
87
117
|
keep: z.ZodArray<z.ZodString>;
|
|
88
118
|
}, z.core.$strip>>;
|
|
89
119
|
}, z.core.$strip>>;
|
|
120
|
+
access: z.ZodOptional<z.ZodObject<{
|
|
121
|
+
availability: z.ZodDefault<z.ZodEnum<{
|
|
122
|
+
web: "web";
|
|
123
|
+
api: "api";
|
|
124
|
+
}>>;
|
|
125
|
+
auth: z.ZodDefault<z.ZodEnum<{
|
|
126
|
+
none: "none";
|
|
127
|
+
user: "user";
|
|
128
|
+
token: "token";
|
|
129
|
+
password: "password";
|
|
130
|
+
}>>;
|
|
131
|
+
schedule: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
132
|
+
type: z.ZodLiteral<"always">;
|
|
133
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
134
|
+
type: z.ZodLiteral<"range">;
|
|
135
|
+
from: z.ZodOptional<z.ZodString>;
|
|
136
|
+
to: z.ZodOptional<z.ZodString>;
|
|
137
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
138
|
+
type: z.ZodLiteral<"yearly">;
|
|
139
|
+
from: z.ZodObject<{
|
|
140
|
+
month: z.ZodNumber;
|
|
141
|
+
day: z.ZodNumber;
|
|
142
|
+
time: z.ZodString;
|
|
143
|
+
}, z.core.$strip>;
|
|
144
|
+
to: z.ZodObject<{
|
|
145
|
+
month: z.ZodNumber;
|
|
146
|
+
day: z.ZodNumber;
|
|
147
|
+
time: z.ZodString;
|
|
148
|
+
}, z.core.$strip>;
|
|
149
|
+
timezone: z.ZodString;
|
|
150
|
+
}, z.core.$strip>], "type">>;
|
|
151
|
+
}, z.core.$strip>>;
|
|
90
152
|
delivery: z.ZodOptional<z.ZodObject<{
|
|
91
153
|
email: z.ZodOptional<z.ZodObject<{
|
|
92
154
|
to: z.ZodArray<z.ZodString>;
|
|
93
155
|
subject: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
156
|
+
body: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
157
|
+
routes: z.ZodOptional<z.ZodObject<{
|
|
158
|
+
field: z.ZodString;
|
|
159
|
+
to: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
|
|
160
|
+
}, z.core.$strip>>;
|
|
161
|
+
replyToField: z.ZodOptional<z.ZodString>;
|
|
94
162
|
}, z.core.$strip>>;
|
|
95
163
|
}, z.core.$strip>>;
|
|
96
164
|
}, z.core.$strip>;
|
|
@@ -114,9 +182,11 @@ export declare const formPatch: z.ZodObject<{
|
|
|
114
182
|
showIf: z.ZodOptional<z.ZodObject<{
|
|
115
183
|
field: z.ZodString;
|
|
116
184
|
op: z.ZodEnum<{
|
|
185
|
+
in: "in";
|
|
117
186
|
eq: "eq";
|
|
118
187
|
neq: "neq";
|
|
119
|
-
|
|
188
|
+
empty: "empty";
|
|
189
|
+
notEmpty: "notEmpty";
|
|
120
190
|
truthy: "truthy";
|
|
121
191
|
}>;
|
|
122
192
|
value: z.ZodOptional<z.ZodUnknown>;
|
|
@@ -125,16 +195,17 @@ export declare const formPatch: z.ZodObject<{
|
|
|
125
195
|
key: z.ZodString;
|
|
126
196
|
type: z.ZodEnum<{
|
|
127
197
|
number: "number";
|
|
128
|
-
|
|
198
|
+
date: "date";
|
|
199
|
+
file: "file";
|
|
129
200
|
email: "email";
|
|
201
|
+
text: "text";
|
|
130
202
|
tel: "tel";
|
|
131
203
|
textarea: "textarea";
|
|
132
204
|
select: "select";
|
|
133
205
|
radio: "radio";
|
|
134
206
|
checkbox: "checkbox";
|
|
135
|
-
date: "date";
|
|
136
|
-
file: "file";
|
|
137
207
|
markdown: "markdown";
|
|
208
|
+
captcha: "captcha";
|
|
138
209
|
}>;
|
|
139
210
|
content: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
140
211
|
label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
@@ -158,12 +229,39 @@ export declare const formPatch: z.ZodObject<{
|
|
|
158
229
|
accept: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
159
230
|
message: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
160
231
|
}, z.core.$strip>>;
|
|
232
|
+
phone: z.ZodOptional<z.ZodObject<{
|
|
233
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
234
|
+
simple: "simple";
|
|
235
|
+
country: "country";
|
|
236
|
+
}>>;
|
|
237
|
+
countries: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
238
|
+
defaultCountry: z.ZodOptional<z.ZodString>;
|
|
239
|
+
}, z.core.$strip>>;
|
|
240
|
+
captcha: z.ZodOptional<z.ZodObject<{
|
|
241
|
+
provider: z.ZodOptional<z.ZodEnum<{
|
|
242
|
+
turnstile: "turnstile";
|
|
243
|
+
recaptcha: "recaptcha";
|
|
244
|
+
hcaptcha: "hcaptcha";
|
|
245
|
+
}>>;
|
|
246
|
+
siteKey: z.ZodOptional<z.ZodString>;
|
|
247
|
+
theme: z.ZodOptional<z.ZodEnum<{
|
|
248
|
+
auto: "auto";
|
|
249
|
+
light: "light";
|
|
250
|
+
dark: "dark";
|
|
251
|
+
}>>;
|
|
252
|
+
size: z.ZodOptional<z.ZodEnum<{
|
|
253
|
+
normal: "normal";
|
|
254
|
+
compact: "compact";
|
|
255
|
+
}>>;
|
|
256
|
+
}, z.core.$strip>>;
|
|
161
257
|
showIf: z.ZodOptional<z.ZodObject<{
|
|
162
258
|
field: z.ZodString;
|
|
163
259
|
op: z.ZodEnum<{
|
|
260
|
+
in: "in";
|
|
164
261
|
eq: "eq";
|
|
165
262
|
neq: "neq";
|
|
166
|
-
|
|
263
|
+
empty: "empty";
|
|
264
|
+
notEmpty: "notEmpty";
|
|
167
265
|
truthy: "truthy";
|
|
168
266
|
}>;
|
|
169
267
|
value: z.ZodOptional<z.ZodUnknown>;
|
|
@@ -178,10 +276,48 @@ export declare const formPatch: z.ZodObject<{
|
|
|
178
276
|
keep: z.ZodArray<z.ZodString>;
|
|
179
277
|
}, z.core.$strip>>;
|
|
180
278
|
}, z.core.$strip>>;
|
|
279
|
+
access: z.ZodOptional<z.ZodObject<{
|
|
280
|
+
availability: z.ZodDefault<z.ZodEnum<{
|
|
281
|
+
web: "web";
|
|
282
|
+
api: "api";
|
|
283
|
+
}>>;
|
|
284
|
+
auth: z.ZodDefault<z.ZodEnum<{
|
|
285
|
+
none: "none";
|
|
286
|
+
user: "user";
|
|
287
|
+
token: "token";
|
|
288
|
+
password: "password";
|
|
289
|
+
}>>;
|
|
290
|
+
schedule: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
291
|
+
type: z.ZodLiteral<"always">;
|
|
292
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
293
|
+
type: z.ZodLiteral<"range">;
|
|
294
|
+
from: z.ZodOptional<z.ZodString>;
|
|
295
|
+
to: z.ZodOptional<z.ZodString>;
|
|
296
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
297
|
+
type: z.ZodLiteral<"yearly">;
|
|
298
|
+
from: z.ZodObject<{
|
|
299
|
+
month: z.ZodNumber;
|
|
300
|
+
day: z.ZodNumber;
|
|
301
|
+
time: z.ZodString;
|
|
302
|
+
}, z.core.$strip>;
|
|
303
|
+
to: z.ZodObject<{
|
|
304
|
+
month: z.ZodNumber;
|
|
305
|
+
day: z.ZodNumber;
|
|
306
|
+
time: z.ZodString;
|
|
307
|
+
}, z.core.$strip>;
|
|
308
|
+
timezone: z.ZodString;
|
|
309
|
+
}, z.core.$strip>], "type">>;
|
|
310
|
+
}, z.core.$strip>>;
|
|
181
311
|
delivery: z.ZodOptional<z.ZodObject<{
|
|
182
312
|
email: z.ZodOptional<z.ZodObject<{
|
|
183
313
|
to: z.ZodArray<z.ZodString>;
|
|
184
314
|
subject: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
315
|
+
body: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
316
|
+
routes: z.ZodOptional<z.ZodObject<{
|
|
317
|
+
field: z.ZodString;
|
|
318
|
+
to: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
|
|
319
|
+
}, z.core.$strip>>;
|
|
320
|
+
replyToField: z.ZodOptional<z.ZodString>;
|
|
185
321
|
}, z.core.$strip>>;
|
|
186
322
|
}, z.core.$strip>>;
|
|
187
323
|
}, z.core.$strip>>;
|
|
@@ -211,9 +347,11 @@ export declare const importInput: z.ZodObject<{
|
|
|
211
347
|
showIf: z.ZodOptional<z.ZodObject<{
|
|
212
348
|
field: z.ZodString;
|
|
213
349
|
op: z.ZodEnum<{
|
|
350
|
+
in: "in";
|
|
214
351
|
eq: "eq";
|
|
215
352
|
neq: "neq";
|
|
216
|
-
|
|
353
|
+
empty: "empty";
|
|
354
|
+
notEmpty: "notEmpty";
|
|
217
355
|
truthy: "truthy";
|
|
218
356
|
}>;
|
|
219
357
|
value: z.ZodOptional<z.ZodUnknown>;
|
|
@@ -222,16 +360,17 @@ export declare const importInput: z.ZodObject<{
|
|
|
222
360
|
key: z.ZodString;
|
|
223
361
|
type: z.ZodEnum<{
|
|
224
362
|
number: "number";
|
|
225
|
-
|
|
363
|
+
date: "date";
|
|
364
|
+
file: "file";
|
|
226
365
|
email: "email";
|
|
366
|
+
text: "text";
|
|
227
367
|
tel: "tel";
|
|
228
368
|
textarea: "textarea";
|
|
229
369
|
select: "select";
|
|
230
370
|
radio: "radio";
|
|
231
371
|
checkbox: "checkbox";
|
|
232
|
-
date: "date";
|
|
233
|
-
file: "file";
|
|
234
372
|
markdown: "markdown";
|
|
373
|
+
captcha: "captcha";
|
|
235
374
|
}>;
|
|
236
375
|
content: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
237
376
|
label: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
@@ -255,12 +394,39 @@ export declare const importInput: z.ZodObject<{
|
|
|
255
394
|
accept: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
256
395
|
message: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
257
396
|
}, z.core.$strip>>;
|
|
397
|
+
phone: z.ZodOptional<z.ZodObject<{
|
|
398
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
399
|
+
simple: "simple";
|
|
400
|
+
country: "country";
|
|
401
|
+
}>>;
|
|
402
|
+
countries: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
403
|
+
defaultCountry: z.ZodOptional<z.ZodString>;
|
|
404
|
+
}, z.core.$strip>>;
|
|
405
|
+
captcha: z.ZodOptional<z.ZodObject<{
|
|
406
|
+
provider: z.ZodOptional<z.ZodEnum<{
|
|
407
|
+
turnstile: "turnstile";
|
|
408
|
+
recaptcha: "recaptcha";
|
|
409
|
+
hcaptcha: "hcaptcha";
|
|
410
|
+
}>>;
|
|
411
|
+
siteKey: z.ZodOptional<z.ZodString>;
|
|
412
|
+
theme: z.ZodOptional<z.ZodEnum<{
|
|
413
|
+
auto: "auto";
|
|
414
|
+
light: "light";
|
|
415
|
+
dark: "dark";
|
|
416
|
+
}>>;
|
|
417
|
+
size: z.ZodOptional<z.ZodEnum<{
|
|
418
|
+
normal: "normal";
|
|
419
|
+
compact: "compact";
|
|
420
|
+
}>>;
|
|
421
|
+
}, z.core.$strip>>;
|
|
258
422
|
showIf: z.ZodOptional<z.ZodObject<{
|
|
259
423
|
field: z.ZodString;
|
|
260
424
|
op: z.ZodEnum<{
|
|
425
|
+
in: "in";
|
|
261
426
|
eq: "eq";
|
|
262
427
|
neq: "neq";
|
|
263
|
-
|
|
428
|
+
empty: "empty";
|
|
429
|
+
notEmpty: "notEmpty";
|
|
264
430
|
truthy: "truthy";
|
|
265
431
|
}>;
|
|
266
432
|
value: z.ZodOptional<z.ZodUnknown>;
|
|
@@ -275,10 +441,48 @@ export declare const importInput: z.ZodObject<{
|
|
|
275
441
|
keep: z.ZodArray<z.ZodString>;
|
|
276
442
|
}, z.core.$strip>>;
|
|
277
443
|
}, z.core.$strip>>;
|
|
444
|
+
access: z.ZodOptional<z.ZodObject<{
|
|
445
|
+
availability: z.ZodDefault<z.ZodEnum<{
|
|
446
|
+
web: "web";
|
|
447
|
+
api: "api";
|
|
448
|
+
}>>;
|
|
449
|
+
auth: z.ZodDefault<z.ZodEnum<{
|
|
450
|
+
none: "none";
|
|
451
|
+
user: "user";
|
|
452
|
+
token: "token";
|
|
453
|
+
password: "password";
|
|
454
|
+
}>>;
|
|
455
|
+
schedule: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
456
|
+
type: z.ZodLiteral<"always">;
|
|
457
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
458
|
+
type: z.ZodLiteral<"range">;
|
|
459
|
+
from: z.ZodOptional<z.ZodString>;
|
|
460
|
+
to: z.ZodOptional<z.ZodString>;
|
|
461
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
462
|
+
type: z.ZodLiteral<"yearly">;
|
|
463
|
+
from: z.ZodObject<{
|
|
464
|
+
month: z.ZodNumber;
|
|
465
|
+
day: z.ZodNumber;
|
|
466
|
+
time: z.ZodString;
|
|
467
|
+
}, z.core.$strip>;
|
|
468
|
+
to: z.ZodObject<{
|
|
469
|
+
month: z.ZodNumber;
|
|
470
|
+
day: z.ZodNumber;
|
|
471
|
+
time: z.ZodString;
|
|
472
|
+
}, z.core.$strip>;
|
|
473
|
+
timezone: z.ZodString;
|
|
474
|
+
}, z.core.$strip>], "type">>;
|
|
475
|
+
}, z.core.$strip>>;
|
|
278
476
|
delivery: z.ZodOptional<z.ZodObject<{
|
|
279
477
|
email: z.ZodOptional<z.ZodObject<{
|
|
280
478
|
to: z.ZodArray<z.ZodString>;
|
|
281
479
|
subject: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
480
|
+
body: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
481
|
+
routes: z.ZodOptional<z.ZodObject<{
|
|
482
|
+
field: z.ZodString;
|
|
483
|
+
to: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
|
|
484
|
+
}, z.core.$strip>>;
|
|
485
|
+
replyToField: z.ZodOptional<z.ZodString>;
|
|
282
486
|
}, z.core.$strip>>;
|
|
283
487
|
}, z.core.$strip>>;
|
|
284
488
|
}, z.core.$strip>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { H3Event } from 'h3';
|
|
2
|
-
/**
|
|
3
|
-
* Gate for the admin routes (form definitions, submissions, files). This repo has no shared
|
|
4
|
-
* permission system, so the host app decides through the `forms:authorize` Nitro hook.
|
|
5
|
-
* Denied by default:
|
|
6
|
-
*
|
|
7
|
-
* nitroApp.hooks.hook('forms:authorize', (ctx) => { ctx.allowed = isStaff(ctx.userId) })
|
|
8
|
-
*/
|
|
2
|
+
/** Gate for the admin routes (form definitions, submissions, files). */
|
|
9
3
|
export declare function requireFormsManager(event: H3Event): Promise<{
|
|
10
4
|
userId: number;
|
|
11
5
|
}>;
|
|
6
|
+
/** Non-throwing form of the `forms:authorize` policy for the caller of an already-authenticated request (MCP). */
|
|
7
|
+
export declare const canManageForms: (event: H3Event, principal: {
|
|
8
|
+
user: {
|
|
9
|
+
id: number;
|
|
10
|
+
};
|
|
11
|
+
}) => any;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
const gate = definePolicyGate("forms:authorize");
|
|
1
2
|
export async function requireFormsManager(event) {
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
await useNitroApp().hooks.callHook("forms:authorize", ctx);
|
|
5
|
-
if (!ctx.allowed) throw createError({ statusCode: 403, statusMessage: "You are not allowed to manage forms." });
|
|
3
|
+
const user = await requireUser(event, { scopes: ["forms:manage"] });
|
|
4
|
+
await gate.assert(event, user.id, "You are not allowed to manage forms.");
|
|
6
5
|
return { userId: user.id };
|
|
7
6
|
}
|
|
7
|
+
export const canManageForms = (event, principal) => gate.isAllowed(event, principal.user.id);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
2
|
+
import type { CaptchaProvider } from '../../../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Verifies a captcha response token. The `forms:captcha:verify` hook runs first and can decide
|
|
5
|
+
* (`ctx.valid`) without any network call; otherwise the provider's siteverify endpoint is asked with the
|
|
6
|
+
* secret key: the form's own encrypted secret first, then `runtimeConfig.forms.captcha.<provider>.secretKey`
|
|
7
|
+
* (env `NUXT_FORMS_CAPTCHA_<PROVIDER>_SECRET_KEY`). Fails closed: a provider without a secret key, or an
|
|
8
|
+
* unreachable endpoint, is an error, never a pass.
|
|
9
|
+
*/
|
|
10
|
+
export declare function verifyCaptcha(event: H3Event, provider: CaptchaProvider, token: string, formId?: string): Promise<boolean>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const VERIFY_URLS = {
|
|
2
|
+
turnstile: "https://challenges.cloudflare.com/turnstile/v0/siteverify",
|
|
3
|
+
recaptcha: "https://www.google.com/recaptcha/api/siteverify",
|
|
4
|
+
hcaptcha: "https://api.hcaptcha.com/siteverify"
|
|
5
|
+
};
|
|
6
|
+
export async function verifyCaptcha(event, provider, token, formId) {
|
|
7
|
+
const ip = getRequestIP(event, { xForwardedFor: true });
|
|
8
|
+
const { secretKey: secret, source } = await resolveCaptchaSecret(event, provider, formId);
|
|
9
|
+
const ctx = { event, provider, token, ip, formId, secretKey: secret, secretSource: source, valid: void 0 };
|
|
10
|
+
await useNitroApp().hooks.callHook("forms:captcha:verify", ctx);
|
|
11
|
+
if (ctx.valid !== void 0) return ctx.valid;
|
|
12
|
+
if (!secret) {
|
|
13
|
+
console.error(`[forms] captcha provider "${provider}" has no secret key: set one on the form or NUXT_FORMS_CAPTCHA_${provider.toUpperCase()}_SECRET_KEY.`);
|
|
14
|
+
throw createError({ statusCode: 500, statusMessage: `The ${provider} captcha is not configured on the server.`, data: { captcha: true } });
|
|
15
|
+
}
|
|
16
|
+
const body = new URLSearchParams({ secret, response: token });
|
|
17
|
+
if (ip) body.set("remoteip", ip);
|
|
18
|
+
try {
|
|
19
|
+
const result = await $fetch(VERIFY_URLS[provider], { method: "POST", body, timeout: 1e4 });
|
|
20
|
+
return result?.success === true;
|
|
21
|
+
} catch (cause) {
|
|
22
|
+
console.error(`[forms] ${provider} verification request failed:`, cause);
|
|
23
|
+
throw createError({ statusCode: 502, statusMessage: "The captcha could not be verified. Please try again.", data: { captcha: true } });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare class FormsDecryptionError extends Error {
|
|
2
|
+
name: string;
|
|
3
|
+
constructor(message?: string, options?: {
|
|
4
|
+
cause?: unknown;
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
/** True for a `FormsDecryptionError`, also across separately bundled copies of this file. */
|
|
8
|
+
export declare const isDecryptionError: (error: unknown) => boolean;
|
|
9
|
+
export declare function initEncryptionKey(secret: string | undefined | null): void;
|
|
10
|
+
/** Encrypts any JSON value into a `{ ct, iv, tag }` string with a fresh random 12-byte IV. */
|
|
11
|
+
export declare function encryptSync(plaintext: unknown): string;
|
|
12
|
+
export declare function decryptSync(raw: string): unknown;
|
|
13
|
+
declare global {
|
|
14
|
+
var __frigearFormsCrypto: {
|
|
15
|
+
encrypt: (value: unknown) => string;
|
|
16
|
+
decrypt: (raw: string) => unknown;
|
|
17
|
+
} | undefined;
|
|
18
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "node:crypto";
|
|
2
|
+
const KEY_LENGTH = 32;
|
|
3
|
+
const SCRYPT_SALT = "frigear-nu:forms:encrypted-json";
|
|
4
|
+
const store = globalThis;
|
|
5
|
+
export class FormsDecryptionError extends Error {
|
|
6
|
+
name = "FormsDecryptionError";
|
|
7
|
+
constructor(message = "Stored secrets could not be decrypted. The encryption key has changed or the data is corrupt.", options) {
|
|
8
|
+
super(message, options);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export const isDecryptionError = (error) => error instanceof Error && error.name === "FormsDecryptionError";
|
|
12
|
+
export function initEncryptionKey(secret) {
|
|
13
|
+
store.__frigearFormsKey = secret ? scryptSync(secret, SCRYPT_SALT, KEY_LENGTH) : void 0;
|
|
14
|
+
}
|
|
15
|
+
function requireKey() {
|
|
16
|
+
if (!store.__frigearFormsKey) {
|
|
17
|
+
const secret = process.env.NUXT_FORMS_ENCRYPTION_KEY || process.env.NUXT_ENCRYPTION_KEY || process.env.NUXT_SESSION_PASSWORD;
|
|
18
|
+
if (secret) initEncryptionKey(secret);
|
|
19
|
+
}
|
|
20
|
+
if (!store.__frigearFormsKey) {
|
|
21
|
+
throw new Error("[forms] No encryption key: set NUXT_FORMS_ENCRYPTION_KEY, NUXT_ENCRYPTION_KEY or NUXT_SESSION_PASSWORD.");
|
|
22
|
+
}
|
|
23
|
+
return store.__frigearFormsKey;
|
|
24
|
+
}
|
|
25
|
+
export function encryptSync(plaintext) {
|
|
26
|
+
const key = requireKey();
|
|
27
|
+
const iv = randomBytes(12);
|
|
28
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
29
|
+
const ciphertext = Buffer.concat([cipher.update(JSON.stringify(plaintext), "utf8"), cipher.final()]);
|
|
30
|
+
return JSON.stringify({
|
|
31
|
+
ct: ciphertext.toString("base64"),
|
|
32
|
+
iv: iv.toString("base64"),
|
|
33
|
+
tag: cipher.getAuthTag().toString("base64")
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export function decryptSync(raw) {
|
|
37
|
+
const key = requireKey();
|
|
38
|
+
try {
|
|
39
|
+
const { ct, iv, tag } = JSON.parse(raw);
|
|
40
|
+
const decipher = createDecipheriv("aes-256-gcm", key, Buffer.from(iv, "base64"));
|
|
41
|
+
decipher.setAuthTag(Buffer.from(tag, "base64"));
|
|
42
|
+
const decrypted = Buffer.concat([decipher.update(Buffer.from(ct, "base64")), decipher.final()]);
|
|
43
|
+
return JSON.parse(decrypted.toString("utf8"));
|
|
44
|
+
} catch (cause) {
|
|
45
|
+
throw new FormsDecryptionError(void 0, { cause });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
globalThis.__frigearFormsCrypto = { encrypt: encryptSync, decrypt: decryptSync };
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { schema } from '@nuxthub/db';
|
|
2
|
-
import type { FormAdminDto, PublicFormDto } from '../../../types.js';
|
|
2
|
+
import type { FormAccessTokenDto, FormAdminDto, PublicFormDto } from '../../../types.js';
|
|
3
|
+
import type { z } from 'zod';
|
|
4
|
+
import type { formInput, formPatch } from './admin-schemas.js';
|
|
3
5
|
type FormRow = typeof schema.forms.$inferSelect;
|
|
4
6
|
export declare const toPublicForm: (row: FormRow) => PublicFormDto;
|
|
7
|
+
export declare const toTokenDto: (row: typeof schema.formAccessTokens.$inferSelect) => FormAccessTokenDto;
|
|
5
8
|
export declare const toAdminForm: (row: FormRow) => FormAdminDto;
|
|
6
9
|
export declare function findFormById(id: string): Promise<FormRow>;
|
|
7
10
|
export declare function findFormBySlug(slug: string): Promise<FormRow | undefined>;
|
|
8
11
|
export declare function findPublishedForm(slug: string): Promise<FormRow>;
|
|
12
|
+
export declare function createForm(input: z.output<typeof formInput>): Promise<FormRow>;
|
|
13
|
+
export declare function patchForm(id: string, input: z.output<typeof formPatch>): Promise<FormRow>;
|
|
9
14
|
export {};
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import { eq } from "drizzle-orm";
|
|
2
2
|
import { db, schema } from "@nuxthub/db";
|
|
3
|
+
import { newId } from "../../utils/index.js";
|
|
3
4
|
export const toPublicForm = (row) => {
|
|
4
5
|
const { delivery: _delivery, ...definition } = row.definition;
|
|
5
6
|
return { id: row.id, slug: row.slug, name: row.name, version: row.version, definition };
|
|
6
7
|
};
|
|
8
|
+
export const toTokenDto = (row) => ({
|
|
9
|
+
id: row.id,
|
|
10
|
+
name: row.name,
|
|
11
|
+
prefix: row.prefix,
|
|
12
|
+
createdAt: row.createdAt.toISOString(),
|
|
13
|
+
lastUsedAt: row.lastUsedAt?.toISOString() ?? null,
|
|
14
|
+
revokedAt: row.revokedAt?.toISOString() ?? null
|
|
15
|
+
});
|
|
7
16
|
export const toAdminForm = (row) => ({
|
|
8
17
|
id: row.id,
|
|
9
18
|
slug: row.slug,
|
|
@@ -28,3 +37,22 @@ export async function findPublishedForm(slug) {
|
|
|
28
37
|
if (!row || row.status !== "published") throw createError({ statusCode: 404, statusMessage: "Form not found." });
|
|
29
38
|
return row;
|
|
30
39
|
}
|
|
40
|
+
export async function createForm(input) {
|
|
41
|
+
if (await findFormBySlug(input.slug)) {
|
|
42
|
+
throw createError({ statusCode: 409, statusMessage: "A form with this slug already exists." });
|
|
43
|
+
}
|
|
44
|
+
const [row] = await db.insert(schema.forms).values({ id: newId("frm"), ...input }).returning();
|
|
45
|
+
return row;
|
|
46
|
+
}
|
|
47
|
+
export async function patchForm(id, input) {
|
|
48
|
+
const current = await findFormById(id);
|
|
49
|
+
if (input.slug && input.slug !== current.slug && await findFormBySlug(input.slug)) {
|
|
50
|
+
throw createError({ statusCode: 409, statusMessage: "A form with this slug already exists." });
|
|
51
|
+
}
|
|
52
|
+
const definitionChanged = input.definition !== void 0 && JSON.stringify(input.definition) !== JSON.stringify(current.definition);
|
|
53
|
+
const [row] = await db.update(schema.forms).set({
|
|
54
|
+
...input,
|
|
55
|
+
version: definitionChanged ? current.version + 1 : current.version
|
|
56
|
+
}).where(eq(schema.forms.id, current.id)).returning();
|
|
57
|
+
return row;
|
|
58
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
2
|
+
type FormRow = Parameters<typeof authorizeFormAccess>[1];
|
|
3
|
+
/**
|
|
4
|
+
* Forms reachable over MCP: those open to everyone or to signed-in users. Forms gated by a
|
|
5
|
+
* per-form token or password are refused, since the MCP credential is the user's own and must
|
|
6
|
+
* never be presented as (or confused with) a form credential.
|
|
7
|
+
*/
|
|
8
|
+
export declare function assertMcpFormAccess(event: H3Event, form: FormRow): Promise<void>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { resolveAccess } from "../../utils/index.js";
|
|
2
|
+
export async function assertMcpFormAccess(event, form) {
|
|
3
|
+
const { auth } = resolveAccess(form.definition);
|
|
4
|
+
if (auth === "token" || auth === "password") {
|
|
5
|
+
throw createError({ statusCode: 403, statusMessage: "This form needs its own access token or password and cannot be used through MCP." });
|
|
6
|
+
}
|
|
7
|
+
await authorizeFormAccess(event, form);
|
|
8
|
+
}
|
|
@@ -4,3 +4,6 @@ import type { H3Event } from 'h3';
|
|
|
4
4
|
* write), which is fine for throttling casual abuse. `rateLimit: 0` disables it.
|
|
5
5
|
*/
|
|
6
6
|
export declare function enforceRateLimit(event: H3Event, slug: string): Promise<void>;
|
|
7
|
+
/** Refuses further password guesses once `rateLimit` wrong ones were made in the window. `rateLimit: 0` disables. */
|
|
8
|
+
export declare function assertPasswordAttemptsLeft(event: H3Event, slug: string): Promise<void>;
|
|
9
|
+
export declare function recordPasswordFailure(event: H3Event, slug: string): Promise<void>;
|
|
@@ -12,3 +12,22 @@ export async function enforceRateLimit(event, slug) {
|
|
|
12
12
|
}
|
|
13
13
|
await kv.set(key, used + 1, { ttl: rateLimitWindowSeconds });
|
|
14
14
|
}
|
|
15
|
+
const attemptKey = (event, slug) => {
|
|
16
|
+
const { rateLimitWindowSeconds } = useRuntimeConfig().forms;
|
|
17
|
+
const ip = getRequestIP(event, { xForwardedFor: true }) ?? "unknown";
|
|
18
|
+
return `forms:password:${slug}:${ip}:${Math.floor(Date.now() / (rateLimitWindowSeconds * 1e3))}`;
|
|
19
|
+
};
|
|
20
|
+
export async function assertPasswordAttemptsLeft(event, slug) {
|
|
21
|
+
const { rateLimit, rateLimitWindowSeconds } = useRuntimeConfig().forms;
|
|
22
|
+
if (!rateLimit) return;
|
|
23
|
+
if (Number(await kv.get(attemptKey(event, slug)) ?? 0) >= rateLimit) {
|
|
24
|
+
setResponseHeader(event, "retry-after", rateLimitWindowSeconds);
|
|
25
|
+
throw createError({ statusCode: 429, statusMessage: "Too many attempts, try again later." });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export async function recordPasswordFailure(event, slug) {
|
|
29
|
+
const { rateLimit, rateLimitWindowSeconds } = useRuntimeConfig().forms;
|
|
30
|
+
if (!rateLimit) return;
|
|
31
|
+
const key = attemptKey(event, slug);
|
|
32
|
+
await kv.set(key, Number(await kv.get(key) ?? 0) + 1, { ttl: rateLimitWindowSeconds });
|
|
33
|
+
}
|
|
@@ -1,11 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export type RenderedAnswer
|
|
3
|
-
key: string;
|
|
4
|
-
label: string;
|
|
5
|
-
value: string;
|
|
6
|
-
};
|
|
7
|
-
/** Answers as label/value rows in form order, using the definition the answers were given against. */
|
|
8
|
-
export declare function renderAnswers(definition: FormDefinition, answers: Record<string, unknown>, locale: string): RenderedAnswer[];
|
|
9
|
-
export declare const escapeHtml: (value: string) => string;
|
|
10
|
-
export declare function answersToText(rows: RenderedAnswer[]): string;
|
|
11
|
-
export declare function answersToHtml(rows: RenderedAnswer[]): string;
|
|
1
|
+
export { renderAnswers, escapeHtml, answersToText, answersToHtml } from '../../utils/template.js';
|
|
2
|
+
export type { RenderedAnswer } from '../../utils/template.js';
|