@nebutra/audit 0.1.0 → 0.1.2

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.
@@ -0,0 +1,354 @@
1
+ import { z } from "zod";
2
+ export declare const ActorTypeSchema: z.ZodEnum<{
3
+ user: "user";
4
+ system: "system";
5
+ api_key: "api_key";
6
+ service: "service";
7
+ }>;
8
+ export type ActorType = z.infer<typeof ActorTypeSchema>;
9
+ export declare const OutcomeSchema: z.ZodEnum<{
10
+ success: "success";
11
+ failure: "failure";
12
+ denied: "denied";
13
+ }>;
14
+ export type Outcome = z.infer<typeof OutcomeSchema>;
15
+ export declare const SeveritySchema: z.ZodEnum<{
16
+ info: "info";
17
+ warning: "warning";
18
+ critical: "critical";
19
+ }>;
20
+ export type Severity = z.infer<typeof SeveritySchema>;
21
+ export declare const ActorSchema: z.ZodObject<{
22
+ id: z.ZodString;
23
+ type: z.ZodEnum<{
24
+ user: "user";
25
+ system: "system";
26
+ api_key: "api_key";
27
+ service: "service";
28
+ }>;
29
+ email: z.ZodOptional<z.ZodString>;
30
+ name: z.ZodOptional<z.ZodString>;
31
+ }, z.core.$strip>;
32
+ export type Actor = z.infer<typeof ActorSchema>;
33
+ export declare const ResourceSchema: z.ZodObject<{
34
+ type: z.ZodString;
35
+ id: z.ZodString;
36
+ name: z.ZodOptional<z.ZodString>;
37
+ }, z.core.$strip>;
38
+ export type Resource = z.infer<typeof ResourceSchema>;
39
+ export declare const GeoSchema: z.ZodObject<{
40
+ country: z.ZodOptional<z.ZodString>;
41
+ region: z.ZodOptional<z.ZodString>;
42
+ }, z.core.$strip>;
43
+ export type Geo = z.infer<typeof GeoSchema>;
44
+ export declare const ContextSchema: z.ZodObject<{
45
+ ip: z.ZodOptional<z.ZodString>;
46
+ userAgent: z.ZodOptional<z.ZodString>;
47
+ requestId: z.ZodOptional<z.ZodString>;
48
+ sessionId: z.ZodOptional<z.ZodString>;
49
+ geo: z.ZodOptional<z.ZodObject<{
50
+ country: z.ZodOptional<z.ZodString>;
51
+ region: z.ZodOptional<z.ZodString>;
52
+ }, z.core.$strip>>;
53
+ }, z.core.$strip>;
54
+ export type Context = z.infer<typeof ContextSchema>;
55
+ export declare const ChangesSchema: z.ZodObject<{
56
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
57
+ after: z.ZodRecord<z.ZodString, z.ZodUnknown>;
58
+ }, z.core.$strip>;
59
+ export type Changes = z.infer<typeof ChangesSchema>;
60
+ export declare const ActionSchema: z.ZodString;
61
+ export declare const AuditEventSchema: z.ZodObject<{
62
+ id: z.ZodString;
63
+ timestamp: z.ZodUnion<[z.ZodString, z.ZodPipe<z.ZodDate, z.ZodTransform<string, Date>>]>;
64
+ actor: z.ZodObject<{
65
+ id: z.ZodString;
66
+ type: z.ZodEnum<{
67
+ user: "user";
68
+ system: "system";
69
+ api_key: "api_key";
70
+ service: "service";
71
+ }>;
72
+ email: z.ZodOptional<z.ZodString>;
73
+ name: z.ZodOptional<z.ZodString>;
74
+ }, z.core.$strip>;
75
+ tenantId: z.ZodString;
76
+ action: z.ZodString;
77
+ resource: z.ZodObject<{
78
+ type: z.ZodString;
79
+ id: z.ZodString;
80
+ name: z.ZodOptional<z.ZodString>;
81
+ }, z.core.$strip>;
82
+ outcome: z.ZodEnum<{
83
+ success: "success";
84
+ failure: "failure";
85
+ denied: "denied";
86
+ }>;
87
+ severity: z.ZodDefault<z.ZodEnum<{
88
+ info: "info";
89
+ warning: "warning";
90
+ critical: "critical";
91
+ }>>;
92
+ context: z.ZodDefault<z.ZodObject<{
93
+ ip: z.ZodOptional<z.ZodString>;
94
+ userAgent: z.ZodOptional<z.ZodString>;
95
+ requestId: z.ZodOptional<z.ZodString>;
96
+ sessionId: z.ZodOptional<z.ZodString>;
97
+ geo: z.ZodOptional<z.ZodObject<{
98
+ country: z.ZodOptional<z.ZodString>;
99
+ region: z.ZodOptional<z.ZodString>;
100
+ }, z.core.$strip>>;
101
+ }, z.core.$strip>>;
102
+ changes: z.ZodOptional<z.ZodObject<{
103
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
104
+ after: z.ZodRecord<z.ZodString, z.ZodUnknown>;
105
+ }, z.core.$strip>>;
106
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
107
+ }, z.core.$strip>;
108
+ export type AuditEvent = z.infer<typeof AuditEventSchema>;
109
+ export declare const AuditEventInputSchema: z.ZodObject<{
110
+ id: z.ZodOptional<z.ZodString>;
111
+ timestamp: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodPipe<z.ZodDate, z.ZodTransform<string, Date>>]>>;
112
+ actor: z.ZodObject<{
113
+ id: z.ZodString;
114
+ type: z.ZodEnum<{
115
+ user: "user";
116
+ system: "system";
117
+ api_key: "api_key";
118
+ service: "service";
119
+ }>;
120
+ email: z.ZodOptional<z.ZodString>;
121
+ name: z.ZodOptional<z.ZodString>;
122
+ }, z.core.$strip>;
123
+ tenantId: z.ZodString;
124
+ action: z.ZodString;
125
+ resource: z.ZodObject<{
126
+ type: z.ZodString;
127
+ id: z.ZodString;
128
+ name: z.ZodOptional<z.ZodString>;
129
+ }, z.core.$strip>;
130
+ outcome: z.ZodEnum<{
131
+ success: "success";
132
+ failure: "failure";
133
+ denied: "denied";
134
+ }>;
135
+ severity: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
136
+ info: "info";
137
+ warning: "warning";
138
+ critical: "critical";
139
+ }>>>;
140
+ context: z.ZodOptional<z.ZodDefault<z.ZodObject<{
141
+ ip: z.ZodOptional<z.ZodString>;
142
+ userAgent: z.ZodOptional<z.ZodString>;
143
+ requestId: z.ZodOptional<z.ZodString>;
144
+ sessionId: z.ZodOptional<z.ZodString>;
145
+ geo: z.ZodOptional<z.ZodObject<{
146
+ country: z.ZodOptional<z.ZodString>;
147
+ region: z.ZodOptional<z.ZodString>;
148
+ }, z.core.$strip>>;
149
+ }, z.core.$strip>>>;
150
+ changes: z.ZodOptional<z.ZodObject<{
151
+ before: z.ZodRecord<z.ZodString, z.ZodUnknown>;
152
+ after: z.ZodRecord<z.ZodString, z.ZodUnknown>;
153
+ }, z.core.$strip>>;
154
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
155
+ }, z.core.$strip>;
156
+ export type AuditEventInput = z.input<typeof AuditEventInputSchema>;
157
+ export declare const AuditQueryFilterSchema: z.ZodObject<{
158
+ tenantId: z.ZodOptional<z.ZodString>;
159
+ actorId: z.ZodOptional<z.ZodString>;
160
+ action: z.ZodOptional<z.ZodString>;
161
+ resourceType: z.ZodOptional<z.ZodString>;
162
+ resourceId: z.ZodOptional<z.ZodString>;
163
+ outcome: z.ZodOptional<z.ZodEnum<{
164
+ success: "success";
165
+ failure: "failure";
166
+ denied: "denied";
167
+ }>>;
168
+ startDate: z.ZodOptional<z.ZodDate>;
169
+ endDate: z.ZodOptional<z.ZodDate>;
170
+ limit: z.ZodDefault<z.ZodNumber>;
171
+ offset: z.ZodDefault<z.ZodNumber>;
172
+ }, z.core.$strip>;
173
+ export type AuditQueryFilter = z.input<typeof AuditQueryFilterSchema>;
174
+ /**
175
+ * Declare a strongly-typed audit action.
176
+ *
177
+ * @example
178
+ * ```ts
179
+ * const ROLE_CHANGED = defineAction("org.member.role_changed", z.object({
180
+ * oldRole: z.string(),
181
+ * newRole: z.string(),
182
+ * }));
183
+ *
184
+ * await audit.log({
185
+ * action: ROLE_CHANGED.name,
186
+ * metadata: ROLE_CHANGED.metadata.parse({ oldRole: "member", newRole: "admin" }),
187
+ * ...
188
+ * });
189
+ * ```
190
+ */
191
+ export declare function defineAction<TName extends string, TMeta extends z.ZodTypeAny = z.ZodUnknown>(name: TName, metadataSchema?: TMeta): {
192
+ name: TName;
193
+ metadata: TMeta;
194
+ };
195
+ export declare const ACTIONS: {
196
+ readonly AUTH_LOGIN_SUCCESS: {
197
+ name: "auth.login.success";
198
+ metadata: z.ZodUnknown;
199
+ };
200
+ readonly AUTH_LOGIN_FAILURE: {
201
+ name: "auth.login.failure";
202
+ metadata: z.ZodUnknown;
203
+ };
204
+ readonly AUTH_LOGOUT: {
205
+ name: "auth.logout";
206
+ metadata: z.ZodUnknown;
207
+ };
208
+ readonly AUTH_SIGNUP: {
209
+ name: "auth.signup";
210
+ metadata: z.ZodUnknown;
211
+ };
212
+ readonly AUTH_SESSION_REVOKED: {
213
+ name: "auth.session.revoked";
214
+ metadata: z.ZodUnknown;
215
+ };
216
+ readonly AUTH_SESSION_REVOKED_OTHER: {
217
+ name: "auth.session.revoked_other";
218
+ metadata: z.ZodUnknown;
219
+ };
220
+ readonly AUTH_PASSWORD_CHANGED: {
221
+ name: "auth.password.changed";
222
+ metadata: z.ZodUnknown;
223
+ };
224
+ readonly AUTH_2FA_ENABLED: {
225
+ name: "auth.2fa.enabled";
226
+ metadata: z.ZodUnknown;
227
+ };
228
+ readonly AUTH_2FA_DISABLED: {
229
+ name: "auth.2fa.disabled";
230
+ metadata: z.ZodUnknown;
231
+ };
232
+ readonly ORG_CREATED: {
233
+ name: "org.created";
234
+ metadata: z.ZodUnknown;
235
+ };
236
+ readonly ORG_UPDATED: {
237
+ name: "org.updated";
238
+ metadata: z.ZodUnknown;
239
+ };
240
+ readonly ORG_DELETED: {
241
+ name: "org.deleted";
242
+ metadata: z.ZodUnknown;
243
+ };
244
+ readonly ORG_MEMBER_ADDED: {
245
+ name: "org.member.added";
246
+ metadata: z.ZodUnknown;
247
+ };
248
+ readonly ORG_MEMBER_REMOVED: {
249
+ name: "org.member.removed";
250
+ metadata: z.ZodUnknown;
251
+ };
252
+ readonly ORG_MEMBER_ROLE_CHANGED: {
253
+ name: "org.member.role_changed";
254
+ metadata: z.ZodObject<{
255
+ oldRole: z.ZodString;
256
+ newRole: z.ZodString;
257
+ }, z.core.$strip>;
258
+ };
259
+ readonly ADMIN_IMPERSONATE_STARTED: {
260
+ name: "admin.impersonate.started";
261
+ metadata: z.ZodUnknown;
262
+ };
263
+ readonly ADMIN_IMPERSONATE_ENDED: {
264
+ name: "admin.impersonate.ended";
265
+ metadata: z.ZodUnknown;
266
+ };
267
+ readonly ADMIN_USER_UPDATED: {
268
+ name: "admin.user.updated";
269
+ metadata: z.ZodUnknown;
270
+ };
271
+ readonly ADMIN_USER_DELETED: {
272
+ name: "admin.user.deleted";
273
+ metadata: z.ZodUnknown;
274
+ };
275
+ readonly ADMIN_ORG_UPDATED: {
276
+ name: "admin.org.updated";
277
+ metadata: z.ZodUnknown;
278
+ };
279
+ readonly ADMIN_ORG_DELETED: {
280
+ name: "admin.org.deleted";
281
+ metadata: z.ZodUnknown;
282
+ };
283
+ readonly ACCOUNT_EMAIL_CHANGED: {
284
+ name: "account.email.changed";
285
+ metadata: z.ZodUnknown;
286
+ };
287
+ readonly BILLING_CHECKOUT_STARTED: {
288
+ name: "billing.checkout.started";
289
+ metadata: z.ZodObject<{
290
+ priceId: z.ZodString;
291
+ quantity: z.ZodOptional<z.ZodNumber>;
292
+ }, z.core.$strip>;
293
+ };
294
+ readonly BILLING_SUBSCRIPTION_CREATED: {
295
+ name: "billing.subscription.created";
296
+ metadata: z.ZodUnknown;
297
+ };
298
+ readonly BILLING_SUBSCRIPTION_UPDATED: {
299
+ name: "billing.subscription.updated";
300
+ metadata: z.ZodUnknown;
301
+ };
302
+ readonly BILLING_SUBSCRIPTION_CANCELLED: {
303
+ name: "billing.subscription.cancelled";
304
+ metadata: z.ZodUnknown;
305
+ };
306
+ readonly DATA_EXPORT_REQUESTED: {
307
+ name: "data.export.requested";
308
+ metadata: z.ZodUnknown;
309
+ };
310
+ readonly DATA_EXPORT_COMPLETED: {
311
+ name: "data.export.completed";
312
+ metadata: z.ZodUnknown;
313
+ };
314
+ readonly SETTINGS_UPDATED: {
315
+ name: "settings.updated";
316
+ metadata: z.ZodUnknown;
317
+ };
318
+ readonly SECRET_ACCESS: {
319
+ name: "secret.access";
320
+ metadata: z.ZodUnknown;
321
+ };
322
+ readonly WEBHOOK_CREATED: {
323
+ name: "webhook.created";
324
+ metadata: z.ZodUnknown;
325
+ };
326
+ readonly WEBHOOK_UPDATED: {
327
+ name: "webhook.updated";
328
+ metadata: z.ZodUnknown;
329
+ };
330
+ readonly WEBHOOK_DELETED: {
331
+ name: "webhook.deleted";
332
+ metadata: z.ZodUnknown;
333
+ };
334
+ readonly WEBHOOK_DISPATCHED: {
335
+ name: "webhook.dispatched";
336
+ metadata: z.ZodUnknown;
337
+ };
338
+ readonly CRON_RUN: {
339
+ name: "cron.run";
340
+ metadata: z.ZodObject<{
341
+ job: z.ZodString;
342
+ durationMs: z.ZodOptional<z.ZodNumber>;
343
+ }, z.core.$strip>;
344
+ };
345
+ readonly API_KEY_CREATED: {
346
+ name: "api_key.created";
347
+ metadata: z.ZodUnknown;
348
+ };
349
+ readonly API_KEY_REVOKED: {
350
+ name: "api_key.revoked";
351
+ metadata: z.ZodUnknown;
352
+ };
353
+ };
354
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,eAAO,MAAM,eAAe;;;;;EAAmD,CAAC;AAChF,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,eAAO,MAAM,aAAa;;;;EAA2C,CAAC;AACtE,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,eAAO,MAAM,cAAc;;;;EAA0C,CAAC;AACtE,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAMtD,eAAO,MAAM,WAAW;;;;;;;;;;iBAKtB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD,eAAO,MAAM,cAAc;;;;iBAIzB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD,eAAO,MAAM,SAAS;;;iBAGpB,CAAC;AACH,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAE5C,eAAO,MAAM,aAAa;;;;;;;;;iBAMxB,CAAC;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,eAAO,MAAM,aAAa;;;iBAGxB,CAAC;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAOpD,eAAO,MAAM,YAAY,aAMrB,CAAC;AAML,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAe3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAKhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAMpE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;iBAWjC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAMtE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,KAAK,SAAS,MAAM,EAAE,KAAK,SAAS,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,EAC1F,IAAI,EAAE,KAAK,EACX,cAAc,CAAC,EAAE,KAAK,GACrB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,KAAK,CAAA;CAAE,CAQlC;AAMD,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkEV,CAAC"}
package/dist/schema.js ADDED
@@ -0,0 +1,186 @@
1
+ // =============================================================================
2
+ // Audit Event Schema (Zod)
3
+ // =============================================================================
4
+ // Single source of truth for the structure of an audit event. Every persistence
5
+ // provider, every API gateway middleware, and every test fixture validates
6
+ // against the schemas defined here.
7
+ //
8
+ // Design goals (SOC 2 alignment):
9
+ // - WHO : `actor` — the principal that performed the action
10
+ // - WHAT : `action` + `resource` — dotted action name + the thing acted on
11
+ // - WHEN : `timestamp` — ISO 8601, UTC
12
+ // - WHERE : `context.ip` + `context.geo` + `context.userAgent`
13
+ // - TENANT: `tenantId` — every event MUST be scoped to a tenant (or "system")
14
+ // - STATE : `changes.before` / `changes.after` for any state-mutating action
15
+ //
16
+ // The audit table itself is APPEND-ONLY — see MIGRATION.md.
17
+ // =============================================================================
18
+ import { z } from "zod";
19
+ // -----------------------------------------------------------------------------
20
+ // Primitive enums
21
+ // -----------------------------------------------------------------------------
22
+ export const ActorTypeSchema = z.enum(["user", "system", "api_key", "service"]);
23
+ export const OutcomeSchema = z.enum(["success", "failure", "denied"]);
24
+ export const SeveritySchema = z.enum(["info", "warning", "critical"]);
25
+ // -----------------------------------------------------------------------------
26
+ // Sub-schemas
27
+ // -----------------------------------------------------------------------------
28
+ export const ActorSchema = z.object({
29
+ id: z.string().min(1),
30
+ type: ActorTypeSchema,
31
+ email: z.string().email().optional(),
32
+ name: z.string().optional(),
33
+ });
34
+ export const ResourceSchema = z.object({
35
+ type: z.string().min(1),
36
+ id: z.string().min(1),
37
+ name: z.string().optional(),
38
+ });
39
+ export const GeoSchema = z.object({
40
+ country: z.string().optional(),
41
+ region: z.string().optional(),
42
+ });
43
+ export const ContextSchema = z.object({
44
+ ip: z.string().optional(),
45
+ userAgent: z.string().optional(),
46
+ requestId: z.string().optional(),
47
+ sessionId: z.string().optional(),
48
+ geo: GeoSchema.optional(),
49
+ });
50
+ export const ChangesSchema = z.object({
51
+ before: z.record(z.string(), z.unknown()),
52
+ after: z.record(z.string(), z.unknown()),
53
+ });
54
+ // -----------------------------------------------------------------------------
55
+ // Action name — dotted string with at least two segments
56
+ // e.g. "auth.login.success", "billing.subscription.created"
57
+ // -----------------------------------------------------------------------------
58
+ export const ActionSchema = z
59
+ .string()
60
+ .min(3)
61
+ .regex(/^[a-z][a-z0-9_]*(\.[a-z0-9_]+)+$/, {
62
+ message: "Action must be lowercase, dotted, and contain at least two segments (e.g. 'billing.subscription.created').",
63
+ });
64
+ // -----------------------------------------------------------------------------
65
+ // Full audit event
66
+ // -----------------------------------------------------------------------------
67
+ export const AuditEventSchema = z.object({
68
+ id: z.string().uuid(),
69
+ timestamp: z
70
+ .string()
71
+ .datetime({ offset: true })
72
+ .or(z.date().transform((d) => d.toISOString())),
73
+ actor: ActorSchema,
74
+ tenantId: z.string().min(1),
75
+ action: ActionSchema,
76
+ resource: ResourceSchema,
77
+ outcome: OutcomeSchema,
78
+ severity: SeveritySchema.default("info"),
79
+ context: ContextSchema.default({}),
80
+ changes: ChangesSchema.optional(),
81
+ metadata: z.record(z.string(), z.unknown()).optional(),
82
+ });
83
+ // Input shape — `id` and `timestamp` are auto-generated when callers use the
84
+ // helper APIs but may be supplied explicitly (e.g. backfills).
85
+ export const AuditEventInputSchema = AuditEventSchema.partial({
86
+ id: true,
87
+ timestamp: true,
88
+ severity: true,
89
+ context: true,
90
+ });
91
+ // -----------------------------------------------------------------------------
92
+ // Query filter
93
+ // -----------------------------------------------------------------------------
94
+ export const AuditQueryFilterSchema = z.object({
95
+ tenantId: z.string().optional(),
96
+ actorId: z.string().optional(),
97
+ action: z.string().optional(),
98
+ resourceType: z.string().optional(),
99
+ resourceId: z.string().optional(),
100
+ outcome: OutcomeSchema.optional(),
101
+ startDate: z.date().optional(),
102
+ endDate: z.date().optional(),
103
+ limit: z.number().int().positive().max(1000).default(100),
104
+ offset: z.number().int().nonnegative().default(0),
105
+ });
106
+ // -----------------------------------------------------------------------------
107
+ // defineAction — strongly-typed action constructor
108
+ // -----------------------------------------------------------------------------
109
+ /**
110
+ * Declare a strongly-typed audit action.
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * const ROLE_CHANGED = defineAction("org.member.role_changed", z.object({
115
+ * oldRole: z.string(),
116
+ * newRole: z.string(),
117
+ * }));
118
+ *
119
+ * await audit.log({
120
+ * action: ROLE_CHANGED.name,
121
+ * metadata: ROLE_CHANGED.metadata.parse({ oldRole: "member", newRole: "admin" }),
122
+ * ...
123
+ * });
124
+ * ```
125
+ */
126
+ export function defineAction(name, metadataSchema) {
127
+ // Validate the action name shape eagerly so misnamed actions fail at module
128
+ // load rather than first-call.
129
+ ActionSchema.parse(name);
130
+ return {
131
+ name,
132
+ metadata: (metadataSchema ?? z.unknown()),
133
+ };
134
+ }
135
+ // -----------------------------------------------------------------------------
136
+ // Canonical action catalog (extend freely, keep names stable)
137
+ // -----------------------------------------------------------------------------
138
+ export const ACTIONS = {
139
+ // ── Auth ──
140
+ AUTH_LOGIN_SUCCESS: defineAction("auth.login.success"),
141
+ AUTH_LOGIN_FAILURE: defineAction("auth.login.failure"),
142
+ AUTH_LOGOUT: defineAction("auth.logout"),
143
+ AUTH_SIGNUP: defineAction("auth.signup"),
144
+ AUTH_SESSION_REVOKED: defineAction("auth.session.revoked"),
145
+ AUTH_SESSION_REVOKED_OTHER: defineAction("auth.session.revoked_other"),
146
+ AUTH_PASSWORD_CHANGED: defineAction("auth.password.changed"),
147
+ AUTH_2FA_ENABLED: defineAction("auth.2fa.enabled"),
148
+ AUTH_2FA_DISABLED: defineAction("auth.2fa.disabled"),
149
+ // ── Organizations ──
150
+ ORG_CREATED: defineAction("org.created"),
151
+ ORG_UPDATED: defineAction("org.updated"),
152
+ ORG_DELETED: defineAction("org.deleted"),
153
+ ORG_MEMBER_ADDED: defineAction("org.member.added"),
154
+ ORG_MEMBER_REMOVED: defineAction("org.member.removed"),
155
+ ORG_MEMBER_ROLE_CHANGED: defineAction("org.member.role_changed", z.object({ oldRole: z.string(), newRole: z.string() })),
156
+ // ── Admin ──
157
+ ADMIN_IMPERSONATE_STARTED: defineAction("admin.impersonate.started"),
158
+ ADMIN_IMPERSONATE_ENDED: defineAction("admin.impersonate.ended"),
159
+ ADMIN_USER_UPDATED: defineAction("admin.user.updated"),
160
+ ADMIN_USER_DELETED: defineAction("admin.user.deleted"),
161
+ ADMIN_ORG_UPDATED: defineAction("admin.org.updated"),
162
+ ADMIN_ORG_DELETED: defineAction("admin.org.deleted"),
163
+ // ── Account ──
164
+ ACCOUNT_EMAIL_CHANGED: defineAction("account.email.changed"),
165
+ // ── Billing ──
166
+ BILLING_CHECKOUT_STARTED: defineAction("billing.checkout.started", z.object({ priceId: z.string(), quantity: z.number().optional() })),
167
+ BILLING_SUBSCRIPTION_CREATED: defineAction("billing.subscription.created"),
168
+ BILLING_SUBSCRIPTION_UPDATED: defineAction("billing.subscription.updated"),
169
+ BILLING_SUBSCRIPTION_CANCELLED: defineAction("billing.subscription.cancelled"),
170
+ // ── Data ──
171
+ DATA_EXPORT_REQUESTED: defineAction("data.export.requested"),
172
+ DATA_EXPORT_COMPLETED: defineAction("data.export.completed"),
173
+ // ── Settings & Secrets ──
174
+ SETTINGS_UPDATED: defineAction("settings.updated"),
175
+ SECRET_ACCESS: defineAction("secret.access"),
176
+ // ── Webhooks ──
177
+ WEBHOOK_CREATED: defineAction("webhook.created"),
178
+ WEBHOOK_UPDATED: defineAction("webhook.updated"),
179
+ WEBHOOK_DELETED: defineAction("webhook.deleted"),
180
+ WEBHOOK_DISPATCHED: defineAction("webhook.dispatched"),
181
+ // ── Cron ──
182
+ CRON_RUN: defineAction("cron.run", z.object({ job: z.string(), durationMs: z.number().optional() })),
183
+ // ── API Keys ──
184
+ API_KEY_CREATED: defineAction("api_key.created"),
185
+ API_KEY_REVOKED: defineAction("api_key.revoked"),
186
+ };
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@nebutra/audit",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Audit logging for sensitive operations",
5
5
  "private": false,
6
- "license": "AGPL-3.0",
6
+ "license": "MIT",
7
7
  "type": "module",
8
8
  "nebutra": {
9
9
  "status": "wip",
@@ -47,10 +47,10 @@
47
47
  ],
48
48
  "dependencies": {
49
49
  "zod": "^4.3.6",
50
- "@nebutra/logger": "0.1.0"
50
+ "@nebutra/logger": "0.1.2"
51
51
  },
52
52
  "peerDependencies": {
53
- "@nebutra/db": "0.1.0"
53
+ "@nebutra/db": "0.1.2"
54
54
  },
55
55
  "peerDependenciesMeta": {
56
56
  "@nebutra/db": {
@@ -61,7 +61,7 @@
61
61
  "@types/node": "^22.19.15",
62
62
  "typescript": "^5.9.3",
63
63
  "vitest": "^4.0.18",
64
- "@nebutra/db": "0.1.0"
64
+ "@nebutra/db": "0.1.2"
65
65
  },
66
66
  "homepage": "https://github.com/Nebutra/Nebutra-Sailor/tree/main/packages/iam/audit#readme",
67
67
  "repository": {