@proofkit/cli 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +19 -0
  4. package/dist/acorn-AKFTBDM6.js +15 -0
  5. package/dist/angular-BOQ6FHSU.js +2 -0
  6. package/dist/babel-ZTOORN7K.js +15 -0
  7. package/dist/chunk-4LISTI44.js +1 -0
  8. package/dist/estree-KOJPX4S6.js +36 -0
  9. package/dist/flow-RCI44GYZ.js +19 -0
  10. package/dist/glimmer-GV5EF5E4.js +30 -0
  11. package/dist/graphql-YXQNPQWM.js +29 -0
  12. package/dist/html-ZAJTRROK.js +22 -0
  13. package/dist/index.js +300 -0
  14. package/dist/markdown-Q75DTQI7.js +63 -0
  15. package/dist/meriyah-32K7GBV5.js +4 -0
  16. package/dist/postcss-WWYO4PGL.js +54 -0
  17. package/dist/typescript-M6N7JDNQ.js +20 -0
  18. package/dist/yaml-LY7PNAYV.js +161 -0
  19. package/index.d.ts +19 -0
  20. package/package.json +122 -0
  21. package/template/extras/_cursor/conditional-rules/nextjs-framework.mdc +51 -0
  22. package/template/extras/_cursor/conditional-rules/npm.mdc +60 -0
  23. package/template/extras/_cursor/conditional-rules/pnpm.mdc +65 -0
  24. package/template/extras/_cursor/conditional-rules/yarn.mdc +60 -0
  25. package/template/extras/_cursor/rules/cursor-rules.mdc +88 -0
  26. package/template/extras/_cursor/rules/filemaker-api.mdc +176 -0
  27. package/template/extras/_cursor/rules/troubleshooting-patterns.mdc +240 -0
  28. package/template/extras/_cursor/rules/ui-components.mdc +57 -0
  29. package/template/extras/config/_eslint.js +27 -0
  30. package/template/extras/config/_prettier.config.js +6 -0
  31. package/template/extras/config/drizzle-config-mysql.ts +12 -0
  32. package/template/extras/config/drizzle-config-postgres.ts +12 -0
  33. package/template/extras/config/drizzle-config-sqlite.ts +12 -0
  34. package/template/extras/config/fmschema.config.mjs +9 -0
  35. package/template/extras/config/get-query-client.ts +6 -0
  36. package/template/extras/config/postcss.config.cjs +7 -0
  37. package/template/extras/config/query-provider-vite.tsx +19 -0
  38. package/template/extras/config/query-provider.tsx +21 -0
  39. package/template/extras/emailProviders/none/email.tsx +24 -0
  40. package/template/extras/emailProviders/plunk/email.tsx +26 -0
  41. package/template/extras/emailProviders/plunk/service.ts +4 -0
  42. package/template/extras/emailProviders/resend/email.tsx +23 -0
  43. package/template/extras/emailProviders/resend/service.ts +4 -0
  44. package/template/extras/fmaddon-auth/app/(main)/auth/profile/actions.ts +93 -0
  45. package/template/extras/fmaddon-auth/app/(main)/auth/profile/page.tsx +27 -0
  46. package/template/extras/fmaddon-auth/app/(main)/auth/profile/profile-form.tsx +56 -0
  47. package/template/extras/fmaddon-auth/app/(main)/auth/profile/reset-password-form.tsx +110 -0
  48. package/template/extras/fmaddon-auth/app/(main)/auth/profile/schema.ts +19 -0
  49. package/template/extras/fmaddon-auth/app/auth/forgot-password/actions.ts +37 -0
  50. package/template/extras/fmaddon-auth/app/auth/forgot-password/forgot-form.tsx +41 -0
  51. package/template/extras/fmaddon-auth/app/auth/forgot-password/page.tsx +21 -0
  52. package/template/extras/fmaddon-auth/app/auth/forgot-password/schema.ts +5 -0
  53. package/template/extras/fmaddon-auth/app/auth/login/actions.ts +34 -0
  54. package/template/extras/fmaddon-auth/app/auth/login/login-form.tsx +64 -0
  55. package/template/extras/fmaddon-auth/app/auth/login/page.tsx +26 -0
  56. package/template/extras/fmaddon-auth/app/auth/login/schema.ts +6 -0
  57. package/template/extras/fmaddon-auth/app/auth/reset-password/actions.ts +50 -0
  58. package/template/extras/fmaddon-auth/app/auth/reset-password/page.tsx +32 -0
  59. package/template/extras/fmaddon-auth/app/auth/reset-password/reset-password-form.tsx +59 -0
  60. package/template/extras/fmaddon-auth/app/auth/reset-password/schema.ts +14 -0
  61. package/template/extras/fmaddon-auth/app/auth/reset-password/verify-email/actions.ts +45 -0
  62. package/template/extras/fmaddon-auth/app/auth/reset-password/verify-email/page.tsx +32 -0
  63. package/template/extras/fmaddon-auth/app/auth/reset-password/verify-email/schema.ts +5 -0
  64. package/template/extras/fmaddon-auth/app/auth/reset-password/verify-email/verify-email-form.tsx +48 -0
  65. package/template/extras/fmaddon-auth/app/auth/signup/actions.ts +49 -0
  66. package/template/extras/fmaddon-auth/app/auth/signup/page.tsx +26 -0
  67. package/template/extras/fmaddon-auth/app/auth/signup/schema.ts +12 -0
  68. package/template/extras/fmaddon-auth/app/auth/signup/signup-form.tsx +67 -0
  69. package/template/extras/fmaddon-auth/app/auth/verify-email/actions.ts +110 -0
  70. package/template/extras/fmaddon-auth/app/auth/verify-email/email-verification-form.tsx +45 -0
  71. package/template/extras/fmaddon-auth/app/auth/verify-email/page.tsx +38 -0
  72. package/template/extras/fmaddon-auth/app/auth/verify-email/resend-button.tsx +35 -0
  73. package/template/extras/fmaddon-auth/app/auth/verify-email/schema.ts +5 -0
  74. package/template/extras/fmaddon-auth/components/auth/actions.ts +16 -0
  75. package/template/extras/fmaddon-auth/components/auth/protect.tsx +17 -0
  76. package/template/extras/fmaddon-auth/components/auth/redirect.tsx +26 -0
  77. package/template/extras/fmaddon-auth/components/auth/use-user.ts +59 -0
  78. package/template/extras/fmaddon-auth/components/auth/user-menu.tsx +51 -0
  79. package/template/extras/fmaddon-auth/emails/auth-code.tsx +156 -0
  80. package/template/extras/fmaddon-auth/middleware.ts +45 -0
  81. package/template/extras/fmaddon-auth/server/auth/utils/email-verification.ts +136 -0
  82. package/template/extras/fmaddon-auth/server/auth/utils/encryption.ts +51 -0
  83. package/template/extras/fmaddon-auth/server/auth/utils/index.ts +16 -0
  84. package/template/extras/fmaddon-auth/server/auth/utils/password-reset.ts +152 -0
  85. package/template/extras/fmaddon-auth/server/auth/utils/password.ts +67 -0
  86. package/template/extras/fmaddon-auth/server/auth/utils/redirect.ts +8 -0
  87. package/template/extras/fmaddon-auth/server/auth/utils/session.ts +192 -0
  88. package/template/extras/fmaddon-auth/server/auth/utils/user.ts +147 -0
  89. package/template/extras/prisma/schema/base-planetscale.prisma +24 -0
  90. package/template/extras/prisma/schema/base.prisma +20 -0
  91. package/template/extras/prisma/schema/with-auth-planetscale.prisma +77 -0
  92. package/template/extras/prisma/schema/with-auth.prisma +74 -0
  93. package/template/extras/src/app/_components/post-tw.tsx +50 -0
  94. package/template/extras/src/app/_components/post.tsx +54 -0
  95. package/template/extras/src/app/api/auth/[...nextauth]/route.ts +4 -0
  96. package/template/extras/src/app/api/trpc/[trpc]/route.ts +34 -0
  97. package/template/extras/src/app/clerk-auth/layout.tsx +10 -0
  98. package/template/extras/src/app/clerk-auth/signin/[[...sign-in]]/page.tsx +5 -0
  99. package/template/extras/src/app/clerk-auth/signup/[[...sign-up]]/page.tsx +5 -0
  100. package/template/extras/src/app/layout/base.tsx +34 -0
  101. package/template/extras/src/app/layout/main-shell.tsx +37 -0
  102. package/template/extras/src/app/layout/with-trpc-tw.tsx +24 -0
  103. package/template/extras/src/app/layout/with-trpc.tsx +24 -0
  104. package/template/extras/src/app/layout/with-tw.tsx +20 -0
  105. package/template/extras/src/app/next-auth/layout.tsx +22 -0
  106. package/template/extras/src/app/next-auth/signin/page.tsx +82 -0
  107. package/template/extras/src/app/next-auth/signup/action.ts +24 -0
  108. package/template/extras/src/app/next-auth/signup/page.tsx +40 -0
  109. package/template/extras/src/app/next-auth/signup/validation.ts +12 -0
  110. package/template/extras/src/app/page/base.tsx +6 -0
  111. package/template/extras/src/app/page/with-auth-trpc-tw.tsx +67 -0
  112. package/template/extras/src/app/page/with-auth-trpc.tsx +68 -0
  113. package/template/extras/src/app/page/with-trpc-tw.tsx +53 -0
  114. package/template/extras/src/app/page/with-trpc.tsx +54 -0
  115. package/template/extras/src/app/page/with-tw.tsx +37 -0
  116. package/template/extras/src/components/clerk-auth/clerk-provider.tsx +18 -0
  117. package/template/extras/src/components/clerk-auth/user-menu-mobile.tsx +36 -0
  118. package/template/extras/src/components/clerk-auth/user-menu.tsx +24 -0
  119. package/template/extras/src/components/next-auth/next-auth-provider.tsx +14 -0
  120. package/template/extras/src/components/next-auth/user-menu-mobile.tsx +31 -0
  121. package/template/extras/src/components/next-auth/user-menu.tsx +38 -0
  122. package/template/extras/src/env/with-auth.ts +31 -0
  123. package/template/extras/src/env/with-clerk.ts +20 -0
  124. package/template/extras/src/index.module.css +177 -0
  125. package/template/extras/src/middleware/clerk.ts +20 -0
  126. package/template/extras/src/middleware/next-auth.ts +5 -0
  127. package/template/extras/src/pages/_app/base.tsx +14 -0
  128. package/template/extras/src/pages/_app/with-auth-trpc-tw.tsx +23 -0
  129. package/template/extras/src/pages/_app/with-auth-trpc.tsx +23 -0
  130. package/template/extras/src/pages/_app/with-auth-tw.tsx +21 -0
  131. package/template/extras/src/pages/_app/with-auth.tsx +21 -0
  132. package/template/extras/src/pages/_app/with-trpc-tw.tsx +16 -0
  133. package/template/extras/src/pages/_app/with-trpc.tsx +16 -0
  134. package/template/extras/src/pages/_app/with-tw.tsx +14 -0
  135. package/template/extras/src/pages/api/auth/[...nextauth].ts +5 -0
  136. package/template/extras/src/pages/api/trpc/[trpc].ts +19 -0
  137. package/template/extras/src/pages/index/base.tsx +47 -0
  138. package/template/extras/src/pages/index/with-auth-trpc-tw.tsx +80 -0
  139. package/template/extras/src/pages/index/with-auth-trpc.tsx +81 -0
  140. package/template/extras/src/pages/index/with-trpc-tw.tsx +52 -0
  141. package/template/extras/src/pages/index/with-trpc.tsx +53 -0
  142. package/template/extras/src/pages/index/with-tw.tsx +45 -0
  143. package/template/extras/src/server/api/root.ts +23 -0
  144. package/template/extras/src/server/api/routers/post/base.ts +40 -0
  145. package/template/extras/src/server/api/routers/post/with-auth-drizzle.ts +39 -0
  146. package/template/extras/src/server/api/routers/post/with-auth-prisma.ts +41 -0
  147. package/template/extras/src/server/api/routers/post/with-auth.ts +37 -0
  148. package/template/extras/src/server/api/routers/post/with-drizzle.ts +30 -0
  149. package/template/extras/src/server/api/routers/post/with-prisma.ts +31 -0
  150. package/template/extras/src/server/api/trpc-app/base.ts +103 -0
  151. package/template/extras/src/server/api/trpc-app/with-auth-db.ts +133 -0
  152. package/template/extras/src/server/api/trpc-app/with-auth.ts +130 -0
  153. package/template/extras/src/server/api/trpc-app/with-db.ts +106 -0
  154. package/template/extras/src/server/api/trpc-pages/base.ts +122 -0
  155. package/template/extras/src/server/api/trpc-pages/with-auth-db.ts +160 -0
  156. package/template/extras/src/server/api/trpc-pages/with-auth.ts +158 -0
  157. package/template/extras/src/server/api/trpc-pages/with-db.ts +125 -0
  158. package/template/extras/src/server/data/users.ts +23 -0
  159. package/template/extras/src/server/db/db-prisma-planetscale.ts +22 -0
  160. package/template/extras/src/server/db/db-prisma.ts +17 -0
  161. package/template/extras/src/server/db/index-drizzle/with-mysql.ts +18 -0
  162. package/template/extras/src/server/db/index-drizzle/with-planetscale.ts +7 -0
  163. package/template/extras/src/server/db/index-drizzle/with-postgres.ts +18 -0
  164. package/template/extras/src/server/db/index-drizzle/with-sqlite.ts +19 -0
  165. package/template/extras/src/server/db/schema-drizzle/base-mysql.ts +34 -0
  166. package/template/extras/src/server/db/schema-drizzle/base-planetscale.ts +34 -0
  167. package/template/extras/src/server/db/schema-drizzle/base-postgres.ts +36 -0
  168. package/template/extras/src/server/db/schema-drizzle/base-sqlite.ts +30 -0
  169. package/template/extras/src/server/db/schema-drizzle/with-auth-mysql.ts +123 -0
  170. package/template/extras/src/server/db/schema-drizzle/with-auth-planetscale.ts +117 -0
  171. package/template/extras/src/server/db/schema-drizzle/with-auth-postgres.ts +130 -0
  172. package/template/extras/src/server/db/schema-drizzle/with-auth-sqlite.ts +116 -0
  173. package/template/extras/src/server/next-auth/base.ts +111 -0
  174. package/template/extras/src/server/next-auth/password.ts +13 -0
  175. package/template/extras/src/server/next-auth/with-drizzle.ts +83 -0
  176. package/template/extras/src/server/next-auth/with-prisma.ts +72 -0
  177. package/template/extras/src/trpc/query-client.ts +25 -0
  178. package/template/extras/src/trpc/react.tsx +76 -0
  179. package/template/extras/src/trpc/server.ts +30 -0
  180. package/template/extras/src/utils/api.ts +68 -0
  181. package/template/extras/start-database/mysql.sh +54 -0
  182. package/template/extras/start-database/postgres.sh +55 -0
  183. package/template/fm-addon/ProofKitAuth/de.xml +518 -0
  184. package/template/fm-addon/ProofKitAuth/en.xml +518 -0
  185. package/template/fm-addon/ProofKitAuth/es.xml +518 -0
  186. package/template/fm-addon/ProofKitAuth/fr.xml +518 -0
  187. package/template/fm-addon/ProofKitAuth/icon.png +0 -0
  188. package/template/fm-addon/ProofKitAuth/icon@2x.png +0 -0
  189. package/template/fm-addon/ProofKitAuth/info.json +11 -0
  190. package/template/fm-addon/ProofKitAuth/info_de.json +18 -0
  191. package/template/fm-addon/ProofKitAuth/info_en.json +8 -0
  192. package/template/fm-addon/ProofKitAuth/info_es.json +18 -0
  193. package/template/fm-addon/ProofKitAuth/info_fr.json +18 -0
  194. package/template/fm-addon/ProofKitAuth/info_it.json +18 -0
  195. package/template/fm-addon/ProofKitAuth/info_ja.json +18 -0
  196. package/template/fm-addon/ProofKitAuth/info_ko.json +18 -0
  197. package/template/fm-addon/ProofKitAuth/info_nl.json +18 -0
  198. package/template/fm-addon/ProofKitAuth/info_pt.json +18 -0
  199. package/template/fm-addon/ProofKitAuth/info_sv.json +18 -0
  200. package/template/fm-addon/ProofKitAuth/info_zh.json +18 -0
  201. package/template/fm-addon/ProofKitAuth/it.xml +518 -0
  202. package/template/fm-addon/ProofKitAuth/ja.xml +518 -0
  203. package/template/fm-addon/ProofKitAuth/ko.xml +518 -0
  204. package/template/fm-addon/ProofKitAuth/nl.xml +518 -0
  205. package/template/fm-addon/ProofKitAuth/preview.png +0 -0
  206. package/template/fm-addon/ProofKitAuth/pt.xml +518 -0
  207. package/template/fm-addon/ProofKitAuth/sv.xml +518 -0
  208. package/template/fm-addon/ProofKitAuth/template.xml +0 -0
  209. package/template/fm-addon/ProofKitAuth/zh.xml +518 -0
  210. package/template/fm-addon/ProofKitWV/de.xml +896 -0
  211. package/template/fm-addon/ProofKitWV/en.xml +896 -0
  212. package/template/fm-addon/ProofKitWV/es.xml +896 -0
  213. package/template/fm-addon/ProofKitWV/fr.xml +896 -0
  214. package/template/fm-addon/ProofKitWV/icon.png +0 -0
  215. package/template/fm-addon/ProofKitWV/icon@2x.png +0 -0
  216. package/template/fm-addon/ProofKitWV/info.json +11 -0
  217. package/template/fm-addon/ProofKitWV/info_de.json +18 -0
  218. package/template/fm-addon/ProofKitWV/info_en.json +11 -0
  219. package/template/fm-addon/ProofKitWV/info_es.json +18 -0
  220. package/template/fm-addon/ProofKitWV/info_fr.json +18 -0
  221. package/template/fm-addon/ProofKitWV/info_it.json +18 -0
  222. package/template/fm-addon/ProofKitWV/info_ja.json +18 -0
  223. package/template/fm-addon/ProofKitWV/info_ko.json +18 -0
  224. package/template/fm-addon/ProofKitWV/info_nl.json +18 -0
  225. package/template/fm-addon/ProofKitWV/info_pt.json +18 -0
  226. package/template/fm-addon/ProofKitWV/info_sv.json +18 -0
  227. package/template/fm-addon/ProofKitWV/info_zh.json +18 -0
  228. package/template/fm-addon/ProofKitWV/it.xml +896 -0
  229. package/template/fm-addon/ProofKitWV/ja.xml +896 -0
  230. package/template/fm-addon/ProofKitWV/ko.xml +896 -0
  231. package/template/fm-addon/ProofKitWV/nl.xml +896 -0
  232. package/template/fm-addon/ProofKitWV/preview.png +0 -0
  233. package/template/fm-addon/ProofKitWV/pt.xml +896 -0
  234. package/template/fm-addon/ProofKitWV/records_de.xml +0 -0
  235. package/template/fm-addon/ProofKitWV/records_en.xml +0 -0
  236. package/template/fm-addon/ProofKitWV/records_es.xml +0 -0
  237. package/template/fm-addon/ProofKitWV/records_fr.xml +0 -0
  238. package/template/fm-addon/ProofKitWV/records_it.xml +0 -0
  239. package/template/fm-addon/ProofKitWV/records_ja.xml +0 -0
  240. package/template/fm-addon/ProofKitWV/records_ko.xml +0 -0
  241. package/template/fm-addon/ProofKitWV/records_nl.xml +0 -0
  242. package/template/fm-addon/ProofKitWV/records_pt.xml +0 -0
  243. package/template/fm-addon/ProofKitWV/records_sv.xml +0 -0
  244. package/template/fm-addon/ProofKitWV/records_zh.xml +0 -0
  245. package/template/fm-addon/ProofKitWV/sv.xml +896 -0
  246. package/template/fm-addon/ProofKitWV/template.xml +0 -0
  247. package/template/fm-addon/ProofKitWV/zh.xml +896 -0
  248. package/template/nextjs/README.md +27 -0
  249. package/template/nextjs/_gitignore +37 -0
  250. package/template/nextjs/next.config.ts +12 -0
  251. package/template/nextjs/package.json +50 -0
  252. package/template/nextjs/postcss.config.cjs +14 -0
  253. package/template/nextjs/proofkit.json +1 -0
  254. package/template/nextjs/public/favicon.ico +0 -0
  255. package/template/nextjs/public/proofkit.png +0 -0
  256. package/template/nextjs/src/app/(main)/layout.tsx +6 -0
  257. package/template/nextjs/src/app/(main)/page.tsx +90 -0
  258. package/template/nextjs/src/app/layout.tsx +39 -0
  259. package/template/nextjs/src/app/navigation.tsx +12 -0
  260. package/template/nextjs/src/components/AppLogo.tsx +6 -0
  261. package/template/nextjs/src/components/AppShell/internal/AppShell.tsx +21 -0
  262. package/template/nextjs/src/components/AppShell/internal/Header.module.css +40 -0
  263. package/template/nextjs/src/components/AppShell/internal/Header.tsx +34 -0
  264. package/template/nextjs/src/components/AppShell/internal/HeaderMobileMenu.tsx +27 -0
  265. package/template/nextjs/src/components/AppShell/internal/HeaderNavLink.tsx +31 -0
  266. package/template/nextjs/src/components/AppShell/internal/config.ts +1 -0
  267. package/template/nextjs/src/components/AppShell/slot-header-center.tsx +13 -0
  268. package/template/nextjs/src/components/AppShell/slot-header-left.tsx +23 -0
  269. package/template/nextjs/src/components/AppShell/slot-header-mobile-content.tsx +43 -0
  270. package/template/nextjs/src/components/AppShell/slot-header-right.tsx +26 -0
  271. package/template/nextjs/src/config/env.ts +13 -0
  272. package/template/nextjs/src/config/theme/globals.css +1 -0
  273. package/template/nextjs/src/config/theme/mantine-theme.ts +22 -0
  274. package/template/nextjs/src/server/safe-action.ts +3 -0
  275. package/template/nextjs/src/utils/notification-helpers.ts +32 -0
  276. package/template/nextjs/tsconfig.json +40 -0
  277. package/template/pages/nextjs/blank/page.tsx +5 -0
  278. package/template/pages/nextjs/table/page.tsx +17 -0
  279. package/template/pages/nextjs/table/table.tsx +18 -0
  280. package/template/pages/nextjs/table-edit/actions.ts +23 -0
  281. package/template/pages/nextjs/table-edit/page.tsx +28 -0
  282. package/template/pages/nextjs/table-edit/schema.ts +4 -0
  283. package/template/pages/nextjs/table-edit/table.tsx +43 -0
  284. package/template/pages/nextjs/table-infinite/actions.ts +62 -0
  285. package/template/pages/nextjs/table-infinite/page.tsx +11 -0
  286. package/template/pages/nextjs/table-infinite/query.ts +44 -0
  287. package/template/pages/nextjs/table-infinite/table.tsx +107 -0
  288. package/template/pages/nextjs/table-infinite-edit/actions.ts +84 -0
  289. package/template/pages/nextjs/table-infinite-edit/page.tsx +23 -0
  290. package/template/pages/nextjs/table-infinite-edit/query.ts +81 -0
  291. package/template/pages/nextjs/table-infinite-edit/schema.ts +4 -0
  292. package/template/pages/nextjs/table-infinite-edit/table.tsx +130 -0
  293. package/template/pages/vite-wv/blank/index.tsx +0 -0
  294. package/template/pages/vite-wv/table/index.tsx +34 -0
  295. package/template/pages/vite-wv/table-edit/index.tsx +72 -0
  296. package/template/vite-wv/.vscode/settings.json +11 -0
  297. package/template/vite-wv/_gitignore +18 -0
  298. package/template/vite-wv/index.html +13 -0
  299. package/template/vite-wv/package.json +52 -0
  300. package/template/vite-wv/pnpm-lock.yaml +2294 -0
  301. package/template/vite-wv/postcss.config.cjs +14 -0
  302. package/template/vite-wv/proofkit.json +1 -0
  303. package/template/vite-wv/scripts/launch-fm.sh +3 -0
  304. package/template/vite-wv/scripts/upload.js +21 -0
  305. package/template/vite-wv/src/components/AppLogo.tsx +5 -0
  306. package/template/vite-wv/src/components/full-screen-loader.tsx +9 -0
  307. package/template/vite-wv/src/config/env.ts +16 -0
  308. package/template/vite-wv/src/config/theme/globals.css +1 -0
  309. package/template/vite-wv/src/config/theme/mantine-theme.ts +22 -0
  310. package/template/vite-wv/src/main.tsx +42 -0
  311. package/template/vite-wv/src/routeTree.gen.ts +111 -0
  312. package/template/vite-wv/src/routes/__root.tsx +21 -0
  313. package/template/vite-wv/src/routes/index.tsx +63 -0
  314. package/template/vite-wv/src/routes/secondary.tsx +28 -0
  315. package/template/vite-wv/src/utils/notification-helpers.ts +32 -0
  316. package/template/vite-wv/tsconfig.json +14 -0
  317. package/template/vite-wv/vite.config.ts +18 -0
@@ -0,0 +1,156 @@
1
+ import {
2
+ Body,
3
+ Container,
4
+ Head,
5
+ Heading,
6
+ Html,
7
+ Img,
8
+ Section,
9
+ Text,
10
+ } from "@react-email/components";
11
+ import * as React from "react";
12
+
13
+ interface AuthCodeEmailProps {
14
+ validationCode: string;
15
+ type: "verification" | "password-reset";
16
+ }
17
+
18
+ export const AuthCodeEmail = ({ validationCode, type }: AuthCodeEmailProps) => (
19
+ <Html>
20
+ <Head />
21
+ <Body style={main}>
22
+ <Container style={container}>
23
+ <Img
24
+ // TODO: Replace with your logo
25
+ src="https://proofkit.dev/_astro/proofkit.DNcFg0_B_1JN3Dz.webp"
26
+ width="238"
27
+ height="175"
28
+ alt="ProofKit"
29
+ style={logo}
30
+ />
31
+ <Text style={tertiary}>
32
+ {type === "verification"
33
+ ? "Verify Your Email"
34
+ : "Reset Your Password"}
35
+ </Text>
36
+ <Heading style={secondary}>
37
+ Enter the following code to{" "}
38
+ {type === "verification"
39
+ ? "verify your email"
40
+ : "reset your password"}
41
+ </Heading>
42
+ <Section style={codeContainer}>
43
+ <Text style={code}>{validationCode}</Text>
44
+ </Section>
45
+ <Text style={paragraph}>
46
+ If you did not request this code, you can ignore this email.
47
+ </Text>
48
+ </Container>
49
+ </Body>
50
+ </Html>
51
+ );
52
+
53
+ AuthCodeEmail.PreviewProps = {
54
+ validationCode: "D7CU4GOV",
55
+ type: "verification",
56
+ } as AuthCodeEmailProps;
57
+
58
+ export default AuthCodeEmail;
59
+
60
+ const main = {
61
+ backgroundColor: "#ffffff",
62
+ fontFamily: "HelveticaNeue,Helvetica,Arial,sans-serif",
63
+ };
64
+
65
+ const container = {
66
+ backgroundColor: "#ffffff",
67
+ border: "1px solid #eee",
68
+ borderRadius: "5px",
69
+ boxShadow: "0 5px 10px rgba(20,50,70,.2)",
70
+ marginTop: "20px",
71
+ maxWidth: "360px",
72
+ margin: "0 auto",
73
+ padding: "68px 0 130px",
74
+ };
75
+
76
+ const logo: React.CSSProperties = {
77
+ margin: "0 auto",
78
+ };
79
+
80
+ const tertiary = {
81
+ color: "#0a85ea",
82
+ fontSize: "11px",
83
+ fontWeight: 700,
84
+ fontFamily: "HelveticaNeue,Helvetica,Arial,sans-serif",
85
+ height: "16px",
86
+ letterSpacing: "0",
87
+ lineHeight: "16px",
88
+ margin: "16px 8px 8px 8px",
89
+ textTransform: "uppercase" as const,
90
+ textAlign: "center" as const,
91
+ };
92
+
93
+ const secondary = {
94
+ color: "#000",
95
+ display: "inline-block",
96
+ fontFamily: "HelveticaNeue-Medium,Helvetica,Arial,sans-serif",
97
+ fontSize: "20px",
98
+ fontWeight: 500,
99
+ lineHeight: "24px",
100
+ marginBottom: "0",
101
+ marginTop: "0",
102
+ textAlign: "center" as const,
103
+ padding: "0 40px",
104
+ };
105
+
106
+ const codeContainer = {
107
+ background: "rgba(0,0,0,.05)",
108
+ borderRadius: "4px",
109
+ margin: "16px auto 14px",
110
+ verticalAlign: "middle",
111
+ width: "280px",
112
+ };
113
+
114
+ const code = {
115
+ color: "#000",
116
+ display: "inline-block",
117
+ fontFamily: "HelveticaNeue-Bold",
118
+ fontSize: "32px",
119
+ fontWeight: 700,
120
+ letterSpacing: "6px",
121
+ lineHeight: "40px",
122
+ paddingBottom: "8px",
123
+ paddingTop: "8px",
124
+ margin: "0 auto",
125
+ width: "100%",
126
+ textAlign: "center" as const,
127
+ };
128
+
129
+ const paragraph = {
130
+ color: "#444",
131
+ fontSize: "15px",
132
+ fontFamily: "HelveticaNeue,Helvetica,Arial,sans-serif",
133
+ letterSpacing: "0",
134
+ lineHeight: "23px",
135
+ padding: "0 40px",
136
+ margin: "0",
137
+ textAlign: "center" as const,
138
+ };
139
+
140
+ const link = {
141
+ color: "#444",
142
+ textDecoration: "underline",
143
+ };
144
+
145
+ const footer = {
146
+ color: "#000",
147
+ fontSize: "12px",
148
+ fontWeight: 800,
149
+ letterSpacing: "0",
150
+ lineHeight: "23px",
151
+ margin: "0",
152
+ marginTop: "20px",
153
+ fontFamily: "HelveticaNeue,Helvetica,Arial,sans-serif",
154
+ textAlign: "center" as const,
155
+ textTransform: "uppercase" as const,
156
+ };
@@ -0,0 +1,45 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ import type { NextRequest } from "next/server";
4
+
5
+ export async function middleware(request: NextRequest): Promise<NextResponse> {
6
+ if (request.method === "GET") {
7
+ const response = NextResponse.next();
8
+ const token = request.cookies.get("session")?.value ?? null;
9
+ if (token !== null) {
10
+ // Only extend cookie expiration on GET requests since we can be sure
11
+ // a new session wasn't set when handling the request.
12
+ response.cookies.set("session", token, {
13
+ path: "/",
14
+ maxAge: 60 * 60 * 24 * 30,
15
+ sameSite: "lax",
16
+ httpOnly: true,
17
+ secure: process.env.NODE_ENV === "production",
18
+ });
19
+ }
20
+ return response;
21
+ }
22
+
23
+ const originHeader = request.headers.get("Origin");
24
+ // NOTE: You may need to use `X-Forwarded-Host` instead
25
+ const hostHeader = request.headers.get("Host");
26
+ if (originHeader === null || hostHeader === null) {
27
+ return new NextResponse(null, {
28
+ status: 403,
29
+ });
30
+ }
31
+ let origin: URL;
32
+ try {
33
+ origin = new URL(originHeader);
34
+ } catch {
35
+ return new NextResponse(null, {
36
+ status: 403,
37
+ });
38
+ }
39
+ if (origin.host !== hostHeader) {
40
+ return new NextResponse(null, {
41
+ status: 403,
42
+ });
43
+ }
44
+ return NextResponse.next();
45
+ }
@@ -0,0 +1,136 @@
1
+ import { generateRandomOTP } from "./index";
2
+ import { encodeBase32 } from "@oslojs/encoding";
3
+ import { cookies } from "next/headers";
4
+ import { getCurrentSession } from "./session";
5
+ import { emailVerificationLayout } from "../db/client";
6
+ import { TemailVerification } from "../db/emailVerification";
7
+ import { sendEmail } from "../email";
8
+
9
+ /**
10
+ * An Email Verification Request is a record in the email verification table that is created when a user requests to change their email address. It's like a temporary session which can expire if the user doesn't verify the new email address within a certain amount of time.
11
+ */
12
+
13
+ /**
14
+ * Get a user's email verification request.
15
+ * @param userId - The ID of the user.
16
+ * @param id - The ID of the email verification request.
17
+ * @returns The email verification request, or null if it doesn't exist.
18
+ */
19
+ export async function getUserEmailVerificationRequest(
20
+ userId: string,
21
+ id: string,
22
+ ): Promise<TemailVerification | null> {
23
+ const result = await emailVerificationLayout.maybeFindFirst({
24
+ query: { id_user: `==${userId}`, id: `==${id}` },
25
+ });
26
+ return result?.data.fieldData ?? null;
27
+ }
28
+
29
+ /**
30
+ * Create a new email verification request for a user.
31
+ * @param id_user - The ID of the user.
32
+ * @param email - The email address to verify.
33
+ * @returns The email verification request.
34
+ */
35
+ export async function createEmailVerificationRequest(
36
+ id_user: string,
37
+ email: string,
38
+ ): Promise<TemailVerification> {
39
+ deleteUserEmailVerificationRequest(id_user);
40
+ const idBytes = new Uint8Array(20);
41
+ crypto.getRandomValues(idBytes);
42
+ const id = encodeBase32(idBytes).toLowerCase();
43
+
44
+ const code = generateRandomOTP();
45
+ const expiresAt = new Date(Date.now() + 1000 * 60 * 10);
46
+
47
+ const request: TemailVerification = {
48
+ id,
49
+ code,
50
+ expires_at: Math.floor(expiresAt.getTime() / 1000),
51
+ email,
52
+ id_user,
53
+ };
54
+
55
+ await emailVerificationLayout.create({
56
+ fieldData: request,
57
+ });
58
+
59
+ return request;
60
+ }
61
+
62
+ /**
63
+ * Delete a user's email verification request.
64
+ * @param id_user - The ID of the user.
65
+ */
66
+ export async function deleteUserEmailVerificationRequest(
67
+ id_user: string,
68
+ ): Promise<void> {
69
+ const result = await emailVerificationLayout.maybeFindFirst({
70
+ query: { id_user: `==${id_user}` },
71
+ });
72
+ if (result === null) return;
73
+
74
+ await emailVerificationLayout.delete({ recordId: result.data.recordId });
75
+ }
76
+
77
+ /**
78
+ * Send a verification email to a user.
79
+ * @param email - The email address to send the verification email to.
80
+ * @param code - The verification code to send to the user.
81
+ */
82
+ export async function sendVerificationEmail(
83
+ email: string,
84
+ code: string,
85
+ ): Promise<void> {
86
+ await sendEmail({ to: email, code, type: "verification" });
87
+ }
88
+
89
+ /**
90
+ * Set a cookie for a user's email verification request.
91
+ * @param request - The email verification request.
92
+ */
93
+ export async function setEmailVerificationRequestCookie(
94
+ request: TemailVerification,
95
+ ): Promise<void> {
96
+ (await cookies()).set("email_verification", request.id, {
97
+ httpOnly: true,
98
+ path: "/",
99
+ secure: process.env.NODE_ENV === "production",
100
+ sameSite: "lax",
101
+ expires: request.expires_at
102
+ ? new Date(request.expires_at * 1000)
103
+ : new Date(Date.now() + 1000 * 60 * 60),
104
+ });
105
+ }
106
+
107
+ /**
108
+ * Delete the cookie for a user's email verification request.
109
+ */
110
+ export async function deleteEmailVerificationRequestCookie(): Promise<void> {
111
+ (await cookies()).set("email_verification", "", {
112
+ httpOnly: true,
113
+ path: "/",
114
+ secure: process.env.NODE_ENV === "production",
115
+ sameSite: "lax",
116
+ maxAge: 0,
117
+ });
118
+ }
119
+
120
+ /**
121
+ * Get a user's email verification request from the cookie.
122
+ * @returns The email verification request, or null if it doesn't exist.
123
+ */
124
+ export async function getUserEmailVerificationRequestFromRequest(): Promise<TemailVerification | null> {
125
+ const { user } = await getCurrentSession();
126
+ if (user === null) {
127
+ return null;
128
+ }
129
+ const id = (await cookies()).get("email_verification")?.value ?? null;
130
+ if (id === null) {
131
+ return null;
132
+ }
133
+ const request = await getUserEmailVerificationRequest(user.id, id);
134
+
135
+ return request;
136
+ }
@@ -0,0 +1,51 @@
1
+ import { decodeBase64 } from "@oslojs/encoding";
2
+ import { createCipheriv, createDecipheriv } from "crypto";
3
+ import { DynamicBuffer } from "@oslojs/binary";
4
+
5
+ const key = decodeBase64(process.env.ENCRYPTION_KEY ?? "");
6
+
7
+ export function encrypt(data: Uint8Array): Uint8Array {
8
+ const iv = new Uint8Array(16);
9
+ crypto.getRandomValues(iv);
10
+ const cipher = createCipheriv("aes-128-gcm", key, iv);
11
+ const encrypted = new DynamicBuffer(0);
12
+ encrypted.write(iv);
13
+ encrypted.write(cipher.update(data));
14
+ encrypted.write(cipher.final());
15
+ encrypted.write(cipher.getAuthTag());
16
+ return encrypted.bytes();
17
+ }
18
+
19
+ /**
20
+ * Encrypt a string for storage in the database.
21
+ * Here we're returning a base64 encoded string since FileMaker doesn't store binary data.
22
+ * @param data - The string to encrypt.
23
+ * @returns The encrypted string.
24
+ */
25
+ export function encryptString(data: string): string {
26
+ const encrypted = encrypt(new TextEncoder().encode(data));
27
+ return Buffer.from(encrypted).toString("base64");
28
+ }
29
+
30
+ /**
31
+ * Decrypt a string stored in the database.
32
+ * @param encrypted - The encrypted string to decrypt.
33
+ * @returns The decrypted string.
34
+ */
35
+ export function decrypt(encrypted: Uint8Array): Uint8Array {
36
+ if (encrypted.byteLength < 33) {
37
+ throw new Error("Invalid data");
38
+ }
39
+ const decipher = createDecipheriv("aes-128-gcm", key, encrypted.slice(0, 16));
40
+ decipher.setAuthTag(encrypted.slice(encrypted.byteLength - 16));
41
+ const decrypted = new DynamicBuffer(0);
42
+ decrypted.write(
43
+ decipher.update(encrypted.slice(16, encrypted.byteLength - 16)),
44
+ );
45
+ decrypted.write(decipher.final());
46
+ return decrypted.bytes();
47
+ }
48
+
49
+ export function decryptToString(data: Uint8Array): string {
50
+ return new TextDecoder().decode(decrypt(data));
51
+ }
@@ -0,0 +1,16 @@
1
+ import { encodeBase32UpperCaseNoPadding } from "@oslojs/encoding";
2
+
3
+ export function generateRandomOTP(): string {
4
+ const bytes = new Uint8Array(5);
5
+ crypto.getRandomValues(bytes);
6
+ const code = encodeBase32UpperCaseNoPadding(bytes);
7
+ return code;
8
+ }
9
+
10
+ export const options = {
11
+ password: {
12
+ minLength: 8,
13
+ maxLength: 255,
14
+ checkCompromised: false, // set to true to prevent known compromised passwords on signup
15
+ },
16
+ };
@@ -0,0 +1,152 @@
1
+ import { encodeHexLowerCase } from "@oslojs/encoding";
2
+ import { generateRandomOTP } from "./index";
3
+ import { sha256 } from "@oslojs/crypto/sha2";
4
+ import { cookies } from "next/headers";
5
+ import { passwordResetLayout } from "../db/client";
6
+ import { TpasswordReset } from "../db/passwordReset";
7
+
8
+ import type { User } from "./user";
9
+ import { sendEmail } from "../email";
10
+ type PasswordResetSession = Omit<
11
+ TpasswordReset,
12
+ | "proofkit_auth_users::email"
13
+ | "proofkit_auth_users::emailVerified"
14
+ | "proofkit_auth_users::username"
15
+ >;
16
+
17
+ export async function createPasswordResetSession(
18
+ token: string,
19
+ id_user: string,
20
+ email: string
21
+ ): Promise<PasswordResetSession> {
22
+ const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
23
+ const session: PasswordResetSession = {
24
+ id: sessionId,
25
+ id_user,
26
+ email,
27
+ expires_at: Math.floor(
28
+ new Date(Date.now() + 1000 * 60 * 10).getTime() / 1000
29
+ ),
30
+ code: generateRandomOTP(),
31
+ email_verified: 0,
32
+ };
33
+ await passwordResetLayout.create({ fieldData: session });
34
+
35
+ return session;
36
+ }
37
+
38
+ /**
39
+ * Validate a password reset session token.
40
+ * @param token - The password reset session token.
41
+ * @returns The password reset session, or null if it doesn't exist.
42
+ */
43
+ export async function validatePasswordResetSessionToken(
44
+ token: string
45
+ ): Promise<PasswordResetSessionValidationResult> {
46
+ const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
47
+ const row = await passwordResetLayout.maybeFindFirst({
48
+ query: { id: `==${sessionId}` },
49
+ });
50
+
51
+ if (row === null) {
52
+ return { session: null, user: null };
53
+ }
54
+ const session: PasswordResetSession = {
55
+ id: row.data.fieldData.id,
56
+ id_user: row.data.fieldData.id_user,
57
+ email: row.data.fieldData.email,
58
+ code: row.data.fieldData.code,
59
+ expires_at: row.data.fieldData.expires_at,
60
+ email_verified: row.data.fieldData.email_verified,
61
+ };
62
+
63
+ const user: User = {
64
+ id: row.data.fieldData.id_user,
65
+ email: row.data.fieldData["proofkit_auth_users::email"],
66
+ username: row.data.fieldData["proofkit_auth_users::username"],
67
+ emailVerified: Boolean(
68
+ row.data.fieldData["proofkit_auth_users::emailVerified"]
69
+ ),
70
+ };
71
+ if (session.expires_at && Date.now() >= session.expires_at * 1000) {
72
+ await passwordResetLayout.delete({ recordId: row.data.recordId });
73
+ return { session: null, user: null };
74
+ }
75
+ return { session, user };
76
+ }
77
+
78
+ async function fetchPasswordResetSession(sessionId: string) {
79
+ return (
80
+ await passwordResetLayout.findOne({ query: { id: `==${sessionId}` } })
81
+ ).data;
82
+ }
83
+
84
+ export async function setPasswordResetSessionAsEmailVerified(
85
+ sessionId: string
86
+ ): Promise<void> {
87
+ const { recordId } = await fetchPasswordResetSession(sessionId);
88
+ await passwordResetLayout.update({
89
+ recordId,
90
+ fieldData: { email_verified: 1 },
91
+ });
92
+ }
93
+
94
+ export async function invalidateUserPasswordResetSessions(
95
+ userId: string
96
+ ): Promise<void> {
97
+ const sessions = await passwordResetLayout.find({
98
+ query: { id_user: `==${userId}` },
99
+ ignoreEmptyResult: true,
100
+ });
101
+ for (const session of sessions.data) {
102
+ await passwordResetLayout.delete({ recordId: session.recordId });
103
+ }
104
+ }
105
+
106
+ export async function validatePasswordResetSessionRequest(): Promise<PasswordResetSessionValidationResult> {
107
+ const token = (await cookies()).get("password_reset_session")?.value ?? null;
108
+ if (token === null) {
109
+ return { session: null, user: null };
110
+ }
111
+ const result = await validatePasswordResetSessionToken(token);
112
+ if (result.session === null) {
113
+ deletePasswordResetSessionTokenCookie();
114
+ }
115
+ return result;
116
+ }
117
+
118
+ export async function setPasswordResetSessionTokenCookie(
119
+ token: string,
120
+ expiresAt: number | null
121
+ ): Promise<void> {
122
+ (await cookies()).set("password_reset_session", token, {
123
+ expires: expiresAt
124
+ ? new Date(expiresAt * 1000)
125
+ : new Date(Date.now() + 60 * 60 * 1000),
126
+ sameSite: "lax",
127
+ httpOnly: true,
128
+ path: "/",
129
+ secure: process.env.NODE_ENV === "production",
130
+ });
131
+ }
132
+
133
+ export async function deletePasswordResetSessionTokenCookie(): Promise<void> {
134
+ (await cookies()).set("password_reset_session", "", {
135
+ maxAge: 0,
136
+ sameSite: "lax",
137
+ httpOnly: true,
138
+ path: "/",
139
+ secure: process.env.NODE_ENV === "production",
140
+ });
141
+ }
142
+
143
+ export async function sendPasswordResetEmail(
144
+ email: string,
145
+ code: string
146
+ ): Promise<void> {
147
+ await sendEmail({ to: email, code, type: "password-reset" });
148
+ }
149
+
150
+ export type PasswordResetSessionValidationResult =
151
+ | { session: PasswordResetSession; user: User }
152
+ | { session: null; user: null };
@@ -0,0 +1,67 @@
1
+ import { options } from ".";
2
+ import { hash, verify } from "@node-rs/argon2";
3
+ import { sha1 } from "@oslojs/crypto/sha1";
4
+ import { encodeHexLowerCase } from "@oslojs/encoding";
5
+
6
+ /**
7
+ * Hash a password using Argon2.
8
+ * @param password - The password to hash.
9
+ * @returns The hashed password.
10
+ */
11
+ export async function hashPassword(password: string): Promise<string> {
12
+ return await hash(password, {
13
+ memoryCost: 19456,
14
+ timeCost: 2,
15
+ outputLen: 32,
16
+ parallelism: 1,
17
+ });
18
+ }
19
+
20
+ /**
21
+ * Verify that a password matches a hash.
22
+ * @param hash - The hash to verify against.
23
+ * @param password - The password to verify.
24
+ * @returns True if the password matches the hash, false otherwise.
25
+ */
26
+ export async function verifyPasswordHash(
27
+ hash: string,
28
+ password: string
29
+ ): Promise<boolean> {
30
+ return await verify(hash, password);
31
+ }
32
+
33
+ /**
34
+ * Verify that a password is strong enough.
35
+ * @param password - The password to verify.
36
+ * @returns True if the password is strong enough, false otherwise.
37
+ */
38
+ export async function verifyPasswordStrength(
39
+ password: string
40
+ ): Promise<boolean> {
41
+ if (
42
+ password.length < options.password.minLength ||
43
+ password.length > options.password.maxLength
44
+ ) {
45
+ return false;
46
+ }
47
+
48
+ if (options.password.checkCompromised) {
49
+ const hash = encodeHexLowerCase(sha1(new TextEncoder().encode(password)));
50
+ const hashPrefix = hash.slice(0, 5);
51
+ const response = await fetch(
52
+ `https://api.pwnedpasswords.com/range/${hashPrefix}`
53
+ );
54
+ const data = await response.text();
55
+ const items = data.split("\n");
56
+ for (const item of items) {
57
+ const hashSuffix = item.slice(0, 35).toLowerCase();
58
+ if (hash === hashPrefix + hashSuffix) {
59
+ console.log(
60
+ "User's new password was found in list of compromised passwords, reject"
61
+ );
62
+ return false;
63
+ }
64
+ }
65
+ }
66
+ return true;
67
+ }
@@ -0,0 +1,8 @@
1
+ import { cookies } from "next/headers";
2
+
3
+ export async function getRedirectCookie() {
4
+ const cookieStore = await cookies();
5
+ const redirectTo = cookieStore.get("redirectTo")?.value;
6
+ cookieStore.delete("redirectTo");
7
+ return redirectTo ?? "/";
8
+ }