@unchainedshop/events 5.0.0-alpha.3 → 5.0.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,164 @@
1
+ import { createLogger } from '@unchainedshop/logger';
2
+ import { OTLP_SEVERITY_NUMBER, } from "./otlp-types.js";
3
+ const logger = createLogger('unchained:audit');
4
+ const SEVERITY_NUMBER_MAP = {
5
+ 0: OTLP_SEVERITY_NUMBER.UNSPECIFIED,
6
+ 1: OTLP_SEVERITY_NUMBER.INFO,
7
+ 2: OTLP_SEVERITY_NUMBER.INFO2,
8
+ 3: OTLP_SEVERITY_NUMBER.WARN,
9
+ 4: OTLP_SEVERITY_NUMBER.ERROR,
10
+ 5: OTLP_SEVERITY_NUMBER.FATAL,
11
+ 6: OTLP_SEVERITY_NUMBER.FATAL4,
12
+ };
13
+ const SEVERITY_TEXT_MAP = {
14
+ 0: 'Unknown',
15
+ 1: 'Informational',
16
+ 2: 'Low',
17
+ 3: 'Medium',
18
+ 4: 'High',
19
+ 5: 'Critical',
20
+ 6: 'Fatal',
21
+ };
22
+ export function toAnyValue(value) {
23
+ if (typeof value === 'string')
24
+ return { stringValue: value };
25
+ if (typeof value === 'boolean')
26
+ return { boolValue: value };
27
+ if (typeof value === 'bigint')
28
+ return { intValue: value.toString() };
29
+ if (typeof value === 'number') {
30
+ return Number.isSafeInteger(value) ? { intValue: String(value) } : { doubleValue: value };
31
+ }
32
+ if (Array.isArray(value))
33
+ return { arrayValue: { values: value.map(toAnyValue) } };
34
+ if (value !== null && typeof value === 'object') {
35
+ const values = [];
36
+ for (const [key, entry] of Object.entries(value)) {
37
+ if (entry === undefined)
38
+ continue;
39
+ values.push({ key, value: toAnyValue(entry) });
40
+ }
41
+ return { kvlistValue: { values } };
42
+ }
43
+ return { stringValue: String(value ?? null) };
44
+ }
45
+ function toMillisTimeUnixNano(timeMs) {
46
+ return (BigInt(Math.round(timeMs)) * 1000000n).toString();
47
+ }
48
+ export function encodeOCSFLogRecord(event, observedTimeMs) {
49
+ const attributes = [];
50
+ const attr = (key, value) => {
51
+ if (value === undefined)
52
+ return;
53
+ attributes.push({ key, value: toAnyValue(value) });
54
+ };
55
+ const user = event.user ?? event.actor?.user;
56
+ const sessionId = event.session?.uid ?? event.actor?.session?.uid;
57
+ attr('ocsf.class_uid', event.class_uid);
58
+ attr('ocsf.category_uid', event.category_uid);
59
+ attr('ocsf.activity_id', event.activity_id);
60
+ attr('ocsf.type_uid', event.type_uid);
61
+ attr('ocsf.severity_id', event.severity_id);
62
+ attr('ocsf.status_id', event.status_id);
63
+ attr('user.id', user?.uid);
64
+ attr('user.name', user?.name);
65
+ attr('client.address', event.src_endpoint?.ip);
66
+ attr('session.id', sessionId);
67
+ attr('ocsf.api.operation', event.api?.operation);
68
+ attr('log.record.uid', event.metadata.uid);
69
+ return {
70
+ timeUnixNano: toMillisTimeUnixNano(event.time),
71
+ observedTimeUnixNano: toMillisTimeUnixNano(observedTimeMs),
72
+ severityNumber: SEVERITY_NUMBER_MAP[event.severity_id] ?? OTLP_SEVERITY_NUMBER.UNSPECIFIED,
73
+ severityText: SEVERITY_TEXT_MAP[event.severity_id],
74
+ body: toAnyValue(event),
75
+ attributes,
76
+ };
77
+ }
78
+ export function buildExportLogsRequest(events, observedTimeMs = Date.now()) {
79
+ const resourceAttributes = [
80
+ {
81
+ key: 'service.name',
82
+ value: toAnyValue(process.env.OTEL_SERVICE_NAME || 'unchained-engine'),
83
+ },
84
+ ];
85
+ if (process.env.npm_package_version) {
86
+ resourceAttributes.push({
87
+ key: 'service.version',
88
+ value: toAnyValue(process.env.npm_package_version),
89
+ });
90
+ }
91
+ return {
92
+ resourceLogs: [
93
+ {
94
+ resource: { attributes: resourceAttributes },
95
+ scopeLogs: [
96
+ {
97
+ scope: { name: 'unchained:audit' },
98
+ logRecords: events.map((event) => encodeOCSFLogRecord(event, observedTimeMs)),
99
+ },
100
+ ],
101
+ },
102
+ ],
103
+ };
104
+ }
105
+ export function parseOtlpHeaders(raw) {
106
+ const headers = {};
107
+ if (!raw)
108
+ return headers;
109
+ for (const pair of raw.split(',')) {
110
+ const separatorIndex = pair.indexOf('=');
111
+ if (separatorIndex <= 0)
112
+ continue;
113
+ const key = pair.slice(0, separatorIndex).trim();
114
+ let value = pair.slice(separatorIndex + 1).trim();
115
+ try {
116
+ value = decodeURIComponent(value);
117
+ }
118
+ catch {
119
+ }
120
+ if (key)
121
+ headers[key] = value;
122
+ }
123
+ return headers;
124
+ }
125
+ export function resolveCollectorUrl(explicit) {
126
+ if (explicit)
127
+ return explicit;
128
+ if (process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) {
129
+ return process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
130
+ }
131
+ if (process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {
132
+ return `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT.replace(/\/+$/, '')}/v1/logs`;
133
+ }
134
+ return undefined;
135
+ }
136
+ export function resolveCollectorHeaders(explicit) {
137
+ return {
138
+ ...parseOtlpHeaders(process.env.OTEL_EXPORTER_OTLP_HEADERS),
139
+ ...parseOtlpHeaders(process.env.OTEL_EXPORTER_OTLP_LOGS_HEADERS),
140
+ ...explicit,
141
+ };
142
+ }
143
+ export async function exportLogs(collectorUrl, collectorHeaders, events) {
144
+ const response = await fetch(collectorUrl, {
145
+ method: 'POST',
146
+ headers: {
147
+ 'Content-Type': 'application/json',
148
+ ...collectorHeaders,
149
+ },
150
+ body: JSON.stringify(buildExportLogsRequest(events)),
151
+ });
152
+ if (!response.ok) {
153
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
154
+ }
155
+ try {
156
+ const body = (await response.json());
157
+ const rejected = Number(body?.partialSuccess?.rejectedLogRecords || 0);
158
+ if (rejected > 0) {
159
+ logger.warn(`Collector rejected ${rejected} audit log records: ${body.partialSuccess?.errorMessage || 'unknown reason'}`);
160
+ }
161
+ }
162
+ catch {
163
+ }
164
+ }
@@ -0,0 +1,8 @@
1
+ export interface AuditRequestContext {
2
+ userId?: string;
3
+ userName?: string;
4
+ remoteAddress?: string;
5
+ sessionId?: string;
6
+ }
7
+ export declare function runWithAuditContext<T>(context: AuditRequestContext, fn: () => T): T;
8
+ export declare function getAuditContext(): AuditRequestContext | undefined;
@@ -0,0 +1,8 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ const asyncLocalStorage = new AsyncLocalStorage();
3
+ export function runWithAuditContext(context, fn) {
4
+ return asyncLocalStorage.run(context, fn);
5
+ }
6
+ export function getAuditContext() {
7
+ return asyncLocalStorage.getStore();
8
+ }
@@ -2,5 +2,6 @@ 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';
5
+ export { AuditLog, createAuditLog, setAuditLogInstance, getAuditLogInstance, OCSF_CLASS, OCSF_CATEGORY, OCSF_SEVERITY, OCSF_STATUS, OCSF_AUTH_ACTIVITY, OCSF_ACCOUNT_ACTIVITY, OCSF_API_ACTIVITY, OCSF_API_ACTIVITY_NAMES, type AuditLogOptions, type AuthenticationInput, type AccountChangeInput, type ApiActivityInput, type OCSFBaseEvent, type OCSFAuthenticationEvent, type OCSFAccountChangeEvent, type OCSFApiActivityEvent, type OCSFEvent, type OCSFMetadata, type OCSFUser, type OCSFActor, type OCSFEndpoint, type OCSFApi, type OCSFService, type OCSFSession, } from './audit/index.ts';
6
6
  export { configureAuditIntegration, AUDITED_EVENTS } from './audit/audit-integration.ts';
7
+ export { runWithAuditContext, getAuditContext, type AuditRequestContext, } from './audit/request-context.ts';
@@ -3,5 +3,6 @@ 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";
6
+ export { AuditLog, createAuditLog, setAuditLogInstance, getAuditLogInstance, OCSF_CLASS, OCSF_CATEGORY, OCSF_SEVERITY, OCSF_STATUS, OCSF_AUTH_ACTIVITY, OCSF_ACCOUNT_ACTIVITY, OCSF_API_ACTIVITY, OCSF_API_ACTIVITY_NAMES, } from "./audit/index.js";
7
7
  export { configureAuditIntegration, AUDITED_EVENTS } from "./audit/audit-integration.js";
8
+ export { runWithAuditContext, getAuditContext, } from "./audit/request-context.js";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@unchainedshop/events",
3
3
  "description": "Event emitter abstraction layer for the Unchained Engine",
4
- "version": "5.0.0-alpha.3",
4
+ "version": "5.0.0-alpha.5",
5
5
  "main": "lib/events-index.js",
6
6
  "types": "lib/events-index.d.ts",
7
7
  "type": "module",
@@ -34,10 +34,10 @@
34
34
  },
35
35
  "homepage": "https://github.com/unchainedshop/unchained#readme",
36
36
  "dependencies": {
37
- "@unchainedshop/logger": "^5.0.0-alpha.1"
37
+ "@unchainedshop/logger": "^5.0.0-alpha.5"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^26.2.0",
41
- "typescript": "^5.8.3"
41
+ "typescript": "^6.0.3"
42
42
  }
43
43
  }