studiocms 0.2.0 → 0.4.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 (293) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/dist/cli/add/index.d.ts +2 -2
  3. package/dist/cli/add/index.js +4 -3
  4. package/dist/cli/add/npm-utils.d.ts +6 -6
  5. package/dist/cli/add/tryToInstallPlugins.d.ts +1 -1
  6. package/dist/cli/add/tryToInstallPlugins.js +6 -5
  7. package/dist/cli/add/updateStudioCMSConfig.d.ts +1 -1
  8. package/dist/cli/add/updateStudioCMSConfig.js +3 -4
  9. package/dist/cli/add/validatePlugins.d.ts +1 -2
  10. package/dist/cli/add/validatePlugins.js +15 -9
  11. package/dist/cli/crypto/genJWT/index.d.ts +1 -1
  12. package/dist/cli/crypto/genJWT/index.js +8 -9
  13. package/dist/cli/crypto/index.d.ts +1 -1
  14. package/dist/cli/init/steps/env.js +14 -4
  15. package/dist/cli/init/steps/next.d.ts +1 -1
  16. package/dist/cli/init/steps/next.js +6 -5
  17. package/dist/cli/migrator/index.d.ts +1 -1
  18. package/dist/cli/migrator/index.js +2 -2
  19. package/dist/cli/users/index.d.ts +1 -1
  20. package/dist/cli/users/shared.js +2 -2
  21. package/dist/cli/users/steps/createUsers.js +7 -7
  22. package/dist/cli/users/steps/modifyUsers.js +2 -2
  23. package/dist/cli/users/steps/next.d.ts +1 -1
  24. package/dist/cli/utils/checkRequiredEnvVars.js +2 -2
  25. package/dist/cli/utils/context.d.ts +2 -4
  26. package/dist/cli/utils/context.js +1 -3
  27. package/dist/cli/utils/getCliDbClient.d.ts +1 -1
  28. package/dist/cli/utils/intro.d.ts +1 -1
  29. package/dist/cli/utils/loadConfig.d.ts +54 -49
  30. package/dist/cli/utils/loadConfig.js +5 -8
  31. package/dist/cli/utils/logger.js +3 -3
  32. package/dist/cli/utils/user-utils.d.ts +1 -1
  33. package/dist/cli/utils/user-utils.js +4 -3
  34. package/dist/client/apiClient.d.ts +4923 -0
  35. package/dist/client/apiClient.js +72 -0
  36. package/dist/config.d.ts +1734 -1
  37. package/dist/consts.d.ts +5 -5
  38. package/dist/consts.js +3 -2
  39. package/dist/db/plugins.d.ts +1 -1
  40. package/dist/db/plugins.js +5 -8
  41. package/dist/handlers/frontend/routes.d.ts +4 -18
  42. package/dist/handlers/frontend/routes.js +13 -152
  43. package/dist/handlers/frontend/types.d.ts +1 -1
  44. package/dist/handlers/frontend/utils.js +0 -18
  45. package/dist/handlers/pluginHandler.d.ts +34 -257
  46. package/dist/handlers/pluginHandler.js +92 -46
  47. package/dist/handlers/routeHandler.js +32 -11
  48. package/dist/handlers/setupDbStudio.d.ts +3 -1
  49. package/dist/handlers/setupDbStudio.js +19 -10
  50. package/dist/handlers/storage-manager/core/effectify-astro-context.d.ts +25 -0
  51. package/dist/handlers/storage-manager/core/effectify-astro-context.js +78 -0
  52. package/dist/handlers/storage-manager/no-op.d.ts +2 -2
  53. package/dist/handlers/storage-manager/no-op.js +2 -3
  54. package/dist/index.d.ts +0 -1
  55. package/dist/index.js +10 -20
  56. package/dist/integrations/robots/index.d.ts +2 -2
  57. package/dist/integrations/robots/index.js +1 -3
  58. package/dist/integrations/robots/schema.d.ts +102 -273
  59. package/dist/integrations/robots/schema.js +220 -209
  60. package/dist/plugins/analytics/assets/schemas.d.ts +14 -9
  61. package/dist/plugins/analytics/assets/schemas.js +25 -17
  62. package/dist/plugins/analytics/db-client.d.ts +1 -1
  63. package/dist/plugins/analytics/index.d.ts +823 -3
  64. package/dist/plugins/analytics/index.js +4 -5
  65. package/dist/plugins/analytics/schemas.d.ts +54 -62
  66. package/dist/plugins/analytics/schemas.js +64 -13
  67. package/dist/plugins/analytics/table.d.ts +1 -1
  68. package/dist/plugins.d.ts +0 -1
  69. package/dist/schemas/config/api.d.ts +17 -0
  70. package/dist/schemas/config/api.js +14 -0
  71. package/dist/schemas/config/auth.d.ts +55 -59
  72. package/dist/schemas/config/auth.js +34 -11
  73. package/dist/schemas/config/dashboard.d.ts +43 -79
  74. package/dist/schemas/config/dashboard.js +43 -12
  75. package/dist/schemas/config/db.d.ts +15 -17
  76. package/dist/schemas/config/db.js +13 -5
  77. package/dist/schemas/config/developer.d.ts +33 -45
  78. package/dist/schemas/config/developer.js +22 -5
  79. package/dist/schemas/config/index.d.ts +398 -521
  80. package/dist/schemas/config/index.js +115 -57
  81. package/dist/schemas/config/sdk.d.ts +50 -196
  82. package/dist/schemas/config/sdk.js +61 -73
  83. package/dist/schemas/custom.d.ts +40 -0
  84. package/dist/schemas/custom.js +41 -0
  85. package/dist/schemas/external-schemas.d.ts +171 -0
  86. package/dist/schemas/external-schemas.js +179 -0
  87. package/dist/schemas/index.d.ts +2 -0
  88. package/dist/schemas/index.js +2 -0
  89. package/dist/schemas/plugins/i18n.d.ts +59 -39
  90. package/dist/schemas/plugins/i18n.js +42 -5
  91. package/dist/schemas/plugins/index.d.ts +7126 -10296
  92. package/dist/schemas/plugins/index.js +260 -276
  93. package/dist/schemas/plugins/shared.d.ts +1293 -3718
  94. package/dist/schemas/plugins/shared.js +320 -329
  95. package/dist/test-utils.d.ts +15 -4
  96. package/dist/test-utils.js +27 -11
  97. package/dist/toolbar/db-viewer/db-shared-types.d.ts +6 -6
  98. package/dist/toolbar/db-viewer/studio/connection.d.ts +8 -4
  99. package/dist/toolbar/db-viewer/studio/connection.js +2 -28
  100. package/dist/toolbar/db-viewer/studio/env/libsql.d.ts +7 -0
  101. package/dist/toolbar/db-viewer/studio/env/libsql.js +17 -0
  102. package/dist/toolbar/db-viewer/studio/env/mysql.d.ts +7 -0
  103. package/dist/toolbar/db-viewer/studio/env/mysql.js +23 -0
  104. package/dist/toolbar/db-viewer/studio/env/postgres.d.ts +7 -0
  105. package/dist/toolbar/db-viewer/studio/env/postgres.js +23 -0
  106. package/dist/toolbar/db-viewer/studio/index.js +20 -56
  107. package/dist/toolbar/db-viewer/studio/type.d.ts +1 -2
  108. package/dist/toolbar/db-viewer/studio/virtual-connection/libsql.d.ts +3 -0
  109. package/dist/toolbar/db-viewer/studio/virtual-connection/libsql.js +24 -0
  110. package/dist/toolbar/db-viewer/studio/virtual-connection/mysql.d.ts +3 -0
  111. package/dist/toolbar/db-viewer/studio/virtual-connection/mysql.js +9 -0
  112. package/dist/toolbar/db-viewer/studio/virtual-connection/postgres.d.ts +3 -0
  113. package/dist/toolbar/db-viewer/studio/virtual-connection/postgres.js +9 -0
  114. package/dist/toolbar/db-viewer/viewer.js +20 -21
  115. package/dist/types.d.ts +30 -0
  116. package/dist/utils/effects/smtp.d.ts +1 -1
  117. package/dist/utils/lang-helper.d.ts +10 -2
  118. package/dist/virtual.d.ts +35 -28
  119. package/dist/virtuals/auth/core.d.ts +5 -5
  120. package/dist/virtuals/auth/verify-email.d.ts +6 -6
  121. package/dist/virtuals/components/Generator.astro +2 -2
  122. package/dist/virtuals/components/Renderer.astro +9 -1
  123. package/dist/virtuals/components/renderFn.d.ts +3 -1
  124. package/dist/virtuals/components/renderFn.js +18 -0
  125. package/dist/virtuals/lib/headDefaults.d.ts +4 -2
  126. package/dist/virtuals/lib/headDefaults.js +0 -2
  127. package/dist/virtuals/lib/routeMap.d.ts +0 -12
  128. package/dist/virtuals/lib/routeMap.js +2 -14
  129. package/dist/virtuals/mailer/index.d.ts +3 -3
  130. package/dist/virtuals/notifier/index.d.ts +5 -5
  131. package/dist/virtuals/plugins/dashboard-pages.d.ts +2 -64
  132. package/dist/virtuals/scripts/StorageFileBrowser.d.ts +1 -172
  133. package/dist/virtuals/scripts/StorageFileBrowser.js +216 -119
  134. package/dist/virtuals/template-engine/index.d.ts +4 -4
  135. package/frontend/components/dashboard/configuration/ConfigForm.astro +218 -110
  136. package/frontend/components/dashboard/content-mgmt/ContentSearch.astro +21 -22
  137. package/frontend/components/dashboard/content-mgmt/CreateFolder.astro +66 -54
  138. package/frontend/components/dashboard/content-mgmt/CreatePage.astro +58 -104
  139. package/frontend/components/dashboard/content-mgmt/EditFolder.astro +65 -67
  140. package/frontend/components/dashboard/content-mgmt/EditPage.astro +86 -134
  141. package/frontend/components/dashboard/content-mgmt/InnerSidebarElement.astro +0 -1
  142. package/frontend/components/dashboard/content-mgmt/PageHeader.astro +33 -52
  143. package/frontend/components/dashboard/content-mgmt/PageTypeHandler.astro +2 -2
  144. package/frontend/components/dashboard/profile/APITokens.astro +219 -158
  145. package/frontend/components/dashboard/profile/BasicInfo.astro +165 -106
  146. package/frontend/components/dashboard/profile/Notifications.astro +27 -18
  147. package/frontend/components/dashboard/profile/UpdatePassword.astro +134 -94
  148. package/frontend/components/dashboard/sidebar/VersionCheck.astro +31 -16
  149. package/frontend/components/dashboard/sidebar/VersionCheckChangelog.astro +18 -11
  150. package/frontend/components/dashboard/sidebar-modals/VersionModal.astro +2 -2
  151. package/frontend/components/dashboard/smtp-config/TemplateEditor.astro +14 -14
  152. package/frontend/components/dashboard/taxonomy/InnerSidebarElement.astro +0 -1
  153. package/frontend/components/dashboard/taxonomy/MetaContainer.astro +0 -2
  154. package/frontend/components/dashboard/taxonomy/PageHeader.astro +16 -24
  155. package/frontend/components/dashboard/taxonomy/TaxonomySearch.astro +23 -27
  156. package/frontend/components/dashboard/user-mgmt/InnerSidebarElement.astro +111 -104
  157. package/frontend/components/dashboard/user-mgmt/UserListItem.astro +9 -22
  158. package/frontend/components/dashboard/user-mgmt/UserListItems.astro +18 -0
  159. package/frontend/components/first-time-setup/snippets/{opt2-studiocms.config.diff → studiocms.config.diff} +1 -0
  160. package/frontend/components/shared/Code.astro +1 -4
  161. package/frontend/components/shared/DynamicSettingsRenderer.astro +1 -1
  162. package/frontend/components/shared/SSRUser.astro +2 -4
  163. package/frontend/components/shared/foldertree/FolderTreeNode.astro +0 -6
  164. package/frontend/components/shared/storage-manager/StorageCopyOutput.astro +0 -1
  165. package/frontend/components/shared/taxonomy/TaxonomyTreeNode.astro +0 -6
  166. package/frontend/layouts/DashboardLayout.astro +1 -10
  167. package/frontend/layouts/TaxonomyLayout.astro +0 -1
  168. package/frontend/middleware/index.ts +102 -61
  169. package/frontend/pages/404.astro +5 -9
  170. package/frontend/pages/[dashboard]/[...pluginPage].astro +10 -1
  171. package/frontend/pages/[dashboard]/configuration.astro +10 -1
  172. package/frontend/pages/[dashboard]/content-management/createfolder.astro +10 -1
  173. package/frontend/pages/[dashboard]/content-management/createpage.astro +10 -1
  174. package/frontend/pages/[dashboard]/content-management/diff.astro +39 -14
  175. package/frontend/pages/[dashboard]/content-management/editfolder.astro +10 -1
  176. package/frontend/pages/[dashboard]/content-management/editpage.astro +10 -1
  177. package/frontend/pages/[dashboard]/content-management/index.astro +10 -1
  178. package/frontend/pages/[dashboard]/index.astro +10 -1
  179. package/frontend/pages/[dashboard]/login.astro +86 -25
  180. package/frontend/pages/[dashboard]/password-reset.astro +22 -16
  181. package/frontend/pages/[dashboard]/plugins/[plugin].astro +10 -1
  182. package/frontend/pages/[dashboard]/profile.astro +10 -1
  183. package/frontend/pages/[dashboard]/signup.astro +153 -52
  184. package/frontend/pages/[dashboard]/smtp-configuration.astro +77 -75
  185. package/frontend/pages/[dashboard]/system-management.astro +10 -1
  186. package/frontend/pages/[dashboard]/taxonomy/categories.astro +30 -41
  187. package/frontend/pages/[dashboard]/taxonomy/index.astro +10 -0
  188. package/frontend/pages/[dashboard]/taxonomy/tags.astro +33 -43
  189. package/frontend/pages/[dashboard]/unverified-email.astro +29 -21
  190. package/frontend/pages/[dashboard]/user-management/edit.astro +170 -90
  191. package/frontend/pages/[dashboard]/user-management/index.astro +10 -1
  192. package/frontend/pages/studiocms_api/[...all].ts +106 -0
  193. package/frontend/pages/studiocms_api/_handlers/_utils/auth.ts +26 -0
  194. package/frontend/pages/studiocms_api/_handlers/_utils/changelog.ts +147 -0
  195. package/frontend/pages/studiocms_api/_handlers/_utils/db-studio-driver.ts +46 -0
  196. package/frontend/pages/studiocms_api/_handlers/_utils/parseLogLevel.ts +27 -0
  197. package/frontend/pages/studiocms_api/_handlers/auth/auth.ts +459 -0
  198. package/frontend/pages/studiocms_api/_handlers/auth/index.ts +17 -0
  199. package/frontend/pages/studiocms_api/_handlers/auth/oauth.ts +91 -0
  200. package/frontend/pages/studiocms_api/_handlers/dashboard/_shared.ts +57 -0
  201. package/frontend/pages/studiocms_api/_handlers/dashboard/apiTokens.ts +134 -0
  202. package/frontend/pages/studiocms_api/_handlers/dashboard/config.ts +64 -0
  203. package/frontend/pages/studiocms_api/_handlers/dashboard/content.ts +741 -0
  204. package/frontend/pages/studiocms_api/_handlers/dashboard/create.ts +480 -0
  205. package/frontend/pages/studiocms_api/_handlers/dashboard/emailNotifications.ts +49 -0
  206. package/frontend/pages/studiocms_api/_handlers/dashboard/index.ts +45 -0
  207. package/frontend/pages/studiocms_api/_handlers/dashboard/mailer.ts +136 -0
  208. package/frontend/pages/studiocms_api/_handlers/dashboard/plugins.ts +80 -0
  209. package/frontend/pages/studiocms_api/_handlers/dashboard/profile.ts +275 -0
  210. package/frontend/pages/studiocms_api/_handlers/dashboard/resetPassword.ts +140 -0
  211. package/frontend/pages/studiocms_api/_handlers/dashboard/search.ts +63 -0
  212. package/frontend/pages/studiocms_api/_handlers/dashboard/taxonomy.ts +285 -0
  213. package/frontend/pages/studiocms_api/_handlers/dashboard/templates.ts +75 -0
  214. package/frontend/pages/studiocms_api/_handlers/dashboard/users.ts +312 -0
  215. package/frontend/pages/studiocms_api/_handlers/dashboard/verifyEndpoints.ts +307 -0
  216. package/frontend/pages/studiocms_api/_handlers/integration/dbStudio.ts +98 -0
  217. package/frontend/pages/studiocms_api/_handlers/integration/index.ts +17 -0
  218. package/frontend/pages/studiocms_api/_handlers/integration/storageManager.ts +107 -0
  219. package/frontend/pages/studiocms_api/_handlers/rest-api/index.ts +8 -0
  220. package/frontend/pages/studiocms_api/_handlers/rest-api/v1/_shared.ts +41 -0
  221. package/frontend/pages/studiocms_api/_handlers/rest-api/v1/index.ts +17 -0
  222. package/frontend/pages/studiocms_api/_handlers/rest-api/v1/public.ts +195 -0
  223. package/frontend/pages/studiocms_api/_handlers/rest-api/v1/secure.ts +1726 -0
  224. package/frontend/pages/studiocms_api/_handlers/sdk.ts +129 -0
  225. package/frontend/pages/studiocms_api/_middleware/astroLocals.ts +36 -0
  226. package/frontend/pages/studiocms_api/_middleware/restApi.ts +56 -0
  227. package/frontend/pages/studiocms_api/integrations/[...all].ts +8 -0
  228. package/frontend/scripts/formdata.ts +219 -0
  229. package/frontend/setup-pages/3-done.astro +4 -20
  230. package/frontend/setup-pages/studiocms_api/dashboard/step-2.ts +3 -6
  231. package/frontend/styles/dashboard-base.css +0 -1
  232. package/frontend/web-vitals/endpoint.ts +2 -1
  233. package/package.json +35 -31
  234. package/dist/global.d.ts +0 -9
  235. package/frontend/components/dashboard/LoginChecker.astro +0 -68
  236. package/frontend/components/dashboard/user-mgmt/RankCheck.astro +0 -57
  237. package/frontend/components/first-time-setup/snippets/opt1-astro.config.diff +0 -14
  238. package/frontend/components/first-time-setup/snippets/opt2-astro.config.diff +0 -9
  239. package/frontend/middleware/_authmap.ts +0 -63
  240. package/frontend/pages/studiocms_api/auth/[path].ts +0 -390
  241. package/frontend/pages/studiocms_api/auth/[provider]/[...id].ts +0 -64
  242. package/frontend/pages/studiocms_api/auth/[provider]/_effects/index.ts +0 -101
  243. package/frontend/pages/studiocms_api/auth/_shared.ts +0 -52
  244. package/frontend/pages/studiocms_api/dashboard/api-tokens.ts +0 -115
  245. package/frontend/pages/studiocms_api/dashboard/config.ts +0 -74
  246. package/frontend/pages/studiocms_api/dashboard/content/diff.ts +0 -73
  247. package/frontend/pages/studiocms_api/dashboard/content/folder.ts +0 -220
  248. package/frontend/pages/studiocms_api/dashboard/content/page.ts +0 -359
  249. package/frontend/pages/studiocms_api/dashboard/create-reset-link.ts +0 -77
  250. package/frontend/pages/studiocms_api/dashboard/create-user-invite.ts +0 -231
  251. package/frontend/pages/studiocms_api/dashboard/create-user.ts +0 -186
  252. package/frontend/pages/studiocms_api/dashboard/email-notification-settings-site.ts +0 -74
  253. package/frontend/pages/studiocms_api/dashboard/mailer/check-email.ts +0 -75
  254. package/frontend/pages/studiocms_api/dashboard/mailer/config.ts +0 -136
  255. package/frontend/pages/studiocms_api/dashboard/plugins/[plugin].ts +0 -80
  256. package/frontend/pages/studiocms_api/dashboard/profile.ts +0 -245
  257. package/frontend/pages/studiocms_api/dashboard/resend-verify-email.ts +0 -77
  258. package/frontend/pages/studiocms_api/dashboard/reset-password.ts +0 -124
  259. package/frontend/pages/studiocms_api/dashboard/search-list.ts +0 -59
  260. package/frontend/pages/studiocms_api/dashboard/taxonomy-search.ts +0 -47
  261. package/frontend/pages/studiocms_api/dashboard/taxonomy.ts +0 -230
  262. package/frontend/pages/studiocms_api/dashboard/templates.ts +0 -74
  263. package/frontend/pages/studiocms_api/dashboard/update-user-notifications.ts +0 -86
  264. package/frontend/pages/studiocms_api/dashboard/users.ts +0 -236
  265. package/frontend/pages/studiocms_api/dashboard/verify-email.ts +0 -83
  266. package/frontend/pages/studiocms_api/dashboard/verify-session.ts +0 -187
  267. package/frontend/pages/studiocms_api/integrations/[type]/[...id].ts +0 -15
  268. package/frontend/pages/studiocms_api/integrations/[type]/_routes/db-studio.ts +0 -173
  269. package/frontend/pages/studiocms_api/integrations/[type]/_routes/storage.ts +0 -88
  270. package/frontend/pages/studiocms_api/partials/editor.astro +0 -74
  271. package/frontend/pages/studiocms_api/partials/render.astro +0 -39
  272. package/frontend/pages/studiocms_api/partials/user-list-items.astro +0 -43
  273. package/frontend/pages/studiocms_api/rest/utils/auth-token.ts +0 -59
  274. package/frontend/pages/studiocms_api/rest/v1/[type]/[...id].ts +0 -23
  275. package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/categories.ts +0 -267
  276. package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/folders.ts +0 -283
  277. package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/pages.ts +0 -505
  278. package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/settings.ts +0 -100
  279. package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/tags.ts +0 -229
  280. package/frontend/pages/studiocms_api/rest/v1/[type]/_routes/users.ts +0 -553
  281. package/frontend/pages/studiocms_api/rest/v1/public/[type]/[...id].ts +0 -19
  282. package/frontend/pages/studiocms_api/rest/v1/public/[type]/_routes/categories.ts +0 -74
  283. package/frontend/pages/studiocms_api/rest/v1/public/[type]/_routes/folders.ts +0 -85
  284. package/frontend/pages/studiocms_api/rest/v1/public/[type]/_routes/pages.ts +0 -103
  285. package/frontend/pages/studiocms_api/rest/v1/public/[type]/_routes/tags.ts +0 -67
  286. package/frontend/pages/studiocms_api/sdk/[...path].ts +0 -97
  287. package/frontend/pages/studiocms_api/sdk/utils/changelog.ts +0 -119
  288. package/frontend/scripts/auth/formListener.ts +0 -81
  289. package/frontend/scripts/formdata-utils.ts +0 -116
  290. package/frontend/utils/build-partial-schema.ts +0 -46
  291. package/frontend/utils/errors.ts +0 -6
  292. package/frontend/utils/param-extractor.ts +0 -83
  293. package/frontend/utils/rest-router.ts +0 -444
@@ -0,0 +1,46 @@
1
+ import { Data, Effect } from 'effect';
2
+ import { type BaseDriver, getDriver } from '#toolbar/db-studio';
3
+
4
+ /**
5
+ * Custom error class used for quick escaping from deep error handling in the Effect chain.
6
+ */
7
+ export class DriverError extends Data.TaggedError('DriverError')<{ message: string }> {}
8
+
9
+ // Singleton instance of the database driver
10
+ let driver: BaseDriver | undefined;
11
+
12
+ /**
13
+ * Utility function to wrap driver-related promises and convert any errors into DriverError instances for consistent error handling in the Effect chain.
14
+ *
15
+ * @param _try A function that returns a promise for a driver-related operation.
16
+ * @returns An Effect that resolves with the result of the promise or fails with a DriverError if the promise rejects.
17
+ */
18
+ export const useDriverErrorPromise = <T>(_try: () => Promise<T>) =>
19
+ Effect.tryPromise({
20
+ try: _try,
21
+ catch: (error) =>
22
+ new DriverError({ message: error instanceof Error ? error.message : String(error) }),
23
+ });
24
+
25
+ /**
26
+ * Retrieves the singleton instance of the database driver, initializing it if it hasn't been created yet. If any step in the retrieval or initialization process fails, it returns a DriverError with details about the failure.
27
+ *
28
+ * @returns An Effect that resolves with the initialized database driver or fails with a DriverError if the driver cannot be retrieved or initialized.
29
+ */
30
+ export const getDriverInstance = Effect.fn('getDriverInstance')(function* () {
31
+ // Return existing driver if already initialized
32
+ if (driver) return driver;
33
+
34
+ // Attempt to get and initialize the driver
35
+ driver = yield* useDriverErrorPromise(() => getDriver()).pipe(
36
+ Effect.tap((drv) => useDriverErrorPromise(() => drv.init()))
37
+ );
38
+
39
+ // If driver is still undefined, return an error
40
+ if (!driver) {
41
+ return yield* new DriverError({ message: 'Failed to get database driver' });
42
+ }
43
+
44
+ // Return the initialized driver
45
+ return driver;
46
+ });
@@ -0,0 +1,27 @@
1
+ import type { LoggerLevel } from '@withstudiocms/effect';
2
+
3
+ /**
4
+ * Utility function to parse a log level string from the configuration and convert it into a LoggerLevel type that can be used by the CMSLogger. If the input log level is not recognized, it defaults to 'silent' to avoid logging.
5
+ *
6
+ * @param level The log level string from the configuration, which can be one of 'All', 'Fatal', 'Error', 'Warning', 'Info', 'Debug', 'Trace', or 'None'.
7
+ * @returns The corresponding LoggerLevel type that can be used by the CMSLogger.
8
+ */
9
+ export const parseLogLevel = (
10
+ level: 'All' | 'Fatal' | 'Error' | 'Warning' | 'Info' | 'Debug' | 'Trace' | 'None'
11
+ ): LoggerLevel => {
12
+ switch (level) {
13
+ case 'Info':
14
+ return 'info';
15
+ case 'Warning':
16
+ return 'warn';
17
+ case 'Error':
18
+ return 'error';
19
+ case 'All':
20
+ case 'Fatal':
21
+ case 'Debug':
22
+ case 'Trace':
23
+ return 'debug';
24
+ case 'None':
25
+ return 'silent';
26
+ }
27
+ };
@@ -0,0 +1,459 @@
1
+ import { site } from 'astro:config/server';
2
+ import { Password, Session, User, VerifyEmail } from 'studiocms:auth/lib';
3
+ import { authConfig, developerConfig } from 'studiocms:config';
4
+ import { StudioCMSRoutes } from 'studiocms:lib';
5
+ import { Mailer } from 'studiocms:mailer';
6
+ import { Notifications } from 'studiocms:notifier';
7
+ import { SDKCore } from 'studiocms:sdk';
8
+ import templateEngine from 'studiocms:template-engine';
9
+ import routeConfig from 'virtual:studiocms/route-config';
10
+ import { HttpApiBuilder, HttpServerResponse } from '@effect/platform';
11
+ import { NotFound } from '@effect/platform/HttpApiError';
12
+ import { StudioCMSAuthApi } from '@withstudiocms/api-spec';
13
+ import { AuthAPIError } from '@withstudiocms/api-spec/auth';
14
+ import { appendSearchParamsToUrl } from '@withstudiocms/effect';
15
+ import { Effect, Layer, pipe } from 'effect';
16
+ import { AstroAPIContext } from 'effectify/astro/context';
17
+ import { AuthSessionCookieName } from '#consts';
18
+ import { AuthAPIUtils } from '../_utils/auth.js';
19
+
20
+ const loginRegisterDependencies = Layer.mergeAll(AuthAPIUtils.Default, VerifyEmail.Default);
21
+ const forgotPasswordDependencies = Layer.mergeAll(
22
+ Mailer.Default,
23
+ Notifications.Default,
24
+ AuthAPIUtils.Default
25
+ );
26
+
27
+ const authEnabled = routeConfig.dashboardEnabled;
28
+
29
+ const usernameAndPasswordRoutesEnabled =
30
+ authConfig.enabled && authConfig.providers.usernameAndPassword;
31
+ const userRegistrationEnabled =
32
+ authConfig.enabled && authConfig.providers.usernameAndPasswordConfig.allowUserRegistration;
33
+
34
+ const sharedCatchTags = {
35
+ DBClientInitializationError: () =>
36
+ new AuthAPIError({ error: 'Database client initialization failed' }),
37
+ SDKInitializationError: () => new AuthAPIError({ error: 'SDK initialization failed' }),
38
+ };
39
+
40
+ /**
41
+ * Generates a password reset link using the provided token and context.
42
+ * The link is constructed using the main links from the route map and appending
43
+ * the user ID, token, and token ID as search parameters.
44
+ *
45
+ * @param token - An object containing the token details (id, userId, token).
46
+ * @param context - The API context containing the route map for generating the link.
47
+ * @returns A URL object representing the password reset link.
48
+ */
49
+ function generateResetLink(token: { id: string; userId: string; token: string }) {
50
+ return pipe(
51
+ new URL(StudioCMSRoutes.mainLinks.passwordReset, site),
52
+ appendSearchParamsToUrl('userid', token.userId),
53
+ appendSearchParamsToUrl('token', token.token),
54
+ appendSearchParamsToUrl('id', token.id)
55
+ );
56
+ }
57
+
58
+ /**
59
+ * Effect Layer providing the authentication API handlers, including login, logout, and forgot password functionality.
60
+ */
61
+ export const AuthAPIHandler = HttpApiBuilder.group(StudioCMSAuthApi, 'auth', (handlers) =>
62
+ handlers
63
+ .handle(
64
+ 'forgotPassword',
65
+ Effect.fn(
66
+ function* ({ payload: { email } }) {
67
+ // If auth is not enabled, return a 404 to avoid exposing the existence of the endpoint
68
+ // If username and password routes are not enabled, return a 404 to avoid exposing the existence of the endpoint
69
+ if (!authEnabled || !usernameAndPasswordRoutesEnabled) {
70
+ return yield* new NotFound();
71
+ }
72
+
73
+ // Get the necessary dependencies for the forgot password handler and run them in parallel
74
+ const [sdk, { sendMail }, { sendAdminNotification }, { validateEmail }, ctx] =
75
+ yield* Effect.all([SDKCore, Mailer, Notifications, AuthAPIUtils, AstroAPIContext]);
76
+
77
+ // If demo mode is enabled, return an error as this action is not allowed in demo mode to prevent abuse of the forgot password functionality which could lead to spamming users with password reset emails.
78
+ if (developerConfig.demoMode !== false) {
79
+ return yield* new AuthAPIError({
80
+ error: 'Demo mode is enabled, this action is not allowed.',
81
+ });
82
+ }
83
+
84
+ // Check if the mailer is enabled
85
+ const config = ctx.locals.StudioCMS.siteConfig.data;
86
+
87
+ // If the mailer is not enabled, return an error as we cannot send the password reset email without a mailer configured.
88
+ if (!config.enableMailer) {
89
+ return yield* new AuthAPIError({
90
+ error: 'Mailer is not enabled in the site configuration.',
91
+ });
92
+ }
93
+
94
+ // If the email is invalid, return an error
95
+ const checkEmail = yield* validateEmail(email).pipe(
96
+ Effect.catchAll(
97
+ (err) => new AuthAPIError({ error: `Email validation failed: ${String(err)}` })
98
+ )
99
+ );
100
+
101
+ // If the email provided is not a valid email address, return an error. We do this to prevent abuse of the forgot password functionality which could lead to spamming users with password reset emails.
102
+ if (!checkEmail.success) {
103
+ return yield* new AuthAPIError({
104
+ error: checkEmail.error.message,
105
+ });
106
+ }
107
+
108
+ // Search for the user by email
109
+ const { emailSearch } = yield* sdk.AUTH.user
110
+ .searchUsersForUsernameOrEmail('', checkEmail.data)
111
+ .pipe(Effect.catchAll(() => new AuthAPIError({ error: 'Unknown Server Error' })));
112
+
113
+ // If no user is found with the provided email, return a success message to prevent exposing the existence of the email in the system. This is a common practice to prevent user enumeration attacks.
114
+ if (emailSearch.length === 0) {
115
+ return {
116
+ message: `If an account with the email ${email} exists, a password reset email has been sent.`,
117
+ };
118
+ }
119
+
120
+ // Get the first user from the search results. There should only be one user with a given email address as we enforce unique email addresses in the system.
121
+ const user = emailSearch[0];
122
+
123
+ // Create a new password reset token for the user
124
+ const token = yield* sdk.resetTokenBucket
125
+ .new(user.id)
126
+ .pipe(
127
+ Effect.catchAll(
128
+ (err) => new AuthAPIError({ error: `Token creation failed: ${err.toString()}` })
129
+ )
130
+ );
131
+
132
+ // If token creation failed, return an error
133
+ if (!token) {
134
+ return yield* new AuthAPIError({
135
+ error: 'Failed to create password reset token.',
136
+ });
137
+ }
138
+
139
+ // Send an admin notification that the user has been updated
140
+ yield* sendAdminNotification('user_updated', user.username).pipe(
141
+ Effect.catchAll(
142
+ (err) =>
143
+ new AuthAPIError({ error: `Failed to send admin notification: ${err.toString()}` })
144
+ )
145
+ );
146
+
147
+ // Generate the reset link using the token and context
148
+ const resetLink = generateResetLink(token);
149
+
150
+ // If the user does not have an email address, return an error
151
+ // This should not happen, but we check it just in case
152
+ // as the user may have been created without an email address
153
+ // or the email address may have been removed
154
+ // after the user was created.
155
+ if (!user.email) {
156
+ return yield* new AuthAPIError({
157
+ error: 'User does not have an email address.',
158
+ });
159
+ }
160
+
161
+ // Get the HTML template for the password reset email
162
+ const engine = yield* templateEngine;
163
+ const { title: siteTitle, description, siteIcon } = config;
164
+
165
+ // Render the password reset email template with the reset link and site information
166
+ const passwordResetTemplate = yield* engine.render('passwordReset', {
167
+ site: { title: siteTitle, description, icon: siteIcon ?? undefined },
168
+ data: { link: resetLink.toString() },
169
+ });
170
+
171
+ // Send the password reset email to the user
172
+ const mailRes = yield* sendMail({
173
+ to: user.email,
174
+ subject: 'Password Reset',
175
+ html: passwordResetTemplate,
176
+ }).pipe(
177
+ Effect.catchAll(
178
+ (err) =>
179
+ new AuthAPIError({
180
+ error: `Failed to send password reset email: ${err.toString()}`,
181
+ })
182
+ )
183
+ );
184
+
185
+ // If sending the email failed, return an error
186
+ if (!mailRes) {
187
+ return yield* new AuthAPIError({
188
+ error: 'Failed to send password reset email.',
189
+ });
190
+ }
191
+
192
+ // If the mailer returned an error, return an error
193
+ if ('error' in mailRes) {
194
+ return yield* new AuthAPIError({
195
+ error: `Failed to send password reset email: ${mailRes.error}`,
196
+ });
197
+ }
198
+
199
+ // Return a success message indicating that the password reset email has been sent. We do this regardless of whether the email was actually sent or not to prevent abuse of the forgot password functionality which could lead to spamming users with password reset emails.
200
+ return {
201
+ message: `If an account with the email ${email} exists, a password reset email has been sent.`,
202
+ };
203
+ },
204
+ // Provide the necessary dependencies for the forgot password handler
205
+ Effect.provide(forgotPasswordDependencies),
206
+ // Catch any errors that occur during the forgot password process and return a generic error message to prevent exposing sensitive information about the failure.
207
+ Effect.catchTags({
208
+ ...sharedCatchTags,
209
+ ConfigError: () => new AuthAPIError({ error: 'Site configuration is invalid' }),
210
+ DBCallbackFailure: () => new AuthAPIError({ error: 'Database callback failed' }),
211
+ QueryParseError: () => new AuthAPIError({ error: 'Database query failed' }),
212
+ QueryError: () => new AuthAPIError({ error: 'Database query failed' }),
213
+ NotFoundError: () => new AuthAPIError({ error: 'User not found' }),
214
+ UnknownException: () => new AuthAPIError({ error: 'An unknown error occurred' }),
215
+ SMTPError: () => new AuthAPIError({ error: 'Failed to send email due to SMTP error' }),
216
+ TemplateEngineError: () => new AuthAPIError({ error: 'Failed to render email template' }),
217
+ })
218
+ )
219
+ )
220
+ .handle(
221
+ 'login',
222
+ Effect.fn(
223
+ function* ({ payload: { username, password } }) {
224
+ // If auth is not enabled, return a 404 to avoid exposing the existence of the endpoint
225
+ // If username and password routes are not enabled, return a 404 to avoid exposing the existence of the endpoint
226
+ if (!authEnabled || !usernameAndPasswordRoutesEnabled) {
227
+ return yield* new NotFound();
228
+ }
229
+
230
+ yield* Effect.log(`Login attempt for username: ${username}`); // Log the login attempt with the provided username
231
+
232
+ // Get the necessary dependencies for the login handler and run them in parallel
233
+ const [sdk, { verifyPasswordHash }, { createUserSession }, { isEmailVerified }, ctx] =
234
+ yield* Effect.all([
235
+ SDKCore,
236
+ Password,
237
+ Session.pipe(
238
+ Effect.catchAll(
239
+ () => new AuthAPIError({ error: 'Session management initialization failed' })
240
+ )
241
+ ),
242
+ VerifyEmail,
243
+ AstroAPIContext,
244
+ ]);
245
+
246
+ // Get the user by username. We need to get the user first before we can verify the password to prevent timing attacks that could be used to enumerate valid usernames in the system.
247
+ const existingUser = yield* sdk.GET.users
248
+ .byUsername(username)
249
+ .pipe(
250
+ Effect.catchAll(() => new AuthAPIError({ error: 'Invalid username or password' }))
251
+ );
252
+
253
+ // If no user is found with the provided username, return an error
254
+ if (!existingUser) {
255
+ return yield* new AuthAPIError({ error: 'Invalid username or password' });
256
+ }
257
+
258
+ // If the user is found but does not have a password hash, return an error. This could happen if the user was created with OAuth and does not have a local password.
259
+ if (!existingUser.password) {
260
+ return yield* new AuthAPIError({ error: 'Invalid username or password' });
261
+ }
262
+
263
+ // Verify the provided password against the stored password hash
264
+ const validPassword = yield* verifyPasswordHash(existingUser.password, password);
265
+
266
+ // If the password is invalid, return an error
267
+ if (!validPassword) {
268
+ return yield* new AuthAPIError({ error: 'Invalid username or password' });
269
+ }
270
+
271
+ // If the user's email is not verified, return an error. We require email verification for users with local credentials to ensure that they have access to the email address associated with their account, which is important for account recovery and security notifications.
272
+ const emailVerified = yield* isEmailVerified(existingUser);
273
+
274
+ // If the email is not verified, return an error prompting the user to verify their email before logging in. We do this to prevent users from logging in with unverified email addresses, which could lead to security issues and account recovery problems.
275
+ if (!emailVerified) {
276
+ return yield* new AuthAPIError({
277
+ error: 'Email address is not verified. Please verify your email before logging in.',
278
+ });
279
+ }
280
+
281
+ // Create a new session for the user
282
+ yield* createUserSession(existingUser.id, ctx);
283
+
284
+ yield* Effect.log(`User ${existingUser.username} logged in successfully.`);
285
+
286
+ // Return a success message indicating that the login was successful. The frontend can then handle the response and redirect the user to the appropriate page.
287
+ return {
288
+ ok: true,
289
+ };
290
+ },
291
+ // Provide the necessary dependencies for the login handler
292
+ Effect.provide(loginRegisterDependencies),
293
+ // Catch any errors that occur during the login process and return a generic error message to prevent exposing sensitive information about the failure.
294
+ Effect.catchTags(sharedCatchTags)
295
+ )
296
+ )
297
+ .handle(
298
+ 'logout',
299
+ Effect.fn(function* () {
300
+ // If auth is not enabled, return a 404 to avoid exposing the existence of the endpoint
301
+ if (!authEnabled) {
302
+ return yield* new NotFound();
303
+ }
304
+
305
+ // Get the necessary dependencies for the logout handler and run them in parallel
306
+ const [{ validateSessionToken, deleteSessionTokenCookie, invalidateSession }, ctx] =
307
+ yield* Effect.all([
308
+ Session.pipe(
309
+ Effect.catchAll(
310
+ () => new AuthAPIError({ error: 'Session management initialization failed' })
311
+ )
312
+ ),
313
+ AstroAPIContext,
314
+ ]);
315
+
316
+ // Get the cookies from the context to access the session token cookie
317
+ const { cookies } = ctx;
318
+
319
+ // Get the session token from the cookies, if it exists
320
+ const sessionToken = cookies.get(AuthSessionCookieName)?.value ?? null;
321
+
322
+ // If no session token is found, clear the session cookie just in case and redirect to the login page. We do this to ensure that if there is an invalid or expired session token cookie, we clear it and prompt the user to log in again rather than leaving them with a non-functional session cookie.
323
+ if (!sessionToken) {
324
+ yield* deleteSessionTokenCookie(ctx).pipe(
325
+ Effect.catchAll(() => new AuthAPIError({ error: 'Failed to clear session cookie' }))
326
+ );
327
+ return HttpServerResponse.redirect(StudioCMSRoutes.authLinks.loginURL); // Redirect to login if no session token is found
328
+ }
329
+
330
+ // Validate the session token to get the associated session and user data. If the token is invalid or expired, clear the session cookie and redirect to the login page. If the token is valid, invalidate the session and clear the session cookie to log the user out, then redirect to the main site URL.
331
+ const { session, user } = yield* validateSessionToken(sessionToken).pipe(
332
+ Effect.catchAll(() => new AuthAPIError({ error: 'Invalid session token' }))
333
+ );
334
+
335
+ // If the session token is invalid or expired, clear the session cookie and redirect to the login page. We do this to ensure that if there is an invalid or expired session token cookie, we clear it and prompt the user to log in again rather than leaving them with a non-functional session cookie.
336
+ if (!session || !user) {
337
+ yield* deleteSessionTokenCookie(ctx).pipe(
338
+ Effect.catchAll(() => new AuthAPIError({ error: 'Failed to clear session cookie' }))
339
+ );
340
+ return HttpServerResponse.redirect(StudioCMSRoutes.authLinks.loginURL);
341
+ }
342
+
343
+ // Invalidate the session and delete the session token cookie
344
+ yield* Effect.all([invalidateSession(session.id), deleteSessionTokenCookie(ctx)]).pipe(
345
+ Effect.catchAll(() => new AuthAPIError({ error: 'Failed to log out' }))
346
+ );
347
+
348
+ // Redirect to the main site URL after successful logout
349
+ return HttpServerResponse.redirect(StudioCMSRoutes.mainLinks.baseSiteURL);
350
+ })
351
+ )
352
+ .handle(
353
+ 'register',
354
+ Effect.fn(
355
+ function* ({ payload: { username, password, email, displayname } }) {
356
+ // If auth is not enabled, return a 404 to avoid exposing the existence of the endpoint
357
+ if (!authEnabled || !usernameAndPasswordRoutesEnabled || !userRegistrationEnabled) {
358
+ return yield* new NotFound();
359
+ }
360
+
361
+ // Get the necessary dependencies for the register handler and run them in parallel
362
+ const [
363
+ sdk,
364
+ { validateEmail },
365
+ { verifyUsernameInput, createLocalUser },
366
+ { sendVerificationEmail },
367
+ { verifyPasswordStrength },
368
+ { createUserSession },
369
+ ctx,
370
+ ] = yield* Effect.all([
371
+ SDKCore,
372
+ AuthAPIUtils,
373
+ User.pipe(
374
+ Effect.catchAll(
375
+ () => new AuthAPIError({ error: 'User management initialization failed' })
376
+ )
377
+ ),
378
+ VerifyEmail,
379
+ Password,
380
+ Session.pipe(
381
+ Effect.catchAll(
382
+ () => new AuthAPIError({ error: 'Session management initialization failed' })
383
+ )
384
+ ),
385
+ AstroAPIContext,
386
+ ]);
387
+
388
+ const [verifyUsernameResponse, verifyPasswordResponse, checkEmail] = yield* Effect.all([
389
+ verifyUsernameInput(username),
390
+ verifyPasswordStrength(password),
391
+ validateEmail(email),
392
+ ]).pipe(
393
+ Effect.catchAll((err) => {
394
+ console.error('Validation error:', err);
395
+ return new AuthAPIError({ error: `Validation failed: ${String(err)}` });
396
+ })
397
+ );
398
+
399
+ // If the username validation failed, return an error with the validation message
400
+ if (verifyUsernameResponse !== true) {
401
+ return yield* new AuthAPIError({
402
+ error: `Invalid username: ${verifyUsernameResponse}`,
403
+ });
404
+ }
405
+
406
+ // If the password validation failed, return an error with the validation message
407
+ if (verifyPasswordResponse !== true) {
408
+ return yield* new AuthAPIError({
409
+ error: `Invalid password: ${verifyPasswordResponse}`,
410
+ });
411
+ }
412
+
413
+ // If the email provided is not a valid email address, return an error. We do this to prevent abuse of the forgot password functionality which could lead to spamming users with password reset emails.
414
+ if (!checkEmail.success) {
415
+ return yield* new AuthAPIError({
416
+ error: checkEmail.error.message,
417
+ });
418
+ }
419
+
420
+ // Check if the username or email already exists in the system. We do this after validating the input to prevent unnecessary database queries for invalid input.
421
+ const { usernameSearch, emailSearch } = yield* sdk.AUTH.user
422
+ .searchUsersForUsernameOrEmail(username, checkEmail.data)
423
+ .pipe(Effect.catchAll(() => new AuthAPIError({ error: 'Unknown Server Error' })));
424
+
425
+ // If either the username or email already exists, return an error indicating that an account with the provided username or email already exists. We do this to prevent duplicate accounts and to enforce unique usernames and email addresses in the system.
426
+ if (usernameSearch.length > 0 || emailSearch.length > 0) {
427
+ return yield* new AuthAPIError({
428
+ error: 'An account with this username or email already exists.',
429
+ });
430
+ }
431
+
432
+ // Create the new user, send the verification email, and create a session for the user in parallel. We do this to optimize the registration process and reduce the time it takes for the user to be able to use their account after registering.
433
+ yield* createLocalUser(displayname, username, email, password).pipe(
434
+ Effect.flatMap((newUser) =>
435
+ Effect.all([
436
+ sendVerificationEmail(newUser.id).pipe(
437
+ Effect.catchAll(
438
+ (err) => new AuthAPIError({ error: `User creation failed: ${String(err)}` })
439
+ )
440
+ ),
441
+ createUserSession(newUser.id, ctx).pipe(
442
+ Effect.catchAll(
443
+ (err) => new AuthAPIError({ error: `User creation failed: ${String(err)}` })
444
+ )
445
+ ),
446
+ ])
447
+ )
448
+ );
449
+
450
+ // Return a success message indicating that the registration was successful. The frontend can then handle the response and redirect the user to the appropriate page, such as a page prompting them to verify their email address.
451
+ return { ok: true };
452
+ },
453
+ // Provide the necessary dependencies for the login handler
454
+ Effect.provide(loginRegisterDependencies),
455
+ // Catch any errors that occur during the login process and return a generic error message to prevent exposing sensitive information about the failure.
456
+ Effect.catchTags(sharedCatchTags)
457
+ )
458
+ )
459
+ );
@@ -0,0 +1,17 @@
1
+ import { HttpApiBuilder } from '@effect/platform';
2
+ import { StudioCMSAuthApi } from '@withstudiocms/api-spec';
3
+ import { Layer } from 'effect';
4
+ import { AuthAPIHandler } from './auth.js';
5
+ import { OAuthAPIHandler } from './oauth.js';
6
+
7
+ /**
8
+ * Combined API Handler for all authentication-related endpoints, including both primary auth handlers and OAuth handlers.
9
+ */
10
+ const AuthApiHandlersGroup = Layer.merge(AuthAPIHandler, OAuthAPIHandler);
11
+
12
+ /**
13
+ * Live implementation of the authentication API, providing handlers for login, logout, and forgot password functionality.
14
+ */
15
+ export const AuthAPILive = HttpApiBuilder.api(StudioCMSAuthApi).pipe(
16
+ Layer.provide(AuthApiHandlersGroup)
17
+ );
@@ -0,0 +1,91 @@
1
+ import { oAuthProviders } from 'studiocms:plugins/auth/providers';
2
+ import routeConfig from 'virtual:studiocms/route-config';
3
+ import { HttpApiBuilder } from '@effect/platform';
4
+ import { NotFound } from '@effect/platform/HttpApiError';
5
+ import { StudioCMSAuthApi } from '@withstudiocms/api-spec';
6
+ import { AuthAPIError } from '@withstudiocms/api-spec/auth';
7
+ import { Effect } from 'effect';
8
+ import { AstroAPIContext } from 'effectify/astro/context';
9
+ import { ResponseToHttpServerResponse } from 'effectify/webHandler';
10
+
11
+ const authEnabled = routeConfig.dashboardEnabled;
12
+ const oAuthEnabled = routeConfig.oAuthEnabled;
13
+
14
+ /**
15
+ * OAuth API Handler for managing OAuth authentication flows, including initiating OAuth sessions and handling OAuth callbacks.
16
+ */
17
+ export const OAuthAPIHandler = HttpApiBuilder.group(StudioCMSAuthApi, 'oauth', (handlers) =>
18
+ handlers
19
+ .handle(
20
+ 'oAuthInit',
21
+ Effect.fn(function* ({ path: { provider } }) {
22
+ // If auth or oAuth is not enabled, return a 404 to avoid exposing the existence of the endpoint
23
+ if (!authEnabled || !oAuthEnabled) {
24
+ return yield* new NotFound();
25
+ }
26
+
27
+ // Find the provider configuration based on the provider name in the path parameters.
28
+ const matchedProvider = oAuthProviders.find((p) => p.safeName === provider);
29
+
30
+ // If the provider is not found, not enabled, or does not have an initSession handler, return a 404 to avoid exposing the existence of the endpoint or the provider.
31
+ if (!matchedProvider || !matchedProvider.enabled || !matchedProvider.initSession) {
32
+ return yield* new NotFound();
33
+ }
34
+
35
+ // Get the API context to pass to the provider handler, which may need it to access request information, cookies, etc.
36
+ const ctx = yield* AstroAPIContext;
37
+
38
+ // Call the provider's initSession handler to start the OAuth flow. This will typically redirect the user to the provider's authentication page. We wrap this in a try-catch to handle any errors that may occur during the provider handler execution and return a generic error message to prevent exposing sensitive information about the failure.
39
+ const res = yield* Effect.tryPromise(async () => matchedProvider.initSession?.(ctx)).pipe(
40
+ Effect.catchAll((error) => {
41
+ console.error('API Error:', error);
42
+ return new AuthAPIError({ error: 'Internal Server Error' });
43
+ })
44
+ );
45
+
46
+ // If the provider handler did not return a response, return an error indicating that the OAuth session initialization failed.
47
+ if (!res) {
48
+ return yield* new AuthAPIError({ error: 'Failed to initialize OAuth session' });
49
+ }
50
+
51
+ // Convert the provider handler's response to an HTTP server response that can be returned to the client. This will typically be a redirect response to the provider's authentication page.
52
+ return yield* ResponseToHttpServerResponse(res);
53
+ })
54
+ )
55
+ .handle(
56
+ 'oAuthCallback',
57
+ Effect.fn(function* ({ path: { provider } }) {
58
+ // If auth or oAuth is not enabled, return a 404 to avoid exposing the existence of the endpoint
59
+ if (!authEnabled || !oAuthEnabled) {
60
+ return yield* new NotFound();
61
+ }
62
+
63
+ // Find the provider configuration based on the provider name in the path parameters.
64
+ const matchedProvider = oAuthProviders.find((p) => p.safeName === provider);
65
+
66
+ // If the provider is not found, not enabled, or does not have an initCallback handler, return a 404 to avoid exposing the existence of the endpoint or the provider.
67
+ if (!matchedProvider || !matchedProvider.enabled || !matchedProvider.initCallback) {
68
+ return yield* new NotFound();
69
+ }
70
+
71
+ // Get the API context to pass to the provider handler, which may need it to access request information, cookies, etc.
72
+ const ctx = yield* AstroAPIContext;
73
+
74
+ // Call the provider's initCallback handler to complete the OAuth flow. This will typically handle the callback from the provider after the user has authenticated and create a session for the user in the system. We wrap this in a try-catch to handle any errors that may occur during the provider handler execution and return a generic error message to prevent exposing sensitive information about the failure.
75
+ const res = yield* Effect.tryPromise(async () => matchedProvider.initCallback?.(ctx)).pipe(
76
+ Effect.catchAll((error) => {
77
+ console.error('API Error:', error);
78
+ return new AuthAPIError({ error: 'Internal Server Error' });
79
+ })
80
+ );
81
+
82
+ // If the provider handler did not return a response, return an error indicating that the OAuth callback initialization failed.
83
+ if (!res) {
84
+ return yield* new AuthAPIError({ error: 'Failed to initialize OAuth callback' });
85
+ }
86
+
87
+ // Convert the provider handler's response to an HTTP server response that can be returned to the client. This will typically be a redirect response to the main site or dashboard after successful authentication.
88
+ return yield* ResponseToHttpServerResponse(res);
89
+ })
90
+ )
91
+ );