@frigear-nu/forms 0.0.5 → 0.0.7

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 +64 -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 +237 -3
  81. package/dist/runtime/i18n/lang/en.json +237 -3
  82. package/dist/runtime/menus.js +12 -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 +14 -5
@@ -0,0 +1,8 @@
1
+ import { eq } from "drizzle-orm";
2
+ import { db, schema } from "@nuxthub/db";
3
+ export default defineEventHandler(async (event) => {
4
+ await requireFormsManager(event);
5
+ const form = await findFormById(getRouterParam(event, "id"));
6
+ await db.delete(schema.formAccess).where(eq(schema.formAccess.formId, form.id));
7
+ return { set: false };
8
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,8 @@
1
+ import { eq } from "drizzle-orm";
2
+ import { db, schema } from "@nuxthub/db";
3
+ export default defineEventHandler(async (event) => {
4
+ await requireFormsManager(event);
5
+ const form = await findFormById(getRouterParam(event, "id"));
6
+ const [row] = await db.select({ formId: schema.formAccess.formId }).from(schema.formAccess).where(eq(schema.formAccess.formId, form.id)).limit(1);
7
+ return { set: !!row };
8
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,14 @@
1
+ import { z } from "zod";
2
+ import { db, schema } from "@nuxthub/db";
3
+ const input = z.object({ password: z.string().min(6).max(200) });
4
+ export default defineEventHandler(async (event) => {
5
+ await requireFormsManager(event);
6
+ const form = await findFormById(getRouterParam(event, "id"));
7
+ const { password } = await readValidatedBody(event, input.parse);
8
+ const passwordHash = await hashPassword(password);
9
+ await db.insert(schema.formAccess).values({ formId: form.id, passwordHash }).onConflictDoUpdate({
10
+ target: schema.formAccess.formId,
11
+ set: { passwordHash, updatedAt: /* @__PURE__ */ new Date() }
12
+ });
13
+ return { set: true };
14
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,8 @@
1
+ export default defineEventHandler(async (event) => {
2
+ await requireFormsManager(event);
3
+ const form = await findFormById(getRouterParam(event, "id"));
4
+ const key = getRouterParam(event, "key");
5
+ if (!isFormSecretKey(key)) throw createError({ statusCode: 400, statusMessage: "Unknown secret key." });
6
+ await deleteFormSecret(form.id, key);
7
+ return describeFormSecrets(event, form.id);
8
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,5 @@
1
+ export default defineEventHandler(async (event) => {
2
+ await requireFormsManager(event);
3
+ const form = await findFormById(getRouterParam(event, "id"));
4
+ return describeFormSecrets(event, form.id);
5
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,12 @@
1
+ import { z } from "zod";
2
+ const input = z.object({
3
+ key: z.string().refine(isFormSecretKey, "Unknown secret key."),
4
+ value: z.string().trim().min(1).max(2e3)
5
+ });
6
+ export default defineEventHandler(async (event) => {
7
+ await requireFormsManager(event);
8
+ const form = await findFormById(getRouterParam(event, "id"));
9
+ const { key, value } = await readValidatedBody(event, input.parse);
10
+ await setFormSecret(form.id, key, value);
11
+ return describeFormSecrets(event, form.id);
12
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,12 @@
1
+ import { and, eq } from "drizzle-orm";
2
+ import { db, schema } from "@nuxthub/db";
3
+ export default defineEventHandler(async (event) => {
4
+ await requireFormsManager(event);
5
+ const form = await findFormById(getRouterParam(event, "id"));
6
+ const [row] = await db.update(schema.formAccessTokens).set({ revokedAt: /* @__PURE__ */ new Date() }).where(and(
7
+ eq(schema.formAccessTokens.id, getRouterParam(event, "tokenId")),
8
+ eq(schema.formAccessTokens.formId, form.id)
9
+ )).returning();
10
+ if (!row) throw createError({ statusCode: 404, statusMessage: "Token not found." });
11
+ return toTokenDto(row);
12
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,8 @@
1
+ import { desc, eq } from "drizzle-orm";
2
+ import { db, schema } from "@nuxthub/db";
3
+ export default defineEventHandler(async (event) => {
4
+ await requireFormsManager(event);
5
+ const form = await findFormById(getRouterParam(event, "id"));
6
+ const rows = await db.select().from(schema.formAccessTokens).where(eq(schema.formAccessTokens.formId, form.id)).orderBy(desc(schema.formAccessTokens.createdAt));
7
+ return rows.map(toTokenDto);
8
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,12 @@
1
+ import { z } from "zod";
2
+ import { db, schema } from "@nuxthub/db";
3
+ const input = z.object({ name: z.string().trim().min(1).max(100) });
4
+ export default defineEventHandler(async (event) => {
5
+ await requireFormsManager(event);
6
+ const form = await findFormById(getRouterParam(event, "id"));
7
+ const { name } = await readValidatedBody(event, input.parse);
8
+ const { id, token, tokenHash, prefix } = await generateToken();
9
+ const [row] = await db.insert(schema.formAccessTokens).values({ id, formId: form.id, name, tokenHash, prefix }).returning();
10
+ setResponseStatus(event, 201);
11
+ return { ...toTokenDto(row), token };
12
+ });
@@ -1,4 +1,8 @@
1
+ import { count, eq } from "drizzle-orm";
2
+ import { db, schema } from "@nuxthub/db";
1
3
  export default defineEventHandler(async (event) => {
2
4
  await requireFormsManager(event);
3
- return toAdminForm(await findFormById(getRouterParam(event, "id")));
5
+ const row = await findFormById(getRouterParam(event, "id"));
6
+ const [totals] = await db.select({ total: count() }).from(schema.formSubmissions).where(eq(schema.formSubmissions.formId, row.id));
7
+ return { ...toAdminForm(row), submissionCount: totals?.total ?? 0 };
4
8
  });
@@ -1,17 +1,8 @@
1
- import { eq } from "drizzle-orm";
2
- import { db, schema } from "@nuxthub/db";
3
1
  import { formPatch } from "../../utils/admin-schemas.js";
4
2
  export default defineEventHandler(async (event) => {
5
3
  await requireFormsManager(event);
6
- const current = await findFormById(getRouterParam(event, "id"));
4
+ const id = getRouterParam(event, "id");
5
+ await findFormById(id);
7
6
  const input = await readValidatedBody(event, formPatch.parse);
8
- if (input.slug && input.slug !== current.slug && await findFormBySlug(input.slug)) {
9
- throw createError({ statusCode: 409, statusMessage: "A form with this slug already exists." });
10
- }
11
- const definitionChanged = input.definition !== void 0 && JSON.stringify(input.definition) !== JSON.stringify(current.definition);
12
- const [row] = await db.update(schema.forms).set({
13
- ...input,
14
- version: definitionChanged ? current.version + 1 : current.version
15
- }).where(eq(schema.forms.id, current.id)).returning();
16
- return toAdminForm(row);
7
+ return toAdminForm(await patchForm(id, input));
17
8
  });
@@ -1,13 +1,8 @@
1
- import { db, schema } from "@nuxthub/db";
2
- import { newId } from "../../../utils/index.js";
3
1
  import { formInput } from "../../utils/admin-schemas.js";
4
2
  export default defineEventHandler(async (event) => {
5
3
  await requireFormsManager(event);
6
4
  const input = await readValidatedBody(event, formInput.parse);
7
- if (await findFormBySlug(input.slug)) {
8
- throw createError({ statusCode: 409, statusMessage: "A form with this slug already exists." });
9
- }
10
- const [row] = await db.insert(schema.forms).values({ id: newId("frm"), ...input }).returning();
5
+ const row = await createForm(input);
11
6
  setResponseStatus(event, 201);
12
7
  return toAdminForm(row);
13
8
  });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,3 @@
1
+ export default defineEventHandler(async (event) => {
2
+ return describeFormAccess(event, await findPublishedForm(getRouterParam(event, "slug")));
3
+ });
@@ -1,3 +1,5 @@
1
1
  export default defineEventHandler(async (event) => {
2
- return toPublicForm(await findPublishedForm(getRouterParam(event, "slug")));
2
+ const form = await findPublishedForm(getRouterParam(event, "slug"));
3
+ await authorizeFormAccess(event, form);
4
+ return toPublicForm(form);
3
5
  });
@@ -2,6 +2,6 @@ export default defineEventHandler(async (event) => {
2
2
  const slug = getRouterParam(event, "slug");
3
3
  await enforceRateLimit(event, slug);
4
4
  const form = await findPublishedForm(slug);
5
- const session = await getUserSession(event);
6
- return processSubmission(event, form, session.user?.id ?? null);
5
+ const { userId } = await authorizeFormAccess(event, form);
6
+ return processSubmission(event, form, userId);
7
7
  });
@@ -0,0 +1,27 @@
1
+ import type { H3Event } from 'h3';
2
+ import { schema } from '@nuxthub/db';
3
+ import type { FormAccessInfoDto } from '../../../types.js';
4
+ type FormRow = typeof schema.forms.$inferSelect;
5
+ export declare const TOKEN_HEADER = "x-form-token";
6
+ export declare const PASSWORD_HEADER = "x-form-password";
7
+ export declare function hashToken(token: string): Promise<string>;
8
+ /** A new random token (shown once), its hash and its display prefix. */
9
+ export declare function generateToken(): Promise<{
10
+ id: string;
11
+ token: string;
12
+ tokenHash: string;
13
+ prefix: string;
14
+ }>;
15
+ /** `Authorization: Bearer <token>` or `x-form-token: <token>`. */
16
+ export declare function readToken(event: H3Event): string | undefined;
17
+ /**
18
+ * The single access gate of the public routes (definition and submit). Resolves the submitter's user id
19
+ * (when signed in) and throws 401 unless the form's `access.auth` is satisfied. API-only forms are
20
+ * still served here: `availability` only removes the public page.
21
+ */
22
+ export declare function authorizeFormAccess(event: H3Event, form: FormRow): Promise<{
23
+ userId: number | null;
24
+ }>;
25
+ /** Public facts the page needs to pick between the form, a gate and "not found". */
26
+ export declare function describeFormAccess(event: H3Event, form: FormRow): Promise<FormAccessInfoDto>;
27
+ export {};
@@ -0,0 +1,63 @@
1
+ import { and, eq, isNull } from "drizzle-orm";
2
+ import { db, schema } from "@nuxthub/db";
3
+ import { isFormOpen, newId, nextFormOpening, resolveAccess } from "../../utils/index.js";
4
+ export const TOKEN_HEADER = "x-form-token";
5
+ export const PASSWORD_HEADER = "x-form-password";
6
+ const TOKEN_PREFIX = "ffk_";
7
+ const toHex = (bytes) => [...new Uint8Array(bytes)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
8
+ export async function hashToken(token) {
9
+ return toHex(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token)));
10
+ }
11
+ export async function generateToken() {
12
+ const bytes = crypto.getRandomValues(new Uint8Array(32));
13
+ const token = `${TOKEN_PREFIX}${btoa(String.fromCharCode(...bytes)).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "")}`;
14
+ return { id: newId("fat"), token, tokenHash: await hashToken(token), prefix: token.slice(0, 8) };
15
+ }
16
+ export function readToken(event) {
17
+ const authorization = getRequestHeader(event, "authorization")?.trim() ?? "";
18
+ const bearer = authorization.slice(0, 7).toLowerCase() === "bearer " ? authorization.slice(7).trim() : "";
19
+ return bearer || getRequestHeader(event, TOKEN_HEADER)?.trim() || void 0;
20
+ }
21
+ const unauthorized = (event, message, scheme) => {
22
+ if (scheme) setResponseHeader(event, "www-authenticate", scheme);
23
+ return createError({ statusCode: 401, statusMessage: message, data: { access: true } });
24
+ };
25
+ async function verifyToken(event, form) {
26
+ const token = readToken(event);
27
+ if (!token) throw unauthorized(event, "A form token is required.", "Bearer");
28
+ const [row] = await db.select({ id: schema.formAccessTokens.id }).from(schema.formAccessTokens).where(and(
29
+ eq(schema.formAccessTokens.formId, form.id),
30
+ eq(schema.formAccessTokens.tokenHash, await hashToken(token)),
31
+ isNull(schema.formAccessTokens.revokedAt)
32
+ )).limit(1);
33
+ if (!row) throw unauthorized(event, "Invalid or revoked form token.", "Bearer");
34
+ await db.update(schema.formAccessTokens).set({ lastUsedAt: /* @__PURE__ */ new Date() }).where(eq(schema.formAccessTokens.id, row.id));
35
+ }
36
+ async function verifyFormPassword(event, form) {
37
+ const password = getRequestHeader(event, PASSWORD_HEADER);
38
+ if (!password) throw unauthorized(event, "A form password is required.");
39
+ await assertPasswordAttemptsLeft(event, form.slug);
40
+ const [row] = await db.select().from(schema.formAccess).where(eq(schema.formAccess.formId, form.id)).limit(1);
41
+ if (!row || !await verifyPassword(row.passwordHash, password)) {
42
+ await recordPasswordFailure(event, form.slug);
43
+ throw unauthorized(event, "Wrong form password.");
44
+ }
45
+ }
46
+ export async function authorizeFormAccess(event, form) {
47
+ const { auth, schedule } = resolveAccess(form.definition);
48
+ if (!isFormOpen(schedule)) {
49
+ const opensAt = nextFormOpening(schedule);
50
+ throw createError({ statusCode: 403, statusMessage: "This form is closed.", data: { closed: true, opensAt: opensAt?.toISOString() ?? null } });
51
+ }
52
+ const user = await getUser(event, { scopes: ["forms:submit"] });
53
+ if (auth === "user" && !user) throw unauthorized(event, "Sign in to use this form.");
54
+ if (auth === "token") await verifyToken(event, form);
55
+ if (auth === "password") await verifyFormPassword(event, form);
56
+ return { userId: user?.id ?? null };
57
+ }
58
+ export async function describeFormAccess(event, form) {
59
+ const user = await getUser(event, { scopes: ["forms:submit"] });
60
+ const access = resolveAccess(form.definition);
61
+ const open = isFormOpen(access.schedule);
62
+ return { slug: form.slug, name: form.name, access, signedIn: !!user, open, opensAt: open ? null : nextFormOpening(access.schedule)?.toISOString() ?? null };
63
+ }