alepha 0.15.3 → 0.15.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/README.md +26 -11
- package/dist/api/audits/index.d.ts +335 -335
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +11 -3
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +3 -3
- package/dist/api/files/index.js +4 -3
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +198 -155
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +103 -5
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +198 -198
- package/dist/api/keys/index.d.ts.map +1 -1
- package/dist/api/keys/index.js +3 -3
- package/dist/api/keys/index.js.map +1 -1
- package/dist/api/notifications/index.browser.js +1 -0
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.d.ts +3 -3
- package/dist/api/notifications/index.js +4 -3
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.d.ts +263 -263
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +41 -30
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/users/index.d.ts +383 -77
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +284 -72
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +131 -131
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js +3 -3
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/batch/index.d.ts +3 -3
- package/dist/batch/index.js +3 -3
- package/dist/batch/index.js.map +1 -1
- package/dist/bucket/index.d.ts +3 -3
- package/dist/bucket/index.js +6 -6
- package/dist/bucket/index.js.map +1 -1
- package/dist/cache/core/index.d.ts +3 -3
- package/dist/cache/core/index.js +3 -3
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cli/index.d.ts +5612 -20
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +122 -91
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +11 -4
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +8 -6
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +4 -8
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.d.ts +3 -3
- package/dist/datetime/index.js +3 -3
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/index.d.ts +16 -16
- package/dist/email/index.d.ts.map +1 -1
- package/dist/email/index.js +10562 -10
- package/dist/email/index.js.map +1 -1
- package/dist/fake/index.d.ts +3 -3
- package/dist/fake/index.js +3 -3
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +9 -4
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/core/index.js +12 -4
- package/dist/lock/core/index.js.map +1 -1
- package/dist/logger/index.d.ts +3 -3
- package/dist/logger/index.js +6 -3
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +3 -3
- package/dist/mcp/index.js +3 -3
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/index.d.ts +12 -12
- package/dist/orm/index.js +4 -4
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/core/index.d.ts +3 -3
- package/dist/queue/core/index.js +3 -3
- package/dist/queue/core/index.js.map +1 -1
- package/dist/react/auth/index.browser.js +2 -1
- package/dist/react/auth/index.browser.js.map +1 -1
- package/dist/react/auth/index.d.ts +3 -3
- package/dist/react/auth/index.js +5 -4
- package/dist/react/auth/index.js.map +1 -1
- package/dist/react/core/index.d.ts +6 -6
- package/dist/react/core/index.js +3 -3
- package/dist/react/core/index.js.map +1 -1
- package/dist/react/form/index.d.ts +3 -3
- package/dist/react/form/index.js +3 -3
- package/dist/react/form/index.js.map +1 -1
- package/dist/react/head/index.d.ts +3 -3
- package/dist/react/head/index.js +3 -3
- package/dist/react/head/index.js.map +1 -1
- package/dist/react/i18n/index.d.ts +3 -3
- package/dist/react/i18n/index.js +3 -3
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/intro/index.css +337 -0
- package/dist/react/intro/index.css.map +1 -0
- package/dist/react/intro/index.d.ts +10 -0
- package/dist/react/intro/index.d.ts.map +1 -0
- package/dist/react/intro/index.js +222 -0
- package/dist/react/intro/index.js.map +1 -0
- package/dist/react/router/index.browser.js +2 -2
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +11 -1
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +21 -11
- package/dist/react/router/index.js.map +1 -1
- package/dist/redis/index.d.ts +22 -22
- package/dist/redis/index.js +3 -3
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.d.ts +3 -3
- package/dist/retry/index.js +3 -3
- package/dist/retry/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +16 -4
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +45 -7
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts +3 -3
- package/dist/security/index.js +5 -5
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +3 -3
- package/dist/server/auth/index.js +3 -3
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.d.ts +3 -3
- package/dist/server/cache/index.js +3 -3
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/compress/index.d.ts +3 -3
- package/dist/server/compress/index.d.ts.map +1 -1
- package/dist/server/compress/index.js +4 -3
- package/dist/server/compress/index.js.map +1 -1
- package/dist/server/cookies/index.d.ts +3 -3
- package/dist/server/cookies/index.js +3 -3
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.d.ts +14 -25
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +13 -29
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/cors/index.d.ts +3 -3
- package/dist/server/cors/index.js +3 -3
- package/dist/server/cors/index.js.map +1 -1
- package/dist/server/health/index.d.ts +20 -20
- package/dist/server/health/index.js +3 -3
- package/dist/server/health/index.js.map +1 -1
- package/dist/server/helmet/index.d.ts +3 -3
- package/dist/server/helmet/index.js +3 -3
- package/dist/server/helmet/index.js.map +1 -1
- package/dist/server/links/index.d.ts +42 -42
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/links/index.js +4 -4
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.d.ts +3 -3
- package/dist/server/metrics/index.js +3 -3
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/multipart/index.d.ts +3 -3
- package/dist/server/multipart/index.js +3 -3
- package/dist/server/multipart/index.js.map +1 -1
- package/dist/server/proxy/index.d.ts +3 -3
- package/dist/server/proxy/index.js +3 -3
- package/dist/server/proxy/index.js.map +1 -1
- package/dist/server/rate-limit/index.d.ts +3 -3
- package/dist/server/rate-limit/index.js +3 -3
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/static/index.d.ts +3 -3
- package/dist/server/static/index.js +6 -6
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +3 -3
- package/dist/server/swagger/index.js +6 -6
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +3 -3
- package/dist/sms/index.js +6 -6
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.d.ts +3 -3
- package/dist/system/index.js +3 -3
- package/dist/system/index.js.map +1 -1
- package/dist/thread/index.d.ts +3 -3
- package/dist/thread/index.js +3 -3
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/core/index.d.ts +3 -3
- package/dist/topic/core/index.js +3 -3
- package/dist/topic/core/index.js.map +1 -1
- package/dist/vite/index.d.ts +6286 -4
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +28 -2
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.d.ts +37 -37
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +3 -3
- package/dist/websocket/index.js.map +1 -1
- package/package.json +12 -4
- package/src/api/audits/controllers/AdminAuditController.ts +8 -0
- package/src/api/audits/index.ts +3 -3
- package/src/api/files/controllers/AdminFileStatsController.ts +1 -0
- package/src/api/files/index.ts +3 -3
- package/src/api/jobs/controllers/AdminJobController.ts +18 -2
- package/src/api/jobs/index.ts +4 -3
- package/src/api/jobs/services/JobAudits.spec.ts +89 -0
- package/src/api/jobs/services/JobAudits.ts +101 -0
- package/src/api/keys/index.ts +3 -3
- package/src/api/notifications/controllers/AdminNotificationController.ts +1 -0
- package/src/api/notifications/index.ts +3 -3
- package/src/api/parameters/controllers/AdminConfigController.ts +10 -0
- package/src/api/parameters/index.ts +5 -3
- package/src/api/users/__tests__/ApiKeys-integration.spec.ts +1 -1
- package/src/api/users/__tests__/ApiKeys.spec.ts +1 -1
- package/src/api/users/__tests__/EmailVerification.spec.ts +16 -1
- package/src/api/users/__tests__/PasswordReset.spec.ts +11 -0
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +10 -0
- package/src/api/users/controllers/AdminIdentityController.ts +3 -0
- package/src/api/users/controllers/AdminSessionController.ts +3 -0
- package/src/api/users/controllers/AdminUserController.ts +5 -0
- package/src/api/users/index.ts +8 -9
- package/src/api/users/primitives/$realm.ts +117 -19
- package/src/api/users/providers/RealmProvider.ts +15 -7
- package/src/api/users/services/CredentialService.spec.ts +11 -0
- package/src/api/users/services/CredentialService.ts +47 -24
- package/src/api/users/services/IdentityService.ts +12 -4
- package/src/api/users/services/RegistrationService.spec.ts +11 -0
- package/src/api/users/services/RegistrationService.ts +33 -12
- package/src/api/users/services/SessionService.ts +83 -12
- package/src/api/users/services/UserAudits.ts +47 -0
- package/src/api/users/services/UserFiles.ts +19 -0
- package/src/api/users/services/UserJobs.spec.ts +107 -0
- package/src/api/users/services/UserJobs.ts +62 -0
- package/src/api/users/services/UserParameters.ts +23 -0
- package/src/api/users/services/UserService.ts +34 -17
- package/src/api/verifications/index.ts +3 -3
- package/src/batch/index.ts +3 -3
- package/src/bucket/index.ts +3 -3
- package/src/cache/core/index.ts +3 -3
- package/src/cli/commands/build.ts +1 -0
- package/src/cli/commands/db.ts +9 -0
- package/src/cli/commands/init.spec.ts +2 -17
- package/src/cli/commands/init.ts +37 -1
- package/src/cli/providers/ViteDevServerProvider.ts +36 -2
- package/src/cli/services/AlephaCliUtils.ts +17 -0
- package/src/cli/services/PackageManagerUtils.ts +15 -1
- package/src/cli/services/ProjectScaffolder.ts +8 -13
- package/src/cli/templates/agentMd.ts +2 -25
- package/src/cli/templates/apiAppSecurityTs.ts +37 -2
- package/src/cli/templates/mainCss.ts +2 -32
- package/src/cli/templates/webAppRouterTs.ts +5 -5
- package/src/cli/templates/webHomeComponentTsx.ts +10 -0
- package/src/command/helpers/Runner.ts +14 -1
- package/src/command/index.ts +3 -3
- package/src/core/helpers/primitive.ts +0 -5
- package/src/core/index.ts +3 -3
- package/src/datetime/index.ts +3 -3
- package/src/email/index.ts +3 -3
- package/src/email/index.workerd.ts +36 -0
- package/src/email/providers/LocalEmailProvider.ts +2 -2
- package/src/email/providers/WorkermailerEmailProvider.ts +221 -0
- package/src/fake/index.ts +3 -3
- package/src/lock/core/index.ts +3 -3
- package/src/lock/core/primitives/$lock.ts +13 -1
- package/src/logger/index.ts +3 -3
- package/src/logger/providers/PrettyFormatterProvider.ts +7 -0
- package/src/mcp/index.ts +3 -3
- package/src/orm/index.ts +3 -3
- package/src/orm/providers/drivers/NodeSqliteProvider.ts +1 -1
- package/src/queue/core/index.ts +3 -3
- package/src/react/auth/index.ts +3 -3
- package/src/react/auth/services/ReactAuth.ts +3 -1
- package/src/react/core/index.ts +3 -3
- package/src/react/form/index.ts +3 -3
- package/src/react/head/index.ts +3 -3
- package/src/react/i18n/index.ts +3 -3
- package/src/react/intro/components/GettingStarted.css +334 -0
- package/src/react/intro/components/GettingStarted.tsx +276 -0
- package/src/react/intro/index.ts +1 -0
- package/src/react/router/atoms/ssrManifestAtom.ts +7 -0
- package/src/react/router/index.browser.ts +2 -0
- package/src/react/router/index.ts +2 -0
- package/src/react/router/providers/ReactServerProvider.ts +14 -4
- package/src/react/router/providers/SSRManifestProvider.ts +7 -0
- package/src/redis/index.ts +3 -3
- package/src/retry/index.ts +3 -3
- package/src/router/index.ts +3 -3
- package/src/scheduler/index.ts +3 -3
- package/src/scheduler/index.workerd.ts +43 -0
- package/src/scheduler/providers/CronProvider.ts +53 -6
- package/src/scheduler/providers/WorkerdCronProvider.ts +102 -0
- package/src/security/index.ts +3 -3
- package/src/security/providers/JwtProvider.ts +2 -2
- package/src/server/auth/index.ts +3 -3
- package/src/server/cache/index.ts +3 -3
- package/src/server/compress/index.ts +3 -3
- package/src/server/compress/providers/ServerCompressProvider.ts +6 -0
- package/src/server/cookies/index.ts +3 -3
- package/src/server/core/index.ts +3 -3
- package/src/server/core/primitives/$action.spec.ts +3 -2
- package/src/server/core/primitives/$action.ts +6 -2
- package/src/server/core/providers/NodeHttpServerProvider.ts +2 -15
- package/src/server/core/providers/ServerProvider.ts +4 -2
- package/src/server/core/providers/ServerRouterProvider.ts +5 -27
- package/src/server/cors/index.ts +3 -3
- package/src/server/health/index.ts +3 -3
- package/src/server/helmet/index.ts +3 -3
- package/src/server/links/index.ts +3 -3
- package/src/server/links/providers/ServerLinksProvider.spec.ts +332 -0
- package/src/server/links/providers/ServerLinksProvider.ts +1 -1
- package/src/server/metrics/index.ts +3 -3
- package/src/server/multipart/index.ts +3 -3
- package/src/server/proxy/index.ts +3 -3
- package/src/server/rate-limit/index.ts +3 -3
- package/src/server/static/index.ts +3 -3
- package/src/server/swagger/index.ts +3 -3
- package/src/sms/index.ts +3 -3
- package/src/system/index.ts +3 -3
- package/src/thread/index.ts +3 -3
- package/src/topic/core/index.ts +3 -3
- package/src/vite/tasks/generateCloudflare.ts +38 -2
- package/src/websocket/index.ts +3 -3
- package/src/cli/templates/webHelloComponentTsx.ts +0 -30
- /package/src/api/users/{notifications → services}/UserNotifications.ts +0 -0
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { $context } from "alepha";
|
|
2
|
-
import { AlephaApiAudits } from "alepha/api/audits";
|
|
3
|
-
import { AlephaApiFiles } from "alepha/api/files";
|
|
4
|
-
import { AlephaApiJobs } from "alepha/api/jobs";
|
|
5
2
|
import { AlephaApiKeys, ApiKeyService } from "alepha/api/keys";
|
|
3
|
+
import { AlephaApiVerification } from "alepha/api/verifications";
|
|
6
4
|
import type { Repository } from "alepha/orm";
|
|
7
5
|
import {
|
|
8
6
|
$issuer,
|
|
@@ -27,6 +25,11 @@ import type { sessions } from "../entities/sessions.ts";
|
|
|
27
25
|
import { DEFAULT_USER_REALM_NAME, type users } from "../entities/users.ts";
|
|
28
26
|
import { RealmProvider } from "../providers/RealmProvider.ts";
|
|
29
27
|
import { SessionService } from "../services/SessionService.ts";
|
|
28
|
+
import { UserAudits } from "../services/UserAudits.ts";
|
|
29
|
+
import { UserFiles } from "../services/UserFiles.ts";
|
|
30
|
+
import { UserJobs } from "../services/UserJobs.ts";
|
|
31
|
+
import { UserNotifications } from "../services/UserNotifications.ts";
|
|
32
|
+
import { UserParameters } from "../services/UserParameters.ts";
|
|
30
33
|
|
|
31
34
|
export type RealmPrimitive = IssuerPrimitive & WithLinkFn & WithLoginFn;
|
|
32
35
|
|
|
@@ -66,12 +69,50 @@ export const $realm = (options: RealmOptions = {}): RealmPrimitive => {
|
|
|
66
69
|
options.settings.phoneEnabled = true;
|
|
67
70
|
}
|
|
68
71
|
|
|
72
|
+
// Merge features with defaults
|
|
73
|
+
const features: RealmFeatures = {
|
|
74
|
+
jobs: false,
|
|
75
|
+
notifications: false,
|
|
76
|
+
apiKeys: false,
|
|
77
|
+
parameters: false,
|
|
78
|
+
files: false,
|
|
79
|
+
audits: false,
|
|
80
|
+
organizations: false,
|
|
81
|
+
...options.features,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// When notifications are disabled, force verification-dependent settings to false
|
|
85
|
+
// These features require sending codes via email/SMS which won't work without notifications
|
|
86
|
+
if (!features.notifications) {
|
|
87
|
+
options.settings.verifyEmailRequired = false;
|
|
88
|
+
options.settings.verifyPhoneRequired = false;
|
|
89
|
+
options.settings.resetPasswordAllowed = false;
|
|
90
|
+
}
|
|
91
|
+
|
|
69
92
|
const realmRegistration = realmProvider.register(name, options);
|
|
70
93
|
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
94
|
+
// Enable features based on configuration
|
|
95
|
+
// Each feature registers its wrapper service which internally uses the module primitives
|
|
96
|
+
if (features.files) {
|
|
97
|
+
alepha.with(UserFiles);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (features.audits) {
|
|
101
|
+
alepha.with(UserAudits);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (features.jobs) {
|
|
105
|
+
alepha.with(UserJobs);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (features.notifications) {
|
|
109
|
+
alepha.with(AlephaApiVerification);
|
|
110
|
+
alepha.with(UserNotifications);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (features.parameters) {
|
|
114
|
+
alepha.with(UserParameters);
|
|
115
|
+
}
|
|
75
116
|
|
|
76
117
|
// Collect custom resolvers that will be registered during $issuer.onInit()
|
|
77
118
|
// This ensures they are registered AFTER the realm is created (not on the default test realm)
|
|
@@ -80,7 +121,7 @@ export const $realm = (options: RealmOptions = {}): RealmPrimitive => {
|
|
|
80
121
|
];
|
|
81
122
|
|
|
82
123
|
// Enable API key authentication - must be added to customResolvers before $issuer() call
|
|
83
|
-
if (
|
|
124
|
+
if (features.apiKeys) {
|
|
84
125
|
alepha.with(AlephaApiKeys);
|
|
85
126
|
const apiKeyService = alepha.inject(ApiKeyService);
|
|
86
127
|
customResolvers.push(apiKeyService.createResolver());
|
|
@@ -176,6 +217,71 @@ export const $realm = (options: RealmOptions = {}): RealmPrimitive => {
|
|
|
176
217
|
|
|
177
218
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
178
219
|
|
|
220
|
+
export interface RealmFeatures {
|
|
221
|
+
/**
|
|
222
|
+
* Enable job execution tracking and purge functionality.
|
|
223
|
+
*
|
|
224
|
+
* @default false
|
|
225
|
+
*/
|
|
226
|
+
jobs?: boolean;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Enable notification system for password reset, verification emails, etc.
|
|
230
|
+
*
|
|
231
|
+
* @default false
|
|
232
|
+
*/
|
|
233
|
+
notifications?: boolean;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Enable API key authentication for programmatic access.
|
|
237
|
+
*
|
|
238
|
+
* When enabled, users can create API keys to access protected endpoints
|
|
239
|
+
* without using JWT tokens. API keys are useful for:
|
|
240
|
+
* - Programmatic access (CLI tools, scripts)
|
|
241
|
+
* - Long-lived authentication tokens
|
|
242
|
+
* - Third-party integrations (MCP servers)
|
|
243
|
+
*
|
|
244
|
+
* API keys can be passed via:
|
|
245
|
+
* - Query parameter: `?api_key=ak_xxx`
|
|
246
|
+
* - Bearer header: `Authorization: Bearer ak_xxx`
|
|
247
|
+
*
|
|
248
|
+
* @default false
|
|
249
|
+
*/
|
|
250
|
+
apiKeys?: boolean;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Enable runtime configuration management.
|
|
254
|
+
*
|
|
255
|
+
* Allows configuring realm settings at runtime with versioning and scheduled activation.
|
|
256
|
+
*
|
|
257
|
+
* @default false
|
|
258
|
+
*/
|
|
259
|
+
parameters?: boolean;
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Enable file management for avatar uploads and attachments.
|
|
263
|
+
*
|
|
264
|
+
* @default false
|
|
265
|
+
*/
|
|
266
|
+
files?: boolean;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Enable audit trail for compliance and event logging.
|
|
270
|
+
*
|
|
271
|
+
* @default false
|
|
272
|
+
*/
|
|
273
|
+
audits?: boolean;
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Enable organization management to group users.
|
|
277
|
+
*
|
|
278
|
+
* @default false
|
|
279
|
+
*/
|
|
280
|
+
organizations?: boolean;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
284
|
+
|
|
179
285
|
export interface RealmOptions {
|
|
180
286
|
/**
|
|
181
287
|
* Secret key for signing tokens.
|
|
@@ -209,17 +315,9 @@ export interface RealmOptions {
|
|
|
209
315
|
};
|
|
210
316
|
|
|
211
317
|
/**
|
|
212
|
-
* Enable
|
|
213
|
-
*
|
|
214
|
-
* When enabled, users can create API keys to access protected endpoints
|
|
215
|
-
* without using JWT tokens. API keys are useful for:
|
|
216
|
-
* - Programmatic access (CLI tools, scripts)
|
|
217
|
-
* - Long-lived authentication tokens
|
|
218
|
-
* - Third-party integrations
|
|
318
|
+
* Enable or disable realm features.
|
|
219
319
|
*
|
|
220
|
-
*
|
|
221
|
-
* - Query parameter: `?api_key=ak_xxx`
|
|
222
|
-
* - Bearer header: `Authorization: Bearer ak_xxx`
|
|
320
|
+
* Features control which modules are loaded with the realm.
|
|
223
321
|
*/
|
|
224
|
-
|
|
322
|
+
features?: Partial<RealmFeatures>;
|
|
225
323
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { $inject, Alepha, AlephaError } from "alepha";
|
|
2
|
-
import { $bucket } from "alepha/bucket";
|
|
3
2
|
import { $repository, type Repository } from "alepha/orm";
|
|
4
3
|
import {
|
|
5
4
|
type RealmAuthSettings,
|
|
@@ -8,7 +7,7 @@ import {
|
|
|
8
7
|
import { identities } from "../entities/identities.ts";
|
|
9
8
|
import { sessions } from "../entities/sessions.ts";
|
|
10
9
|
import { DEFAULT_USER_REALM_NAME, users } from "../entities/users.ts";
|
|
11
|
-
import type { RealmOptions } from "../primitives/$realm.ts";
|
|
10
|
+
import type { RealmFeatures, RealmOptions } from "../primitives/$realm.ts";
|
|
12
11
|
|
|
13
12
|
export interface RealmRepositories {
|
|
14
13
|
identities: Repository<typeof identities.schema>;
|
|
@@ -20,6 +19,7 @@ export interface Realm {
|
|
|
20
19
|
name: string;
|
|
21
20
|
repositories: RealmRepositories;
|
|
22
21
|
settings: RealmAuthSettings;
|
|
22
|
+
features: RealmFeatures;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export class RealmProvider {
|
|
@@ -31,12 +31,19 @@ export class RealmProvider {
|
|
|
31
31
|
|
|
32
32
|
protected realms = new Map<string, Realm>();
|
|
33
33
|
|
|
34
|
-
public avatars = $bucket({
|
|
35
|
-
maxSize: 5 * 1024 * 1024, // 5 MB
|
|
36
|
-
mimeTypes: ["image/jpeg", "image/png", "image/gif", "image/webp"],
|
|
37
|
-
});
|
|
38
|
-
|
|
39
34
|
public register(realmName: string, realmOptions: RealmOptions = {}) {
|
|
35
|
+
// Merge features with defaults
|
|
36
|
+
const features: RealmFeatures = {
|
|
37
|
+
jobs: false,
|
|
38
|
+
notifications: false,
|
|
39
|
+
apiKeys: false,
|
|
40
|
+
parameters: false,
|
|
41
|
+
files: false,
|
|
42
|
+
audits: false,
|
|
43
|
+
organizations: false,
|
|
44
|
+
...realmOptions.features,
|
|
45
|
+
};
|
|
46
|
+
|
|
40
47
|
this.realms.set(realmName, {
|
|
41
48
|
name: realmName,
|
|
42
49
|
repositories: {
|
|
@@ -53,6 +60,7 @@ export class RealmProvider {
|
|
|
53
60
|
...realmOptions.settings?.passwordPolicy,
|
|
54
61
|
},
|
|
55
62
|
},
|
|
63
|
+
features,
|
|
56
64
|
});
|
|
57
65
|
return this.getRealm(realmName);
|
|
58
66
|
}
|
|
@@ -8,7 +8,9 @@ import { describe, it } from "vitest";
|
|
|
8
8
|
import {
|
|
9
9
|
AlephaApiUsers,
|
|
10
10
|
CredentialService,
|
|
11
|
+
RealmProvider,
|
|
11
12
|
SessionService,
|
|
13
|
+
UserNotifications,
|
|
12
14
|
UserService,
|
|
13
15
|
} from "../index.ts";
|
|
14
16
|
|
|
@@ -21,9 +23,18 @@ const setup = async () => {
|
|
|
21
23
|
alepha.with(AlephaEmail);
|
|
22
24
|
alepha.with(AlephaApiVerification);
|
|
23
25
|
alepha.with(AlephaApiUsers);
|
|
26
|
+
alepha.with(UserNotifications);
|
|
24
27
|
|
|
25
28
|
await alepha.start();
|
|
26
29
|
|
|
30
|
+
// Enable notifications for the default realm
|
|
31
|
+
const realmProvider = alepha.inject(RealmProvider);
|
|
32
|
+
realmProvider.register("default", {
|
|
33
|
+
features: {
|
|
34
|
+
notifications: true,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
27
38
|
const emailProvider = alepha.inject(MemoryEmailProvider);
|
|
28
39
|
emailProvider.records = [];
|
|
29
40
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { $inject } from "alepha";
|
|
3
|
-
import { AuditService } from "alepha/api/audits";
|
|
2
|
+
import { $inject, Alepha } from "alepha";
|
|
4
3
|
import type { VerificationController } from "alepha/api/verifications";
|
|
5
4
|
import { $cache } from "alepha/cache";
|
|
6
5
|
import { DateTimeProvider } from "alepha/datetime";
|
|
@@ -8,10 +7,11 @@ import { $logger } from "alepha/logger";
|
|
|
8
7
|
import { CryptoProvider } from "alepha/security";
|
|
9
8
|
import { BadRequestError, HttpError } from "alepha/server";
|
|
10
9
|
import { $client } from "alepha/server/links";
|
|
11
|
-
import { UserNotifications } from "../notifications/UserNotifications.ts";
|
|
12
10
|
import { RealmProvider } from "../providers/RealmProvider.ts";
|
|
13
11
|
import type { CompletePasswordResetRequest } from "../schemas/completePasswordResetRequestSchema.ts";
|
|
14
12
|
import type { PasswordResetIntentResponse } from "../schemas/passwordResetIntentResponseSchema.ts";
|
|
13
|
+
import { UserAudits } from "./UserAudits.ts";
|
|
14
|
+
import { UserNotifications } from "./UserNotifications.ts";
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Intent stored in cache during the password reset flow.
|
|
@@ -27,13 +27,28 @@ interface PasswordResetIntent {
|
|
|
27
27
|
const INTENT_TTL_MINUTES = 10;
|
|
28
28
|
|
|
29
29
|
export class CredentialService {
|
|
30
|
+
protected readonly alepha = $inject(Alepha);
|
|
30
31
|
protected readonly log = $logger();
|
|
31
32
|
protected readonly cryptoProvider = $inject(CryptoProvider);
|
|
32
33
|
protected readonly dateTimeProvider = $inject(DateTimeProvider);
|
|
33
34
|
protected readonly verificationController = $client<VerificationController>();
|
|
34
|
-
protected readonly userNotifications = $inject(UserNotifications);
|
|
35
35
|
protected readonly realmProvider = $inject(RealmProvider);
|
|
36
|
-
|
|
36
|
+
|
|
37
|
+
protected userAudits(realmName?: string) {
|
|
38
|
+
const realm = this.realmProvider.getRealm(realmName);
|
|
39
|
+
if (realm.features.audits) {
|
|
40
|
+
return this.alepha.inject(UserAudits);
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
protected userNotifications(realmName?: string) {
|
|
46
|
+
const realm = this.realmProvider.getRealm(realmName);
|
|
47
|
+
if (realm.features.notifications) {
|
|
48
|
+
return this.alepha.inject(UserNotifications);
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
37
52
|
|
|
38
53
|
protected readonly intentCache = $cache<PasswordResetIntent>({
|
|
39
54
|
name: "password-reset-intents",
|
|
@@ -118,7 +133,7 @@ export class CredentialService {
|
|
|
118
133
|
});
|
|
119
134
|
|
|
120
135
|
// Send password reset notification with the code
|
|
121
|
-
await this.userNotifications
|
|
136
|
+
await this.userNotifications(userRealmName)?.passwordReset.push({
|
|
122
137
|
contact: email,
|
|
123
138
|
variables: {
|
|
124
139
|
email,
|
|
@@ -225,7 +240,7 @@ export class CredentialService {
|
|
|
225
240
|
const realm = this.realmProvider.getRealm(intent.realmName);
|
|
226
241
|
|
|
227
242
|
// Audit: password reset
|
|
228
|
-
await this.
|
|
243
|
+
await this.userAudits(intent.realmName)?.recordUser("update", {
|
|
229
244
|
userId: intent.userId,
|
|
230
245
|
userEmail: intent.email,
|
|
231
246
|
userRealm: realm.name,
|
|
@@ -235,14 +250,18 @@ export class CredentialService {
|
|
|
235
250
|
});
|
|
236
251
|
|
|
237
252
|
// Audit: sessions invalidated (security event)
|
|
238
|
-
await this.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
253
|
+
await this.userAudits(intent.realmName)?.record(
|
|
254
|
+
"security",
|
|
255
|
+
"sessions_invalidated",
|
|
256
|
+
{
|
|
257
|
+
userId: intent.userId,
|
|
258
|
+
userEmail: intent.email,
|
|
259
|
+
userRealm: realm.name,
|
|
260
|
+
resourceId: intent.userId,
|
|
261
|
+
severity: "warning",
|
|
262
|
+
description: "All sessions invalidated after password reset",
|
|
263
|
+
},
|
|
264
|
+
);
|
|
246
265
|
}
|
|
247
266
|
|
|
248
267
|
// Legacy methods kept for backward compatibility
|
|
@@ -333,7 +352,7 @@ export class CredentialService {
|
|
|
333
352
|
const realm = this.realmProvider.getRealm(userRealmName);
|
|
334
353
|
|
|
335
354
|
// Audit: password reset (legacy method)
|
|
336
|
-
await this.
|
|
355
|
+
await this.userAudits(userRealmName)?.recordUser("update", {
|
|
337
356
|
userId: user.id,
|
|
338
357
|
userEmail: email,
|
|
339
358
|
userRealm: realm.name,
|
|
@@ -343,13 +362,17 @@ export class CredentialService {
|
|
|
343
362
|
});
|
|
344
363
|
|
|
345
364
|
// Audit: sessions invalidated
|
|
346
|
-
await this.
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
365
|
+
await this.userAudits(userRealmName)?.record(
|
|
366
|
+
"security",
|
|
367
|
+
"sessions_invalidated",
|
|
368
|
+
{
|
|
369
|
+
userId: user.id,
|
|
370
|
+
userEmail: email,
|
|
371
|
+
userRealm: realm.name,
|
|
372
|
+
resourceId: user.id,
|
|
373
|
+
severity: "warning",
|
|
374
|
+
description: "All sessions invalidated after password reset",
|
|
375
|
+
},
|
|
376
|
+
);
|
|
354
377
|
}
|
|
355
378
|
}
|
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
import { $inject } from "alepha";
|
|
2
|
-
import { AuditService } from "alepha/api/audits";
|
|
1
|
+
import { $inject, Alepha } from "alepha";
|
|
3
2
|
import { $logger } from "alepha/logger";
|
|
4
3
|
import type { Page } from "alepha/orm";
|
|
5
4
|
import type { IdentityEntity } from "../entities/identities.ts";
|
|
6
5
|
import { RealmProvider } from "../providers/RealmProvider.ts";
|
|
7
6
|
import type { IdentityQuery } from "../schemas/identityQuerySchema.ts";
|
|
7
|
+
import { UserAudits } from "./UserAudits.ts";
|
|
8
8
|
|
|
9
9
|
export class IdentityService {
|
|
10
|
+
protected readonly alepha = $inject(Alepha);
|
|
10
11
|
protected readonly log = $logger();
|
|
11
12
|
protected readonly realmProvider = $inject(RealmProvider);
|
|
12
|
-
|
|
13
|
+
|
|
14
|
+
protected userAudits(realmName?: string) {
|
|
15
|
+
const realm = this.realmProvider.getRealm(realmName);
|
|
16
|
+
if (realm.features.audits) {
|
|
17
|
+
return this.alepha.inject(UserAudits);
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
13
21
|
|
|
14
22
|
public identities(userRealmName?: string) {
|
|
15
23
|
return this.realmProvider.identityRepository(userRealmName);
|
|
@@ -87,7 +95,7 @@ export class IdentityService {
|
|
|
87
95
|
|
|
88
96
|
const realm = this.realmProvider.getRealm(userRealmName);
|
|
89
97
|
|
|
90
|
-
await this.
|
|
98
|
+
await this.userAudits(userRealmName)?.recordUser("update", {
|
|
91
99
|
userRealm: realm.name,
|
|
92
100
|
resourceId: identity.userId,
|
|
93
101
|
description: `Identity provider disconnected: ${identity.provider}`,
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
RealmProvider,
|
|
11
11
|
RegistrationService,
|
|
12
12
|
SessionService,
|
|
13
|
+
UserNotifications,
|
|
13
14
|
UserService,
|
|
14
15
|
} from "../index.ts";
|
|
15
16
|
|
|
@@ -20,6 +21,7 @@ const setup = async (realmSettings?: Record<string, unknown>) => {
|
|
|
20
21
|
alepha.with(AlephaEmail);
|
|
21
22
|
alepha.with(AlephaApiVerification);
|
|
22
23
|
alepha.with(AlephaApiUsers);
|
|
24
|
+
alepha.with(UserNotifications);
|
|
23
25
|
|
|
24
26
|
await alepha.start();
|
|
25
27
|
|
|
@@ -84,6 +86,9 @@ describe("alepha/api/users - RegistrationService", () => {
|
|
|
84
86
|
|
|
85
87
|
// Register realm with email verification required
|
|
86
88
|
realmProvider.register("verify-email-realm", {
|
|
89
|
+
features: {
|
|
90
|
+
notifications: true,
|
|
91
|
+
},
|
|
87
92
|
settings: {
|
|
88
93
|
verifyEmailRequired: true,
|
|
89
94
|
} as never,
|
|
@@ -315,6 +320,9 @@ describe("alepha/api/users - RegistrationService", () => {
|
|
|
315
320
|
await setup();
|
|
316
321
|
|
|
317
322
|
realmProvider.register("email-verify-realm", {
|
|
323
|
+
features: {
|
|
324
|
+
notifications: true,
|
|
325
|
+
},
|
|
318
326
|
settings: {
|
|
319
327
|
verifyEmailRequired: true,
|
|
320
328
|
} as never,
|
|
@@ -586,6 +594,9 @@ describe("alepha/api/users - RegistrationService", () => {
|
|
|
586
594
|
} = await setup();
|
|
587
595
|
|
|
588
596
|
realmProvider.register("full-verify-realm", {
|
|
597
|
+
features: {
|
|
598
|
+
notifications: true,
|
|
599
|
+
},
|
|
589
600
|
settings: {
|
|
590
601
|
verifyEmailRequired: true,
|
|
591
602
|
} as never,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { $inject } from "alepha";
|
|
3
|
-
import { AuditService } from "alepha/api/audits";
|
|
2
|
+
import { $inject, Alepha } from "alepha";
|
|
4
3
|
import type { VerificationController } from "alepha/api/verifications";
|
|
5
4
|
import { $cache } from "alepha/cache";
|
|
6
5
|
import { DateTimeProvider } from "alepha/datetime";
|
|
@@ -9,11 +8,12 @@ import { CryptoProvider } from "alepha/security";
|
|
|
9
8
|
import { BadRequestError, ConflictError, HttpError } from "alepha/server";
|
|
10
9
|
import { $client } from "alepha/server/links";
|
|
11
10
|
import type { UserEntity } from "../entities/users.ts";
|
|
12
|
-
import { UserNotifications } from "../notifications/UserNotifications.ts";
|
|
13
11
|
import { RealmProvider } from "../providers/RealmProvider.ts";
|
|
14
12
|
import type { CompleteRegistrationRequest } from "../schemas/completeRegistrationRequestSchema.ts";
|
|
15
13
|
import type { RegisterRequest } from "../schemas/registerRequestSchema.ts";
|
|
16
14
|
import type { RegistrationIntentResponse } from "../schemas/registrationIntentResponseSchema.ts";
|
|
15
|
+
import { UserAudits } from "./UserAudits.ts";
|
|
16
|
+
import { UserNotifications } from "./UserNotifications.ts";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Intent stored in cache during the registration flow.
|
|
@@ -40,19 +40,34 @@ interface RegistrationIntent {
|
|
|
40
40
|
const INTENT_TTL_MINUTES = 10;
|
|
41
41
|
|
|
42
42
|
export class RegistrationService {
|
|
43
|
+
protected readonly alepha = $inject(Alepha);
|
|
43
44
|
protected readonly log = $logger();
|
|
44
45
|
protected readonly dateTimeProvider = $inject(DateTimeProvider);
|
|
45
46
|
protected readonly cryptoProvider = $inject(CryptoProvider);
|
|
46
47
|
protected readonly verificationController = $client<VerificationController>();
|
|
47
|
-
protected readonly userNotifications = $inject(UserNotifications);
|
|
48
48
|
protected readonly realmProvider = $inject(RealmProvider);
|
|
49
|
-
protected readonly auditService = $inject(AuditService);
|
|
50
49
|
|
|
51
50
|
protected readonly intentCache = $cache<RegistrationIntent>({
|
|
52
51
|
name: "registration-intents",
|
|
53
52
|
ttl: [INTENT_TTL_MINUTES, "minutes"],
|
|
54
53
|
});
|
|
55
54
|
|
|
55
|
+
protected userAudits(realmName?: string) {
|
|
56
|
+
const realm = this.realmProvider.getRealm(realmName);
|
|
57
|
+
if (realm.features.audits) {
|
|
58
|
+
return this.alepha.inject(UserAudits);
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
protected userNotifications(realmName?: string) {
|
|
64
|
+
const realm = this.realmProvider.getRealm(realmName);
|
|
65
|
+
if (realm.features.notifications) {
|
|
66
|
+
return this.alepha.inject(UserNotifications);
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
56
71
|
/**
|
|
57
72
|
* Phase 1: Create a registration intent.
|
|
58
73
|
*
|
|
@@ -130,11 +145,11 @@ export class RegistrationService {
|
|
|
130
145
|
|
|
131
146
|
// Create verification sessions and send codes
|
|
132
147
|
if (requirements.email && body.email) {
|
|
133
|
-
await this.sendEmailVerification(body.email);
|
|
148
|
+
await this.sendEmailVerification(body.email, userRealmName);
|
|
134
149
|
}
|
|
135
150
|
|
|
136
151
|
if (requirements.phone && body.phoneNumber) {
|
|
137
|
-
await this.sendPhoneVerification(body.phoneNumber);
|
|
152
|
+
await this.sendPhoneVerification(body.phoneNumber, userRealmName);
|
|
138
153
|
}
|
|
139
154
|
|
|
140
155
|
// Generate intent ID and expiration
|
|
@@ -291,7 +306,7 @@ export class RegistrationService {
|
|
|
291
306
|
|
|
292
307
|
const realm = this.realmProvider.getRealm(userRealmName);
|
|
293
308
|
|
|
294
|
-
await this.
|
|
309
|
+
await this.userAudits(userRealmName)?.recordUser("create", {
|
|
295
310
|
userId: user.id,
|
|
296
311
|
userEmail: user.email ?? undefined,
|
|
297
312
|
userRealm: realm.name,
|
|
@@ -353,7 +368,10 @@ export class RegistrationService {
|
|
|
353
368
|
/**
|
|
354
369
|
* Send email verification code.
|
|
355
370
|
*/
|
|
356
|
-
protected async sendEmailVerification(
|
|
371
|
+
protected async sendEmailVerification(
|
|
372
|
+
email: string,
|
|
373
|
+
realmName?: string,
|
|
374
|
+
): Promise<void> {
|
|
357
375
|
this.log.debug("Sending email verification code", { email });
|
|
358
376
|
|
|
359
377
|
const verification =
|
|
@@ -362,7 +380,7 @@ export class RegistrationService {
|
|
|
362
380
|
body: { target: email },
|
|
363
381
|
});
|
|
364
382
|
|
|
365
|
-
await this.userNotifications
|
|
383
|
+
await this.userNotifications(realmName)?.emailVerification.push({
|
|
366
384
|
contact: email,
|
|
367
385
|
variables: {
|
|
368
386
|
email,
|
|
@@ -377,7 +395,10 @@ export class RegistrationService {
|
|
|
377
395
|
/**
|
|
378
396
|
* Send phone verification code.
|
|
379
397
|
*/
|
|
380
|
-
protected async sendPhoneVerification(
|
|
398
|
+
protected async sendPhoneVerification(
|
|
399
|
+
phoneNumber: string,
|
|
400
|
+
realmName?: string,
|
|
401
|
+
): Promise<void> {
|
|
381
402
|
this.log.debug("Sending phone verification code", { phoneNumber });
|
|
382
403
|
try {
|
|
383
404
|
const verification =
|
|
@@ -386,7 +407,7 @@ export class RegistrationService {
|
|
|
386
407
|
body: { target: phoneNumber },
|
|
387
408
|
});
|
|
388
409
|
|
|
389
|
-
await this.userNotifications
|
|
410
|
+
await this.userNotifications(realmName)?.phoneVerification.push({
|
|
390
411
|
contact: phoneNumber,
|
|
391
412
|
variables: {
|
|
392
413
|
phoneNumber,
|