@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.
Files changed (185) hide show
  1. package/README.md +16 -1
  2. package/dist/module.d.mts +148 -8
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +65 -35
  5. package/dist/runtime/app/components/FormBuilder.d.vue.ts +26 -0
  6. package/dist/runtime/app/components/FormBuilder.vue +220 -0
  7. package/dist/runtime/app/components/FormBuilder.vue.d.ts +26 -0
  8. package/dist/runtime/app/components/FormBuilderAccess.d.vue.ts +16 -0
  9. package/dist/runtime/app/components/FormBuilderAccess.vue +404 -0
  10. package/dist/runtime/app/components/FormBuilderAccess.vue.d.ts +16 -0
  11. package/dist/runtime/app/components/FormBuilderCaptchaSettings.d.vue.ts +15 -0
  12. package/dist/runtime/app/components/FormBuilderCaptchaSettings.vue +126 -0
  13. package/dist/runtime/app/components/FormBuilderCaptchaSettings.vue.d.ts +15 -0
  14. package/dist/runtime/app/components/FormBuilderCondition.d.vue.ts +16 -0
  15. package/dist/runtime/app/components/FormBuilderCondition.vue +62 -0
  16. package/dist/runtime/app/components/FormBuilderCondition.vue.d.ts +16 -0
  17. package/dist/runtime/app/components/FormBuilderFieldRow.d.vue.ts +27 -0
  18. package/dist/runtime/app/components/FormBuilderFieldRow.vue +87 -0
  19. package/dist/runtime/app/components/FormBuilderFieldRow.vue.d.ts +27 -0
  20. package/dist/runtime/app/components/FormBuilderFieldSettings.d.vue.ts +25 -0
  21. package/dist/runtime/app/components/FormBuilderFieldSettings.vue +170 -0
  22. package/dist/runtime/app/components/FormBuilderFieldSettings.vue.d.ts +25 -0
  23. package/dist/runtime/app/components/FormBuilderInsertRow.d.vue.ts +20 -0
  24. package/dist/runtime/app/components/FormBuilderInsertRow.vue +51 -0
  25. package/dist/runtime/app/components/FormBuilderInsertRow.vue.d.ts +20 -0
  26. package/dist/runtime/app/components/FormBuilderOptions.d.vue.ts +11 -0
  27. package/dist/runtime/app/components/FormBuilderOptions.vue +81 -0
  28. package/dist/runtime/app/components/FormBuilderOptions.vue.d.ts +11 -0
  29. package/dist/runtime/app/components/FormBuilderPhoneSettings.d.vue.ts +11 -0
  30. package/dist/runtime/app/components/FormBuilderPhoneSettings.vue +63 -0
  31. package/dist/runtime/app/components/FormBuilderPhoneSettings.vue.d.ts +11 -0
  32. package/dist/runtime/app/components/FormBuilderSettings.d.vue.ts +11 -0
  33. package/dist/runtime/app/components/FormBuilderSettings.vue +233 -0
  34. package/dist/runtime/app/components/FormBuilderSettings.vue.d.ts +11 -0
  35. package/dist/runtime/app/components/FormBuilderStepHeader.d.vue.ts +27 -0
  36. package/dist/runtime/app/components/FormBuilderStepHeader.vue +71 -0
  37. package/dist/runtime/app/components/FormBuilderStepHeader.vue.d.ts +27 -0
  38. package/dist/runtime/app/components/FormBuilderStepSettings.d.vue.ts +13 -0
  39. package/dist/runtime/app/components/FormBuilderStepSettings.vue +33 -0
  40. package/dist/runtime/app/components/FormBuilderStepSettings.vue.d.ts +13 -0
  41. package/dist/runtime/app/components/FormBuilderText.d.vue.ts +19 -0
  42. package/dist/runtime/app/components/FormBuilderText.vue +127 -0
  43. package/dist/runtime/app/components/FormBuilderText.vue.d.ts +19 -0
  44. package/dist/runtime/app/components/FormCaptcha.d.vue.ts +19 -0
  45. package/dist/runtime/app/components/FormCaptcha.vue +95 -0
  46. package/dist/runtime/app/components/FormCaptcha.vue.d.ts +19 -0
  47. package/dist/runtime/app/components/FormEmbed.d.vue.ts +17 -0
  48. package/dist/runtime/app/components/FormEmbed.vue +113 -0
  49. package/dist/runtime/app/components/FormEmbed.vue.d.ts +17 -0
  50. package/dist/runtime/app/components/FormField.d.vue.ts +4 -0
  51. package/dist/runtime/app/components/FormField.vue +35 -3
  52. package/dist/runtime/app/components/FormField.vue.d.ts +4 -0
  53. package/dist/runtime/app/components/FormPhoneInput.d.vue.ts +20 -0
  54. package/dist/runtime/app/components/FormPhoneInput.vue +56 -0
  55. package/dist/runtime/app/components/FormPhoneInput.vue.d.ts +20 -0
  56. package/dist/runtime/app/components/FormPreview.d.vue.ts +12 -0
  57. package/dist/runtime/app/components/FormPreview.vue +244 -0
  58. package/dist/runtime/app/components/FormPreview.vue.d.ts +12 -0
  59. package/dist/runtime/app/components/FormRenderer.d.vue.ts +6 -0
  60. package/dist/runtime/app/components/FormRenderer.vue +16 -7
  61. package/dist/runtime/app/components/FormRenderer.vue.d.ts +6 -0
  62. package/dist/runtime/app/components/FormSubmissionView.vue +2 -2
  63. package/dist/runtime/app/composables/index.d.ts +4 -1
  64. package/dist/runtime/app/composables/index.js +4 -1
  65. package/dist/runtime/app/composables/useCaptcha.d.ts +22 -0
  66. package/dist/runtime/app/composables/useCaptcha.js +39 -0
  67. package/dist/runtime/app/composables/useCaptchaSecrets.d.ts +7 -0
  68. package/dist/runtime/app/composables/useCaptchaSecrets.js +69 -0
  69. package/dist/runtime/app/composables/useFormBuilderEditor.d.ts +49 -0
  70. package/dist/runtime/app/composables/useFormBuilderEditor.js +210 -0
  71. package/dist/runtime/app/composables/useFormBuilderLabels.d.ts +8 -0
  72. package/dist/runtime/app/composables/useFormBuilderLabels.js +41 -0
  73. package/dist/runtime/app/composables/useForms.d.ts +18 -3
  74. package/dist/runtime/app/composables/useForms.js +38 -5
  75. package/dist/runtime/app/pages/admin-form.vue +112 -69
  76. package/dist/runtime/app/pages/admin-submission.vue +7 -16
  77. package/dist/runtime/app/pages/admin-submissions.vue +41 -46
  78. package/dist/runtime/app/pages/admin.vue +128 -44
  79. package/dist/runtime/app/pages/form.vue +71 -5
  80. package/dist/runtime/i18n/lang/da.json +231 -3
  81. package/dist/runtime/i18n/lang/en.json +231 -3
  82. package/dist/runtime/menus.js +10 -0
  83. package/dist/runtime/server/db/schema.sqlite.d.ts +283 -0
  84. package/dist/runtime/server/db/schema.sqlite.js +33 -1
  85. package/dist/runtime/server/mcp/schemas.d.ts +9 -0
  86. package/dist/runtime/server/mcp/schemas.js +8 -0
  87. package/dist/runtime/server/mcp/tools/admin/create-form.d.ts +2 -0
  88. package/dist/runtime/server/mcp/tools/admin/create-form.js +21 -0
  89. package/dist/runtime/server/mcp/tools/admin/get-form-definition.d.ts +2 -0
  90. package/dist/runtime/server/mcp/tools/admin/get-form-definition.js +14 -0
  91. package/dist/runtime/server/mcp/tools/admin/get-submission.d.ts +2 -0
  92. package/dist/runtime/server/mcp/tools/admin/get-submission.js +26 -0
  93. package/dist/runtime/server/mcp/tools/admin/list-all-forms.d.ts +2 -0
  94. package/dist/runtime/server/mcp/tools/admin/list-all-forms.js +21 -0
  95. package/dist/runtime/server/mcp/tools/admin/list-submissions.d.ts +2 -0
  96. package/dist/runtime/server/mcp/tools/admin/list-submissions.js +22 -0
  97. package/dist/runtime/server/mcp/tools/admin/update-form.d.ts +2 -0
  98. package/dist/runtime/server/mcp/tools/admin/update-form.js +24 -0
  99. package/dist/runtime/server/mcp/tools/get-form.d.ts +2 -0
  100. package/dist/runtime/server/mcp/tools/get-form.js +12 -0
  101. package/dist/runtime/server/mcp/tools/list-forms.d.ts +2 -0
  102. package/dist/runtime/server/mcp/tools/list-forms.js +11 -0
  103. package/dist/runtime/server/mcp/tools/submit-form.d.ts +2 -0
  104. package/dist/runtime/server/mcp/tools/submit-form.js +17 -0
  105. package/dist/runtime/server/plugins/email-delivery.js +16 -11
  106. package/dist/runtime/server/plugins/encryption.d.ts +2 -0
  107. package/dist/runtime/server/plugins/encryption.js +4 -0
  108. package/dist/runtime/server/plugins/staff-scopes.d.ts +2 -0
  109. package/dist/runtime/server/plugins/staff-scopes.js +1 -0
  110. package/dist/runtime/server/routes/admin/form-clone.post.d.ts +2 -0
  111. package/dist/runtime/server/routes/admin/form-clone.post.js +17 -0
  112. package/dist/runtime/server/routes/admin/form-password.delete.d.ts +2 -0
  113. package/dist/runtime/server/routes/admin/form-password.delete.js +8 -0
  114. package/dist/runtime/server/routes/admin/form-password.get.d.ts +2 -0
  115. package/dist/runtime/server/routes/admin/form-password.get.js +8 -0
  116. package/dist/runtime/server/routes/admin/form-password.put.d.ts +2 -0
  117. package/dist/runtime/server/routes/admin/form-password.put.js +14 -0
  118. package/dist/runtime/server/routes/admin/form-secret.delete.d.ts +2 -0
  119. package/dist/runtime/server/routes/admin/form-secret.delete.js +8 -0
  120. package/dist/runtime/server/routes/admin/form-secrets.get.d.ts +2 -0
  121. package/dist/runtime/server/routes/admin/form-secrets.get.js +5 -0
  122. package/dist/runtime/server/routes/admin/form-secrets.put.d.ts +2 -0
  123. package/dist/runtime/server/routes/admin/form-secrets.put.js +12 -0
  124. package/dist/runtime/server/routes/admin/form-token.delete.d.ts +2 -0
  125. package/dist/runtime/server/routes/admin/form-token.delete.js +12 -0
  126. package/dist/runtime/server/routes/admin/form-tokens.get.d.ts +2 -0
  127. package/dist/runtime/server/routes/admin/form-tokens.get.js +8 -0
  128. package/dist/runtime/server/routes/admin/form-tokens.post.d.ts +2 -0
  129. package/dist/runtime/server/routes/admin/form-tokens.post.js +12 -0
  130. package/dist/runtime/server/routes/admin/form.get.js +5 -1
  131. package/dist/runtime/server/routes/admin/form.patch.js +3 -12
  132. package/dist/runtime/server/routes/admin/forms.post.js +1 -6
  133. package/dist/runtime/server/routes/public/access.get.d.ts +2 -0
  134. package/dist/runtime/server/routes/public/access.get.js +3 -0
  135. package/dist/runtime/server/routes/public/form.get.js +3 -1
  136. package/dist/runtime/server/routes/public/submit.post.js +2 -2
  137. package/dist/runtime/server/utils/access.d.ts +27 -0
  138. package/dist/runtime/server/utils/access.js +63 -0
  139. package/dist/runtime/server/utils/admin-schemas.d.ts +219 -15
  140. package/dist/runtime/server/utils/auth.d.ts +7 -7
  141. package/dist/runtime/server/utils/auth.js +4 -4
  142. package/dist/runtime/server/utils/captcha.d.ts +10 -0
  143. package/dist/runtime/server/utils/captcha.js +25 -0
  144. package/dist/runtime/server/utils/crypto.d.ts +18 -0
  145. package/dist/runtime/server/utils/crypto.js +48 -0
  146. package/dist/runtime/server/utils/forms.d.ts +6 -1
  147. package/dist/runtime/server/utils/forms.js +28 -0
  148. package/dist/runtime/server/utils/mcp-access.d.ts +9 -0
  149. package/dist/runtime/server/utils/mcp-access.js +8 -0
  150. package/dist/runtime/server/utils/rate-limit.d.ts +3 -0
  151. package/dist/runtime/server/utils/rate-limit.js +19 -0
  152. package/dist/runtime/server/utils/render.d.ts +2 -11
  153. package/dist/runtime/server/utils/render.js +1 -26
  154. package/dist/runtime/server/utils/secrets.d.ts +28 -0
  155. package/dist/runtime/server/utils/secrets.js +60 -0
  156. package/dist/runtime/server/utils/submit.d.ts +12 -1
  157. package/dist/runtime/server/utils/submit.js +18 -4
  158. package/dist/runtime/utils/builder.d.ts +73 -0
  159. package/dist/runtime/utils/builder.js +220 -0
  160. package/dist/runtime/utils/captcha.d.ts +9 -0
  161. package/dist/runtime/utils/captcha.js +7 -0
  162. package/dist/runtime/utils/conditions.d.ts +3 -1
  163. package/dist/runtime/utils/conditions.js +4 -0
  164. package/dist/runtime/utils/definition.d.ts +116 -6
  165. package/dist/runtime/utils/definition.js +53 -4
  166. package/dist/runtime/utils/embed.d.ts +8 -0
  167. package/dist/runtime/utils/embed.js +7 -0
  168. package/dist/runtime/utils/index.d.ts +16 -3
  169. package/dist/runtime/utils/index.js +66 -3
  170. package/dist/runtime/utils/markdown.d.ts +1 -5
  171. package/dist/runtime/utils/markdown.js +1 -5
  172. package/dist/runtime/utils/phone.d.ts +29 -0
  173. package/dist/runtime/utils/phone.js +294 -0
  174. package/dist/runtime/utils/preview.d.ts +12 -0
  175. package/dist/runtime/utils/preview.js +17 -0
  176. package/dist/runtime/utils/schedule.d.ts +1 -0
  177. package/dist/runtime/utils/schedule.js +1 -0
  178. package/dist/runtime/utils/schema.d.ts +2 -0
  179. package/dist/runtime/utils/schema.js +12 -4
  180. package/dist/runtime/utils/template.d.ts +57 -0
  181. package/dist/runtime/utils/template.js +147 -0
  182. package/dist/runtime/utils/text.d.ts +0 -3
  183. package/dist/runtime/utils/text.js +0 -5
  184. package/dist/types.d.mts +5 -1
  185. 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
- in: "in";
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
- text: "text";
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
- in: "in";
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
- in: "in";
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
- text: "text";
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
- in: "in";
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
- in: "in";
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
- text: "text";
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
- in: "in";
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 { user } = await requireUserSession(event);
3
- const ctx = { event, userId: user.id, allowed: false };
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
- import type { FormDefinition } from '../../../types.js';
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';