@unchainedshop/events 4.5.0 → 4.6.0
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/lib/EventDirector.js +1 -1
- package/lib/audit/audit-integration.d.ts +3 -0
- package/lib/audit/audit-integration.js +200 -0
- package/lib/audit/index.d.ts +95 -0
- package/lib/audit/index.js +416 -0
- package/lib/audit/ocsf-types.d.ts +165 -0
- package/lib/audit/ocsf-types.js +64 -0
- package/lib/events-index.d.ts +2 -0
- package/lib/events-index.js +2 -0
- package/package.json +3 -2
package/lib/EventDirector.js
CHANGED
|
@@ -31,7 +31,7 @@ export const EventDirector = {
|
|
|
31
31
|
HistoryAdapter?.publish(eventName, {
|
|
32
32
|
payload,
|
|
33
33
|
});
|
|
34
|
-
logger.debug(`EventDirector -> Emitted ${eventName}
|
|
34
|
+
logger.debug(`EventDirector -> Emitted ${eventName}`);
|
|
35
35
|
},
|
|
36
36
|
subscribe: (eventName, callback) => {
|
|
37
37
|
const currentSubscription = `${eventName}${callback?.toString()}`;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { AuditLog } from './index.ts';
|
|
2
|
+
export declare function configureAuditIntegration(auditLog: AuditLog): void;
|
|
3
|
+
export declare const AUDITED_EVENTS: readonly ["API_LOGIN_TOKEN_CREATED", "API_LOGOUT", "USER_CREATE", "USER_REMOVE", "USER_UPDATE_PASSWORD", "USER_ADD_ROLES", "USER_UPDATE_ROLE", "USER_ACCOUNT_ACTION", "ORDER_CREATE", "ORDER_CHECKOUT", "ORDER_CONFIRMED", "ORDER_FULFILLED", "ORDER_REJECTED", "ORDER_ADD_PRODUCT", "ORDER_REMOVE_CART_ITEM", "ORDER_PAY", "ORDER_UPDATE_PAYMENT"];
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
2
|
+
import { EventDirector } from "../EventDirector.js";
|
|
3
|
+
import { OCSF_AUTH_ACTIVITY, OCSF_ACCOUNT_ACTIVITY, OCSF_API_ACTIVITY } from "./ocsf-types.js";
|
|
4
|
+
const logger = createLogger('unchained:audit-integration');
|
|
5
|
+
function extractContext(payload) {
|
|
6
|
+
return {
|
|
7
|
+
userId: payload.userId || payload.user?._id || payload.user?.id,
|
|
8
|
+
userName: payload.userName || payload.user?.username || payload.user?.emails?.[0]?.address,
|
|
9
|
+
remoteAddress: payload.remoteAddress || payload.ip,
|
|
10
|
+
sessionId: payload.sessionId,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const ACCOUNT_ACTION_MAP = {
|
|
14
|
+
'reset-password': {
|
|
15
|
+
activity: OCSF_ACCOUNT_ACTIVITY.PASSWORD_RESET,
|
|
16
|
+
message: 'Password reset requested',
|
|
17
|
+
},
|
|
18
|
+
'verify-email': { activity: OCSF_ACCOUNT_ACTIVITY.ENABLE, message: 'Email verified' },
|
|
19
|
+
'enroll-account': { activity: OCSF_ACCOUNT_ACTIVITY.ENABLE, message: 'Account enrolled' },
|
|
20
|
+
};
|
|
21
|
+
export function configureAuditIntegration(auditLog) {
|
|
22
|
+
const sub = (event, handler) => {
|
|
23
|
+
try {
|
|
24
|
+
EventDirector.subscribe(event, async ({ payload }) => {
|
|
25
|
+
try {
|
|
26
|
+
await handler(payload);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
logger.error(`Audit log error for ${event}: ${error}`);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
sub('API_LOGIN_TOKEN_CREATED', async (p) => {
|
|
37
|
+
const ctx = extractContext(p);
|
|
38
|
+
await auditLog.logAuthentication({ activity: OCSF_AUTH_ACTIVITY.LOGON, ...ctx, success: true });
|
|
39
|
+
});
|
|
40
|
+
sub('API_LOGOUT', async (p) => {
|
|
41
|
+
const ctx = extractContext(p);
|
|
42
|
+
await auditLog.logAuthentication({ activity: OCSF_AUTH_ACTIVITY.LOGOFF, ...ctx, success: true });
|
|
43
|
+
});
|
|
44
|
+
sub('USER_CREATE', async (p) => {
|
|
45
|
+
const ctx = extractContext(p);
|
|
46
|
+
await auditLog.logAccountChange({ activity: OCSF_ACCOUNT_ACTIVITY.CREATE, ...ctx, success: true });
|
|
47
|
+
});
|
|
48
|
+
sub('USER_REMOVE', async (p) => {
|
|
49
|
+
const ctx = extractContext(p);
|
|
50
|
+
await auditLog.logAccountChange({ activity: OCSF_ACCOUNT_ACTIVITY.DELETE, ...ctx, success: true });
|
|
51
|
+
});
|
|
52
|
+
sub('USER_UPDATE_PASSWORD', async (p) => {
|
|
53
|
+
const ctx = extractContext(p);
|
|
54
|
+
await auditLog.logAccountChange({
|
|
55
|
+
activity: OCSF_ACCOUNT_ACTIVITY.PASSWORD_CHANGE,
|
|
56
|
+
...ctx,
|
|
57
|
+
success: true,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
sub('USER_ADD_ROLES', async (p) => {
|
|
61
|
+
const ctx = extractContext(p);
|
|
62
|
+
await auditLog.logAccountChange({
|
|
63
|
+
activity: OCSF_ACCOUNT_ACTIVITY.ATTACH_POLICY,
|
|
64
|
+
...ctx,
|
|
65
|
+
success: true,
|
|
66
|
+
message: `Roles added: ${p.roles?.join(', ') || 'unknown'}`,
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
sub('USER_UPDATE_ROLE', async (p) => {
|
|
70
|
+
const ctx = extractContext(p);
|
|
71
|
+
await auditLog.logAccountChange({
|
|
72
|
+
activity: OCSF_ACCOUNT_ACTIVITY.ATTACH_POLICY,
|
|
73
|
+
...ctx,
|
|
74
|
+
success: true,
|
|
75
|
+
message: 'User roles updated',
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
sub('USER_ACCOUNT_ACTION', async (p) => {
|
|
79
|
+
const ctx = extractContext(p);
|
|
80
|
+
const mapped = ACCOUNT_ACTION_MAP[p.action] || {
|
|
81
|
+
activity: OCSF_ACCOUNT_ACTIVITY.OTHER,
|
|
82
|
+
message: 'Account action',
|
|
83
|
+
};
|
|
84
|
+
await auditLog.logAccountChange({ ...mapped, ...ctx, success: true });
|
|
85
|
+
});
|
|
86
|
+
sub('ORDER_CREATE', async (p) => {
|
|
87
|
+
const ctx = extractContext(p);
|
|
88
|
+
await auditLog.logApiActivity({
|
|
89
|
+
activity: OCSF_API_ACTIVITY.CREATE,
|
|
90
|
+
...ctx,
|
|
91
|
+
userId: p.order?.userId || ctx.userId,
|
|
92
|
+
success: true,
|
|
93
|
+
operation: 'createOrder',
|
|
94
|
+
message: `Order created: ${p.order?._id || 'unknown'}`,
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
sub('ORDER_CHECKOUT', async (p) => {
|
|
98
|
+
const ctx = extractContext(p);
|
|
99
|
+
await auditLog.logApiActivity({
|
|
100
|
+
activity: OCSF_API_ACTIVITY.CHECKOUT,
|
|
101
|
+
...ctx,
|
|
102
|
+
userId: p.order?.userId || ctx.userId,
|
|
103
|
+
success: true,
|
|
104
|
+
operation: 'checkoutOrder',
|
|
105
|
+
message: `Order checkout: ${p.order?._id || 'unknown'}`,
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
sub('ORDER_CONFIRMED', async (p) => {
|
|
109
|
+
const ctx = extractContext(p);
|
|
110
|
+
await auditLog.logApiActivity({
|
|
111
|
+
activity: OCSF_API_ACTIVITY.UPDATE,
|
|
112
|
+
...ctx,
|
|
113
|
+
userId: p.order?.userId || ctx.userId,
|
|
114
|
+
success: true,
|
|
115
|
+
operation: 'confirmOrder',
|
|
116
|
+
message: `Order confirmed: ${p.order?._id || 'unknown'}`,
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
sub('ORDER_FULFILLED', async (p) => {
|
|
120
|
+
const ctx = extractContext(p);
|
|
121
|
+
await auditLog.logApiActivity({
|
|
122
|
+
activity: OCSF_API_ACTIVITY.UPDATE,
|
|
123
|
+
...ctx,
|
|
124
|
+
userId: p.order?.userId || ctx.userId,
|
|
125
|
+
success: true,
|
|
126
|
+
operation: 'fulfillOrder',
|
|
127
|
+
message: `Order fulfilled: ${p.order?._id || 'unknown'}`,
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
sub('ORDER_REJECTED', async (p) => {
|
|
131
|
+
const ctx = extractContext(p);
|
|
132
|
+
await auditLog.logApiActivity({
|
|
133
|
+
activity: OCSF_API_ACTIVITY.UPDATE,
|
|
134
|
+
...ctx,
|
|
135
|
+
userId: p.order?.userId || ctx.userId,
|
|
136
|
+
success: false,
|
|
137
|
+
operation: 'rejectOrder',
|
|
138
|
+
message: `Order rejected: ${p.order?._id || 'unknown'}`,
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
sub('ORDER_ADD_PRODUCT', async (p) => {
|
|
142
|
+
const ctx = extractContext(p);
|
|
143
|
+
await auditLog.logApiActivity({
|
|
144
|
+
activity: OCSF_API_ACTIVITY.UPDATE,
|
|
145
|
+
...ctx,
|
|
146
|
+
success: true,
|
|
147
|
+
operation: 'addToCart',
|
|
148
|
+
message: `Product added to cart: ${p.orderPosition?.productId || 'unknown'}`,
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
sub('ORDER_REMOVE_CART_ITEM', async (p) => {
|
|
152
|
+
const ctx = extractContext(p);
|
|
153
|
+
await auditLog.logApiActivity({
|
|
154
|
+
activity: OCSF_API_ACTIVITY.DELETE,
|
|
155
|
+
...ctx,
|
|
156
|
+
success: true,
|
|
157
|
+
operation: 'removeFromCart',
|
|
158
|
+
message: 'Product removed from cart',
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
sub('ORDER_PAY', async (p) => {
|
|
162
|
+
const ctx = extractContext(p);
|
|
163
|
+
await auditLog.logApiActivity({
|
|
164
|
+
activity: OCSF_API_ACTIVITY.PAYMENT,
|
|
165
|
+
...ctx,
|
|
166
|
+
success: true,
|
|
167
|
+
operation: 'processPayment',
|
|
168
|
+
message: 'Payment processed for order',
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
sub('ORDER_UPDATE_PAYMENT', async (p) => {
|
|
172
|
+
const ctx = extractContext(p);
|
|
173
|
+
await auditLog.logApiActivity({
|
|
174
|
+
activity: OCSF_API_ACTIVITY.PAYMENT,
|
|
175
|
+
...ctx,
|
|
176
|
+
success: true,
|
|
177
|
+
operation: 'updatePayment',
|
|
178
|
+
message: 'Payment updated',
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
export const AUDITED_EVENTS = [
|
|
183
|
+
'API_LOGIN_TOKEN_CREATED',
|
|
184
|
+
'API_LOGOUT',
|
|
185
|
+
'USER_CREATE',
|
|
186
|
+
'USER_REMOVE',
|
|
187
|
+
'USER_UPDATE_PASSWORD',
|
|
188
|
+
'USER_ADD_ROLES',
|
|
189
|
+
'USER_UPDATE_ROLE',
|
|
190
|
+
'USER_ACCOUNT_ACTION',
|
|
191
|
+
'ORDER_CREATE',
|
|
192
|
+
'ORDER_CHECKOUT',
|
|
193
|
+
'ORDER_CONFIRMED',
|
|
194
|
+
'ORDER_FULFILLED',
|
|
195
|
+
'ORDER_REJECTED',
|
|
196
|
+
'ORDER_ADD_PRODUCT',
|
|
197
|
+
'ORDER_REMOVE_CART_ITEM',
|
|
198
|
+
'ORDER_PAY',
|
|
199
|
+
'ORDER_UPDATE_PAYMENT',
|
|
200
|
+
];
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { type OCSFEvent, OCSF_AUTH_ACTIVITY, OCSF_ACCOUNT_ACTIVITY, OCSF_API_ACTIVITY } from './ocsf-types.ts';
|
|
2
|
+
export * from './ocsf-types.ts';
|
|
3
|
+
export interface AuditLogConfig {
|
|
4
|
+
directory?: string;
|
|
5
|
+
collectorUrl?: string;
|
|
6
|
+
collectorHeaders?: Record<string, string>;
|
|
7
|
+
batchSize?: number;
|
|
8
|
+
flushIntervalMs?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface AuthenticationInput {
|
|
11
|
+
activity: (typeof OCSF_AUTH_ACTIVITY)[keyof typeof OCSF_AUTH_ACTIVITY];
|
|
12
|
+
userId?: string;
|
|
13
|
+
userName?: string;
|
|
14
|
+
success?: boolean;
|
|
15
|
+
remoteAddress?: string;
|
|
16
|
+
sessionId?: string;
|
|
17
|
+
message?: string;
|
|
18
|
+
isMfa?: boolean;
|
|
19
|
+
authProtocol?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface AccountChangeInput {
|
|
22
|
+
activity: (typeof OCSF_ACCOUNT_ACTIVITY)[keyof typeof OCSF_ACCOUNT_ACTIVITY];
|
|
23
|
+
userId?: string;
|
|
24
|
+
userName?: string;
|
|
25
|
+
success?: boolean;
|
|
26
|
+
remoteAddress?: string;
|
|
27
|
+
sessionId?: string;
|
|
28
|
+
message?: string;
|
|
29
|
+
actorUserId?: string;
|
|
30
|
+
actorUserName?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ApiActivityInput {
|
|
33
|
+
activity: (typeof OCSF_API_ACTIVITY)[keyof typeof OCSF_API_ACTIVITY];
|
|
34
|
+
userId?: string;
|
|
35
|
+
userName?: string;
|
|
36
|
+
success?: boolean;
|
|
37
|
+
remoteAddress?: string;
|
|
38
|
+
sessionId?: string;
|
|
39
|
+
message?: string;
|
|
40
|
+
operation?: string;
|
|
41
|
+
httpMethod?: string;
|
|
42
|
+
path?: string;
|
|
43
|
+
responseCode?: number;
|
|
44
|
+
}
|
|
45
|
+
export interface AuditLogQuery {
|
|
46
|
+
classUids?: number[];
|
|
47
|
+
activityIds?: number[];
|
|
48
|
+
userId?: string;
|
|
49
|
+
success?: boolean;
|
|
50
|
+
startTime?: Date;
|
|
51
|
+
endTime?: Date;
|
|
52
|
+
limit?: number;
|
|
53
|
+
offset?: number;
|
|
54
|
+
}
|
|
55
|
+
export interface VerifyResult {
|
|
56
|
+
valid: boolean;
|
|
57
|
+
entries: number;
|
|
58
|
+
verified: number;
|
|
59
|
+
error?: string;
|
|
60
|
+
}
|
|
61
|
+
export declare class AuditLog {
|
|
62
|
+
private readonly dir;
|
|
63
|
+
private readonly collectorUrl?;
|
|
64
|
+
private readonly collectorHeaders;
|
|
65
|
+
private readonly batchSize;
|
|
66
|
+
private readonly flushIntervalMs;
|
|
67
|
+
private lastEvent;
|
|
68
|
+
private writeLock;
|
|
69
|
+
private initialized;
|
|
70
|
+
private pendingEvents;
|
|
71
|
+
private flushTimer?;
|
|
72
|
+
constructor(config?: AuditLogConfig);
|
|
73
|
+
private getFilePath;
|
|
74
|
+
private init;
|
|
75
|
+
private computeHash;
|
|
76
|
+
private createMetadata;
|
|
77
|
+
private createUser;
|
|
78
|
+
private createEndpoint;
|
|
79
|
+
private writeEvent;
|
|
80
|
+
private flushToCollector;
|
|
81
|
+
private matches;
|
|
82
|
+
logAuthentication(input: AuthenticationInput): Promise<string>;
|
|
83
|
+
logAccountChange(input: AccountChangeInput): Promise<string>;
|
|
84
|
+
logApiActivity(input: ApiActivityInput): Promise<string>;
|
|
85
|
+
find(query?: AuditLogQuery): Promise<OCSFEvent[]>;
|
|
86
|
+
count(query?: AuditLogQuery): Promise<number>;
|
|
87
|
+
getFailedLogins(params: {
|
|
88
|
+
userId?: string;
|
|
89
|
+
remoteAddress?: string;
|
|
90
|
+
since?: Date;
|
|
91
|
+
}): Promise<number>;
|
|
92
|
+
verify(): Promise<VerifyResult>;
|
|
93
|
+
close(): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
export declare function createAuditLog(config?: AuditLogConfig | string): AuditLog;
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile, appendFile, readdir } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
5
|
+
import { OCSF_CLASS, OCSF_CATEGORY, OCSF_SEVERITY, OCSF_STATUS, OCSF_AUTH_ACTIVITY, OCSF_ACCOUNT_ACTIVITY, OCSF_API_ACTIVITY, } from "./ocsf-types.js";
|
|
6
|
+
export * from "./ocsf-types.js";
|
|
7
|
+
const logger = createLogger('unchained:audit');
|
|
8
|
+
const OCSF_VERSION = '1.4.0';
|
|
9
|
+
const PRODUCT_NAME = 'Unchained Engine';
|
|
10
|
+
const PRODUCT_VERSION = '4.5';
|
|
11
|
+
const PRODUCT_VENDOR = 'Unchained';
|
|
12
|
+
const GENESIS_HASH = '0'.repeat(64);
|
|
13
|
+
export class AuditLog {
|
|
14
|
+
dir;
|
|
15
|
+
collectorUrl;
|
|
16
|
+
collectorHeaders;
|
|
17
|
+
batchSize;
|
|
18
|
+
flushIntervalMs;
|
|
19
|
+
lastEvent = null;
|
|
20
|
+
writeLock = Promise.resolve();
|
|
21
|
+
initialized = false;
|
|
22
|
+
pendingEvents = [];
|
|
23
|
+
flushTimer;
|
|
24
|
+
constructor(config = {}) {
|
|
25
|
+
this.dir = config.directory || './audit-logs';
|
|
26
|
+
this.collectorUrl = config.collectorUrl;
|
|
27
|
+
this.collectorHeaders = config.collectorHeaders || {};
|
|
28
|
+
this.batchSize = config.batchSize || 10;
|
|
29
|
+
this.flushIntervalMs = config.flushIntervalMs || 5000;
|
|
30
|
+
}
|
|
31
|
+
getFilePath() {
|
|
32
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
33
|
+
return join(this.dir, `audit-${date}.jsonl`);
|
|
34
|
+
}
|
|
35
|
+
async init() {
|
|
36
|
+
if (this.initialized)
|
|
37
|
+
return;
|
|
38
|
+
await mkdir(this.dir, { recursive: true });
|
|
39
|
+
try {
|
|
40
|
+
const files = (await readdir(this.dir)).filter((f) => f.endsWith('.jsonl')).sort();
|
|
41
|
+
for (let i = files.length - 1; i >= 0; i--) {
|
|
42
|
+
const content = await readFile(join(this.dir, files[i]), 'utf-8');
|
|
43
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
44
|
+
if (lines.length > 0) {
|
|
45
|
+
const parsed = JSON.parse(lines[lines.length - 1]);
|
|
46
|
+
if (parsed.unmapped?.hash) {
|
|
47
|
+
this.lastEvent = parsed;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
}
|
|
55
|
+
if (this.collectorUrl && !this.flushTimer) {
|
|
56
|
+
this.flushTimer = setInterval(() => this.flushToCollector(), this.flushIntervalMs);
|
|
57
|
+
}
|
|
58
|
+
this.initialized = true;
|
|
59
|
+
}
|
|
60
|
+
computeHash(event) {
|
|
61
|
+
const { unmapped, ...rest } = event;
|
|
62
|
+
const toHash = {
|
|
63
|
+
...rest,
|
|
64
|
+
unmapped: unmapped ? { seq: unmapped.seq, prev_hash: unmapped.prev_hash } : undefined,
|
|
65
|
+
};
|
|
66
|
+
const data = JSON.stringify(toHash, Object.keys(toHash).sort());
|
|
67
|
+
return createHash('sha256').update(data, 'utf8').digest('hex');
|
|
68
|
+
}
|
|
69
|
+
createMetadata(uid) {
|
|
70
|
+
return {
|
|
71
|
+
version: OCSF_VERSION,
|
|
72
|
+
product: {
|
|
73
|
+
name: PRODUCT_NAME,
|
|
74
|
+
version: PRODUCT_VERSION,
|
|
75
|
+
vendor_name: PRODUCT_VENDOR,
|
|
76
|
+
},
|
|
77
|
+
uid,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
createUser(userId, userName) {
|
|
81
|
+
return {
|
|
82
|
+
uid: userId,
|
|
83
|
+
name: userName,
|
|
84
|
+
email_addr: userName?.includes('@') ? userName : undefined,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
createEndpoint(ip) {
|
|
88
|
+
if (!ip)
|
|
89
|
+
return undefined;
|
|
90
|
+
return { ip };
|
|
91
|
+
}
|
|
92
|
+
async writeEvent(event) {
|
|
93
|
+
const result = this.writeLock.then(async () => {
|
|
94
|
+
await this.init();
|
|
95
|
+
const prevHash = this.lastEvent?.unmapped?.hash || GENESIS_HASH;
|
|
96
|
+
const seq = (this.lastEvent?.unmapped?.seq || 0) + 1;
|
|
97
|
+
const eventWithChain = {
|
|
98
|
+
...event,
|
|
99
|
+
unmapped: {
|
|
100
|
+
seq,
|
|
101
|
+
prev_hash: prevHash,
|
|
102
|
+
hash: '',
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
const hash = this.computeHash(eventWithChain);
|
|
106
|
+
eventWithChain.unmapped.hash = hash;
|
|
107
|
+
const line = JSON.stringify(eventWithChain);
|
|
108
|
+
await appendFile(this.getFilePath(), line + '\n', 'utf-8');
|
|
109
|
+
this.lastEvent = eventWithChain;
|
|
110
|
+
if (this.collectorUrl) {
|
|
111
|
+
this.pendingEvents.push(eventWithChain);
|
|
112
|
+
if (this.pendingEvents.length >= this.batchSize) {
|
|
113
|
+
this.flushToCollector().catch((err) => logger.error(`Failed to flush to collector: ${err.message}`));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
logger.debug(`Audit: ${event.message || 'Event'} [class=${event.class_uid}] seq=${seq}`);
|
|
117
|
+
return eventWithChain.metadata.uid;
|
|
118
|
+
});
|
|
119
|
+
this.writeLock = result;
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
async flushToCollector() {
|
|
123
|
+
if (!this.collectorUrl || this.pendingEvents.length === 0)
|
|
124
|
+
return;
|
|
125
|
+
const events = [...this.pendingEvents];
|
|
126
|
+
this.pendingEvents = [];
|
|
127
|
+
try {
|
|
128
|
+
const response = await fetch(this.collectorUrl, {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers: {
|
|
131
|
+
'Content-Type': 'application/json',
|
|
132
|
+
...this.collectorHeaders,
|
|
133
|
+
},
|
|
134
|
+
body: JSON.stringify({ events }),
|
|
135
|
+
});
|
|
136
|
+
if (!response.ok) {
|
|
137
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
138
|
+
}
|
|
139
|
+
logger.debug(`Flushed ${events.length} events to collector`);
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
this.pendingEvents = [...events, ...this.pendingEvents];
|
|
143
|
+
throw err;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
matches(event, query) {
|
|
147
|
+
if (query.classUids?.length && !query.classUids.includes(event.class_uid))
|
|
148
|
+
return false;
|
|
149
|
+
if (query.activityIds?.length && !query.activityIds.includes(event.activity_id))
|
|
150
|
+
return false;
|
|
151
|
+
const userId = 'user' in event ? event.user?.uid : undefined;
|
|
152
|
+
const actorId = 'actor' in event ? event.actor?.user?.uid : undefined;
|
|
153
|
+
if (query.userId && userId !== query.userId && actorId !== query.userId)
|
|
154
|
+
return false;
|
|
155
|
+
if (query.success !== undefined) {
|
|
156
|
+
const isSuccess = event.status_id === OCSF_STATUS.SUCCESS;
|
|
157
|
+
if (query.success !== isSuccess)
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
if (query.startTime || query.endTime) {
|
|
161
|
+
const ts = new Date(event.time);
|
|
162
|
+
if (query.startTime && ts < query.startTime)
|
|
163
|
+
return false;
|
|
164
|
+
if (query.endTime && ts > query.endTime)
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
async logAuthentication(input) {
|
|
170
|
+
const activityId = input.activity;
|
|
171
|
+
const activityMessages = {
|
|
172
|
+
[OCSF_AUTH_ACTIVITY.LOGON]: 'User Login',
|
|
173
|
+
[OCSF_AUTH_ACTIVITY.LOGOFF]: 'User Logout',
|
|
174
|
+
};
|
|
175
|
+
const event = {
|
|
176
|
+
category_uid: OCSF_CATEGORY.IDENTITY_ACCESS_MGMT,
|
|
177
|
+
class_uid: OCSF_CLASS.AUTHENTICATION,
|
|
178
|
+
type_uid: OCSF_CLASS.AUTHENTICATION * 100 + activityId,
|
|
179
|
+
activity_id: activityId,
|
|
180
|
+
severity_id: input.success === false ? OCSF_SEVERITY.HIGH : OCSF_SEVERITY.INFORMATIONAL,
|
|
181
|
+
status_id: input.success === false ? OCSF_STATUS.FAILURE : OCSF_STATUS.SUCCESS,
|
|
182
|
+
time: Date.now(),
|
|
183
|
+
message: input.message || activityMessages[activityId] || 'Authentication Event',
|
|
184
|
+
metadata: this.createMetadata(crypto.randomUUID()),
|
|
185
|
+
user: this.createUser(input.userId, input.userName),
|
|
186
|
+
src_endpoint: this.createEndpoint(input.remoteAddress),
|
|
187
|
+
is_mfa: input.isMfa,
|
|
188
|
+
auth_protocol: input.authProtocol,
|
|
189
|
+
session: input.sessionId ? { uid: input.sessionId } : undefined,
|
|
190
|
+
};
|
|
191
|
+
return this.writeEvent(event);
|
|
192
|
+
}
|
|
193
|
+
async logAccountChange(input) {
|
|
194
|
+
const activityId = input.activity;
|
|
195
|
+
const activityMessages = {
|
|
196
|
+
[OCSF_ACCOUNT_ACTIVITY.CREATE]: 'User Created',
|
|
197
|
+
[OCSF_ACCOUNT_ACTIVITY.DELETE]: 'User Deleted',
|
|
198
|
+
[OCSF_ACCOUNT_ACTIVITY.PASSWORD_CHANGE]: 'Password Changed',
|
|
199
|
+
[OCSF_ACCOUNT_ACTIVITY.PASSWORD_RESET]: 'Password Reset',
|
|
200
|
+
[OCSF_ACCOUNT_ACTIVITY.ATTACH_POLICY]: 'User Roles Changed',
|
|
201
|
+
[OCSF_ACCOUNT_ACTIVITY.MFA_ENABLE]: 'MFA Enabled',
|
|
202
|
+
[OCSF_ACCOUNT_ACTIVITY.MFA_DISABLE]: 'MFA Disabled',
|
|
203
|
+
};
|
|
204
|
+
const event = {
|
|
205
|
+
category_uid: OCSF_CATEGORY.IDENTITY_ACCESS_MGMT,
|
|
206
|
+
class_uid: OCSF_CLASS.ACCOUNT_CHANGE,
|
|
207
|
+
type_uid: OCSF_CLASS.ACCOUNT_CHANGE * 100 + activityId,
|
|
208
|
+
activity_id: activityId,
|
|
209
|
+
severity_id: input.success === false ? OCSF_SEVERITY.HIGH : OCSF_SEVERITY.INFORMATIONAL,
|
|
210
|
+
status_id: input.success === false ? OCSF_STATUS.FAILURE : OCSF_STATUS.SUCCESS,
|
|
211
|
+
time: Date.now(),
|
|
212
|
+
message: input.message || activityMessages[activityId] || 'Account Change',
|
|
213
|
+
metadata: this.createMetadata(crypto.randomUUID()),
|
|
214
|
+
user: this.createUser(input.userId, input.userName),
|
|
215
|
+
actor: input.actorUserId
|
|
216
|
+
? {
|
|
217
|
+
user: this.createUser(input.actorUserId, input.actorUserName),
|
|
218
|
+
session: input.sessionId ? { uid: input.sessionId } : undefined,
|
|
219
|
+
}
|
|
220
|
+
: undefined,
|
|
221
|
+
src_endpoint: this.createEndpoint(input.remoteAddress),
|
|
222
|
+
};
|
|
223
|
+
return this.writeEvent(event);
|
|
224
|
+
}
|
|
225
|
+
async logApiActivity(input) {
|
|
226
|
+
const activityId = input.activity;
|
|
227
|
+
const activityMessages = {
|
|
228
|
+
[OCSF_API_ACTIVITY.CREATE]: 'Resource Created',
|
|
229
|
+
[OCSF_API_ACTIVITY.READ]: 'Resource Read',
|
|
230
|
+
[OCSF_API_ACTIVITY.UPDATE]: 'Resource Updated',
|
|
231
|
+
[OCSF_API_ACTIVITY.DELETE]: 'Resource Deleted',
|
|
232
|
+
[OCSF_API_ACTIVITY.CHECKOUT]: 'Order Checkout',
|
|
233
|
+
[OCSF_API_ACTIVITY.PAYMENT]: 'Payment Processed',
|
|
234
|
+
[OCSF_API_ACTIVITY.REFUND]: 'Refund Processed',
|
|
235
|
+
[OCSF_API_ACTIVITY.EXPORT]: 'Data Exported',
|
|
236
|
+
[OCSF_API_ACTIVITY.IMPORT]: 'Data Imported',
|
|
237
|
+
[OCSF_API_ACTIVITY.ACCESS_DENIED]: 'Access Denied',
|
|
238
|
+
};
|
|
239
|
+
const isAccessDenied = activityId === OCSF_API_ACTIVITY.ACCESS_DENIED;
|
|
240
|
+
const severity = isAccessDenied || input.success === false ? OCSF_SEVERITY.HIGH : OCSF_SEVERITY.INFORMATIONAL;
|
|
241
|
+
const event = {
|
|
242
|
+
category_uid: OCSF_CATEGORY.APPLICATION_ACTIVITY,
|
|
243
|
+
class_uid: OCSF_CLASS.API_ACTIVITY,
|
|
244
|
+
type_uid: OCSF_CLASS.API_ACTIVITY * 100 + activityId,
|
|
245
|
+
activity_id: activityId,
|
|
246
|
+
severity_id: severity,
|
|
247
|
+
status_id: input.success === false ? OCSF_STATUS.FAILURE : OCSF_STATUS.SUCCESS,
|
|
248
|
+
time: Date.now(),
|
|
249
|
+
message: input.message || activityMessages[activityId] || 'API Activity',
|
|
250
|
+
metadata: this.createMetadata(crypto.randomUUID()),
|
|
251
|
+
actor: {
|
|
252
|
+
user: this.createUser(input.userId, input.userName),
|
|
253
|
+
session: input.sessionId ? { uid: input.sessionId } : undefined,
|
|
254
|
+
},
|
|
255
|
+
api: {
|
|
256
|
+
operation: input.operation,
|
|
257
|
+
response: input.responseCode ? { code: input.responseCode } : undefined,
|
|
258
|
+
},
|
|
259
|
+
src_endpoint: this.createEndpoint(input.remoteAddress),
|
|
260
|
+
http_request: input.httpMethod || input.path
|
|
261
|
+
? {
|
|
262
|
+
http_method: input.httpMethod,
|
|
263
|
+
url: input.path ? { path: input.path } : undefined,
|
|
264
|
+
}
|
|
265
|
+
: undefined,
|
|
266
|
+
};
|
|
267
|
+
return this.writeEvent(event);
|
|
268
|
+
}
|
|
269
|
+
async find(query = {}) {
|
|
270
|
+
await this.init();
|
|
271
|
+
const results = [];
|
|
272
|
+
const limit = query.limit || 100;
|
|
273
|
+
const offset = query.offset || 0;
|
|
274
|
+
let skipped = 0;
|
|
275
|
+
try {
|
|
276
|
+
const files = (await readdir(this.dir)).filter((f) => f.endsWith('.jsonl')).sort();
|
|
277
|
+
for (let i = files.length - 1; i >= 0 && results.length < limit; i--) {
|
|
278
|
+
const content = await readFile(join(this.dir, files[i]), 'utf-8');
|
|
279
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
280
|
+
for (let j = lines.length - 1; j >= 0 && results.length < limit; j--) {
|
|
281
|
+
try {
|
|
282
|
+
const event = JSON.parse(lines[j]);
|
|
283
|
+
if (!this.matches(event, query))
|
|
284
|
+
continue;
|
|
285
|
+
if (skipped < offset) {
|
|
286
|
+
skipped++;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
results.push(event);
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
}
|
|
298
|
+
return results;
|
|
299
|
+
}
|
|
300
|
+
async count(query = {}) {
|
|
301
|
+
await this.init();
|
|
302
|
+
let count = 0;
|
|
303
|
+
try {
|
|
304
|
+
const files = (await readdir(this.dir)).filter((f) => f.endsWith('.jsonl')).sort();
|
|
305
|
+
for (const file of files) {
|
|
306
|
+
const content = await readFile(join(this.dir, file), 'utf-8');
|
|
307
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
308
|
+
for (const line of lines) {
|
|
309
|
+
try {
|
|
310
|
+
const event = JSON.parse(line);
|
|
311
|
+
if (this.matches(event, query))
|
|
312
|
+
count++;
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
}
|
|
321
|
+
return count;
|
|
322
|
+
}
|
|
323
|
+
async getFailedLogins(params) {
|
|
324
|
+
const events = await this.find({
|
|
325
|
+
classUids: [OCSF_CLASS.AUTHENTICATION],
|
|
326
|
+
activityIds: [OCSF_AUTH_ACTIVITY.LOGON],
|
|
327
|
+
userId: params.userId,
|
|
328
|
+
success: false,
|
|
329
|
+
startTime: params.since,
|
|
330
|
+
});
|
|
331
|
+
if (params.remoteAddress) {
|
|
332
|
+
return events.filter((e) => {
|
|
333
|
+
const authEvent = e;
|
|
334
|
+
return authEvent.src_endpoint?.ip === params.remoteAddress;
|
|
335
|
+
}).length;
|
|
336
|
+
}
|
|
337
|
+
return events.length;
|
|
338
|
+
}
|
|
339
|
+
async verify() {
|
|
340
|
+
await this.init();
|
|
341
|
+
let entries = 0;
|
|
342
|
+
let verified = 0;
|
|
343
|
+
let prevHash = GENESIS_HASH;
|
|
344
|
+
let prevSeq = 0;
|
|
345
|
+
try {
|
|
346
|
+
const files = (await readdir(this.dir)).filter((f) => f.endsWith('.jsonl')).sort();
|
|
347
|
+
for (const file of files) {
|
|
348
|
+
const content = await readFile(join(this.dir, file), 'utf-8');
|
|
349
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
350
|
+
for (const line of lines) {
|
|
351
|
+
entries++;
|
|
352
|
+
let event;
|
|
353
|
+
try {
|
|
354
|
+
event = JSON.parse(line);
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
return { valid: false, entries, verified, error: `Parse error at entry ${entries}` };
|
|
358
|
+
}
|
|
359
|
+
if (!event.unmapped) {
|
|
360
|
+
return { valid: false, entries, verified, error: `Missing hash chain at entry ${entries}` };
|
|
361
|
+
}
|
|
362
|
+
if (event.unmapped.seq !== prevSeq + 1) {
|
|
363
|
+
return { valid: false, entries, verified, error: `Sequence gap at ${event.unmapped.seq}` };
|
|
364
|
+
}
|
|
365
|
+
if (event.unmapped.prev_hash !== prevHash) {
|
|
366
|
+
return {
|
|
367
|
+
valid: false,
|
|
368
|
+
entries,
|
|
369
|
+
verified,
|
|
370
|
+
error: `Chain broken at seq ${event.unmapped.seq}`,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
const computedHash = this.computeHash(event);
|
|
374
|
+
if (computedHash !== event.unmapped.hash) {
|
|
375
|
+
return {
|
|
376
|
+
valid: false,
|
|
377
|
+
entries,
|
|
378
|
+
verified,
|
|
379
|
+
error: `Hash mismatch at seq ${event.unmapped.seq}`,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
verified++;
|
|
383
|
+
prevHash = event.unmapped.hash;
|
|
384
|
+
prevSeq = event.unmapped.seq;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
catch (e) {
|
|
389
|
+
return { valid: false, entries, verified, error: `Read error: ${e}` };
|
|
390
|
+
}
|
|
391
|
+
return { valid: true, entries, verified };
|
|
392
|
+
}
|
|
393
|
+
async close() {
|
|
394
|
+
if (this.flushTimer) {
|
|
395
|
+
clearInterval(this.flushTimer);
|
|
396
|
+
this.flushTimer = undefined;
|
|
397
|
+
}
|
|
398
|
+
if (this.collectorUrl && this.pendingEvents.length > 0) {
|
|
399
|
+
try {
|
|
400
|
+
await this.flushToCollector();
|
|
401
|
+
}
|
|
402
|
+
catch (err) {
|
|
403
|
+
logger.error(`Failed to flush pending events on close: ${err.message}`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
await this.writeLock;
|
|
407
|
+
this.initialized = false;
|
|
408
|
+
this.lastEvent = null;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
export function createAuditLog(config) {
|
|
412
|
+
if (typeof config === 'string') {
|
|
413
|
+
return new AuditLog({ directory: config });
|
|
414
|
+
}
|
|
415
|
+
return new AuditLog(config);
|
|
416
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
export declare const OCSF_CATEGORY: {
|
|
2
|
+
readonly IDENTITY_ACCESS_MGMT: 3;
|
|
3
|
+
readonly APPLICATION_ACTIVITY: 6;
|
|
4
|
+
};
|
|
5
|
+
export declare const OCSF_CLASS: {
|
|
6
|
+
readonly ACCOUNT_CHANGE: 3001;
|
|
7
|
+
readonly AUTHENTICATION: 3002;
|
|
8
|
+
readonly AUTHORIZE_SESSION: 3003;
|
|
9
|
+
readonly API_ACTIVITY: 6003;
|
|
10
|
+
};
|
|
11
|
+
export declare const OCSF_SEVERITY: {
|
|
12
|
+
readonly UNKNOWN: 0;
|
|
13
|
+
readonly INFORMATIONAL: 1;
|
|
14
|
+
readonly LOW: 2;
|
|
15
|
+
readonly MEDIUM: 3;
|
|
16
|
+
readonly HIGH: 4;
|
|
17
|
+
readonly CRITICAL: 5;
|
|
18
|
+
readonly FATAL: 6;
|
|
19
|
+
};
|
|
20
|
+
export declare const OCSF_STATUS: {
|
|
21
|
+
readonly UNKNOWN: 0;
|
|
22
|
+
readonly SUCCESS: 1;
|
|
23
|
+
readonly FAILURE: 2;
|
|
24
|
+
readonly OTHER: 99;
|
|
25
|
+
};
|
|
26
|
+
export declare const OCSF_AUTH_ACTIVITY: {
|
|
27
|
+
readonly UNKNOWN: 0;
|
|
28
|
+
readonly LOGON: 1;
|
|
29
|
+
readonly LOGOFF: 2;
|
|
30
|
+
readonly AUTHENTICATION_TICKET: 3;
|
|
31
|
+
readonly SERVICE_TICKET_REQUEST: 4;
|
|
32
|
+
readonly SERVICE_TICKET_RENEW: 5;
|
|
33
|
+
readonly PREAUTH: 6;
|
|
34
|
+
readonly OTHER: 99;
|
|
35
|
+
};
|
|
36
|
+
export declare const OCSF_ACCOUNT_ACTIVITY: {
|
|
37
|
+
readonly UNKNOWN: 0;
|
|
38
|
+
readonly CREATE: 1;
|
|
39
|
+
readonly ENABLE: 2;
|
|
40
|
+
readonly PASSWORD_CHANGE: 3;
|
|
41
|
+
readonly PASSWORD_RESET: 4;
|
|
42
|
+
readonly DISABLE: 5;
|
|
43
|
+
readonly DELETE: 6;
|
|
44
|
+
readonly ATTACH_POLICY: 7;
|
|
45
|
+
readonly DETACH_POLICY: 8;
|
|
46
|
+
readonly LOCK: 9;
|
|
47
|
+
readonly MFA_ENABLE: 10;
|
|
48
|
+
readonly MFA_DISABLE: 11;
|
|
49
|
+
readonly OTHER: 99;
|
|
50
|
+
};
|
|
51
|
+
export declare const OCSF_API_ACTIVITY: {
|
|
52
|
+
readonly UNKNOWN: 0;
|
|
53
|
+
readonly CREATE: 1;
|
|
54
|
+
readonly READ: 2;
|
|
55
|
+
readonly UPDATE: 3;
|
|
56
|
+
readonly DELETE: 4;
|
|
57
|
+
readonly CHECKOUT: 90;
|
|
58
|
+
readonly PAYMENT: 91;
|
|
59
|
+
readonly REFUND: 92;
|
|
60
|
+
readonly EXPORT: 93;
|
|
61
|
+
readonly IMPORT: 94;
|
|
62
|
+
readonly ACCESS_DENIED: 95;
|
|
63
|
+
readonly OTHER: 99;
|
|
64
|
+
};
|
|
65
|
+
export interface OCSFMetadata {
|
|
66
|
+
version: string;
|
|
67
|
+
product: {
|
|
68
|
+
name: string;
|
|
69
|
+
version: string;
|
|
70
|
+
vendor_name?: string;
|
|
71
|
+
};
|
|
72
|
+
uid?: string;
|
|
73
|
+
original_time?: number;
|
|
74
|
+
sequence?: number;
|
|
75
|
+
}
|
|
76
|
+
export interface OCSFUser {
|
|
77
|
+
uid?: string;
|
|
78
|
+
name?: string;
|
|
79
|
+
email_addr?: string;
|
|
80
|
+
type?: string;
|
|
81
|
+
type_id?: number;
|
|
82
|
+
}
|
|
83
|
+
export interface OCSFActor {
|
|
84
|
+
user?: OCSFUser;
|
|
85
|
+
session?: OCSFSession;
|
|
86
|
+
invoked_by?: string;
|
|
87
|
+
}
|
|
88
|
+
export interface OCSFSession {
|
|
89
|
+
uid?: string;
|
|
90
|
+
created_time?: number;
|
|
91
|
+
is_mfa?: boolean;
|
|
92
|
+
}
|
|
93
|
+
export interface OCSFEndpoint {
|
|
94
|
+
ip?: string;
|
|
95
|
+
port?: number;
|
|
96
|
+
hostname?: string;
|
|
97
|
+
agent_list?: {
|
|
98
|
+
name?: string;
|
|
99
|
+
version?: string;
|
|
100
|
+
}[];
|
|
101
|
+
}
|
|
102
|
+
export interface OCSFApi {
|
|
103
|
+
operation?: string;
|
|
104
|
+
service?: {
|
|
105
|
+
name?: string;
|
|
106
|
+
};
|
|
107
|
+
request?: {
|
|
108
|
+
uid?: string;
|
|
109
|
+
};
|
|
110
|
+
response?: {
|
|
111
|
+
code?: number;
|
|
112
|
+
message?: string;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export interface OCSFBaseEvent {
|
|
116
|
+
category_uid: number;
|
|
117
|
+
class_uid: number;
|
|
118
|
+
type_uid: number;
|
|
119
|
+
activity_id: number;
|
|
120
|
+
severity_id: number;
|
|
121
|
+
time: number;
|
|
122
|
+
message?: string;
|
|
123
|
+
metadata: OCSFMetadata;
|
|
124
|
+
status_id?: number;
|
|
125
|
+
status_detail?: string;
|
|
126
|
+
unmapped?: {
|
|
127
|
+
seq: number;
|
|
128
|
+
prev_hash: string;
|
|
129
|
+
hash: string;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
export interface OCSFAuthenticationEvent extends OCSFBaseEvent {
|
|
133
|
+
category_uid: 3;
|
|
134
|
+
class_uid: 3002;
|
|
135
|
+
user: OCSFUser;
|
|
136
|
+
src_endpoint?: OCSFEndpoint;
|
|
137
|
+
dst_endpoint?: OCSFEndpoint;
|
|
138
|
+
auth_protocol_id?: number;
|
|
139
|
+
auth_protocol?: string;
|
|
140
|
+
is_mfa?: boolean;
|
|
141
|
+
logon_type_id?: number;
|
|
142
|
+
session?: OCSFSession;
|
|
143
|
+
}
|
|
144
|
+
export interface OCSFAccountChangeEvent extends OCSFBaseEvent {
|
|
145
|
+
category_uid: 3;
|
|
146
|
+
class_uid: 3001;
|
|
147
|
+
user: OCSFUser;
|
|
148
|
+
actor?: OCSFActor;
|
|
149
|
+
src_endpoint?: OCSFEndpoint;
|
|
150
|
+
}
|
|
151
|
+
export interface OCSFApiActivityEvent extends OCSFBaseEvent {
|
|
152
|
+
category_uid: 6;
|
|
153
|
+
class_uid: 6003;
|
|
154
|
+
actor: OCSFActor;
|
|
155
|
+
api: OCSFApi;
|
|
156
|
+
src_endpoint?: OCSFEndpoint;
|
|
157
|
+
dst_endpoint?: OCSFEndpoint;
|
|
158
|
+
http_request?: {
|
|
159
|
+
http_method?: string;
|
|
160
|
+
url?: {
|
|
161
|
+
path?: string;
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
export type OCSFEvent = OCSFAuthenticationEvent | OCSFAccountChangeEvent | OCSFApiActivityEvent;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export const OCSF_CATEGORY = {
|
|
2
|
+
IDENTITY_ACCESS_MGMT: 3,
|
|
3
|
+
APPLICATION_ACTIVITY: 6,
|
|
4
|
+
};
|
|
5
|
+
export const OCSF_CLASS = {
|
|
6
|
+
ACCOUNT_CHANGE: 3001,
|
|
7
|
+
AUTHENTICATION: 3002,
|
|
8
|
+
AUTHORIZE_SESSION: 3003,
|
|
9
|
+
API_ACTIVITY: 6003,
|
|
10
|
+
};
|
|
11
|
+
export const OCSF_SEVERITY = {
|
|
12
|
+
UNKNOWN: 0,
|
|
13
|
+
INFORMATIONAL: 1,
|
|
14
|
+
LOW: 2,
|
|
15
|
+
MEDIUM: 3,
|
|
16
|
+
HIGH: 4,
|
|
17
|
+
CRITICAL: 5,
|
|
18
|
+
FATAL: 6,
|
|
19
|
+
};
|
|
20
|
+
export const OCSF_STATUS = {
|
|
21
|
+
UNKNOWN: 0,
|
|
22
|
+
SUCCESS: 1,
|
|
23
|
+
FAILURE: 2,
|
|
24
|
+
OTHER: 99,
|
|
25
|
+
};
|
|
26
|
+
export const OCSF_AUTH_ACTIVITY = {
|
|
27
|
+
UNKNOWN: 0,
|
|
28
|
+
LOGON: 1,
|
|
29
|
+
LOGOFF: 2,
|
|
30
|
+
AUTHENTICATION_TICKET: 3,
|
|
31
|
+
SERVICE_TICKET_REQUEST: 4,
|
|
32
|
+
SERVICE_TICKET_RENEW: 5,
|
|
33
|
+
PREAUTH: 6,
|
|
34
|
+
OTHER: 99,
|
|
35
|
+
};
|
|
36
|
+
export const OCSF_ACCOUNT_ACTIVITY = {
|
|
37
|
+
UNKNOWN: 0,
|
|
38
|
+
CREATE: 1,
|
|
39
|
+
ENABLE: 2,
|
|
40
|
+
PASSWORD_CHANGE: 3,
|
|
41
|
+
PASSWORD_RESET: 4,
|
|
42
|
+
DISABLE: 5,
|
|
43
|
+
DELETE: 6,
|
|
44
|
+
ATTACH_POLICY: 7,
|
|
45
|
+
DETACH_POLICY: 8,
|
|
46
|
+
LOCK: 9,
|
|
47
|
+
MFA_ENABLE: 10,
|
|
48
|
+
MFA_DISABLE: 11,
|
|
49
|
+
OTHER: 99,
|
|
50
|
+
};
|
|
51
|
+
export const OCSF_API_ACTIVITY = {
|
|
52
|
+
UNKNOWN: 0,
|
|
53
|
+
CREATE: 1,
|
|
54
|
+
READ: 2,
|
|
55
|
+
UPDATE: 3,
|
|
56
|
+
DELETE: 4,
|
|
57
|
+
CHECKOUT: 90,
|
|
58
|
+
PAYMENT: 91,
|
|
59
|
+
REFUND: 92,
|
|
60
|
+
EXPORT: 93,
|
|
61
|
+
IMPORT: 94,
|
|
62
|
+
ACCESS_DENIED: 95,
|
|
63
|
+
OTHER: 99,
|
|
64
|
+
};
|
package/lib/events-index.d.ts
CHANGED
|
@@ -2,3 +2,5 @@ import { type EmitAdapter, type RawPayloadType } from './EventDirector.ts';
|
|
|
2
2
|
declare const emit: (eventName: string, data?: Record<string, any>) => Promise<void>, getEmitAdapter: () => EmitAdapter, getEmitHistoryAdapter: () => EmitAdapter, getRegisteredEvents: () => string[], registerEvents: (events: string[]) => void, setEmitAdapter: (adapter: EmitAdapter) => void, setEmitHistoryAdapter: (adapter: EmitAdapter) => void, subscribe: <T extends Record<string, any>>(eventName: string, callback: (payload: RawPayloadType<T>) => void) => void;
|
|
3
3
|
export { emit, getEmitAdapter, getEmitHistoryAdapter, getRegisteredEvents, registerEvents, setEmitAdapter, setEmitHistoryAdapter, subscribe, };
|
|
4
4
|
export type { EmitAdapter, RawPayloadType };
|
|
5
|
+
export { AuditLog, createAuditLog, OCSF_CLASS, OCSF_CATEGORY, OCSF_SEVERITY, OCSF_STATUS, OCSF_AUTH_ACTIVITY, OCSF_ACCOUNT_ACTIVITY, OCSF_API_ACTIVITY, type AuditLogConfig, type AuthenticationInput, type AccountChangeInput, type ApiActivityInput, type AuditLogQuery, type VerifyResult, type OCSFBaseEvent, type OCSFAuthenticationEvent, type OCSFAccountChangeEvent, type OCSFApiActivityEvent, type OCSFEvent, type OCSFMetadata, type OCSFUser, type OCSFActor, type OCSFEndpoint, type OCSFApi, type OCSFSession, } from './audit/index.ts';
|
|
6
|
+
export { configureAuditIntegration, AUDITED_EVENTS } from './audit/audit-integration.ts';
|
package/lib/events-index.js
CHANGED
|
@@ -3,3 +3,5 @@ const { emit, getEmitAdapter, getEmitHistoryAdapter, getRegisteredEvents, regist
|
|
|
3
3
|
const GLOBAL_EVENTS = ['PAGE_VIEW'];
|
|
4
4
|
registerEvents(GLOBAL_EVENTS);
|
|
5
5
|
export { emit, getEmitAdapter, getEmitHistoryAdapter, getRegisteredEvents, registerEvents, setEmitAdapter, setEmitHistoryAdapter, subscribe, };
|
|
6
|
+
export { AuditLog, createAuditLog, OCSF_CLASS, OCSF_CATEGORY, OCSF_SEVERITY, OCSF_STATUS, OCSF_AUTH_ACTIVITY, OCSF_ACCOUNT_ACTIVITY, OCSF_API_ACTIVITY, } from "./audit/index.js";
|
|
7
|
+
export { configureAuditIntegration, AUDITED_EVENTS } from "./audit/audit-integration.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unchainedshop/events",
|
|
3
|
-
"
|
|
3
|
+
"description": "Event emitter abstraction layer for the Unchained Engine",
|
|
4
|
+
"version": "4.6.0",
|
|
4
5
|
"main": "lib/events-index.js",
|
|
5
6
|
"types": "lib/events-index.d.ts",
|
|
6
7
|
"type": "module",
|
|
@@ -33,7 +34,7 @@
|
|
|
33
34
|
},
|
|
34
35
|
"homepage": "https://github.com/unchainedshop/unchained#readme",
|
|
35
36
|
"dependencies": {
|
|
36
|
-
"@unchainedshop/logger": "^4.
|
|
37
|
+
"@unchainedshop/logger": "^4.6.0"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
40
|
"@types/node": "^25.0.0",
|