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.
- package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
- package/dist/api/invitations/index.d.ts +790 -0
- package/dist/api/invitations/index.d.ts.map +1 -0
- package/dist/api/invitations/index.js +665 -0
- package/dist/api/invitations/index.js.map +1 -0
- package/dist/api/issues/index.d.ts +810 -0
- package/dist/api/issues/index.d.ts.map +1 -0
- package/dist/api/issues/index.js +447 -0
- package/dist/api/issues/index.js.map +1 -0
- package/dist/api/jobs/index.browser.js +8 -9
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +99 -43
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +257 -40
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/notifications/index.browser.js +0 -1
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +3 -3
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +0 -1
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.browser.js +112 -1
- package/dist/api/parameters/index.browser.js.map +1 -1
- package/dist/api/parameters/index.d.ts +90 -3
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +79 -12
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/{billing → api/payments}/index.d.ts +67 -49
- package/dist/api/payments/index.d.ts.map +1 -0
- package/dist/{billing → api/payments}/index.js +108 -74
- package/dist/api/payments/index.js.map +1 -0
- package/dist/api/subscriptions/index.d.ts +1692 -0
- package/dist/api/subscriptions/index.d.ts.map +1 -0
- package/dist/api/subscriptions/index.js +1870 -0
- package/dist/api/subscriptions/index.js.map +1 -0
- package/dist/api/users/index.d.ts +24 -2
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +176 -36
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +13 -13
- package/dist/api/workflows/index.browser.js +246 -0
- package/dist/api/workflows/index.browser.js.map +1 -0
- package/dist/api/workflows/index.d.ts +1618 -0
- package/dist/api/workflows/index.d.ts.map +1 -0
- package/dist/api/workflows/index.js +1504 -0
- package/dist/api/workflows/index.js.map +1 -0
- package/dist/captcha/index.d.ts +142 -0
- package/dist/captcha/index.d.ts.map +1 -0
- package/dist/captcha/index.js +177 -0
- package/dist/captcha/index.js.map +1 -0
- package/dist/cli/core/index.d.ts +126 -30
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +106 -67
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +84 -10
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +92 -4
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/vendor/index.d.ts +60 -10
- package/dist/cli/vendor/index.d.ts.map +1 -1
- package/dist/cli/vendor/index.js +177 -45
- package/dist/cli/vendor/index.js.map +1 -1
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +2 -3
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +21 -2
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +33 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +21 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +21 -2
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +21 -2
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/email/smtp/index.js +24 -8
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/orm/core/index.browser.js +0 -18
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +6 -23
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +1 -13
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +6 -23
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.bun.js +3 -3
- package/dist/orm/postgres/index.bun.js.map +1 -1
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/orm/postgres/index.js +3 -3
- package/dist/orm/postgres/index.js.map +1 -1
- package/dist/react/i18n/index.d.ts +1 -0
- package/dist/react/i18n/index.d.ts.map +1 -1
- package/dist/react/i18n/index.js +8 -4
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/router/index.browser.js +25 -3
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +16 -1
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +25 -3
- package/dist/react/router/index.js.map +1 -1
- package/dist/security/index.d.ts +28 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +28 -0
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +145 -2
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +364 -63
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cookies/index.d.ts.map +1 -1
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js.map +1 -1
- package/package.json +47 -20
- package/src/api/invitations/__tests__/InvitationService.spec.ts +439 -0
- package/src/api/invitations/controllers/AdminInvitationController.ts +86 -0
- package/src/api/invitations/controllers/InvitationController.ts +84 -0
- package/src/api/invitations/entities/invitations.ts +33 -0
- package/src/api/invitations/index.ts +65 -0
- package/src/api/invitations/jobs/InvitationJobs.ts +37 -0
- package/src/api/invitations/providers/InvitationProvider.ts +45 -0
- package/src/api/invitations/schemas/createInvitationSchema.ts +12 -0
- package/src/api/invitations/schemas/invitationConfigAtom.ts +20 -0
- package/src/api/invitations/schemas/invitationQuerySchema.ts +15 -0
- package/src/api/invitations/schemas/invitationResourceSchema.ts +6 -0
- package/src/api/invitations/schemas/invitationWithResourceInfoSchema.ts +22 -0
- package/src/api/invitations/schemas/myInvitationsQuerySchema.ts +10 -0
- package/src/api/invitations/services/InvitationService.ts +556 -0
- package/src/api/issues/__tests__/IssueService.spec.ts +263 -0
- package/src/api/issues/controllers/AdminIssueController.ts +149 -0
- package/src/api/issues/controllers/IssueController.ts +44 -0
- package/src/api/issues/entities/issues.ts +49 -0
- package/src/api/issues/index.ts +53 -0
- package/src/api/issues/schemas/createIssueSchema.ts +13 -0
- package/src/api/issues/schemas/issueConfigAtom.ts +13 -0
- package/src/api/issues/schemas/issueQuerySchema.ts +18 -0
- package/src/api/issues/schemas/issueResourceSchema.ts +6 -0
- package/src/api/issues/schemas/myIssueQuerySchema.ts +10 -0
- package/src/api/issues/schemas/updateIssueSchema.ts +13 -0
- package/src/api/issues/services/IssueService.ts +264 -0
- package/src/api/jobs/__tests__/$job.spec.ts +876 -0
- package/src/api/jobs/controllers/AdminJobController.ts +44 -0
- package/src/api/jobs/entities/jobExecutionEntity.ts +0 -2
- package/src/api/jobs/index.ts +0 -3
- package/src/api/jobs/primitives/$job.ts +22 -11
- package/src/api/jobs/providers/JobProvider.ts +229 -19
- package/src/api/jobs/schemas/jobConfigAtom.ts +4 -0
- package/src/api/jobs/schemas/jobCronInfoSchema.ts +1 -0
- package/src/api/jobs/schemas/jobExecutionQuerySchema.ts +0 -1
- package/src/api/jobs/schemas/jobQueueDepthSchema.ts +1 -0
- package/src/api/jobs/schemas/jobRegistrationSchema.ts +1 -6
- package/src/api/jobs/services/JobService.ts +51 -12
- package/src/api/notifications/schemas/notificationQuerySchema.ts +0 -1
- package/src/api/parameters/__tests__/$parameter.spec.ts +327 -0
- package/src/api/parameters/controllers/AdminParameterController.ts +29 -3
- package/src/api/parameters/index.browser.ts +12 -0
- package/src/api/parameters/primitives/$parameter.ts +20 -3
- package/src/api/parameters/services/ParameterProvider.ts +48 -7
- package/src/{billing → api/payments}/__tests__/PaymentMethodService.spec.ts +32 -6
- package/src/api/payments/__tests__/PaymentService.spec.ts +279 -0
- package/src/{billing/controllers/AdminBillingController.ts → api/payments/controllers/AdminPaymentController.ts} +26 -21
- package/src/{billing/controllers/BillingController.ts → api/payments/controllers/PaymentController.ts} +23 -11
- package/src/{billing → api/payments}/entities/paymentIntents.ts +1 -0
- package/src/{billing/errors/BillingError.ts → api/payments/errors/PaymentError.ts} +1 -1
- package/src/{billing → api/payments}/index.ts +31 -25
- package/src/{billing/providers/MemoryBillingProvider.ts → api/payments/providers/MemoryPaymentProvider.ts} +4 -4
- package/src/{billing/providers/BillingProvider.ts → api/payments/providers/PaymentProvider.ts} +9 -2
- package/src/{billing → api/payments}/services/PaymentMethodService.ts +5 -5
- package/src/{billing/services/BillingService.ts → api/payments/services/PaymentService.ts} +94 -18
- package/src/api/subscriptions/__tests__/BillingService.spec.ts +218 -0
- package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +278 -0
- package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +212 -0
- package/src/api/subscriptions/controllers/SubscriptionController.ts +189 -0
- package/src/api/subscriptions/entities/subscriptionEvents.ts +54 -0
- package/src/api/subscriptions/entities/subscriptions.ts +68 -0
- package/src/api/subscriptions/index.ts +144 -0
- package/src/api/subscriptions/jobs/SubscriptionJobs.ts +382 -0
- package/src/api/subscriptions/middleware/$requireLimit.ts +50 -0
- package/src/api/subscriptions/middleware/$requirePlan.ts +49 -0
- package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +110 -0
- package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +8 -0
- package/src/api/subscriptions/schemas/changePlanSchema.ts +9 -0
- package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +11 -0
- package/src/api/subscriptions/schemas/entitlementsSchema.ts +21 -0
- package/src/api/subscriptions/schemas/mrrSchema.ts +13 -0
- package/src/api/subscriptions/schemas/planDefinitionSchema.ts +71 -0
- package/src/api/subscriptions/schemas/planResourceSchema.ts +25 -0
- package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +8 -0
- package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +19 -0
- package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +6 -0
- package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +32 -0
- package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +23 -0
- package/src/api/subscriptions/services/BillingService.ts +437 -0
- package/src/api/subscriptions/services/SubscriptionConfig.ts +56 -0
- package/src/api/subscriptions/services/SubscriptionService.ts +867 -0
- package/src/api/subscriptions/services/UsageService.ts +118 -0
- package/src/api/users/__tests__/AdminUserController.spec.ts +80 -1
- package/src/api/users/__tests__/CredentialService.spec.ts +177 -0
- package/src/api/users/__tests__/EmailVerification.spec.ts +29 -18
- package/src/api/users/__tests__/PasswordReset.spec.ts +3 -0
- package/src/api/users/__tests__/RegistrationService.spec.ts +148 -1
- package/src/api/users/__tests__/SessionService.spec.ts +142 -1
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +10 -1
- package/src/api/users/controllers/UserController.ts +3 -8
- package/src/api/users/notifications/UserNotifications.ts +23 -0
- package/src/api/users/primitives/$realm.ts +24 -0
- package/src/api/users/schemas/loginSchema.ts +1 -1
- package/src/api/users/services/CredentialService.ts +57 -7
- package/src/api/users/services/RegistrationService.ts +50 -11
- package/src/api/users/services/SessionService.ts +64 -9
- package/src/api/users/services/UserService.ts +21 -12
- package/src/api/workflows/__tests__/$workflow.spec.ts +616 -0
- package/src/api/workflows/controllers/AdminWorkflowController.ts +191 -0
- package/src/api/workflows/entities/workflowExecutions.ts +74 -0
- package/src/api/workflows/entities/workflowStepExecutions.ts +74 -0
- package/src/api/workflows/entities/workflowStepLogs.ts +13 -0
- package/src/api/workflows/index.browser.ts +22 -0
- package/src/api/workflows/index.ts +124 -0
- package/src/api/workflows/jobs/WorkflowJobs.ts +77 -0
- package/src/api/workflows/primitives/$workflow.ts +202 -0
- package/src/api/workflows/providers/WorkflowProvider.ts +1284 -0
- package/src/api/workflows/schemas/workflowActivitySchema.ts +15 -0
- package/src/api/workflows/schemas/workflowConfigAtom.ts +51 -0
- package/src/api/workflows/schemas/workflowExecutionDetailSchema.ts +18 -0
- package/src/api/workflows/schemas/workflowExecutionQuerySchema.ts +26 -0
- package/src/api/workflows/schemas/workflowExecutionResourceSchema.ts +30 -0
- package/src/api/workflows/schemas/workflowRegistrationSchema.ts +26 -0
- package/src/api/workflows/schemas/workflowStatsSchema.ts +16 -0
- package/src/api/workflows/schemas/workflowStepExecutionResourceSchema.ts +15 -0
- package/src/api/workflows/services/WorkflowService.ts +382 -0
- package/src/captcha/__tests__/MemoryCaptchaProvider.spec.ts +74 -0
- package/src/captcha/index.ts +33 -0
- package/src/captcha/providers/CaptchaProvider.ts +17 -0
- package/src/captcha/providers/MemoryCaptchaProvider.ts +65 -0
- package/src/captcha/providers/TurnstileCaptchaProvider.ts +125 -0
- package/src/cli/core/atoms/buildOptions.ts +57 -0
- package/src/cli/core/commands/build.ts +2 -0
- package/src/cli/core/providers/ViteDevServerProvider.ts +1 -1
- package/src/cli/core/services/ViteUtils.ts +5 -2
- package/src/cli/core/tasks/BuildClientTask.ts +3 -1
- package/src/cli/core/tasks/BuildCloudflareTask.ts +4 -0
- package/src/cli/core/tasks/BuildPwaTask.ts +81 -0
- package/src/cli/core/templates/webAppRouterTs.ts +5 -58
- package/src/cli/platform/adapters/CloudflareAdapter.ts +24 -0
- package/src/cli/platform/atoms/platformOptions.ts +19 -3
- package/src/cli/platform/hooks/PlatformHook.ts +51 -0
- package/src/cli/platform/index.ts +1 -0
- package/src/cli/platform/services/CloudflareApi.ts +22 -1
- package/src/cli/platform/services/PlatformOrchestrator.ts +67 -2
- package/src/cli/vendor/__tests__/VendorService.spec.ts +322 -178
- package/src/cli/vendor/commands/VendorCommand.ts +41 -38
- package/src/cli/vendor/services/VendorService.ts +234 -31
- package/src/command/__tests__/CliProvider.spec.ts +45 -0
- package/src/command/providers/CliProvider.ts +3 -4
- package/src/core/__tests__/TypeProvider.spec.ts +4 -2
- package/src/core/providers/SchemaValidator.ts +1 -1
- package/src/core/providers/TypeProvider.ts +46 -3
- package/src/orm/__tests__/enums.spec.ts +22 -29
- package/src/orm/__tests__/orm-showcase-tests.ts +430 -0
- package/src/orm/__tests__/orm-showcase.spec.ts +167 -0
- package/src/orm/core/providers/DatabaseTypeProvider.ts +0 -29
- package/src/orm/core/services/Repository.ts +20 -6
- package/src/orm/postgres/services/PostgresModelBuilder.ts +3 -6
- package/src/react/i18n/__tests__/I18nProvider.spec.ts +83 -0
- package/src/react/i18n/providers/I18nProvider.ts +12 -10
- package/src/react/router/__tests__/$page.browser.spec.tsx +157 -0
- package/src/react/router/providers/ReactBrowserProvider.ts +39 -0
- package/src/react/router/providers/ReactBrowserRouterProvider.ts +22 -0
- package/src/security/__tests__/$secure-combinations.spec.ts +945 -0
- package/src/security/primitives/$issuer.ts +3 -1
- package/src/security/primitives/$secure.ts +28 -0
- package/src/server/auth/index.ts +7 -0
- package/src/server/auth/primitives/$auth.ts +37 -3
- package/src/server/auth/primitives/$authApple.ts +114 -4
- package/src/server/auth/primitives/$authFacebook.ts +98 -0
- package/src/server/auth/primitives/$authFranceConnect.ts +105 -0
- package/src/server/auth/primitives/$authGithub.ts +22 -16
- package/src/server/auth/primitives/$authMicrosoft.ts +88 -0
- package/src/server/auth/providers/ServerAuthProvider.ts +197 -72
- package/src/server/cookies/providers/ServerCookiesProvider.ts +3 -0
- package/src/server/core/__tests__/ServerRouterProvider-errorHandler.spec.ts +1 -1
- package/src/websocket/providers/NodeWebSocketServerProvider.ts +3 -1
- package/dist/billing/index.d.ts.map +0 -1
- package/dist/billing/index.js.map +0 -1
- package/src/billing/__tests__/BillingService.spec.ts +0 -136
- /package/src/{billing → api/payments}/entities/paymentMethods.ts +0 -0
- /package/src/{billing → api/payments}/entities/refunds.ts +0 -0
- /package/src/{billing → api/payments}/schemas/intentSchemas.ts +0 -0
- /package/src/{billing → api/payments}/schemas/paymentMethodSchemas.ts +0 -0
- /package/src/{billing → api/payments}/schemas/refundSchemas.ts +0 -0
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
CryptoProvider,
|
|
8
8
|
InvalidCredentialsError,
|
|
9
9
|
} from "alepha/security";
|
|
10
|
-
import { BadRequestError } from "alepha/server";
|
|
10
|
+
import { BadRequestError, UnauthorizedError } from "alepha/server";
|
|
11
11
|
import { describe, it } from "vitest";
|
|
12
12
|
import {
|
|
13
13
|
AlephaApiUsers,
|
|
@@ -320,6 +320,32 @@ describe("alepha/api/users - SessionService.login", () => {
|
|
|
320
320
|
).rejects.toThrowError(InvalidCredentialsError);
|
|
321
321
|
});
|
|
322
322
|
|
|
323
|
+
it("should reject login for disabled user", async ({ expect }) => {
|
|
324
|
+
const { sessionService, userService, cryptoProvider, identities } =
|
|
325
|
+
await setup();
|
|
326
|
+
|
|
327
|
+
const password = "disabledUserPass123!";
|
|
328
|
+
const hashedPassword = await cryptoProvider.hashPassword(password);
|
|
329
|
+
|
|
330
|
+
const user = await userService.users().create({
|
|
331
|
+
username: "disableduser",
|
|
332
|
+
email: "disabled@example.com",
|
|
333
|
+
roles: ["user"],
|
|
334
|
+
enabled: false,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
await identities.create({
|
|
338
|
+
provider: "local",
|
|
339
|
+
providerUserId: "disabled@example.com",
|
|
340
|
+
userId: user.id,
|
|
341
|
+
password: hashedPassword,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
await expect(
|
|
345
|
+
sessionService.login("local", "disabled@example.com", password),
|
|
346
|
+
).rejects.toThrowError(InvalidCredentialsError);
|
|
347
|
+
});
|
|
348
|
+
|
|
323
349
|
describe("login rate limiting", () => {
|
|
324
350
|
it("should block account after max failed attempts", async ({ expect }) => {
|
|
325
351
|
const { sessionService, userService, cryptoProvider, identities } =
|
|
@@ -775,4 +801,119 @@ describe("alepha/api/users - SessionService.link", () => {
|
|
|
775
801
|
|
|
776
802
|
expect(result.id).toBe(user.id);
|
|
777
803
|
});
|
|
804
|
+
|
|
805
|
+
it("should refuse auto-link when OAuth provider says email_verified is false", async ({
|
|
806
|
+
expect,
|
|
807
|
+
}) => {
|
|
808
|
+
const { sessionService, userService } = await setup();
|
|
809
|
+
|
|
810
|
+
await userService.users().create({
|
|
811
|
+
username: "verifieduser",
|
|
812
|
+
email: "verified@example.com",
|
|
813
|
+
roles: ["user"],
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
await expect(
|
|
817
|
+
sessionService.link("google", {
|
|
818
|
+
sub: "google-unverified",
|
|
819
|
+
email: "verified@example.com",
|
|
820
|
+
name: "Unverified User",
|
|
821
|
+
email_verified: false,
|
|
822
|
+
}),
|
|
823
|
+
).rejects.toThrowError(BadRequestError);
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
it("should allow auto-link when email_verified is true or undefined", async ({
|
|
827
|
+
expect,
|
|
828
|
+
}) => {
|
|
829
|
+
const { sessionService, userService } = await setup();
|
|
830
|
+
|
|
831
|
+
const user = await userService.users().create({
|
|
832
|
+
username: "linkableuser",
|
|
833
|
+
email: "linkable@example.com",
|
|
834
|
+
roles: ["user"],
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
const result = await sessionService.link("google", {
|
|
838
|
+
sub: "google-verified",
|
|
839
|
+
email: "linkable@example.com",
|
|
840
|
+
name: "Verified User",
|
|
841
|
+
email_verified: true,
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
expect(result.id).toBe(user.id);
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
it("should use defaultRoles from realm settings for new OAuth users", async ({
|
|
848
|
+
expect,
|
|
849
|
+
}) => {
|
|
850
|
+
const { sessionService, alepha } = await setup();
|
|
851
|
+
|
|
852
|
+
const realmProvider = alepha.inject(RealmProvider);
|
|
853
|
+
realmProvider.register("custom-roles", {
|
|
854
|
+
settings: {
|
|
855
|
+
registrationAllowed: true,
|
|
856
|
+
defaultRoles: ["member", "viewer"],
|
|
857
|
+
} as never,
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
const result = await sessionService.link(
|
|
861
|
+
"google",
|
|
862
|
+
{
|
|
863
|
+
sub: "google-newuser-roles",
|
|
864
|
+
email: "newuser-roles@example.com",
|
|
865
|
+
name: "New Role User",
|
|
866
|
+
},
|
|
867
|
+
"custom-roles",
|
|
868
|
+
);
|
|
869
|
+
|
|
870
|
+
expect("roles" in result && result.roles).toEqual(["member", "viewer"]);
|
|
871
|
+
});
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
describe("alepha/api/users - SessionService.refreshSession", () => {
|
|
875
|
+
it("should reject refresh for disabled user and delete session", async ({
|
|
876
|
+
expect,
|
|
877
|
+
}) => {
|
|
878
|
+
const { sessionService, userService, cryptoProvider, identities } =
|
|
879
|
+
await setup();
|
|
880
|
+
|
|
881
|
+
const password = "refreshDisabledPass!";
|
|
882
|
+
const hashedPassword = await cryptoProvider.hashPassword(password);
|
|
883
|
+
|
|
884
|
+
const user = await userService.users().create({
|
|
885
|
+
username: "refreshdisableduser",
|
|
886
|
+
email: "refresh-disabled@example.com",
|
|
887
|
+
roles: ["user"],
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
await identities.create({
|
|
891
|
+
provider: "local",
|
|
892
|
+
providerUserId: "refresh-disabled@example.com",
|
|
893
|
+
userId: user.id,
|
|
894
|
+
password: hashedPassword,
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
// Login to get a session with refresh token
|
|
898
|
+
await sessionService.login(
|
|
899
|
+
"local",
|
|
900
|
+
"refresh-disabled@example.com",
|
|
901
|
+
password,
|
|
902
|
+
);
|
|
903
|
+
|
|
904
|
+
const { refreshToken } = await sessionService.createSession(user, 3600);
|
|
905
|
+
|
|
906
|
+
// Disable the user
|
|
907
|
+
await userService.users().updateById(user.id, { enabled: false });
|
|
908
|
+
|
|
909
|
+
// Refresh should fail with UnauthorizedError
|
|
910
|
+
await expect(
|
|
911
|
+
sessionService.refreshSession(refreshToken),
|
|
912
|
+
).rejects.toThrowError(UnauthorizedError);
|
|
913
|
+
|
|
914
|
+
// Session should be deleted — a second refresh should also fail
|
|
915
|
+
await expect(
|
|
916
|
+
sessionService.refreshSession(refreshToken),
|
|
917
|
+
).rejects.toThrowError();
|
|
918
|
+
});
|
|
778
919
|
});
|
|
@@ -70,6 +70,15 @@ export const realmAuthSettingsAtom = $atom({
|
|
|
70
70
|
description:
|
|
71
71
|
"List of usernames that are automatically promoted to admin role on login",
|
|
72
72
|
}),
|
|
73
|
+
defaultRoles: t.array(t.string(), {
|
|
74
|
+
description: "Default roles assigned to newly registered users",
|
|
75
|
+
}),
|
|
76
|
+
verifyEmailUrl: t.optional(
|
|
77
|
+
t.string({
|
|
78
|
+
description:
|
|
79
|
+
"Base URL for email verification links (used when verification method is 'link'). Token and email are appended as query params.",
|
|
80
|
+
}),
|
|
81
|
+
),
|
|
73
82
|
passwordPolicy: t.object({
|
|
74
83
|
minLength: t.integer({
|
|
75
84
|
description: "Minimum password length",
|
|
@@ -122,7 +131,7 @@ export const realmAuthSettingsAtom = $atom({
|
|
|
122
131
|
firstNameLastName: "none" as FieldRequirement,
|
|
123
132
|
adminEmails: [],
|
|
124
133
|
adminUsernames: [],
|
|
125
|
-
|
|
134
|
+
defaultRoles: ["user"],
|
|
126
135
|
passwordPolicy: {
|
|
127
136
|
minLength: 8,
|
|
128
137
|
requireUppercase: true,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { $inject, t } from "alepha";
|
|
2
|
+
import { $secure } from "alepha/security";
|
|
2
3
|
import { $action, okSchema } from "alepha/server";
|
|
3
4
|
import { completePasswordResetRequestSchema } from "../schemas/completePasswordResetRequestSchema.ts";
|
|
4
5
|
import { completeRegistrationRequestSchema } from "../schemas/completeRegistrationRequestSchema.ts";
|
|
@@ -216,13 +217,7 @@ export class UserController {
|
|
|
216
217
|
t.enum(["code", "link"], {
|
|
217
218
|
default: "code",
|
|
218
219
|
description:
|
|
219
|
-
'Verification method: "code" sends a 6-digit code, "link" sends a clickable verification link.',
|
|
220
|
-
}),
|
|
221
|
-
),
|
|
222
|
-
verifyUrl: t.optional(
|
|
223
|
-
t.string({
|
|
224
|
-
description:
|
|
225
|
-
'Base URL for verification link. Required when method is "link". Token and email will be appended as query params.',
|
|
220
|
+
'Verification method: "code" sends a 6-digit code, "link" sends a clickable verification link. When using "link", configure verifyEmailUrl in realm settings.',
|
|
226
221
|
}),
|
|
227
222
|
),
|
|
228
223
|
}),
|
|
@@ -240,7 +235,6 @@ export class UserController {
|
|
|
240
235
|
body.email,
|
|
241
236
|
query.userRealmName,
|
|
242
237
|
method,
|
|
243
|
-
query.verifyUrl,
|
|
244
238
|
);
|
|
245
239
|
|
|
246
240
|
return {
|
|
@@ -293,6 +287,7 @@ export class UserController {
|
|
|
293
287
|
public checkEmailVerification = $action({
|
|
294
288
|
path: "/users/email-verification/check",
|
|
295
289
|
group: this.group,
|
|
290
|
+
use: [$secure()],
|
|
296
291
|
schema: {
|
|
297
292
|
query: t.object({
|
|
298
293
|
email: t.email(),
|
|
@@ -109,6 +109,29 @@ export class UserNotifications {
|
|
|
109
109
|
}),
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
+
public readonly accountLockout = $notification({
|
|
113
|
+
category: "security",
|
|
114
|
+
description:
|
|
115
|
+
"Email sent to users when their account is temporarily locked due to too many failed login attempts.",
|
|
116
|
+
critical: true,
|
|
117
|
+
sensitive: true,
|
|
118
|
+
email: {
|
|
119
|
+
subject: "Account temporarily locked",
|
|
120
|
+
body: (it) => `
|
|
121
|
+
<h1>Account Temporarily Locked</h1>
|
|
122
|
+
<p>Hi ${it.email},</p>
|
|
123
|
+
<p>Your account has been temporarily locked due to too many failed login attempts.</p>
|
|
124
|
+
<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>
|
|
125
|
+
<p>If this wasn't you, someone may be trying to access your account. We recommend changing your password as soon as possible.</p>
|
|
126
|
+
<p>Best regards,<br>The Team</p>
|
|
127
|
+
`,
|
|
128
|
+
},
|
|
129
|
+
schema: t.object({
|
|
130
|
+
email: t.string({ format: "email" }),
|
|
131
|
+
lockoutMinutes: t.number(),
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
134
|
+
|
|
112
135
|
public readonly emailVerificationLink = $notification({
|
|
113
136
|
category: "security",
|
|
114
137
|
description:
|
|
@@ -12,9 +12,13 @@ import {
|
|
|
12
12
|
SecurityProvider,
|
|
13
13
|
} from "alepha/security";
|
|
14
14
|
import {
|
|
15
|
+
$authApple,
|
|
15
16
|
$authCredentials,
|
|
17
|
+
$authFacebook,
|
|
18
|
+
$authFranceConnect,
|
|
16
19
|
$authGithub,
|
|
17
20
|
$authGoogle,
|
|
21
|
+
$authMicrosoft,
|
|
18
22
|
type AuthPrimitive,
|
|
19
23
|
type Credentials,
|
|
20
24
|
type LinkAccountOptions,
|
|
@@ -205,6 +209,22 @@ export const $realm = (options: RealmOptions = {}): RealmPrimitive => {
|
|
|
205
209
|
auth.github = $authGithub(realm);
|
|
206
210
|
}
|
|
207
211
|
|
|
212
|
+
if (identities.apple) {
|
|
213
|
+
auth.apple = $authApple(realm);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (identities.facebook) {
|
|
217
|
+
auth.facebook = $authFacebook(realm);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (identities.microsoft) {
|
|
221
|
+
auth.microsoft = $authMicrosoft(realm);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (identities.franceconnect) {
|
|
225
|
+
auth.franceconnect = $authFranceConnect(realm);
|
|
226
|
+
}
|
|
227
|
+
|
|
208
228
|
alepha.with(() => auth);
|
|
209
229
|
}
|
|
210
230
|
|
|
@@ -315,6 +335,10 @@ export interface RealmOptions {
|
|
|
315
335
|
credentials?: true;
|
|
316
336
|
google?: true;
|
|
317
337
|
github?: true;
|
|
338
|
+
apple?: true;
|
|
339
|
+
facebook?: true;
|
|
340
|
+
microsoft?: true;
|
|
341
|
+
franceconnect?: true;
|
|
318
342
|
};
|
|
319
343
|
|
|
320
344
|
/**
|
|
@@ -7,6 +7,7 @@ import { $logger } from "alepha/logger";
|
|
|
7
7
|
import { CryptoProvider } from "alepha/security";
|
|
8
8
|
import { BadRequestError, HttpError } from "alepha/server";
|
|
9
9
|
import { $client } from "alepha/server/links";
|
|
10
|
+
import type { RealmAuthSettings } from "../atoms/realmAuthSettingsAtom.ts";
|
|
10
11
|
import { UserAudits } from "../audits/UserAudits.ts";
|
|
11
12
|
import { UserNotifications } from "../notifications/UserNotifications.ts";
|
|
12
13
|
import { RealmProvider } from "../providers/RealmProvider.ts";
|
|
@@ -67,6 +68,38 @@ export class CredentialService {
|
|
|
67
68
|
return this.realmProvider.identityRepository(userRealmName);
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Validate a password against the realm's password policy.
|
|
73
|
+
*/
|
|
74
|
+
public validatePasswordPolicy(
|
|
75
|
+
password: string,
|
|
76
|
+
policy: RealmAuthSettings["passwordPolicy"],
|
|
77
|
+
): void {
|
|
78
|
+
if (password.length < policy.minLength) {
|
|
79
|
+
throw new BadRequestError(
|
|
80
|
+
`Password must be at least ${policy.minLength} characters`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if (policy.requireUppercase && !/[A-Z]/.test(password)) {
|
|
84
|
+
throw new BadRequestError(
|
|
85
|
+
"Password must contain at least one uppercase letter",
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (policy.requireLowercase && !/[a-z]/.test(password)) {
|
|
89
|
+
throw new BadRequestError(
|
|
90
|
+
"Password must contain at least one lowercase letter",
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (policy.requireNumbers && !/\d/.test(password)) {
|
|
94
|
+
throw new BadRequestError("Password must contain at least one number");
|
|
95
|
+
}
|
|
96
|
+
if (policy.requireSpecialCharacters && !/[^a-zA-Z0-9]/.test(password)) {
|
|
97
|
+
throw new BadRequestError(
|
|
98
|
+
"Password must contain at least one special character",
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
70
103
|
/**
|
|
71
104
|
* Phase 1: Create a password reset intent.
|
|
72
105
|
*
|
|
@@ -90,6 +123,14 @@ export class CredentialService {
|
|
|
90
123
|
.add(INTENT_TTL_MINUTES, "minutes")
|
|
91
124
|
.toISOString();
|
|
92
125
|
|
|
126
|
+
// Check if password reset is allowed for this realm
|
|
127
|
+
const realm = this.realmProvider.getRealm(userRealmName);
|
|
128
|
+
const realmSettings = await realm.getSettings();
|
|
129
|
+
if (realmSettings.resetPasswordAllowed === false) {
|
|
130
|
+
this.log.debug("Password reset not allowed for realm", { userRealmName });
|
|
131
|
+
return { intentId, expiresAt };
|
|
132
|
+
}
|
|
133
|
+
|
|
93
134
|
// Find user by email (silent fail for security)
|
|
94
135
|
const user = await this.users(userRealmName).findOne({
|
|
95
136
|
where: { email: { eq: email } },
|
|
@@ -187,6 +228,11 @@ export class CredentialService {
|
|
|
187
228
|
});
|
|
188
229
|
}
|
|
189
230
|
|
|
231
|
+
// Validate password against realm policy before consuming the verification code
|
|
232
|
+
const realm = this.realmProvider.getRealm(intent.realmName);
|
|
233
|
+
const realmSettings = await realm.getSettings();
|
|
234
|
+
this.validatePasswordPolicy(body.newPassword, realmSettings.passwordPolicy);
|
|
235
|
+
|
|
190
236
|
// Verify code using verification controller
|
|
191
237
|
const result = await this.verificationController
|
|
192
238
|
.validateVerificationCode({
|
|
@@ -210,9 +256,6 @@ export class CredentialService {
|
|
|
210
256
|
throw new BadRequestError("Verification code has already been used");
|
|
211
257
|
}
|
|
212
258
|
|
|
213
|
-
// Atomically delete cache key to prevent replay
|
|
214
|
-
await this.intentCache.invalidate(body.intentId);
|
|
215
|
-
|
|
216
259
|
// Hash the new password
|
|
217
260
|
const hashedPassword = await this.cryptoProvider.hashPassword(
|
|
218
261
|
body.newPassword,
|
|
@@ -228,13 +271,17 @@ export class CredentialService {
|
|
|
228
271
|
userId: { eq: intent.userId },
|
|
229
272
|
});
|
|
230
273
|
|
|
274
|
+
// Invalidate intent after all operations succeed,
|
|
275
|
+
// so the user can retry if password update or session cleanup fails.
|
|
276
|
+
// The verification code was already consumed (verifiedAt set), so replay is
|
|
277
|
+
// prevented even if the intent is still in cache.
|
|
278
|
+
await this.intentCache.invalidate(body.intentId);
|
|
279
|
+
|
|
231
280
|
this.log.info("Password reset completed", {
|
|
232
281
|
userId: intent.userId,
|
|
233
282
|
email: intent.email,
|
|
234
283
|
});
|
|
235
284
|
|
|
236
|
-
const realm = this.realmProvider.getRealm(intent.realmName);
|
|
237
|
-
|
|
238
285
|
// Audit: password reset
|
|
239
286
|
await this.userAudits(intent.realmName)?.recordUser("update", {
|
|
240
287
|
userId: intent.userId,
|
|
@@ -320,6 +367,11 @@ export class CredentialService {
|
|
|
320
367
|
throw new BadRequestError("Invalid or expired reset token");
|
|
321
368
|
}
|
|
322
369
|
|
|
370
|
+
// Validate password against realm policy
|
|
371
|
+
const realm = this.realmProvider.getRealm(userRealmName);
|
|
372
|
+
const realmSettings = await realm.getSettings();
|
|
373
|
+
this.validatePasswordPolicy(newPassword, realmSettings.passwordPolicy);
|
|
374
|
+
|
|
323
375
|
// Find user and identity
|
|
324
376
|
const user = await this.users(userRealmName).getOne({
|
|
325
377
|
where: { email: { eq: email } },
|
|
@@ -345,8 +397,6 @@ export class CredentialService {
|
|
|
345
397
|
userId: { eq: user.id },
|
|
346
398
|
});
|
|
347
399
|
|
|
348
|
-
const realm = this.realmProvider.getRealm(userRealmName);
|
|
349
|
-
|
|
350
400
|
// Audit: password reset (legacy method)
|
|
351
401
|
await this.userAudits(userRealmName)?.recordUser("update", {
|
|
352
402
|
userId: user.id,
|
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { $inject, Alepha, AlephaError } from "alepha";
|
|
3
3
|
import type { VerificationController } from "alepha/api/verifications";
|
|
4
4
|
import { $cache } from "alepha/cache";
|
|
5
|
+
import { CaptchaProvider } from "alepha/captcha";
|
|
5
6
|
import { DateTimeProvider } from "alepha/datetime";
|
|
6
7
|
import { $logger } from "alepha/logger";
|
|
7
8
|
import { CryptoProvider } from "alepha/security";
|
|
@@ -14,6 +15,7 @@ import { RealmProvider } from "../providers/RealmProvider.ts";
|
|
|
14
15
|
import type { CompleteRegistrationRequest } from "../schemas/completeRegistrationRequestSchema.ts";
|
|
15
16
|
import type { RegisterRequest } from "../schemas/registerRequestSchema.ts";
|
|
16
17
|
import type { RegistrationIntentResponse } from "../schemas/registrationIntentResponseSchema.ts";
|
|
18
|
+
import { CredentialService } from "./CredentialService.ts";
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* Intent stored in cache during the registration flow.
|
|
@@ -46,12 +48,19 @@ export class RegistrationService {
|
|
|
46
48
|
protected readonly cryptoProvider = $inject(CryptoProvider);
|
|
47
49
|
protected readonly verificationController = $client<VerificationController>();
|
|
48
50
|
protected readonly realmProvider = $inject(RealmProvider);
|
|
51
|
+
protected readonly credentialService = $inject(CredentialService);
|
|
52
|
+
protected readonly captchaProvider = $inject(CaptchaProvider);
|
|
49
53
|
|
|
50
54
|
protected readonly intentCache = $cache<RegistrationIntent>({
|
|
51
55
|
name: "api:users:registrations",
|
|
52
56
|
ttl: [INTENT_TTL_MINUTES, "minutes"],
|
|
53
57
|
});
|
|
54
58
|
|
|
59
|
+
protected readonly rateLimitCache = $cache<number>({
|
|
60
|
+
name: "api:users:registration-rate-limit",
|
|
61
|
+
ttl: [15, "minutes"],
|
|
62
|
+
});
|
|
63
|
+
|
|
55
64
|
protected userAudits(realmName?: string) {
|
|
56
65
|
const realm = this.realmProvider.getRealm(realmName);
|
|
57
66
|
if (realm.features.audits) {
|
|
@@ -88,6 +97,20 @@ export class RegistrationService {
|
|
|
88
97
|
userRealmName,
|
|
89
98
|
});
|
|
90
99
|
|
|
100
|
+
// IP rate limiting
|
|
101
|
+
const request = this.alepha.store.get("alepha.http.request");
|
|
102
|
+
const ipKey = request?.ip ? `register:ip:${request.ip}` : undefined;
|
|
103
|
+
if (ipKey) {
|
|
104
|
+
const count = (await this.rateLimitCache.get(ipKey)) ?? 0;
|
|
105
|
+
if (count >= 10) {
|
|
106
|
+
this.log.warn("Registration rate limit exceeded", { ip: request?.ip });
|
|
107
|
+
throw new BadRequestError(
|
|
108
|
+
"Too many registration attempts, please try again later",
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
await this.rateLimitCache.set(ipKey, count + 1);
|
|
112
|
+
}
|
|
113
|
+
|
|
91
114
|
const realm = this.realmProvider.getRealm(userRealmName);
|
|
92
115
|
const realmSettings = await realm.getSettings();
|
|
93
116
|
|
|
@@ -106,6 +129,9 @@ export class RegistrationService {
|
|
|
106
129
|
}
|
|
107
130
|
|
|
108
131
|
if (body.username) {
|
|
132
|
+
// Security note: usernameRegExp is admin-controlled (from realmAuthSettingsAtom),
|
|
133
|
+
// not user input. Default is ^[a-zA-Z0-9_]{3,30}$ which is ReDoS-safe.
|
|
134
|
+
// No need for regex timeout or safe-regex validation here.
|
|
109
135
|
const usernameRegExp = realmSettings?.usernameRegExp;
|
|
110
136
|
if (usernameRegExp) {
|
|
111
137
|
const regex = new RegExp(usernameRegExp);
|
|
@@ -138,6 +164,12 @@ export class RegistrationService {
|
|
|
138
164
|
// Check for existing users (username, email, phone)
|
|
139
165
|
await this.checkUserAvailability(body, userRealmName);
|
|
140
166
|
|
|
167
|
+
// Validate password against realm policy
|
|
168
|
+
this.credentialService.validatePasswordPolicy(
|
|
169
|
+
body.password,
|
|
170
|
+
realmSettings.passwordPolicy,
|
|
171
|
+
);
|
|
172
|
+
|
|
141
173
|
// Hash the password
|
|
142
174
|
const passwordHash = await this.cryptoProvider.hashPassword(body.password);
|
|
143
175
|
|
|
@@ -261,12 +293,16 @@ export class RegistrationService {
|
|
|
261
293
|
await this.verifyPhoneCode(intent.data.phoneNumber, body.phoneCode);
|
|
262
294
|
}
|
|
263
295
|
|
|
264
|
-
// Validate captcha if required
|
|
296
|
+
// Validate captcha if required
|
|
265
297
|
if (intent.requirements.captcha) {
|
|
266
298
|
if (!body.captchaToken) {
|
|
267
299
|
throw new BadRequestError("Captcha verification is required");
|
|
268
300
|
}
|
|
269
|
-
|
|
301
|
+
|
|
302
|
+
const valid = await this.captchaProvider.verify(body.captchaToken);
|
|
303
|
+
if (!valid) {
|
|
304
|
+
throw new BadRequestError("Captcha verification failed");
|
|
305
|
+
}
|
|
270
306
|
}
|
|
271
307
|
|
|
272
308
|
// Final availability check (race condition guard)
|
|
@@ -279,19 +315,19 @@ export class RegistrationService {
|
|
|
279
315
|
userRealmName,
|
|
280
316
|
);
|
|
281
317
|
|
|
282
|
-
|
|
283
|
-
await
|
|
318
|
+
const realm = this.realmProvider.getRealm(userRealmName);
|
|
319
|
+
const realmSettings = await realm.getSettings();
|
|
284
320
|
|
|
285
321
|
// Create the user
|
|
286
322
|
const user = await userRepository.create({
|
|
287
|
-
realm:
|
|
323
|
+
realm: realm.name,
|
|
288
324
|
username: intent.data.username,
|
|
289
325
|
email: intent.data.email,
|
|
290
326
|
phoneNumber: intent.data.phoneNumber,
|
|
291
327
|
firstName: intent.data.firstName,
|
|
292
328
|
lastName: intent.data.lastName,
|
|
293
329
|
picture: intent.data.picture,
|
|
294
|
-
roles:
|
|
330
|
+
roles: realmSettings.defaultRoles,
|
|
295
331
|
enabled: true,
|
|
296
332
|
emailVerified: intent.requirements.email, // Marked as verified if we verified during registration
|
|
297
333
|
});
|
|
@@ -303,14 +339,16 @@ export class RegistrationService {
|
|
|
303
339
|
password: intent.data.passwordHash,
|
|
304
340
|
});
|
|
305
341
|
|
|
342
|
+
// Invalidate intent after both user and identity creation succeed,
|
|
343
|
+
// so the user can retry if either operation fails.
|
|
344
|
+
await this.intentCache.invalidate(body.intentId);
|
|
345
|
+
|
|
306
346
|
this.log.info("User registered successfully", {
|
|
307
347
|
userId: user.id,
|
|
308
348
|
email: user.email,
|
|
309
349
|
username: user.username,
|
|
310
350
|
});
|
|
311
351
|
|
|
312
|
-
const realm = this.realmProvider.getRealm(userRealmName);
|
|
313
|
-
|
|
314
352
|
await this.userAudits(userRealmName)?.recordUser("create", {
|
|
315
353
|
userId: user.id,
|
|
316
354
|
userEmail: user.email ?? undefined,
|
|
@@ -335,11 +373,12 @@ export class RegistrationService {
|
|
|
335
373
|
body: Pick<RegisterRequest, "username" | "email" | "phoneNumber">,
|
|
336
374
|
userRealmName?: string,
|
|
337
375
|
): Promise<void> {
|
|
376
|
+
const realm = this.realmProvider.getRealm(userRealmName);
|
|
338
377
|
const userRepository = this.realmProvider.userRepository(userRealmName);
|
|
339
378
|
|
|
340
379
|
if (body.username) {
|
|
341
380
|
const existingUser = await userRepository.findOne({
|
|
342
|
-
where: { username: { ilike: body.username } },
|
|
381
|
+
where: { realm: realm.name, username: { ilike: body.username } },
|
|
343
382
|
});
|
|
344
383
|
if (existingUser) {
|
|
345
384
|
this.log.debug("Username already taken", { username: body.username });
|
|
@@ -349,7 +388,7 @@ export class RegistrationService {
|
|
|
349
388
|
|
|
350
389
|
if (body.email) {
|
|
351
390
|
const existingUser = await userRepository.findOne({
|
|
352
|
-
where: { email: { eq: body.email } },
|
|
391
|
+
where: { realm: realm.name, email: { eq: body.email } },
|
|
353
392
|
});
|
|
354
393
|
if (existingUser) {
|
|
355
394
|
this.log.debug("Email already taken", { email: body.email });
|
|
@@ -359,7 +398,7 @@ export class RegistrationService {
|
|
|
359
398
|
|
|
360
399
|
if (body.phoneNumber) {
|
|
361
400
|
const existingUser = await userRepository.findOne({
|
|
362
|
-
where: { phoneNumber: { eq: body.phoneNumber } },
|
|
401
|
+
where: { realm: realm.name, phoneNumber: { eq: body.phoneNumber } },
|
|
363
402
|
});
|
|
364
403
|
if (existingUser) {
|
|
365
404
|
this.log.debug("Phone number already taken", {
|