alepha 0.19.3 → 0.19.5

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 (289) hide show
  1. package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
  2. package/dist/api/invitations/index.d.ts +790 -0
  3. package/dist/api/invitations/index.d.ts.map +1 -0
  4. package/dist/api/invitations/index.js +665 -0
  5. package/dist/api/invitations/index.js.map +1 -0
  6. package/dist/api/issues/index.d.ts +810 -0
  7. package/dist/api/issues/index.d.ts.map +1 -0
  8. package/dist/api/issues/index.js +447 -0
  9. package/dist/api/issues/index.js.map +1 -0
  10. package/dist/api/jobs/index.browser.js +8 -9
  11. package/dist/api/jobs/index.browser.js.map +1 -1
  12. package/dist/api/jobs/index.d.ts +99 -43
  13. package/dist/api/jobs/index.d.ts.map +1 -1
  14. package/dist/api/jobs/index.js +257 -40
  15. package/dist/api/jobs/index.js.map +1 -1
  16. package/dist/api/notifications/index.browser.js +0 -1
  17. package/dist/api/notifications/index.browser.js.map +1 -1
  18. package/dist/api/notifications/index.d.ts +3 -3
  19. package/dist/api/notifications/index.d.ts.map +1 -1
  20. package/dist/api/notifications/index.js +0 -1
  21. package/dist/api/notifications/index.js.map +1 -1
  22. package/dist/api/parameters/index.browser.js +112 -1
  23. package/dist/api/parameters/index.browser.js.map +1 -1
  24. package/dist/api/parameters/index.d.ts +90 -3
  25. package/dist/api/parameters/index.d.ts.map +1 -1
  26. package/dist/api/parameters/index.js +79 -12
  27. package/dist/api/parameters/index.js.map +1 -1
  28. package/dist/{billing → api/payments}/index.d.ts +67 -49
  29. package/dist/api/payments/index.d.ts.map +1 -0
  30. package/dist/{billing → api/payments}/index.js +108 -74
  31. package/dist/api/payments/index.js.map +1 -0
  32. package/dist/api/subscriptions/index.d.ts +1692 -0
  33. package/dist/api/subscriptions/index.d.ts.map +1 -0
  34. package/dist/api/subscriptions/index.js +1870 -0
  35. package/dist/api/subscriptions/index.js.map +1 -0
  36. package/dist/api/users/index.d.ts +24 -2
  37. package/dist/api/users/index.d.ts.map +1 -1
  38. package/dist/api/users/index.js +176 -36
  39. package/dist/api/users/index.js.map +1 -1
  40. package/dist/api/verifications/index.d.ts +13 -13
  41. package/dist/api/workflows/index.browser.js +246 -0
  42. package/dist/api/workflows/index.browser.js.map +1 -0
  43. package/dist/api/workflows/index.d.ts +1618 -0
  44. package/dist/api/workflows/index.d.ts.map +1 -0
  45. package/dist/api/workflows/index.js +1504 -0
  46. package/dist/api/workflows/index.js.map +1 -0
  47. package/dist/captcha/index.d.ts +142 -0
  48. package/dist/captcha/index.d.ts.map +1 -0
  49. package/dist/captcha/index.js +177 -0
  50. package/dist/captcha/index.js.map +1 -0
  51. package/dist/cli/core/index.d.ts +126 -30
  52. package/dist/cli/core/index.d.ts.map +1 -1
  53. package/dist/cli/core/index.js +106 -67
  54. package/dist/cli/core/index.js.map +1 -1
  55. package/dist/cli/platform/index.d.ts +84 -10
  56. package/dist/cli/platform/index.d.ts.map +1 -1
  57. package/dist/cli/platform/index.js +92 -4
  58. package/dist/cli/platform/index.js.map +1 -1
  59. package/dist/cli/vendor/index.d.ts +60 -10
  60. package/dist/cli/vendor/index.d.ts.map +1 -1
  61. package/dist/cli/vendor/index.js +177 -45
  62. package/dist/cli/vendor/index.js.map +1 -1
  63. package/dist/command/index.d.ts.map +1 -1
  64. package/dist/command/index.js +2 -3
  65. package/dist/command/index.js.map +1 -1
  66. package/dist/core/index.browser.js +21 -2
  67. package/dist/core/index.browser.js.map +1 -1
  68. package/dist/core/index.d.ts +33 -2
  69. package/dist/core/index.d.ts.map +1 -1
  70. package/dist/core/index.js +21 -2
  71. package/dist/core/index.js.map +1 -1
  72. package/dist/core/index.native.js +21 -2
  73. package/dist/core/index.native.js.map +1 -1
  74. package/dist/core/index.workerd.js +21 -2
  75. package/dist/core/index.workerd.js.map +1 -1
  76. package/dist/email/smtp/index.js +24 -8
  77. package/dist/email/smtp/index.js.map +1 -1
  78. package/dist/orm/core/index.browser.js +0 -18
  79. package/dist/orm/core/index.browser.js.map +1 -1
  80. package/dist/orm/core/index.bun.js +6 -23
  81. package/dist/orm/core/index.bun.js.map +1 -1
  82. package/dist/orm/core/index.d.ts +1 -13
  83. package/dist/orm/core/index.d.ts.map +1 -1
  84. package/dist/orm/core/index.js +6 -23
  85. package/dist/orm/core/index.js.map +1 -1
  86. package/dist/orm/postgres/index.bun.js +3 -3
  87. package/dist/orm/postgres/index.bun.js.map +1 -1
  88. package/dist/orm/postgres/index.d.ts.map +1 -1
  89. package/dist/orm/postgres/index.js +3 -3
  90. package/dist/orm/postgres/index.js.map +1 -1
  91. package/dist/react/i18n/index.d.ts +1 -0
  92. package/dist/react/i18n/index.d.ts.map +1 -1
  93. package/dist/react/i18n/index.js +8 -4
  94. package/dist/react/i18n/index.js.map +1 -1
  95. package/dist/react/router/index.browser.js +25 -3
  96. package/dist/react/router/index.browser.js.map +1 -1
  97. package/dist/react/router/index.d.ts +16 -1
  98. package/dist/react/router/index.d.ts.map +1 -1
  99. package/dist/react/router/index.js +25 -3
  100. package/dist/react/router/index.js.map +1 -1
  101. package/dist/security/index.d.ts +28 -0
  102. package/dist/security/index.d.ts.map +1 -1
  103. package/dist/security/index.js +28 -0
  104. package/dist/security/index.js.map +1 -1
  105. package/dist/server/auth/index.d.ts +145 -2
  106. package/dist/server/auth/index.d.ts.map +1 -1
  107. package/dist/server/auth/index.js +364 -63
  108. package/dist/server/auth/index.js.map +1 -1
  109. package/dist/server/cookies/index.d.ts.map +1 -1
  110. package/dist/server/cookies/index.js.map +1 -1
  111. package/dist/websocket/index.d.ts.map +1 -1
  112. package/dist/websocket/index.js.map +1 -1
  113. package/package.json +47 -20
  114. package/src/api/invitations/__tests__/InvitationService.spec.ts +439 -0
  115. package/src/api/invitations/controllers/AdminInvitationController.ts +86 -0
  116. package/src/api/invitations/controllers/InvitationController.ts +84 -0
  117. package/src/api/invitations/entities/invitations.ts +33 -0
  118. package/src/api/invitations/index.ts +65 -0
  119. package/src/api/invitations/jobs/InvitationJobs.ts +37 -0
  120. package/src/api/invitations/providers/InvitationProvider.ts +45 -0
  121. package/src/api/invitations/schemas/createInvitationSchema.ts +12 -0
  122. package/src/api/invitations/schemas/invitationConfigAtom.ts +20 -0
  123. package/src/api/invitations/schemas/invitationQuerySchema.ts +15 -0
  124. package/src/api/invitations/schemas/invitationResourceSchema.ts +6 -0
  125. package/src/api/invitations/schemas/invitationWithResourceInfoSchema.ts +22 -0
  126. package/src/api/invitations/schemas/myInvitationsQuerySchema.ts +10 -0
  127. package/src/api/invitations/services/InvitationService.ts +556 -0
  128. package/src/api/issues/__tests__/IssueService.spec.ts +263 -0
  129. package/src/api/issues/controllers/AdminIssueController.ts +149 -0
  130. package/src/api/issues/controllers/IssueController.ts +44 -0
  131. package/src/api/issues/entities/issues.ts +49 -0
  132. package/src/api/issues/index.ts +53 -0
  133. package/src/api/issues/schemas/createIssueSchema.ts +13 -0
  134. package/src/api/issues/schemas/issueConfigAtom.ts +13 -0
  135. package/src/api/issues/schemas/issueQuerySchema.ts +18 -0
  136. package/src/api/issues/schemas/issueResourceSchema.ts +6 -0
  137. package/src/api/issues/schemas/myIssueQuerySchema.ts +10 -0
  138. package/src/api/issues/schemas/updateIssueSchema.ts +13 -0
  139. package/src/api/issues/services/IssueService.ts +264 -0
  140. package/src/api/jobs/__tests__/$job.spec.ts +876 -0
  141. package/src/api/jobs/controllers/AdminJobController.ts +44 -0
  142. package/src/api/jobs/entities/jobExecutionEntity.ts +0 -2
  143. package/src/api/jobs/index.ts +0 -3
  144. package/src/api/jobs/primitives/$job.ts +22 -11
  145. package/src/api/jobs/providers/JobProvider.ts +229 -19
  146. package/src/api/jobs/schemas/jobConfigAtom.ts +4 -0
  147. package/src/api/jobs/schemas/jobCronInfoSchema.ts +1 -0
  148. package/src/api/jobs/schemas/jobExecutionQuerySchema.ts +0 -1
  149. package/src/api/jobs/schemas/jobQueueDepthSchema.ts +1 -0
  150. package/src/api/jobs/schemas/jobRegistrationSchema.ts +1 -6
  151. package/src/api/jobs/services/JobService.ts +51 -12
  152. package/src/api/notifications/schemas/notificationQuerySchema.ts +0 -1
  153. package/src/api/parameters/__tests__/$parameter.spec.ts +327 -0
  154. package/src/api/parameters/controllers/AdminParameterController.ts +29 -3
  155. package/src/api/parameters/index.browser.ts +12 -0
  156. package/src/api/parameters/primitives/$parameter.ts +20 -3
  157. package/src/api/parameters/services/ParameterProvider.ts +48 -7
  158. package/src/{billing → api/payments}/__tests__/PaymentMethodService.spec.ts +32 -6
  159. package/src/api/payments/__tests__/PaymentService.spec.ts +279 -0
  160. package/src/{billing/controllers/AdminBillingController.ts → api/payments/controllers/AdminPaymentController.ts} +26 -21
  161. package/src/{billing/controllers/BillingController.ts → api/payments/controllers/PaymentController.ts} +23 -11
  162. package/src/{billing → api/payments}/entities/paymentIntents.ts +1 -0
  163. package/src/{billing/errors/BillingError.ts → api/payments/errors/PaymentError.ts} +1 -1
  164. package/src/{billing → api/payments}/index.ts +31 -25
  165. package/src/{billing/providers/MemoryBillingProvider.ts → api/payments/providers/MemoryPaymentProvider.ts} +4 -4
  166. package/src/{billing/providers/BillingProvider.ts → api/payments/providers/PaymentProvider.ts} +9 -2
  167. package/src/{billing → api/payments}/services/PaymentMethodService.ts +5 -5
  168. package/src/{billing/services/BillingService.ts → api/payments/services/PaymentService.ts} +94 -18
  169. package/src/api/subscriptions/__tests__/BillingService.spec.ts +218 -0
  170. package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +278 -0
  171. package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +212 -0
  172. package/src/api/subscriptions/controllers/SubscriptionController.ts +189 -0
  173. package/src/api/subscriptions/entities/subscriptionEvents.ts +54 -0
  174. package/src/api/subscriptions/entities/subscriptions.ts +68 -0
  175. package/src/api/subscriptions/index.ts +144 -0
  176. package/src/api/subscriptions/jobs/SubscriptionJobs.ts +382 -0
  177. package/src/api/subscriptions/middleware/$requireLimit.ts +50 -0
  178. package/src/api/subscriptions/middleware/$requirePlan.ts +49 -0
  179. package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +110 -0
  180. package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +8 -0
  181. package/src/api/subscriptions/schemas/changePlanSchema.ts +9 -0
  182. package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +11 -0
  183. package/src/api/subscriptions/schemas/entitlementsSchema.ts +21 -0
  184. package/src/api/subscriptions/schemas/mrrSchema.ts +13 -0
  185. package/src/api/subscriptions/schemas/planDefinitionSchema.ts +71 -0
  186. package/src/api/subscriptions/schemas/planResourceSchema.ts +25 -0
  187. package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +8 -0
  188. package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +19 -0
  189. package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +6 -0
  190. package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +32 -0
  191. package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +23 -0
  192. package/src/api/subscriptions/services/BillingService.ts +437 -0
  193. package/src/api/subscriptions/services/SubscriptionConfig.ts +56 -0
  194. package/src/api/subscriptions/services/SubscriptionService.ts +867 -0
  195. package/src/api/subscriptions/services/UsageService.ts +118 -0
  196. package/src/api/users/__tests__/AdminUserController.spec.ts +80 -1
  197. package/src/api/users/__tests__/CredentialService.spec.ts +177 -0
  198. package/src/api/users/__tests__/EmailVerification.spec.ts +29 -18
  199. package/src/api/users/__tests__/PasswordReset.spec.ts +3 -0
  200. package/src/api/users/__tests__/RegistrationService.spec.ts +148 -1
  201. package/src/api/users/__tests__/SessionService.spec.ts +142 -1
  202. package/src/api/users/atoms/realmAuthSettingsAtom.ts +10 -1
  203. package/src/api/users/controllers/UserController.ts +3 -8
  204. package/src/api/users/notifications/UserNotifications.ts +23 -0
  205. package/src/api/users/primitives/$realm.ts +24 -0
  206. package/src/api/users/schemas/loginSchema.ts +1 -1
  207. package/src/api/users/services/CredentialService.ts +57 -7
  208. package/src/api/users/services/RegistrationService.ts +50 -11
  209. package/src/api/users/services/SessionService.ts +64 -9
  210. package/src/api/users/services/UserService.ts +21 -12
  211. package/src/api/workflows/__tests__/$workflow.spec.ts +616 -0
  212. package/src/api/workflows/controllers/AdminWorkflowController.ts +191 -0
  213. package/src/api/workflows/entities/workflowExecutions.ts +74 -0
  214. package/src/api/workflows/entities/workflowStepExecutions.ts +74 -0
  215. package/src/api/workflows/entities/workflowStepLogs.ts +13 -0
  216. package/src/api/workflows/index.browser.ts +22 -0
  217. package/src/api/workflows/index.ts +124 -0
  218. package/src/api/workflows/jobs/WorkflowJobs.ts +77 -0
  219. package/src/api/workflows/primitives/$workflow.ts +202 -0
  220. package/src/api/workflows/providers/WorkflowProvider.ts +1284 -0
  221. package/src/api/workflows/schemas/workflowActivitySchema.ts +15 -0
  222. package/src/api/workflows/schemas/workflowConfigAtom.ts +51 -0
  223. package/src/api/workflows/schemas/workflowExecutionDetailSchema.ts +18 -0
  224. package/src/api/workflows/schemas/workflowExecutionQuerySchema.ts +26 -0
  225. package/src/api/workflows/schemas/workflowExecutionResourceSchema.ts +30 -0
  226. package/src/api/workflows/schemas/workflowRegistrationSchema.ts +26 -0
  227. package/src/api/workflows/schemas/workflowStatsSchema.ts +16 -0
  228. package/src/api/workflows/schemas/workflowStepExecutionResourceSchema.ts +15 -0
  229. package/src/api/workflows/services/WorkflowService.ts +382 -0
  230. package/src/captcha/__tests__/MemoryCaptchaProvider.spec.ts +74 -0
  231. package/src/captcha/index.ts +33 -0
  232. package/src/captcha/providers/CaptchaProvider.ts +17 -0
  233. package/src/captcha/providers/MemoryCaptchaProvider.ts +65 -0
  234. package/src/captcha/providers/TurnstileCaptchaProvider.ts +125 -0
  235. package/src/cli/core/atoms/buildOptions.ts +57 -0
  236. package/src/cli/core/commands/build.ts +2 -0
  237. package/src/cli/core/providers/ViteDevServerProvider.ts +1 -1
  238. package/src/cli/core/services/ViteUtils.ts +5 -2
  239. package/src/cli/core/tasks/BuildClientTask.ts +3 -1
  240. package/src/cli/core/tasks/BuildCloudflareTask.ts +4 -0
  241. package/src/cli/core/tasks/BuildPwaTask.ts +81 -0
  242. package/src/cli/core/templates/webAppRouterTs.ts +5 -58
  243. package/src/cli/platform/adapters/CloudflareAdapter.ts +24 -0
  244. package/src/cli/platform/atoms/platformOptions.ts +19 -3
  245. package/src/cli/platform/hooks/PlatformHook.ts +51 -0
  246. package/src/cli/platform/index.ts +1 -0
  247. package/src/cli/platform/services/CloudflareApi.ts +22 -1
  248. package/src/cli/platform/services/PlatformOrchestrator.ts +67 -2
  249. package/src/cli/vendor/__tests__/VendorService.spec.ts +322 -178
  250. package/src/cli/vendor/commands/VendorCommand.ts +41 -38
  251. package/src/cli/vendor/services/VendorService.ts +234 -31
  252. package/src/command/__tests__/CliProvider.spec.ts +45 -0
  253. package/src/command/providers/CliProvider.ts +3 -4
  254. package/src/core/__tests__/TypeProvider.spec.ts +4 -2
  255. package/src/core/providers/SchemaValidator.ts +1 -1
  256. package/src/core/providers/TypeProvider.ts +46 -3
  257. package/src/orm/__tests__/enums.spec.ts +22 -29
  258. package/src/orm/__tests__/orm-showcase-tests.ts +430 -0
  259. package/src/orm/__tests__/orm-showcase.spec.ts +167 -0
  260. package/src/orm/core/providers/DatabaseTypeProvider.ts +0 -29
  261. package/src/orm/core/services/Repository.ts +20 -6
  262. package/src/orm/postgres/services/PostgresModelBuilder.ts +3 -6
  263. package/src/react/i18n/__tests__/I18nProvider.spec.ts +83 -0
  264. package/src/react/i18n/providers/I18nProvider.ts +12 -10
  265. package/src/react/router/__tests__/$page.browser.spec.tsx +157 -0
  266. package/src/react/router/providers/ReactBrowserProvider.ts +39 -0
  267. package/src/react/router/providers/ReactBrowserRouterProvider.ts +22 -0
  268. package/src/security/__tests__/$secure-combinations.spec.ts +945 -0
  269. package/src/security/primitives/$issuer.ts +3 -1
  270. package/src/security/primitives/$secure.ts +28 -0
  271. package/src/server/auth/index.ts +7 -0
  272. package/src/server/auth/primitives/$auth.ts +37 -3
  273. package/src/server/auth/primitives/$authApple.ts +114 -4
  274. package/src/server/auth/primitives/$authFacebook.ts +98 -0
  275. package/src/server/auth/primitives/$authFranceConnect.ts +105 -0
  276. package/src/server/auth/primitives/$authGithub.ts +22 -16
  277. package/src/server/auth/primitives/$authMicrosoft.ts +88 -0
  278. package/src/server/auth/providers/ServerAuthProvider.ts +197 -72
  279. package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -0
  280. package/src/server/core/__tests__/ServerRouterProvider-errorHandler.spec.ts +1 -1
  281. package/src/websocket/providers/NodeWebSocketServerProvider.ts +3 -1
  282. package/dist/billing/index.d.ts.map +0 -1
  283. package/dist/billing/index.js.map +0 -1
  284. package/src/billing/__tests__/BillingService.spec.ts +0 -136
  285. /package/src/{billing → api/payments}/entities/paymentMethods.ts +0 -0
  286. /package/src/{billing → api/payments}/entities/refunds.ts +0 -0
  287. /package/src/{billing → api/payments}/schemas/intentSchemas.ts +0 -0
  288. /package/src/{billing → api/payments}/schemas/paymentMethodSchemas.ts +0 -0
  289. /package/src/{billing → api/payments}/schemas/refundSchemas.ts +0 -0
@@ -7,11 +7,12 @@ import { $entity, $repository, db, pageQuerySchema, parseQueryString, sql } from
7
7
  import { $logger } from "alepha/logger";
8
8
  import { $client } from "alepha/server/links";
9
9
  import { $notification } from "alepha/api/notifications";
10
- import { $authCredentials, $authGithub, $authGoogle, ServerAuthProvider, authenticationProviderSchema } from "alepha/server/auth";
10
+ import { $authApple, $authCredentials, $authFacebook, $authFranceConnect, $authGithub, $authGoogle, $authMicrosoft, ServerAuthProvider, authenticationProviderSchema } from "alepha/server/auth";
11
11
  import { $etag } from "alepha/server/etag";
12
12
  import { randomInt, randomUUID } from "node:crypto";
13
13
  import { $cache, CacheProvider } from "alepha/cache";
14
14
  import { DateTimeProvider } from "alepha/datetime";
15
+ import { CaptchaProvider } from "alepha/captcha";
15
16
  import { $job } from "alepha/api/jobs";
16
17
  import { FileSystemProvider } from "alepha/system";
17
18
  import { AlephaApiKeys, ApiKeyService } from "alepha/api/keys";
@@ -168,6 +169,8 @@ const realmAuthSettingsAtom = $atom({
168
169
  resetPasswordAllowed: t.boolean({ description: "Enable forgot password functionality" }),
169
170
  adminEmails: t.array(t.email(), { description: "List of email addresses that are automatically promoted to admin role on login" }),
170
171
  adminUsernames: t.array(t.text(), { description: "List of usernames that are automatically promoted to admin role on login" }),
172
+ defaultRoles: t.array(t.string(), { description: "Default roles assigned to newly registered users" }),
173
+ verifyEmailUrl: t.optional(t.string({ description: "Base URL for email verification links (used when verification method is 'link'). Token and email are appended as query params." })),
171
174
  passwordPolicy: t.object({
172
175
  minLength: t.integer({
173
176
  description: "Minimum password length",
@@ -209,6 +212,7 @@ const realmAuthSettingsAtom = $atom({
209
212
  firstNameLastName: "none",
210
213
  adminEmails: [],
211
214
  adminUsernames: [],
215
+ defaultRoles: ["user"],
212
216
  passwordPolicy: {
213
217
  minLength: 8,
214
218
  requireUppercase: true,
@@ -723,6 +727,27 @@ var UserNotifications = class {
723
727
  expiresInMinutes: t.number()
724
728
  })
725
729
  });
730
+ accountLockout = $notification({
731
+ category: "security",
732
+ description: "Email sent to users when their account is temporarily locked due to too many failed login attempts.",
733
+ critical: true,
734
+ sensitive: true,
735
+ email: {
736
+ subject: "Account temporarily locked",
737
+ body: (it) => `
738
+ <h1>Account Temporarily Locked</h1>
739
+ <p>Hi ${it.email},</p>
740
+ <p>Your account has been temporarily locked due to too many failed login attempts.</p>
741
+ <p>If this was you, please wait ${it.lockoutMinutes} minutes before trying again. If you've forgotten your password, you can reset it using the password reset feature.</p>
742
+ <p>If this wasn't you, someone may be trying to access your account. We recommend changing your password as soon as possible.</p>
743
+ <p>Best regards,<br>The Team</p>
744
+ `
745
+ },
746
+ schema: t.object({
747
+ email: t.string({ format: "email" }),
748
+ lockoutMinutes: t.number()
749
+ })
750
+ });
726
751
  emailVerificationLink = $notification({
727
752
  category: "security",
728
753
  description: "Email sent to users with a link to verify their email address.",
@@ -776,7 +801,7 @@ var UserService = class {
776
801
  * @param method - The verification method: "code" (default) or "link".
777
802
  * @param verifyUrl - Base URL for verification link (required when method is "link").
778
803
  */
779
- async requestEmailVerification(email, userRealmName, method = "code", verifyUrl) {
804
+ async requestEmailVerification(email, userRealmName, method = "code") {
780
805
  this.log.trace("Requesting email verification", {
781
806
  email,
782
807
  userRealmName,
@@ -800,10 +825,12 @@ var UserService = class {
800
825
  body: { target: email }
801
826
  });
802
827
  if (method === "link") {
803
- const url = new URL(verifyUrl || "/verify-email", "http://localhost");
828
+ const realmSettings = await this.realmProvider.getRealm(userRealmName).getSettings();
829
+ const baseUrl = realmSettings.verifyEmailUrl ?? "/verify-email";
830
+ const url = new URL(baseUrl, "http://localhost");
804
831
  url.searchParams.set("email", email);
805
832
  url.searchParams.set("token", verification.token);
806
- const fullVerifyUrl = verifyUrl ? `${verifyUrl}${url.search}` : url.pathname + url.search;
833
+ const fullVerifyUrl = realmSettings.verifyEmailUrl ? `${baseUrl}${url.search}` : url.pathname + url.search;
807
834
  await this.userNotifications(userRealmName)?.emailVerificationLink.push({
808
835
  contact: email,
809
836
  variables: {
@@ -936,27 +963,37 @@ var UserService = class {
936
963
  userRealmName
937
964
  });
938
965
  const realm = this.realmProvider.getRealm(userRealmName);
966
+ const realmSettings = await realm.getSettings();
939
967
  if (data.username) {
940
- if (await this.users(userRealmName).findOne({ where: { username: { ilike: data.username } } })) {
968
+ if (await this.users(userRealmName).findOne({ where: {
969
+ realm: realm.name,
970
+ username: { ilike: data.username }
971
+ } })) {
941
972
  this.log.debug("Username already taken", { username: data.username });
942
973
  throw new BadRequestError("User with this username already exists");
943
974
  }
944
975
  }
945
976
  if (data.email) {
946
- if (await this.users(userRealmName).findOne({ where: { email: { eq: data.email } } })) {
977
+ if (await this.users(userRealmName).findOne({ where: {
978
+ realm: realm.name,
979
+ email: { eq: data.email }
980
+ } })) {
947
981
  this.log.debug("Email already taken", { email: data.email });
948
982
  throw new BadRequestError("User with this email already exists");
949
983
  }
950
984
  }
951
985
  if (data.phoneNumber) {
952
- if (await this.users(userRealmName).findOne({ where: { phoneNumber: { eq: data.phoneNumber } } })) {
986
+ if (await this.users(userRealmName).findOne({ where: {
987
+ realm: realm.name,
988
+ phoneNumber: { eq: data.phoneNumber }
989
+ } })) {
953
990
  this.log.debug("Phone number already taken", { phoneNumber: data.phoneNumber });
954
991
  throw new BadRequestError("User with this phone number already exists");
955
992
  }
956
993
  }
957
994
  const user = await this.users(userRealmName).create({
958
995
  ...data,
959
- roles: data.roles ?? ["user"],
996
+ roles: data.roles ?? realmSettings.defaultRoles,
960
997
  realm: realm.name
961
998
  });
962
999
  this.log.info("User created", {
@@ -1011,6 +1048,8 @@ var UserService = class {
1011
1048
  userRealmName
1012
1049
  });
1013
1050
  const user = await this.getUserById(id, userRealmName);
1051
+ await this.realmProvider.sessionRepository(userRealmName).deleteMany({ userId: { eq: id } });
1052
+ await this.realmProvider.identityRepository(userRealmName).deleteMany({ userId: { eq: id } });
1014
1053
  await this.users(userRealmName).deleteById(id);
1015
1054
  this.log.info("User deleted", { userId: id });
1016
1055
  const realm = this.realmProvider.getRealm(userRealmName);
@@ -1282,6 +1321,16 @@ var CredentialService = class {
1282
1321
  return this.realmProvider.identityRepository(userRealmName);
1283
1322
  }
1284
1323
  /**
1324
+ * Validate a password against the realm's password policy.
1325
+ */
1326
+ validatePasswordPolicy(password, policy) {
1327
+ if (password.length < policy.minLength) throw new BadRequestError(`Password must be at least ${policy.minLength} characters`);
1328
+ if (policy.requireUppercase && !/[A-Z]/.test(password)) throw new BadRequestError("Password must contain at least one uppercase letter");
1329
+ if (policy.requireLowercase && !/[a-z]/.test(password)) throw new BadRequestError("Password must contain at least one lowercase letter");
1330
+ if (policy.requireNumbers && !/\d/.test(password)) throw new BadRequestError("Password must contain at least one number");
1331
+ if (policy.requireSpecialCharacters && !/[^a-zA-Z0-9]/.test(password)) throw new BadRequestError("Password must contain at least one special character");
1332
+ }
1333
+ /**
1285
1334
  * Phase 1: Create a password reset intent.
1286
1335
  *
1287
1336
  * Validates the email, checks for existing user with credentials,
@@ -1298,6 +1347,13 @@ var CredentialService = class {
1298
1347
  });
1299
1348
  const intentId = randomUUID();
1300
1349
  const expiresAt = this.dateTimeProvider.now().add(INTENT_TTL_MINUTES$1, "minutes").toISOString();
1350
+ if ((await this.realmProvider.getRealm(userRealmName).getSettings()).resetPasswordAllowed === false) {
1351
+ this.log.debug("Password reset not allowed for realm", { userRealmName });
1352
+ return {
1353
+ intentId,
1354
+ expiresAt
1355
+ };
1356
+ }
1301
1357
  const user = await this.users(userRealmName).findOne({ where: { email: { eq: email } } });
1302
1358
  if (!user) {
1303
1359
  this.log.debug("Password reset requested for non-existent email", { email });
@@ -1369,6 +1425,9 @@ var CredentialService = class {
1369
1425
  message: "Invalid or expired password reset intent"
1370
1426
  });
1371
1427
  }
1428
+ const realm = this.realmProvider.getRealm(intent.realmName);
1429
+ const realmSettings = await realm.getSettings();
1430
+ this.validatePasswordPolicy(body.newPassword, realmSettings.passwordPolicy);
1372
1431
  if ((await this.verificationController.validateVerificationCode({
1373
1432
  params: { type: "code" },
1374
1433
  body: {
@@ -1388,15 +1447,14 @@ var CredentialService = class {
1388
1447
  });
1389
1448
  throw new BadRequestError("Verification code has already been used");
1390
1449
  }
1391
- await this.intentCache.invalidate(body.intentId);
1392
1450
  const hashedPassword = await this.cryptoProvider.hashPassword(body.newPassword);
1393
1451
  await this.identities(intent.realmName).updateById(intent.identityId, { password: hashedPassword });
1394
1452
  await this.sessions(intent.realmName).deleteMany({ userId: { eq: intent.userId } });
1453
+ await this.intentCache.invalidate(body.intentId);
1395
1454
  this.log.info("Password reset completed", {
1396
1455
  userId: intent.userId,
1397
1456
  email: intent.email
1398
1457
  });
1399
- const realm = this.realmProvider.getRealm(intent.realmName);
1400
1458
  await this.userAudits(intent.realmName)?.recordUser("update", {
1401
1459
  userId: intent.userId,
1402
1460
  userEmail: intent.email,
@@ -1447,6 +1505,9 @@ var CredentialService = class {
1447
1505
  }).catch(() => {
1448
1506
  throw new BadRequestError("Invalid or expired reset token");
1449
1507
  })).alreadyVerified) throw new BadRequestError("Invalid or expired reset token");
1508
+ const realm = this.realmProvider.getRealm(userRealmName);
1509
+ const realmSettings = await realm.getSettings();
1510
+ this.validatePasswordPolicy(newPassword, realmSettings.passwordPolicy);
1450
1511
  const user = await this.users(userRealmName).getOne({ where: { email: { eq: email } } });
1451
1512
  const identity = await this.identities(userRealmName).getOne({ where: {
1452
1513
  userId: { eq: user.id },
@@ -1455,7 +1516,6 @@ var CredentialService = class {
1455
1516
  const hashedPassword = await this.cryptoProvider.hashPassword(newPassword);
1456
1517
  await this.identities(userRealmName).updateById(identity.id, { password: hashedPassword });
1457
1518
  await this.sessions(userRealmName).deleteMany({ userId: { eq: user.id } });
1458
- const realm = this.realmProvider.getRealm(userRealmName);
1459
1519
  await this.userAudits(userRealmName)?.recordUser("update", {
1460
1520
  userId: user.id,
1461
1521
  userEmail: email,
@@ -1484,10 +1544,16 @@ var RegistrationService = class {
1484
1544
  cryptoProvider = $inject(CryptoProvider);
1485
1545
  verificationController = $client();
1486
1546
  realmProvider = $inject(RealmProvider);
1547
+ credentialService = $inject(CredentialService);
1548
+ captchaProvider = $inject(CaptchaProvider);
1487
1549
  intentCache = $cache({
1488
1550
  name: "api:users:registrations",
1489
1551
  ttl: [INTENT_TTL_MINUTES, "minutes"]
1490
1552
  });
1553
+ rateLimitCache = $cache({
1554
+ name: "api:users:registration-rate-limit",
1555
+ ttl: [15, "minutes"]
1556
+ });
1491
1557
  userAudits(realmName) {
1492
1558
  if (this.realmProvider.getRealm(realmName).features.audits) return this.alepha.inject(UserAudits);
1493
1559
  }
@@ -1507,6 +1573,16 @@ var RegistrationService = class {
1507
1573
  username: body.username,
1508
1574
  userRealmName
1509
1575
  });
1576
+ const request = this.alepha.store.get("alepha.http.request");
1577
+ const ipKey = request?.ip ? `register:ip:${request.ip}` : void 0;
1578
+ if (ipKey) {
1579
+ const count = await this.rateLimitCache.get(ipKey) ?? 0;
1580
+ if (count >= 10) {
1581
+ this.log.warn("Registration rate limit exceeded", { ip: request?.ip });
1582
+ throw new BadRequestError("Too many registration attempts, please try again later");
1583
+ }
1584
+ await this.rateLimitCache.set(ipKey, count + 1);
1585
+ }
1510
1586
  const realmSettings = await this.realmProvider.getRealm(userRealmName).getSettings();
1511
1587
  if (realmSettings?.registrationAllowed === false) {
1512
1588
  this.log.warn("Registration not allowed for realm", { userRealmName });
@@ -1537,6 +1613,7 @@ var RegistrationService = class {
1537
1613
  throw new BadRequestError("Phone number is required");
1538
1614
  }
1539
1615
  await this.checkUserAvailability(body, userRealmName);
1616
+ this.credentialService.validatePasswordPolicy(body.password, realmSettings.passwordPolicy);
1540
1617
  const passwordHash = await this.cryptoProvider.hashPassword(body.password);
1541
1618
  const requirements = {
1542
1619
  email: realmSettings?.verifyEmailRequired === true && !!body.email,
@@ -1614,22 +1691,24 @@ var RegistrationService = class {
1614
1691
  }
1615
1692
  if (intent.requirements.captcha) {
1616
1693
  if (!body.captchaToken) throw new BadRequestError("Captcha verification is required");
1694
+ if (!await this.captchaProvider.verify(body.captchaToken)) throw new BadRequestError("Captcha verification failed");
1617
1695
  }
1618
1696
  await this.checkUserAvailability({
1619
1697
  username: intent.data.username,
1620
1698
  email: intent.data.email,
1621
1699
  phoneNumber: intent.data.phoneNumber
1622
1700
  }, userRealmName);
1623
- await this.intentCache.invalidate(body.intentId);
1701
+ const realm = this.realmProvider.getRealm(userRealmName);
1702
+ const realmSettings = await realm.getSettings();
1624
1703
  const user = await userRepository.create({
1625
- realm: userRealmName,
1704
+ realm: realm.name,
1626
1705
  username: intent.data.username,
1627
1706
  email: intent.data.email,
1628
1707
  phoneNumber: intent.data.phoneNumber,
1629
1708
  firstName: intent.data.firstName,
1630
1709
  lastName: intent.data.lastName,
1631
1710
  picture: intent.data.picture,
1632
- roles: ["user"],
1711
+ roles: realmSettings.defaultRoles,
1633
1712
  enabled: true,
1634
1713
  emailVerified: intent.requirements.email
1635
1714
  });
@@ -1638,12 +1717,12 @@ var RegistrationService = class {
1638
1717
  provider: "credentials",
1639
1718
  password: intent.data.passwordHash
1640
1719
  });
1720
+ await this.intentCache.invalidate(body.intentId);
1641
1721
  this.log.info("User registered successfully", {
1642
1722
  userId: user.id,
1643
1723
  email: user.email,
1644
1724
  username: user.username
1645
1725
  });
1646
- const realm = this.realmProvider.getRealm(userRealmName);
1647
1726
  await this.userAudits(userRealmName)?.recordUser("create", {
1648
1727
  userId: user.id,
1649
1728
  userEmail: user.email ?? void 0,
@@ -1663,21 +1742,31 @@ var RegistrationService = class {
1663
1742
  * Check if username, email, and phone are available.
1664
1743
  */
1665
1744
  async checkUserAvailability(body, userRealmName) {
1745
+ const realm = this.realmProvider.getRealm(userRealmName);
1666
1746
  const userRepository = this.realmProvider.userRepository(userRealmName);
1667
1747
  if (body.username) {
1668
- if (await userRepository.findOne({ where: { username: { ilike: body.username } } })) {
1748
+ if (await userRepository.findOne({ where: {
1749
+ realm: realm.name,
1750
+ username: { ilike: body.username }
1751
+ } })) {
1669
1752
  this.log.debug("Username already taken", { username: body.username });
1670
1753
  throw new ConflictError("User with this username already exists");
1671
1754
  }
1672
1755
  }
1673
1756
  if (body.email) {
1674
- if (await userRepository.findOne({ where: { email: { eq: body.email } } })) {
1757
+ if (await userRepository.findOne({ where: {
1758
+ realm: realm.name,
1759
+ email: { eq: body.email }
1760
+ } })) {
1675
1761
  this.log.debug("Email already taken", { email: body.email });
1676
1762
  throw new ConflictError("User with this email already exists");
1677
1763
  }
1678
1764
  }
1679
1765
  if (body.phoneNumber) {
1680
- if (await userRepository.findOne({ where: { phoneNumber: { eq: body.phoneNumber } } })) {
1766
+ if (await userRepository.findOne({ where: {
1767
+ realm: realm.name,
1768
+ phoneNumber: { eq: body.phoneNumber }
1769
+ } })) {
1681
1770
  this.log.debug("Phone number already taken", { phoneNumber: body.phoneNumber });
1682
1771
  throw new ConflictError("User with this phone number already exists");
1683
1772
  }
@@ -1924,9 +2013,8 @@ var UserController = class {
1924
2013
  userRealmName: t.optional(t.string()),
1925
2014
  method: t.optional(t.enum(["code", "link"], {
1926
2015
  default: "code",
1927
- description: "Verification method: \"code\" sends a 6-digit code, \"link\" sends a clickable verification link."
1928
- })),
1929
- verifyUrl: t.optional(t.string({ description: "Base URL for verification link. Required when method is \"link\". Token and email will be appended as query params." }))
2016
+ description: "Verification method: \"code\" sends a 6-digit code, \"link\" sends a clickable verification link. When using \"link\", configure verifyEmailUrl in realm settings."
2017
+ }))
1930
2018
  }),
1931
2019
  body: t.object({ email: t.email() }),
1932
2020
  response: t.object({
@@ -1936,7 +2024,7 @@ var UserController = class {
1936
2024
  },
1937
2025
  handler: async ({ body, query }) => {
1938
2026
  const method = query.method ?? "code";
1939
- await this.userService.requestEmailVerification(body.email, query.userRealmName, method, query.verifyUrl);
2027
+ await this.userService.requestEmailVerification(body.email, query.userRealmName, method);
1940
2028
  return {
1941
2029
  success: true,
1942
2030
  message: method === "link" ? "If an account exists with this email, a verification link has been sent." : "If an account exists with this email, a verification code has been sent."
@@ -1975,6 +2063,7 @@ var UserController = class {
1975
2063
  checkEmailVerification = $action({
1976
2064
  path: "/users/email-verification/check",
1977
2065
  group: this.group,
2066
+ use: [$secure()],
1978
2067
  schema: {
1979
2068
  query: t.object({
1980
2069
  email: t.email(),
@@ -2039,6 +2128,9 @@ var SessionService = class SessionService {
2039
2128
  userAudits(realmName) {
2040
2129
  if (this.realmProvider.getRealm(realmName).features.audits) return this.alepha.inject(UserAudits);
2041
2130
  }
2131
+ userNotifications(realmName) {
2132
+ if (this.realmProvider.getRealm(realmName).features.notifications) return this.alepha.inject(UserNotifications);
2133
+ }
2042
2134
  users(userRealmName) {
2043
2135
  return this.realmProvider.userRepository(userRealmName);
2044
2136
  }
@@ -2103,10 +2195,7 @@ var SessionService = class SessionService {
2103
2195
  if (candidate.length < 3) candidate = `user${candidate}`;
2104
2196
  candidate = candidate.slice(0, maxLength - 2);
2105
2197
  const isAvailable = async (name) => {
2106
- return !(await users.findMany({
2107
- where: { username: { contains: name } },
2108
- limit: 1
2109
- })).some((u) => u.username?.toLowerCase() === name.toLowerCase());
2198
+ return !await users.findOne({ where: { username: { ilike: name } } });
2110
2199
  };
2111
2200
  if (await isAvailable(candidate)) return candidate;
2112
2201
  for (let i = 2; i <= maxSuffixAttempts + 1; i++) {
@@ -2240,6 +2329,22 @@ var SessionService = class SessionService {
2240
2329
  }
2241
2330
  throw new InvalidCredentialsError();
2242
2331
  }
2332
+ if (!user.enabled) {
2333
+ this.log.warn("Login attempt for disabled account", {
2334
+ userId: user.id,
2335
+ realm: name
2336
+ });
2337
+ await this.userAudits(userRealmName)?.recordAuth("login_failed", {
2338
+ userRealm: name,
2339
+ resourceId: user.id,
2340
+ description: "Login attempt for disabled account",
2341
+ metadata: {
2342
+ provider,
2343
+ username
2344
+ }
2345
+ });
2346
+ throw new InvalidCredentialsError();
2347
+ }
2243
2348
  const accountKey = `login:account:${name}:${user.id}`;
2244
2349
  if (await this.isLoginLocked(accountKey, loginRateLimit.accountMaxAttempts)) {
2245
2350
  this.log.warn("Login blocked — account rate limit exceeded", {
@@ -2285,13 +2390,25 @@ var SessionService = class SessionService {
2285
2390
  metadata: { ip: request?.ip }
2286
2391
  });
2287
2392
  }
2288
- if (await this.recordFailedLogin(accountKey, loginRateLimit.accountMaxAttempts, loginRateLimit.windowMs)) await this.userAudits(userRealmName)?.record("security", "rate_limited", {
2289
- userRealm: name,
2290
- resourceId: user.id,
2291
- success: false,
2292
- description: "Account temporarily locked due to too many failed login attempts",
2293
- metadata: { userId: user.id }
2294
- });
2393
+ if (await this.recordFailedLogin(accountKey, loginRateLimit.accountMaxAttempts, loginRateLimit.windowMs)) {
2394
+ await this.userAudits(userRealmName)?.record("security", "rate_limited", {
2395
+ userRealm: name,
2396
+ resourceId: user.id,
2397
+ success: false,
2398
+ description: "Account temporarily locked due to too many failed login attempts",
2399
+ metadata: { userId: user.id }
2400
+ });
2401
+ if (user.email) {
2402
+ const lockoutMinutes = Math.round(loginRateLimit.windowMs / 6e4);
2403
+ await this.userNotifications(userRealmName)?.accountLockout.push({
2404
+ contact: user.email,
2405
+ variables: {
2406
+ email: user.email,
2407
+ lockoutMinutes
2408
+ }
2409
+ });
2410
+ }
2411
+ }
2295
2412
  throw new InvalidCredentialsError();
2296
2413
  }
2297
2414
  await this.userAudits(userRealmName)?.recordAuth("login", {
@@ -2352,6 +2469,14 @@ var SessionService = class SessionService {
2352
2469
  throw new UnauthorizedError("Session expired");
2353
2470
  }
2354
2471
  const user = await this.users(userRealmName).getOne({ where: { id: { eq: session.userId } } });
2472
+ if (!user.enabled) {
2473
+ this.log.warn("Session refresh for disabled account", {
2474
+ userId: user.id,
2475
+ sessionId: session.id
2476
+ });
2477
+ await this.sessions(userRealmName).deleteById(session.id);
2478
+ throw new UnauthorizedError("Account disabled");
2479
+ }
2355
2480
  await this.ensureAdminRole(user, userRealmName);
2356
2481
  this.log.debug("Session refreshed", {
2357
2482
  sessionId: session.id,
@@ -2422,8 +2547,19 @@ var SessionService = class SessionService {
2422
2547
  ...profile
2423
2548
  };
2424
2549
  }
2425
- const existing = await users.findOne({ where: { email: profile.email } });
2550
+ const existing = await users.findOne({ where: {
2551
+ realm: realm.name,
2552
+ email: profile.email
2553
+ } });
2426
2554
  if (existing) {
2555
+ if (profile.email_verified === false) {
2556
+ this.log.warn("OAuth2 profile email not verified by provider, refusing auto-link", {
2557
+ provider,
2558
+ email: profile.email,
2559
+ userId: existing.id
2560
+ });
2561
+ throw new BadRequestError("Cannot link account: email not verified by provider");
2562
+ }
2427
2563
  this.log.debug("Linking OAuth2 profile to existing user by email", {
2428
2564
  provider,
2429
2565
  profileSub: profile.sub,
@@ -2466,7 +2602,7 @@ var SessionService = class SessionService {
2466
2602
  username,
2467
2603
  email: profile.email,
2468
2604
  emailVerified: true,
2469
- roles: ["user"]
2605
+ roles: realmSettings.defaultRoles
2470
2606
  });
2471
2607
  if (profile.picture) {
2472
2608
  this.log.debug("Fetching user profile picture from OAuth2 provider", {
@@ -2621,6 +2757,10 @@ const $realm = (options = {}) => {
2621
2757
  else realmRegistration.settings.registrationAllowed = false;
2622
2758
  if (identities.google) auth.google = $authGoogle(realm);
2623
2759
  if (identities.github) auth.github = $authGithub(realm);
2760
+ if (identities.apple) auth.apple = $authApple(realm);
2761
+ if (identities.facebook) auth.facebook = $authFacebook(realm);
2762
+ if (identities.microsoft) auth.microsoft = $authMicrosoft(realm);
2763
+ if (identities.franceconnect) auth.franceconnect = $authFranceConnect(realm);
2624
2764
  alepha.with(() => auth);
2625
2765
  }
2626
2766
  if (features.parameters) {
@@ -2645,7 +2785,7 @@ const loginSchema = t.object({
2645
2785
  description: "Username or email address for login"
2646
2786
  }),
2647
2787
  password: t.text({
2648
- minLength: 6,
2788
+ minLength: 8,
2649
2789
  description: "User password"
2650
2790
  })
2651
2791
  });