alepha 0.13.6 → 0.13.7
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/dist/api-audits/index.browser.js +116 -0
- package/dist/api-audits/index.browser.js.map +1 -0
- package/dist/api-audits/index.d.ts +1194 -0
- package/dist/api-audits/index.js +674 -0
- package/dist/api-audits/index.js.map +1 -0
- package/dist/api-notifications/index.d.ts +147 -147
- package/dist/api-parameters/index.browser.js +36 -5
- package/dist/api-parameters/index.browser.js.map +1 -1
- package/dist/api-parameters/index.d.ts +711 -33
- package/dist/api-parameters/index.js +831 -17
- package/dist/api-parameters/index.js.map +1 -1
- package/dist/api-users/index.d.ts +793 -780
- package/dist/api-users/index.js +699 -19
- package/dist/api-users/index.js.map +1 -1
- package/dist/api-verifications/index.js +2 -1
- package/dist/api-verifications/index.js.map +1 -1
- package/dist/bin/index.js +1 -0
- package/dist/bin/index.js.map +1 -1
- package/dist/cli/index.d.ts +85 -31
- package/dist/cli/index.js +205 -33
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +67 -6
- package/dist/command/index.js +30 -3
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +241 -61
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +170 -90
- package/dist/core/index.js +264 -67
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +248 -65
- package/dist/core/index.native.js.map +1 -1
- package/dist/email/index.js +15 -10554
- package/dist/email/index.js.map +1 -1
- package/dist/logger/index.d.ts +4 -4
- package/dist/logger/index.js +77 -72
- package/dist/logger/index.js.map +1 -1
- package/dist/orm/index.d.ts +5 -1
- package/dist/orm/index.js +24 -7
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/index.d.ts +4 -4
- package/dist/scheduler/index.d.ts +6 -6
- package/dist/server/index.d.ts +10 -1
- package/dist/server/index.js +20 -6
- package/dist/server/index.js.map +1 -1
- package/dist/server-auth/index.d.ts +163 -152
- package/dist/server-auth/index.js +40 -10
- package/dist/server-auth/index.js.map +1 -1
- package/dist/server-cookies/index.js +5 -1
- package/dist/server-cookies/index.js.map +1 -1
- package/dist/server-links/index.d.ts +33 -33
- package/dist/server-security/index.d.ts +9 -9
- package/dist/thread/index.js +2 -2
- package/dist/thread/index.js.map +1 -1
- package/dist/vite/index.d.ts +2 -2
- package/dist/vite/index.js +102 -45
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js +3 -3
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.js +4 -4
- package/dist/websocket/index.js.map +1 -1
- package/package.json +14 -9
- package/src/api-audits/controllers/AuditController.ts +186 -0
- package/src/api-audits/entities/audits.ts +132 -0
- package/src/api-audits/index.browser.ts +18 -0
- package/src/api-audits/index.ts +58 -0
- package/src/api-audits/primitives/$audit.ts +159 -0
- package/src/api-audits/schemas/auditQuerySchema.ts +23 -0
- package/src/api-audits/schemas/auditResourceSchema.ts +9 -0
- package/src/api-audits/schemas/createAuditSchema.ts +27 -0
- package/src/api-audits/services/AuditService.ts +412 -0
- package/src/api-parameters/controllers/ConfigController.ts +324 -0
- package/src/api-parameters/entities/parameters.ts +93 -10
- package/src/api-parameters/index.ts +43 -4
- package/src/api-parameters/primitives/$config.ts +291 -19
- package/src/api-parameters/schedulers/ConfigActivationScheduler.ts +30 -0
- package/src/api-parameters/services/ConfigStore.ts +491 -0
- package/src/api-users/atoms/realmAuthSettingsAtom.ts +19 -0
- package/src/api-users/controllers/UserRealmController.ts +0 -2
- package/src/api-users/index.ts +2 -0
- package/src/api-users/primitives/$userRealm.ts +18 -3
- package/src/api-users/providers/UserRealmProvider.ts +6 -3
- package/src/api-users/services/RegistrationService.ts +2 -1
- package/src/api-users/services/SessionService.ts +4 -0
- package/src/api-users/services/UserService.ts +3 -0
- package/src/api-verifications/index.ts +7 -1
- package/src/bin/index.ts +1 -0
- package/src/cli/assets/biomeJson.ts +1 -1
- package/src/cli/assets/dummySpecTs.ts +7 -0
- package/src/cli/assets/editorconfig.ts +13 -0
- package/src/cli/assets/mainTs.ts +14 -0
- package/src/cli/commands/BiomeCommands.ts +2 -0
- package/src/cli/commands/CoreCommands.ts +28 -9
- package/src/cli/commands/VerifyCommands.ts +2 -1
- package/src/cli/commands/ViteCommands.ts +8 -9
- package/src/cli/services/AlephaCliUtils.ts +214 -23
- package/src/command/helpers/Asker.ts +0 -1
- package/src/command/primitives/$command.ts +67 -0
- package/src/command/providers/CliProvider.ts +39 -8
- package/src/core/Alepha.ts +40 -30
- package/src/core/helpers/jsonSchemaToTypeBox.ts +307 -0
- package/src/core/index.shared.ts +1 -0
- package/src/core/index.ts +30 -3
- package/src/core/providers/EventManager.ts +1 -1
- package/src/core/providers/StateManager.ts +23 -12
- package/src/core/providers/TypeProvider.ts +26 -34
- package/src/logger/index.ts +8 -6
- package/src/logger/primitives/$logger.ts +1 -1
- package/src/logger/providers/{SimpleFormatterProvider.ts → PrettyFormatterProvider.ts} +10 -1
- package/src/orm/index.ts +6 -0
- package/src/orm/services/PgRelationManager.ts +2 -2
- package/src/orm/services/PostgresModelBuilder.ts +11 -7
- package/src/orm/services/Repository.ts +16 -7
- package/src/orm/services/SqliteModelBuilder.ts +10 -0
- package/src/server/index.ts +6 -0
- package/src/server/primitives/$action.ts +10 -1
- package/src/server/providers/ServerBodyParserProvider.ts +11 -5
- package/src/server/providers/ServerRouterProvider.ts +13 -7
- package/src/server-auth/primitives/$auth.ts +7 -0
- package/src/server-auth/providers/ServerAuthProvider.ts +51 -8
- package/src/server-cookies/index.ts +2 -1
- package/src/thread/primitives/$thread.ts +2 -2
- package/src/vite/index.ts +0 -2
- package/src/vite/tasks/buildServer.ts +3 -4
- package/src/vite/tasks/generateCloudflare.ts +35 -19
- package/src/vite/tasks/generateDocker.ts +18 -4
- package/src/vite/tasks/generateSitemap.ts +5 -7
- package/src/vite/tasks/generateVercel.ts +76 -41
- package/src/vite/tasks/runAlepha.ts +16 -1
- package/src/websocket/providers/NodeWebSocketServerProvider.ts +3 -11
- package/src/websocket/services/WebSocketClient.ts +3 -3
- package/dist/cli/dist-BlfFtOk2.js +0 -2770
- package/dist/cli/dist-BlfFtOk2.js.map +0 -1
- package/src/api-parameters/controllers/ParameterController.ts +0 -45
- package/src/api-parameters/services/ParameterStore.ts +0 -23
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
import { $inject, $module, Alepha, KIND, Primitive, createPrimitive, t } from "alepha";
|
|
2
|
+
import { $entity, $repository, pageQuerySchema, pg } from "alepha/orm";
|
|
3
|
+
import { $action } from "alepha/server";
|
|
4
|
+
import { $logger } from "alepha/logger";
|
|
5
|
+
|
|
6
|
+
//#region ../../src/api-audits/entities/audits.ts
|
|
7
|
+
/**
|
|
8
|
+
* Audit severity levels for categorizing events.
|
|
9
|
+
*/
|
|
10
|
+
const auditSeveritySchema = t.enum([
|
|
11
|
+
"info",
|
|
12
|
+
"warning",
|
|
13
|
+
"critical"
|
|
14
|
+
], {
|
|
15
|
+
default: "info",
|
|
16
|
+
description: "Severity level of the audit event"
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* Audit log entity for tracking important system events.
|
|
20
|
+
*
|
|
21
|
+
* Stores comprehensive audit information including:
|
|
22
|
+
* - Who performed the action (userId, userRealm)
|
|
23
|
+
* - What happened (type, action, resource)
|
|
24
|
+
* - When it happened (createdAt)
|
|
25
|
+
* - Context and details (metadata, ipAddress, userAgent)
|
|
26
|
+
*/
|
|
27
|
+
const audits = $entity({
|
|
28
|
+
name: "audits",
|
|
29
|
+
schema: t.object({
|
|
30
|
+
id: pg.primaryKey(t.bigint()),
|
|
31
|
+
createdAt: pg.createdAt(),
|
|
32
|
+
type: t.text({ description: "Audit event type (e.g., auth, user, payment, system)" }),
|
|
33
|
+
action: t.text({ description: "Specific action performed (e.g., login, create, update)" }),
|
|
34
|
+
severity: pg.default(auditSeveritySchema, "info"),
|
|
35
|
+
userId: t.optional(t.uuid()),
|
|
36
|
+
userRealm: t.optional(t.text()),
|
|
37
|
+
userEmail: t.optional(t.email()),
|
|
38
|
+
resourceType: t.optional(t.text()),
|
|
39
|
+
resourceId: t.optional(t.text()),
|
|
40
|
+
description: t.optional(t.text()),
|
|
41
|
+
metadata: t.optional(t.json()),
|
|
42
|
+
ipAddress: t.optional(t.text()),
|
|
43
|
+
userAgent: t.optional(t.text()),
|
|
44
|
+
sessionId: t.optional(t.uuid()),
|
|
45
|
+
requestId: t.optional(t.text()),
|
|
46
|
+
success: pg.default(t.boolean(), true),
|
|
47
|
+
errorMessage: t.optional(t.text())
|
|
48
|
+
}),
|
|
49
|
+
indexes: [
|
|
50
|
+
"createdAt",
|
|
51
|
+
"type",
|
|
52
|
+
"action",
|
|
53
|
+
"userId",
|
|
54
|
+
"userRealm",
|
|
55
|
+
"resourceType",
|
|
56
|
+
"resourceId",
|
|
57
|
+
"severity",
|
|
58
|
+
{ columns: ["type", "action"] },
|
|
59
|
+
{ columns: ["userId", "createdAt"] },
|
|
60
|
+
{ columns: ["userRealm", "createdAt"] }
|
|
61
|
+
]
|
|
62
|
+
});
|
|
63
|
+
const auditEntitySchema = audits.schema;
|
|
64
|
+
const auditEntityInsertSchema = audits.insertSchema;
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region ../../src/api-audits/schemas/auditQuerySchema.ts
|
|
68
|
+
/**
|
|
69
|
+
* Query schema for searching and filtering audit logs.
|
|
70
|
+
*/
|
|
71
|
+
const auditQuerySchema = t.extend(pageQuerySchema, {
|
|
72
|
+
type: t.optional(t.text({ description: "Filter by audit type" })),
|
|
73
|
+
action: t.optional(t.text({ description: "Filter by action" })),
|
|
74
|
+
severity: t.optional(auditSeveritySchema),
|
|
75
|
+
userId: t.optional(t.uuid({ description: "Filter by user ID" })),
|
|
76
|
+
userRealm: t.optional(t.text({ description: "Filter by user realm" })),
|
|
77
|
+
resourceType: t.optional(t.text({ description: "Filter by resource type" })),
|
|
78
|
+
resourceId: t.optional(t.text({ description: "Filter by resource ID" })),
|
|
79
|
+
success: t.optional(t.boolean({ description: "Filter by success status" })),
|
|
80
|
+
from: t.optional(t.datetime({ description: "Start date filter" })),
|
|
81
|
+
to: t.optional(t.datetime({ description: "End date filter" })),
|
|
82
|
+
search: t.optional(t.text({ description: "Search in description" }))
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region ../../src/api-audits/schemas/auditResourceSchema.ts
|
|
87
|
+
/**
|
|
88
|
+
* Resource schema for audit log responses.
|
|
89
|
+
*/
|
|
90
|
+
const auditResourceSchema = audits.schema;
|
|
91
|
+
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region ../../src/api-audits/schemas/createAuditSchema.ts
|
|
94
|
+
/**
|
|
95
|
+
* Schema for creating a new audit log entry.
|
|
96
|
+
*/
|
|
97
|
+
const createAuditSchema = t.object({
|
|
98
|
+
type: t.text({ description: "Audit event type" }),
|
|
99
|
+
action: t.text({ description: "Specific action performed" }),
|
|
100
|
+
severity: t.optional(auditSeveritySchema),
|
|
101
|
+
userId: t.optional(t.uuid()),
|
|
102
|
+
userRealm: t.optional(t.text()),
|
|
103
|
+
userEmail: t.optional(t.email()),
|
|
104
|
+
resourceType: t.optional(t.text()),
|
|
105
|
+
resourceId: t.optional(t.text()),
|
|
106
|
+
description: t.optional(t.text()),
|
|
107
|
+
metadata: t.optional(t.json()),
|
|
108
|
+
ipAddress: t.optional(t.text()),
|
|
109
|
+
userAgent: t.optional(t.text()),
|
|
110
|
+
sessionId: t.optional(t.uuid()),
|
|
111
|
+
requestId: t.optional(t.text()),
|
|
112
|
+
success: t.optional(t.boolean()),
|
|
113
|
+
errorMessage: t.optional(t.text())
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region ../../src/api-audits/services/AuditService.ts
|
|
118
|
+
/**
|
|
119
|
+
* Service for managing audit logs.
|
|
120
|
+
*
|
|
121
|
+
* Provides methods for:
|
|
122
|
+
* - Creating audit entries
|
|
123
|
+
* - Querying audit history
|
|
124
|
+
* - Aggregating audit statistics
|
|
125
|
+
* - Managing registered audit types
|
|
126
|
+
*/
|
|
127
|
+
var AuditService = class {
|
|
128
|
+
alepha = $inject(Alepha);
|
|
129
|
+
log = $logger();
|
|
130
|
+
repo = $repository(audits);
|
|
131
|
+
/**
|
|
132
|
+
* Registry of audit types and their allowed actions.
|
|
133
|
+
*/
|
|
134
|
+
auditTypes = /* @__PURE__ */ new Map();
|
|
135
|
+
/**
|
|
136
|
+
* Register an audit type with its allowed actions.
|
|
137
|
+
*/
|
|
138
|
+
registerType(definition) {
|
|
139
|
+
this.auditTypes.set(definition.type, definition);
|
|
140
|
+
this.log.debug("Audit type registered", {
|
|
141
|
+
type: definition.type,
|
|
142
|
+
actions: definition.actions
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get all registered audit types.
|
|
147
|
+
*/
|
|
148
|
+
getRegisteredTypes() {
|
|
149
|
+
return Array.from(this.auditTypes.values());
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get current request context if available.
|
|
153
|
+
*/
|
|
154
|
+
getRequestContext() {
|
|
155
|
+
return this.alepha.context.get("request");
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Create a new audit log entry.
|
|
159
|
+
* Automatically populates ipAddress, userAgent, and requestId from the current request context.
|
|
160
|
+
*/
|
|
161
|
+
async create(data) {
|
|
162
|
+
const request = this.getRequestContext();
|
|
163
|
+
const contextData = {};
|
|
164
|
+
if (request) {
|
|
165
|
+
if (!data.ipAddress && request.ip) contextData.ipAddress = request.ip;
|
|
166
|
+
if (!data.userAgent && request.headers["user-agent"]) contextData.userAgent = request.headers["user-agent"];
|
|
167
|
+
if (!data.requestId && request.requestId) contextData.requestId = request.requestId;
|
|
168
|
+
if (!data.sessionId && request.metadata?.sessionId) contextData.sessionId = request.metadata.sessionId;
|
|
169
|
+
const user = request.user;
|
|
170
|
+
if (user) {
|
|
171
|
+
if (!data.userId && user.id) contextData.userId = user.id;
|
|
172
|
+
if (!data.userEmail && user.email) contextData.userEmail = user.email;
|
|
173
|
+
if (!data.userRealm && user.realm) contextData.userRealm = user.realm;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
this.log.trace("Creating audit entry", {
|
|
177
|
+
type: data.type,
|
|
178
|
+
action: data.action,
|
|
179
|
+
userId: data.userId ?? contextData.userId
|
|
180
|
+
});
|
|
181
|
+
const entry = await this.repo.create({
|
|
182
|
+
...contextData,
|
|
183
|
+
...data,
|
|
184
|
+
severity: data.severity ?? "info",
|
|
185
|
+
success: data.success ?? true
|
|
186
|
+
});
|
|
187
|
+
this.log.debug("Audit entry created", {
|
|
188
|
+
id: entry.id,
|
|
189
|
+
type: data.type,
|
|
190
|
+
action: data.action
|
|
191
|
+
});
|
|
192
|
+
return entry;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Record an audit event (convenience method).
|
|
196
|
+
*/
|
|
197
|
+
async record(type, action, options = {}) {
|
|
198
|
+
return this.create({
|
|
199
|
+
type,
|
|
200
|
+
action,
|
|
201
|
+
...options
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Record an authentication event.
|
|
206
|
+
*/
|
|
207
|
+
async recordAuth(action, options = {}) {
|
|
208
|
+
return this.create({
|
|
209
|
+
type: "auth",
|
|
210
|
+
action,
|
|
211
|
+
severity: action === "login_failed" ? "warning" : "info",
|
|
212
|
+
...options
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Record a user management event.
|
|
217
|
+
*/
|
|
218
|
+
async recordUser(action, options = {}) {
|
|
219
|
+
return this.create({
|
|
220
|
+
type: "user",
|
|
221
|
+
action,
|
|
222
|
+
resourceType: "user",
|
|
223
|
+
...options
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Record a data access event.
|
|
228
|
+
*/
|
|
229
|
+
async recordAccess(action, options = {}) {
|
|
230
|
+
return this.create({
|
|
231
|
+
type: "access",
|
|
232
|
+
action,
|
|
233
|
+
...options
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Record a security event.
|
|
238
|
+
*/
|
|
239
|
+
async recordSecurity(action, options = {}) {
|
|
240
|
+
return this.create({
|
|
241
|
+
type: "security",
|
|
242
|
+
action,
|
|
243
|
+
severity: "warning",
|
|
244
|
+
...options
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Record a system event.
|
|
249
|
+
*/
|
|
250
|
+
async recordSystem(action, options = {}) {
|
|
251
|
+
return this.create({
|
|
252
|
+
type: "system",
|
|
253
|
+
action,
|
|
254
|
+
severity: action === "error" ? "critical" : "info",
|
|
255
|
+
...options
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Find audit entries with filtering and pagination.
|
|
260
|
+
*/
|
|
261
|
+
async find(query = {}) {
|
|
262
|
+
this.log.trace("Finding audit entries", { query });
|
|
263
|
+
query.sort ??= "-createdAt";
|
|
264
|
+
const where = this.repo.createQueryWhere();
|
|
265
|
+
if (query.type) where.type = { eq: query.type };
|
|
266
|
+
if (query.action) where.action = { eq: query.action };
|
|
267
|
+
if (query.severity) where.severity = { eq: query.severity };
|
|
268
|
+
if (query.userId) where.userId = { eq: query.userId };
|
|
269
|
+
if (query.userRealm) where.userRealm = { eq: query.userRealm };
|
|
270
|
+
if (query.resourceType) where.resourceType = { eq: query.resourceType };
|
|
271
|
+
if (query.resourceId) where.resourceId = { eq: query.resourceId };
|
|
272
|
+
if (query.success !== void 0) where.success = { eq: query.success };
|
|
273
|
+
if (query.from) where.createdAt = {
|
|
274
|
+
...where.createdAt,
|
|
275
|
+
gte: query.from
|
|
276
|
+
};
|
|
277
|
+
if (query.to) where.createdAt = {
|
|
278
|
+
...where.createdAt,
|
|
279
|
+
lte: query.to
|
|
280
|
+
};
|
|
281
|
+
if (query.search) where.description = { like: `%${query.search}%` };
|
|
282
|
+
const result = await this.repo.paginate(query, { where }, { count: true });
|
|
283
|
+
this.log.debug("Audit entries found", {
|
|
284
|
+
count: result.content.length,
|
|
285
|
+
total: result.page.totalElements
|
|
286
|
+
});
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Get audit entry by ID.
|
|
291
|
+
*/
|
|
292
|
+
async getById(id) {
|
|
293
|
+
return this.repo.findById(id);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Get audit entries for a specific user.
|
|
297
|
+
*/
|
|
298
|
+
async findByUser(userId, query = {}) {
|
|
299
|
+
return this.find({
|
|
300
|
+
...query,
|
|
301
|
+
userId
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Get audit entries for a specific resource.
|
|
306
|
+
*/
|
|
307
|
+
async findByResource(resourceType, resourceId, query = {}) {
|
|
308
|
+
return this.find({
|
|
309
|
+
...query,
|
|
310
|
+
resourceType,
|
|
311
|
+
resourceId
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Get audit statistics for a time period.
|
|
316
|
+
*/
|
|
317
|
+
async getStats(options = {}) {
|
|
318
|
+
this.log.trace("Getting audit stats", options);
|
|
319
|
+
const where = this.repo.createQueryWhere();
|
|
320
|
+
if (options.from) where.createdAt = { gte: options.from.toISOString() };
|
|
321
|
+
if (options.to) where.createdAt = {
|
|
322
|
+
...where.createdAt,
|
|
323
|
+
lte: options.to.toISOString()
|
|
324
|
+
};
|
|
325
|
+
if (options.userRealm) where.userRealm = { eq: options.userRealm };
|
|
326
|
+
const all = await this.repo.findMany({ where });
|
|
327
|
+
const stats = {
|
|
328
|
+
total: all.length,
|
|
329
|
+
byType: {},
|
|
330
|
+
bySeverity: {
|
|
331
|
+
info: 0,
|
|
332
|
+
warning: 0,
|
|
333
|
+
critical: 0
|
|
334
|
+
},
|
|
335
|
+
successRate: 0,
|
|
336
|
+
recentFailures: []
|
|
337
|
+
};
|
|
338
|
+
let successCount = 0;
|
|
339
|
+
for (const entry of all) {
|
|
340
|
+
stats.byType[entry.type] = (stats.byType[entry.type] || 0) + 1;
|
|
341
|
+
const severity = entry.severity;
|
|
342
|
+
stats.bySeverity[severity]++;
|
|
343
|
+
if (entry.success) successCount++;
|
|
344
|
+
}
|
|
345
|
+
stats.successRate = stats.total > 0 ? successCount / stats.total : 1;
|
|
346
|
+
stats.recentFailures = all.filter((e) => !e.success).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()).slice(0, 10);
|
|
347
|
+
return stats;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Delete old audit entries (for retention policy).
|
|
351
|
+
*/
|
|
352
|
+
async deleteOlderThan(date) {
|
|
353
|
+
this.log.info("Deleting old audit entries", { olderThan: date });
|
|
354
|
+
const old = await this.repo.findMany({ where: { createdAt: { lt: date.toISOString() } } });
|
|
355
|
+
for (const entry of old) await this.repo.deleteById(entry.id);
|
|
356
|
+
this.log.info("Old audit entries deleted", { count: old.length });
|
|
357
|
+
return old.length;
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
//#endregion
|
|
362
|
+
//#region ../../src/api-audits/controllers/AuditController.ts
|
|
363
|
+
/**
|
|
364
|
+
* REST API controller for audit log management.
|
|
365
|
+
*
|
|
366
|
+
* Provides endpoints for:
|
|
367
|
+
* - Querying audit logs with filtering
|
|
368
|
+
* - Creating audit entries
|
|
369
|
+
* - Getting audit statistics
|
|
370
|
+
* - Viewing registered audit types
|
|
371
|
+
*/
|
|
372
|
+
var AuditController = class {
|
|
373
|
+
url = "/audits";
|
|
374
|
+
group = "audits";
|
|
375
|
+
auditService = $inject(AuditService);
|
|
376
|
+
/**
|
|
377
|
+
* Find audit entries with filtering and pagination.
|
|
378
|
+
*/
|
|
379
|
+
findAudits = $action({
|
|
380
|
+
path: this.url,
|
|
381
|
+
group: this.group,
|
|
382
|
+
description: "Find audit entries with filtering and pagination",
|
|
383
|
+
schema: {
|
|
384
|
+
query: auditQuerySchema,
|
|
385
|
+
response: pg.page(auditResourceSchema)
|
|
386
|
+
},
|
|
387
|
+
handler: ({ query }) => this.auditService.find(query)
|
|
388
|
+
});
|
|
389
|
+
/**
|
|
390
|
+
* Get a single audit entry by ID.
|
|
391
|
+
*/
|
|
392
|
+
getAudit = $action({
|
|
393
|
+
path: `${this.url}/:id`,
|
|
394
|
+
group: this.group,
|
|
395
|
+
description: "Get a single audit entry by ID",
|
|
396
|
+
schema: {
|
|
397
|
+
params: t.object({ id: t.text() }),
|
|
398
|
+
response: auditResourceSchema
|
|
399
|
+
},
|
|
400
|
+
handler: ({ params }) => this.auditService.getById(params.id)
|
|
401
|
+
});
|
|
402
|
+
/**
|
|
403
|
+
* Create a new audit entry.
|
|
404
|
+
*/
|
|
405
|
+
createAudit = $action({
|
|
406
|
+
method: "POST",
|
|
407
|
+
path: this.url,
|
|
408
|
+
group: this.group,
|
|
409
|
+
description: "Create a new audit entry",
|
|
410
|
+
schema: {
|
|
411
|
+
body: createAuditSchema,
|
|
412
|
+
response: auditResourceSchema
|
|
413
|
+
},
|
|
414
|
+
handler: ({ body }) => this.auditService.create(body)
|
|
415
|
+
});
|
|
416
|
+
/**
|
|
417
|
+
* Get audit entries for a specific user.
|
|
418
|
+
*/
|
|
419
|
+
findByUser = $action({
|
|
420
|
+
path: `${this.url}/user/:userId`,
|
|
421
|
+
group: this.group,
|
|
422
|
+
description: "Get audit entries for a specific user",
|
|
423
|
+
schema: {
|
|
424
|
+
params: t.object({ userId: t.uuid() }),
|
|
425
|
+
query: t.omit(auditQuerySchema, ["userId"]),
|
|
426
|
+
response: pg.page(auditResourceSchema)
|
|
427
|
+
},
|
|
428
|
+
handler: ({ params, query }) => this.auditService.findByUser(params.userId, query)
|
|
429
|
+
});
|
|
430
|
+
/**
|
|
431
|
+
* Get audit entries for a specific resource.
|
|
432
|
+
*/
|
|
433
|
+
findByResource = $action({
|
|
434
|
+
path: `${this.url}/resource/:resourceType/:resourceId`,
|
|
435
|
+
group: this.group,
|
|
436
|
+
description: "Get audit entries for a specific resource",
|
|
437
|
+
schema: {
|
|
438
|
+
params: t.object({
|
|
439
|
+
resourceType: t.text(),
|
|
440
|
+
resourceId: t.text()
|
|
441
|
+
}),
|
|
442
|
+
query: t.omit(auditQuerySchema, ["resourceType", "resourceId"]),
|
|
443
|
+
response: pg.page(auditResourceSchema)
|
|
444
|
+
},
|
|
445
|
+
handler: ({ params, query }) => this.auditService.findByResource(params.resourceType, params.resourceId, query)
|
|
446
|
+
});
|
|
447
|
+
/**
|
|
448
|
+
* Get audit statistics.
|
|
449
|
+
*/
|
|
450
|
+
getStats = $action({
|
|
451
|
+
path: `${this.url}/stats`,
|
|
452
|
+
group: this.group,
|
|
453
|
+
description: "Get audit statistics for a time period",
|
|
454
|
+
schema: {
|
|
455
|
+
query: t.object({
|
|
456
|
+
from: t.optional(t.datetime()),
|
|
457
|
+
to: t.optional(t.datetime()),
|
|
458
|
+
userRealm: t.optional(t.text())
|
|
459
|
+
}),
|
|
460
|
+
response: t.object({
|
|
461
|
+
total: t.integer(),
|
|
462
|
+
byType: t.record(t.text(), t.integer()),
|
|
463
|
+
bySeverity: t.object({
|
|
464
|
+
info: t.integer(),
|
|
465
|
+
warning: t.integer(),
|
|
466
|
+
critical: t.integer()
|
|
467
|
+
}),
|
|
468
|
+
successRate: t.number(),
|
|
469
|
+
recentFailures: t.array(auditResourceSchema)
|
|
470
|
+
})
|
|
471
|
+
},
|
|
472
|
+
handler: ({ query }) => this.auditService.getStats({
|
|
473
|
+
from: query.from ? new Date(query.from) : void 0,
|
|
474
|
+
to: query.to ? new Date(query.to) : void 0,
|
|
475
|
+
userRealm: query.userRealm
|
|
476
|
+
})
|
|
477
|
+
});
|
|
478
|
+
/**
|
|
479
|
+
* Get registered audit types.
|
|
480
|
+
*/
|
|
481
|
+
getTypes = $action({
|
|
482
|
+
path: `${this.url}/types`,
|
|
483
|
+
group: this.group,
|
|
484
|
+
description: "Get all registered audit types",
|
|
485
|
+
schema: { response: t.array(t.object({
|
|
486
|
+
type: t.text(),
|
|
487
|
+
description: t.optional(t.text()),
|
|
488
|
+
actions: t.array(t.text())
|
|
489
|
+
})) },
|
|
490
|
+
handler: () => this.auditService.getRegisteredTypes()
|
|
491
|
+
});
|
|
492
|
+
/**
|
|
493
|
+
* Get distinct values for filters.
|
|
494
|
+
*/
|
|
495
|
+
getFilterOptions = $action({
|
|
496
|
+
path: `${this.url}/filters`,
|
|
497
|
+
group: this.group,
|
|
498
|
+
description: "Get distinct values for audit filters",
|
|
499
|
+
schema: { response: t.object({
|
|
500
|
+
types: t.array(t.text()),
|
|
501
|
+
actions: t.array(t.text()),
|
|
502
|
+
resourceTypes: t.array(t.text()),
|
|
503
|
+
userRealms: t.array(t.text())
|
|
504
|
+
}) },
|
|
505
|
+
handler: async () => {
|
|
506
|
+
const types = this.auditService.getRegisteredTypes();
|
|
507
|
+
return {
|
|
508
|
+
types: types.map((t$1) => t$1.type),
|
|
509
|
+
actions: types.flatMap((t$1) => t$1.actions),
|
|
510
|
+
resourceTypes: [
|
|
511
|
+
"user",
|
|
512
|
+
"session",
|
|
513
|
+
"file",
|
|
514
|
+
"order",
|
|
515
|
+
"payment"
|
|
516
|
+
],
|
|
517
|
+
userRealms: ["default"]
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
//#endregion
|
|
524
|
+
//#region ../../src/api-audits/primitives/$audit.ts
|
|
525
|
+
/**
|
|
526
|
+
* Audit type primitive for registering domain-specific audit events.
|
|
527
|
+
*
|
|
528
|
+
* Provides a type-safe way to define and log audit events within a specific domain.
|
|
529
|
+
*
|
|
530
|
+
* @example
|
|
531
|
+
* ```ts
|
|
532
|
+
* class PaymentAudits {
|
|
533
|
+
* audit = $audit({
|
|
534
|
+
* type: "payment",
|
|
535
|
+
* description: "Payment-related audit events",
|
|
536
|
+
* actions: ["create", "refund", "cancel", "dispute"],
|
|
537
|
+
* });
|
|
538
|
+
*
|
|
539
|
+
* async logPaymentCreated(paymentId: string, userId: string, amount: number) {
|
|
540
|
+
* await this.audit.log("create", {
|
|
541
|
+
* userId,
|
|
542
|
+
* resourceType: "payment",
|
|
543
|
+
* resourceId: paymentId,
|
|
544
|
+
* description: `Payment of ${amount} created`,
|
|
545
|
+
* metadata: { amount },
|
|
546
|
+
* });
|
|
547
|
+
* }
|
|
548
|
+
* }
|
|
549
|
+
* ```
|
|
550
|
+
*/
|
|
551
|
+
var AuditPrimitive = class extends Primitive {
|
|
552
|
+
auditService = $inject(AuditService);
|
|
553
|
+
/**
|
|
554
|
+
* The audit type identifier.
|
|
555
|
+
*/
|
|
556
|
+
get type() {
|
|
557
|
+
return this.options.type;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* The audit type description.
|
|
561
|
+
*/
|
|
562
|
+
get description() {
|
|
563
|
+
return this.options.description;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* The allowed actions for this audit type.
|
|
567
|
+
*/
|
|
568
|
+
get actions() {
|
|
569
|
+
return this.options.actions;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Log an audit event for this type.
|
|
573
|
+
*/
|
|
574
|
+
async log(action, options = {}) {
|
|
575
|
+
await this.auditService.record(this.options.type, action, options);
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Log a successful audit event.
|
|
579
|
+
*/
|
|
580
|
+
async logSuccess(action, options = {}) {
|
|
581
|
+
await this.log(action, {
|
|
582
|
+
...options,
|
|
583
|
+
success: true
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Log a failed audit event.
|
|
588
|
+
*/
|
|
589
|
+
async logFailure(action, errorMessage, options = {}) {
|
|
590
|
+
await this.log(action, {
|
|
591
|
+
...options,
|
|
592
|
+
success: false,
|
|
593
|
+
errorMessage
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Called during initialization to register this audit type.
|
|
598
|
+
*/
|
|
599
|
+
onInit() {
|
|
600
|
+
const definition = {
|
|
601
|
+
type: this.options.type,
|
|
602
|
+
description: this.options.description,
|
|
603
|
+
actions: this.options.actions
|
|
604
|
+
};
|
|
605
|
+
this.auditService.registerType(definition);
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
/**
|
|
609
|
+
* Create an audit type primitive.
|
|
610
|
+
*
|
|
611
|
+
* @example
|
|
612
|
+
* ```ts
|
|
613
|
+
* class OrderAudits {
|
|
614
|
+
* audit = $audit({
|
|
615
|
+
* type: "order",
|
|
616
|
+
* description: "Order management events",
|
|
617
|
+
* actions: ["create", "update", "cancel", "fulfill", "ship"],
|
|
618
|
+
* });
|
|
619
|
+
* }
|
|
620
|
+
* ```
|
|
621
|
+
*/
|
|
622
|
+
const $audit = (options) => {
|
|
623
|
+
return createPrimitive(AuditPrimitive, options);
|
|
624
|
+
};
|
|
625
|
+
$audit[KIND] = AuditPrimitive;
|
|
626
|
+
|
|
627
|
+
//#endregion
|
|
628
|
+
//#region ../../src/api-audits/index.ts
|
|
629
|
+
/**
|
|
630
|
+
* Provides audit logging API endpoints for Alepha applications.
|
|
631
|
+
*
|
|
632
|
+
* This module includes:
|
|
633
|
+
* - Audit log CRUD operations
|
|
634
|
+
* - Filtering and searching audit events
|
|
635
|
+
* - Audit statistics and analytics
|
|
636
|
+
* - `$audit` primitive for domain-specific audit types
|
|
637
|
+
*
|
|
638
|
+
* @module alepha.api.audits
|
|
639
|
+
*
|
|
640
|
+
* @example
|
|
641
|
+
* ```ts
|
|
642
|
+
* // In your app module
|
|
643
|
+
* import { AlephaApiAudits } from "alepha/api/audits";
|
|
644
|
+
*
|
|
645
|
+
* const App = $module({
|
|
646
|
+
* name: "app",
|
|
647
|
+
* services: [AlephaApiAudits, ...],
|
|
648
|
+
* });
|
|
649
|
+
*
|
|
650
|
+
* // Create domain-specific audit types
|
|
651
|
+
* class PaymentAudits {
|
|
652
|
+
* audit = $audit({
|
|
653
|
+
* type: "payment",
|
|
654
|
+
* actions: ["create", "refund", "cancel"],
|
|
655
|
+
* });
|
|
656
|
+
*
|
|
657
|
+
* async onPaymentCreated(paymentId: string, userId: string) {
|
|
658
|
+
* await this.audit.log("create", {
|
|
659
|
+
* userId,
|
|
660
|
+
* resourceType: "payment",
|
|
661
|
+
* resourceId: paymentId,
|
|
662
|
+
* });
|
|
663
|
+
* }
|
|
664
|
+
* }
|
|
665
|
+
* ```
|
|
666
|
+
*/
|
|
667
|
+
const AlephaApiAudits = $module({
|
|
668
|
+
name: "alepha.api.audits",
|
|
669
|
+
services: [AuditService, AuditController]
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
//#endregion
|
|
673
|
+
export { $audit, AlephaApiAudits, AuditController, AuditPrimitive, AuditService, auditEntityInsertSchema, auditEntitySchema, auditQuerySchema, auditResourceSchema, auditSeveritySchema, audits, createAuditSchema };
|
|
674
|
+
//# sourceMappingURL=index.js.map
|