@mondaydotcomorg/atp-providers 0.17.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +430 -0
- package/__tests__/oauth-integration.test.ts +457 -0
- package/__tests__/scope-checkers.test.ts +560 -0
- package/__tests__/token-expiration.test.ts +308 -0
- package/dist/audit/jsonl.d.ts +29 -0
- package/dist/audit/jsonl.d.ts.map +1 -0
- package/dist/audit/jsonl.js +163 -0
- package/dist/audit/jsonl.js.map +1 -0
- package/dist/audit/opentelemetry.d.ts +23 -0
- package/dist/audit/opentelemetry.d.ts.map +1 -0
- package/dist/audit/opentelemetry.js +240 -0
- package/dist/audit/opentelemetry.js.map +1 -0
- package/dist/audit/otel-metrics.d.ts +111 -0
- package/dist/audit/otel-metrics.d.ts.map +1 -0
- package/dist/audit/otel-metrics.js +115 -0
- package/dist/audit/otel-metrics.js.map +1 -0
- package/dist/auth/env.d.ts +21 -0
- package/dist/auth/env.d.ts.map +1 -0
- package/dist/auth/env.js +48 -0
- package/dist/auth/env.js.map +1 -0
- package/dist/cache/file.d.ts +47 -0
- package/dist/cache/file.d.ts.map +1 -0
- package/dist/cache/file.js +262 -0
- package/dist/cache/file.js.map +1 -0
- package/dist/cache/memory.d.ts +30 -0
- package/dist/cache/memory.d.ts.map +1 -0
- package/dist/cache/memory.js +81 -0
- package/dist/cache/memory.js.map +1 -0
- package/dist/cache/redis.d.ts +28 -0
- package/dist/cache/redis.d.ts.map +1 -0
- package/dist/cache/redis.js +124 -0
- package/dist/cache/redis.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth/index.d.ts +2 -0
- package/dist/oauth/index.d.ts.map +1 -0
- package/dist/oauth/index.js +2 -0
- package/dist/oauth/index.js.map +1 -0
- package/dist/oauth/scope-checkers.d.ts +61 -0
- package/dist/oauth/scope-checkers.d.ts.map +1 -0
- package/dist/oauth/scope-checkers.js +166 -0
- package/dist/oauth/scope-checkers.js.map +1 -0
- package/package.json +26 -0
- package/project.json +31 -0
- package/src/audit/jsonl.ts +189 -0
- package/src/audit/opentelemetry.ts +286 -0
- package/src/audit/otel-metrics.ts +122 -0
- package/src/auth/env.ts +65 -0
- package/src/cache/file.ts +330 -0
- package/src/cache/memory.ts +105 -0
- package/src/cache/redis.ts +160 -0
- package/src/index.ts +32 -0
- package/src/oauth/index.ts +1 -0
- package/src/oauth/scope-checkers.ts +196 -0
- package/tsconfig.json +14 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import type { AuthProvider, UserCredentialData } from '@mondaydotcomorg/atp-protocol';
|
|
3
|
+
|
|
4
|
+
// Test implementation that mimics production behavior
|
|
5
|
+
class TestAuthProvider implements AuthProvider {
|
|
6
|
+
name = 'test';
|
|
7
|
+
private userCredentials = new Map<string, Map<string, UserCredentialData>>();
|
|
8
|
+
|
|
9
|
+
async getCredential(key: string): Promise<string | null> {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async setCredential(key: string, value: string): Promise<void> {}
|
|
14
|
+
|
|
15
|
+
async deleteCredential(key: string): Promise<void> {}
|
|
16
|
+
|
|
17
|
+
async getUserCredential(userId: string, provider: string): Promise<UserCredentialData | null> {
|
|
18
|
+
const userMap = this.userCredentials.get(userId);
|
|
19
|
+
const creds = userMap?.get(provider);
|
|
20
|
+
|
|
21
|
+
if (!creds) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Check if token has expired
|
|
26
|
+
if (creds.expiresAt && creds.expiresAt < Date.now()) {
|
|
27
|
+
// In production, would attempt to refresh here
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return creds;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async setUserCredential(
|
|
35
|
+
userId: string,
|
|
36
|
+
provider: string,
|
|
37
|
+
data: UserCredentialData
|
|
38
|
+
): Promise<void> {
|
|
39
|
+
if (!this.userCredentials.has(userId)) {
|
|
40
|
+
this.userCredentials.set(userId, new Map());
|
|
41
|
+
}
|
|
42
|
+
this.userCredentials.get(userId)!.set(provider, data);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async deleteUserCredential(userId: string, provider: string): Promise<void> {
|
|
46
|
+
this.userCredentials.get(userId)?.delete(provider);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async listUserProviders(userId: string): Promise<string[]> {
|
|
50
|
+
const userMap = this.userCredentials.get(userId);
|
|
51
|
+
return userMap ? Array.from(userMap.keys()) : [];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe('Token Expiration Handling', () => {
|
|
56
|
+
let authProvider: TestAuthProvider;
|
|
57
|
+
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
authProvider = new TestAuthProvider();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('getUserCredential with expired token', () => {
|
|
63
|
+
it('should return null for expired token', async () => {
|
|
64
|
+
const userId = 'user123';
|
|
65
|
+
const provider = 'github';
|
|
66
|
+
|
|
67
|
+
// Store credential with expired token
|
|
68
|
+
const expiredTime = Date.now() - 3600000; // 1 hour ago
|
|
69
|
+
await authProvider.setUserCredential(userId, provider, {
|
|
70
|
+
token: 'gho_expired_token',
|
|
71
|
+
scopes: ['repo'],
|
|
72
|
+
expiresAt: expiredTime,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Should return null due to expiration
|
|
76
|
+
const result = await authProvider.getUserCredential(userId, provider);
|
|
77
|
+
|
|
78
|
+
expect(result).toBeNull();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should return credential for non-expired token', async () => {
|
|
82
|
+
const userId = 'user123';
|
|
83
|
+
const provider = 'github';
|
|
84
|
+
|
|
85
|
+
// Store credential with valid token
|
|
86
|
+
const futureTime = Date.now() + 3600000; // 1 hour from now
|
|
87
|
+
const credentials: UserCredentialData = {
|
|
88
|
+
token: 'gho_valid_token',
|
|
89
|
+
scopes: ['repo'],
|
|
90
|
+
expiresAt: futureTime,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
await authProvider.setUserCredential(userId, provider, credentials);
|
|
94
|
+
|
|
95
|
+
// Should return the credential
|
|
96
|
+
const result = await authProvider.getUserCredential(userId, provider);
|
|
97
|
+
|
|
98
|
+
expect(result).toEqual(credentials);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should return credential when no expiration time set', async () => {
|
|
102
|
+
const userId = 'user123';
|
|
103
|
+
const provider = 'github';
|
|
104
|
+
|
|
105
|
+
// Store credential without expiration
|
|
106
|
+
const credentials: UserCredentialData = {
|
|
107
|
+
token: 'gho_no_expiry_token',
|
|
108
|
+
scopes: ['repo'],
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
await authProvider.setUserCredential(userId, provider, credentials);
|
|
112
|
+
|
|
113
|
+
// Should return the credential
|
|
114
|
+
const result = await authProvider.getUserCredential(userId, provider);
|
|
115
|
+
|
|
116
|
+
expect(result).toEqual(credentials);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should handle token that expires right at current time', async () => {
|
|
120
|
+
const userId = 'user123';
|
|
121
|
+
const provider = 'github';
|
|
122
|
+
|
|
123
|
+
// Store credential that expires exactly now
|
|
124
|
+
const nowTime = Date.now();
|
|
125
|
+
await authProvider.setUserCredential(userId, provider, {
|
|
126
|
+
token: 'gho_expires_now_token',
|
|
127
|
+
scopes: ['repo'],
|
|
128
|
+
expiresAt: nowTime - 1, // Expires 1ms in the past to ensure it's expired
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Should return null (expired)
|
|
132
|
+
const result = await authProvider.getUserCredential(userId, provider);
|
|
133
|
+
|
|
134
|
+
expect(result).toBeNull();
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('Token refresh scenarios', () => {
|
|
139
|
+
it('should store refresh token when provided', async () => {
|
|
140
|
+
const userId = 'user123';
|
|
141
|
+
const provider = 'google';
|
|
142
|
+
|
|
143
|
+
const credentials: UserCredentialData = {
|
|
144
|
+
token: 'ya29.access_token',
|
|
145
|
+
refreshToken: 'refresh_token_here',
|
|
146
|
+
scopes: ['calendar'],
|
|
147
|
+
expiresAt: Date.now() + 3600000,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
await authProvider.setUserCredential(userId, provider, credentials);
|
|
151
|
+
|
|
152
|
+
const result = await authProvider.getUserCredential(userId, provider);
|
|
153
|
+
|
|
154
|
+
expect(result?.refreshToken).toBe('refresh_token_here');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should handle credentials without refresh token', async () => {
|
|
158
|
+
const userId = 'user123';
|
|
159
|
+
const provider = 'github';
|
|
160
|
+
|
|
161
|
+
const credentials: UserCredentialData = {
|
|
162
|
+
token: 'gho_token',
|
|
163
|
+
scopes: ['repo'],
|
|
164
|
+
expiresAt: Date.now() + 3600000,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
await authProvider.setUserCredential(userId, provider, credentials);
|
|
168
|
+
|
|
169
|
+
const result = await authProvider.getUserCredential(userId, provider);
|
|
170
|
+
|
|
171
|
+
expect(result?.refreshToken).toBeUndefined();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('Multiple users with different expiration times', () => {
|
|
176
|
+
it('should handle different expiration times for different users', async () => {
|
|
177
|
+
const provider = 'github';
|
|
178
|
+
|
|
179
|
+
// User 1: expired token
|
|
180
|
+
await authProvider.setUserCredential('user1', provider, {
|
|
181
|
+
token: 'token1',
|
|
182
|
+
scopes: ['repo'],
|
|
183
|
+
expiresAt: Date.now() - 1000, // Expired
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// User 2: valid token
|
|
187
|
+
await authProvider.setUserCredential('user2', provider, {
|
|
188
|
+
token: 'token2',
|
|
189
|
+
scopes: ['repo'],
|
|
190
|
+
expiresAt: Date.now() + 3600000, // Valid
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const user1Creds = await authProvider.getUserCredential('user1', provider);
|
|
194
|
+
const user2Creds = await authProvider.getUserCredential('user2', provider);
|
|
195
|
+
|
|
196
|
+
expect(user1Creds).toBeNull(); // Expired
|
|
197
|
+
expect(user2Creds).not.toBeNull(); // Valid
|
|
198
|
+
expect(user2Creds?.token).toBe('token2');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should handle same user with multiple providers at different expiration states', async () => {
|
|
202
|
+
const userId = 'user123';
|
|
203
|
+
|
|
204
|
+
// GitHub: expired
|
|
205
|
+
await authProvider.setUserCredential(userId, 'github', {
|
|
206
|
+
token: 'github_token',
|
|
207
|
+
scopes: ['repo'],
|
|
208
|
+
expiresAt: Date.now() - 1000,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Google: valid
|
|
212
|
+
await authProvider.setUserCredential(userId, 'google', {
|
|
213
|
+
token: 'google_token',
|
|
214
|
+
scopes: ['calendar'],
|
|
215
|
+
expiresAt: Date.now() + 3600000,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const githubCreds = await authProvider.getUserCredential(userId, 'github');
|
|
219
|
+
const googleCreds = await authProvider.getUserCredential(userId, 'google');
|
|
220
|
+
|
|
221
|
+
expect(githubCreds).toBeNull();
|
|
222
|
+
expect(googleCreds).not.toBeNull();
|
|
223
|
+
expect(googleCreds?.token).toBe('google_token');
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('Token metadata handling', () => {
|
|
228
|
+
it('should preserve metadata with expired tokens', async () => {
|
|
229
|
+
const userId = 'user123';
|
|
230
|
+
const provider = 'github';
|
|
231
|
+
|
|
232
|
+
await authProvider.setUserCredential(userId, provider, {
|
|
233
|
+
token: 'token',
|
|
234
|
+
scopes: ['repo'],
|
|
235
|
+
expiresAt: Date.now() + 3600000,
|
|
236
|
+
metadata: {
|
|
237
|
+
username: 'octocat',
|
|
238
|
+
userId: 'github_123',
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const result = await authProvider.getUserCredential(userId, provider);
|
|
243
|
+
|
|
244
|
+
expect(result?.metadata).toEqual({
|
|
245
|
+
username: 'octocat',
|
|
246
|
+
userId: 'github_123',
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('should handle credentials with complex metadata', async () => {
|
|
251
|
+
const userId = 'user123';
|
|
252
|
+
const provider = 'microsoft';
|
|
253
|
+
|
|
254
|
+
const complexMetadata = {
|
|
255
|
+
tenantId: 'tenant-id',
|
|
256
|
+
objectId: 'object-id',
|
|
257
|
+
permissions: ['User.Read', 'Mail.Send'],
|
|
258
|
+
nested: {
|
|
259
|
+
data: {
|
|
260
|
+
value: 123,
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
await authProvider.setUserCredential(userId, provider, {
|
|
266
|
+
token: 'token',
|
|
267
|
+
scopes: ['User.Read'],
|
|
268
|
+
expiresAt: Date.now() + 3600000,
|
|
269
|
+
metadata: complexMetadata,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const result = await authProvider.getUserCredential(userId, provider);
|
|
273
|
+
|
|
274
|
+
expect(result?.metadata).toEqual(complexMetadata);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe('Scope management', () => {
|
|
279
|
+
it('should handle empty scopes array', async () => {
|
|
280
|
+
const userId = 'user123';
|
|
281
|
+
const provider = 'github';
|
|
282
|
+
|
|
283
|
+
await authProvider.setUserCredential(userId, provider, {
|
|
284
|
+
token: 'token',
|
|
285
|
+
scopes: [],
|
|
286
|
+
expiresAt: Date.now() + 3600000,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const result = await authProvider.getUserCredential(userId, provider);
|
|
290
|
+
|
|
291
|
+
expect(result?.scopes).toEqual([]);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should handle undefined scopes', async () => {
|
|
295
|
+
const userId = 'user123';
|
|
296
|
+
const provider = 'github';
|
|
297
|
+
|
|
298
|
+
await authProvider.setUserCredential(userId, provider, {
|
|
299
|
+
token: 'token',
|
|
300
|
+
expiresAt: Date.now() + 3600000,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const result = await authProvider.getUserCredential(userId, provider);
|
|
304
|
+
|
|
305
|
+
expect(result?.scopes).toBeUndefined();
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AuditEvent, AuditFilter, AuditSink } from '@mondaydotcomorg/atp-protocol';
|
|
2
|
+
/**
|
|
3
|
+
* JSONL (JSON Lines) audit sink
|
|
4
|
+
* Writes audit events to a file, one JSON object per line
|
|
5
|
+
* Simple, append-only, easy to parse with standard tools
|
|
6
|
+
*/
|
|
7
|
+
export declare class JSONLAuditSink implements AuditSink {
|
|
8
|
+
name: string;
|
|
9
|
+
private filePath;
|
|
10
|
+
private sanitizeSecrets;
|
|
11
|
+
private buffer;
|
|
12
|
+
private flushInterval;
|
|
13
|
+
private batchSize;
|
|
14
|
+
constructor(options: {
|
|
15
|
+
filePath: string;
|
|
16
|
+
sanitizeSecrets?: boolean;
|
|
17
|
+
batchSize?: number;
|
|
18
|
+
flushIntervalMs?: number;
|
|
19
|
+
});
|
|
20
|
+
write(event: AuditEvent): Promise<void>;
|
|
21
|
+
writeBatch(events: AuditEvent[]): Promise<void>;
|
|
22
|
+
query(filter: AuditFilter): Promise<AuditEvent[]>;
|
|
23
|
+
disconnect(): Promise<void>;
|
|
24
|
+
private flush;
|
|
25
|
+
private sanitizeEvent;
|
|
26
|
+
private sanitizeString;
|
|
27
|
+
private sanitizeObject;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=jsonl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonl.d.ts","sourceRoot":"","sources":["../../src/audit/jsonl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAKxF;;;;GAIG;AACH,qBAAa,cAAe,YAAW,SAAS;IAC/C,IAAI,SAAW;IACf,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,eAAe,CAAU;IACjC,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,aAAa,CAA+B;IACpD,OAAO,CAAC,SAAS,CAAS;gBAEd,OAAO,EAAE;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,eAAe,CAAC,EAAE,MAAM,CAAC;KACzB;IAuBK,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAYvC,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAa/C,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAqCjD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAUnB,KAAK;IAOnB,OAAO,CAAC,aAAa;IAiBrB,OAAO,CAAC,cAAc;IAoBtB,OAAO,CAAC,cAAc;CA0BtB"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { appendFile, readFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* JSONL (JSON Lines) audit sink
|
|
6
|
+
* Writes audit events to a file, one JSON object per line
|
|
7
|
+
* Simple, append-only, easy to parse with standard tools
|
|
8
|
+
*/
|
|
9
|
+
export class JSONLAuditSink {
|
|
10
|
+
name = 'jsonl';
|
|
11
|
+
filePath;
|
|
12
|
+
sanitizeSecrets;
|
|
13
|
+
buffer = [];
|
|
14
|
+
flushInterval = null;
|
|
15
|
+
batchSize;
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this.filePath = options.filePath;
|
|
18
|
+
this.sanitizeSecrets = options.sanitizeSecrets ?? true;
|
|
19
|
+
this.batchSize = options.batchSize || 10;
|
|
20
|
+
const dir = dirname(this.filePath);
|
|
21
|
+
if (!existsSync(dir)) {
|
|
22
|
+
mkdir(dir, { recursive: true }).catch((err) => {
|
|
23
|
+
console.error(`Failed to create audit directory: ${err.message}`);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (options.flushIntervalMs) {
|
|
27
|
+
this.flushInterval = setInterval(() => {
|
|
28
|
+
if (this.buffer.length > 0) {
|
|
29
|
+
this.flush().catch((err) => {
|
|
30
|
+
console.error(`Failed to flush audit buffer: ${err.message}`);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}, options.flushIntervalMs);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async write(event) {
|
|
37
|
+
const sanitized = this.sanitizeSecrets ? this.sanitizeEvent(event) : event;
|
|
38
|
+
const line = JSON.stringify(sanitized) + '\n';
|
|
39
|
+
try {
|
|
40
|
+
await appendFile(this.filePath, line, 'utf8');
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error(`Failed to write audit event: ${error.message}`);
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async writeBatch(events) {
|
|
48
|
+
const sanitized = this.sanitizeSecrets ? events.map((e) => this.sanitizeEvent(e)) : events;
|
|
49
|
+
const lines = sanitized.map((e) => JSON.stringify(e)).join('\n') + '\n';
|
|
50
|
+
try {
|
|
51
|
+
await appendFile(this.filePath, lines, 'utf8');
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error(`Failed to write audit batch: ${error.message}`);
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async query(filter) {
|
|
59
|
+
try {
|
|
60
|
+
const content = await readFile(this.filePath, 'utf8');
|
|
61
|
+
const lines = content.split('\n').filter((line) => line.trim());
|
|
62
|
+
const events = lines.map((line) => JSON.parse(line));
|
|
63
|
+
return events
|
|
64
|
+
.filter((event) => {
|
|
65
|
+
if (filter.clientId && event.clientId !== filter.clientId)
|
|
66
|
+
return false;
|
|
67
|
+
if (filter.userId && event.userId !== filter.userId)
|
|
68
|
+
return false;
|
|
69
|
+
if (filter.from && event.timestamp < filter.from)
|
|
70
|
+
return false;
|
|
71
|
+
if (filter.to && event.timestamp > filter.to)
|
|
72
|
+
return false;
|
|
73
|
+
if (filter.resource && event.resource !== filter.resource)
|
|
74
|
+
return false;
|
|
75
|
+
if (filter.eventType) {
|
|
76
|
+
const types = Array.isArray(filter.eventType) ? filter.eventType : [filter.eventType];
|
|
77
|
+
if (!types.includes(event.eventType))
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
if (filter.status) {
|
|
81
|
+
const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
|
|
82
|
+
if (!statuses.includes(event.status))
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (filter.minRiskScore && (event.riskScore || 0) < filter.minRiskScore)
|
|
86
|
+
return false;
|
|
87
|
+
return true;
|
|
88
|
+
})
|
|
89
|
+
.slice(filter.offset || 0, (filter.offset || 0) + (filter.limit || 100));
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
if (error.code === 'ENOENT') {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async disconnect() {
|
|
99
|
+
if (this.flushInterval) {
|
|
100
|
+
clearInterval(this.flushInterval);
|
|
101
|
+
}
|
|
102
|
+
if (this.buffer.length > 0) {
|
|
103
|
+
await this.flush();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async flush() {
|
|
107
|
+
if (this.buffer.length === 0)
|
|
108
|
+
return;
|
|
109
|
+
await this.writeBatch([...this.buffer]);
|
|
110
|
+
this.buffer = [];
|
|
111
|
+
}
|
|
112
|
+
sanitizeEvent(event) {
|
|
113
|
+
const sanitized = { ...event };
|
|
114
|
+
if (sanitized.code) {
|
|
115
|
+
sanitized.code = this.sanitizeString(sanitized.code);
|
|
116
|
+
}
|
|
117
|
+
if (sanitized.input) {
|
|
118
|
+
sanitized.input = this.sanitizeObject(sanitized.input);
|
|
119
|
+
}
|
|
120
|
+
if (sanitized.output) {
|
|
121
|
+
sanitized.output = this.sanitizeObject(sanitized.output);
|
|
122
|
+
}
|
|
123
|
+
return sanitized;
|
|
124
|
+
}
|
|
125
|
+
sanitizeString(str) {
|
|
126
|
+
const patterns = [
|
|
127
|
+
/api[_-]?key/gi,
|
|
128
|
+
/secret/gi,
|
|
129
|
+
/token/gi,
|
|
130
|
+
/password/gi,
|
|
131
|
+
/bearer/gi,
|
|
132
|
+
/authorization/gi,
|
|
133
|
+
];
|
|
134
|
+
for (const pattern of patterns) {
|
|
135
|
+
str = str.replace(new RegExp(`(${pattern.source})\\s*[:=]\\s*['\"]?([^'\"\\s]+)`, 'gi'), '$1: [REDACTED]');
|
|
136
|
+
}
|
|
137
|
+
return str;
|
|
138
|
+
}
|
|
139
|
+
sanitizeObject(obj) {
|
|
140
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
141
|
+
return obj;
|
|
142
|
+
}
|
|
143
|
+
if (Array.isArray(obj)) {
|
|
144
|
+
return obj.map((item) => this.sanitizeObject(item));
|
|
145
|
+
}
|
|
146
|
+
const sanitized = {};
|
|
147
|
+
const secretPatterns = ['key', 'secret', 'token', 'password', 'bearer', 'auth'];
|
|
148
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
149
|
+
const lowerKey = key.toLowerCase();
|
|
150
|
+
if (secretPatterns.some((pattern) => lowerKey.includes(pattern))) {
|
|
151
|
+
sanitized[key] = '[REDACTED]';
|
|
152
|
+
}
|
|
153
|
+
else if (typeof value === 'object') {
|
|
154
|
+
sanitized[key] = this.sanitizeObject(value);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
sanitized[key] = value;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return sanitized;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=jsonl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonl.js","sourceRoot":"","sources":["../../src/audit/jsonl.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AuditSink, AuditEvent } from '@mondaydotcomorg/atp-protocol';
|
|
2
|
+
/**
|
|
3
|
+
* OpenTelemetry-based audit sink
|
|
4
|
+
* Provides industry-standard observability with distributed tracing and metrics
|
|
5
|
+
*/
|
|
6
|
+
export declare class OpenTelemetryAuditSink implements AuditSink {
|
|
7
|
+
name: string;
|
|
8
|
+
private tracer;
|
|
9
|
+
private meter;
|
|
10
|
+
private executionCounter;
|
|
11
|
+
private toolCallCounter;
|
|
12
|
+
private llmCallCounter;
|
|
13
|
+
private approvalCounter;
|
|
14
|
+
private executionDuration;
|
|
15
|
+
private toolDuration;
|
|
16
|
+
write(event: AuditEvent): Promise<void>;
|
|
17
|
+
writeBatch(events: AuditEvent[]): Promise<void>;
|
|
18
|
+
private buildAttributes;
|
|
19
|
+
private handleEvent;
|
|
20
|
+
private recordMetrics;
|
|
21
|
+
private flattenObject;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=opentelemetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opentelemetry.d.ts","sourceRoot":"","sources":["../../src/audit/opentelemetry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAY3E;;;GAGG;AACH,qBAAa,sBAAuB,YAAW,SAAS;IACvD,IAAI,SAAmB;IACvB,OAAO,CAAC,MAAM,CAAqC;IACnD,OAAO,CAAC,KAAK,CAAqC;IAElD,OAAO,CAAC,gBAAgB,CAGtB;IAEF,OAAO,CAAC,eAAe,CAGrB;IAEF,OAAO,CAAC,cAAc,CAGpB;IAEF,OAAO,CAAC,eAAe,CAGrB;IAEF,OAAO,CAAC,iBAAiB,CAGvB;IAEF,OAAO,CAAC,YAAY,CAGlB;IAEI,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBvC,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrD,OAAO,CAAC,eAAe;IAiDvB,OAAO,CAAC,WAAW;IA8EnB,OAAO,CAAC,aAAa;IA6DrB,OAAO,CAAC,aAAa;CAmBrB"}
|