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
package/dist/api/users/index.js
CHANGED
|
@@ -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"
|
|
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
|
|
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 =
|
|
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: {
|
|
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: {
|
|
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: {
|
|
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 ??
|
|
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
|
-
|
|
1701
|
+
const realm = this.realmProvider.getRealm(userRealmName);
|
|
1702
|
+
const realmSettings = await realm.getSettings();
|
|
1624
1703
|
const user = await userRepository.create({
|
|
1625
|
-
realm:
|
|
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:
|
|
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: {
|
|
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: {
|
|
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: {
|
|
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
|
|
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 !
|
|
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))
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
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: {
|
|
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:
|
|
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:
|
|
2788
|
+
minLength: 8,
|
|
2649
2789
|
description: "User password"
|
|
2650
2790
|
})
|
|
2651
2791
|
});
|