@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,61 @@
|
|
|
1
|
+
import type { ScopeChecker, TokenInfo } from '@mondaydotcomorg/atp-protocol';
|
|
2
|
+
/**
|
|
3
|
+
* Scope checker registry
|
|
4
|
+
* Manages scope checkers for different OAuth providers
|
|
5
|
+
*/
|
|
6
|
+
export declare class ScopeCheckerRegistry {
|
|
7
|
+
private checkers;
|
|
8
|
+
private scopeCache;
|
|
9
|
+
private cleanupInterval?;
|
|
10
|
+
private maxCacheSize;
|
|
11
|
+
private pendingChecks;
|
|
12
|
+
constructor();
|
|
13
|
+
/**
|
|
14
|
+
* Start periodic cache cleanup
|
|
15
|
+
*/
|
|
16
|
+
private startCleanup;
|
|
17
|
+
/**
|
|
18
|
+
* Stop periodic cache cleanup (for testing or shutdown)
|
|
19
|
+
*/
|
|
20
|
+
stopCleanup(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Remove expired entries from cache
|
|
23
|
+
*/
|
|
24
|
+
private cleanupExpiredCache;
|
|
25
|
+
/**
|
|
26
|
+
* Register a custom scope checker
|
|
27
|
+
*/
|
|
28
|
+
register(checker: ScopeChecker): void;
|
|
29
|
+
/**
|
|
30
|
+
* Check if a scope checker is available for a provider
|
|
31
|
+
*/
|
|
32
|
+
hasChecker(provider: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Get scope checker for a provider
|
|
35
|
+
*/
|
|
36
|
+
getChecker(provider: string): ScopeChecker | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Check what scopes a token has (with caching and deduplication)
|
|
39
|
+
* @param provider - Provider name
|
|
40
|
+
* @param token - Access token
|
|
41
|
+
* @param cacheTTL - Cache TTL in seconds (default: 3600 = 1 hour)
|
|
42
|
+
*/
|
|
43
|
+
checkScopes(provider: string, token: string, cacheTTL?: number): Promise<string[]>;
|
|
44
|
+
/**
|
|
45
|
+
* Validate if a token is still valid
|
|
46
|
+
*/
|
|
47
|
+
validateToken(provider: string, token: string): Promise<boolean>;
|
|
48
|
+
/**
|
|
49
|
+
* Get complete token information
|
|
50
|
+
*/
|
|
51
|
+
getTokenInfo(provider: string, token: string): Promise<TokenInfo>;
|
|
52
|
+
/**
|
|
53
|
+
* Clear cached scopes
|
|
54
|
+
*/
|
|
55
|
+
clearCache(provider?: string): void;
|
|
56
|
+
/**
|
|
57
|
+
* Hash token for cache key (SHA-256, first 16 chars)
|
|
58
|
+
*/
|
|
59
|
+
private hashToken;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=scope-checkers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope-checkers.d.ts","sourceRoot":"","sources":["../../src/oauth/scope-checkers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAG7E;;;GAGG;AACH,qBAAa,oBAAoB;IAChC,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,UAAU,CAA8D;IAChF,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,aAAa,CAAwC;;IAM7D;;OAEG;IACH,OAAO,CAAC,YAAY;IAapB;;OAEG;IACH,WAAW,IAAI,IAAI;IAOnB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA+B3B;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI;IAIrC;;OAEG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIrC;;OAEG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAItD;;;;;OAKG;IACG,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,SAAO,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAqCtF;;OAEG;IACG,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAStE;;OAEG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAiBvE;;OAEG;IACH,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAYnC;;OAEG;IACH,OAAO,CAAC,SAAS;CAGjB"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Scope checker registry
|
|
4
|
+
* Manages scope checkers for different OAuth providers
|
|
5
|
+
*/
|
|
6
|
+
export class ScopeCheckerRegistry {
|
|
7
|
+
checkers = new Map();
|
|
8
|
+
scopeCache = new Map();
|
|
9
|
+
cleanupInterval;
|
|
10
|
+
maxCacheSize = 10000;
|
|
11
|
+
pendingChecks = new Map();
|
|
12
|
+
constructor() {
|
|
13
|
+
this.startCleanup();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Start periodic cache cleanup
|
|
17
|
+
*/
|
|
18
|
+
startCleanup() {
|
|
19
|
+
this.cleanupInterval = setInterval(() => {
|
|
20
|
+
this.cleanupExpiredCache();
|
|
21
|
+
}, 5 * 60 * 1000);
|
|
22
|
+
if (this.cleanupInterval.unref) {
|
|
23
|
+
this.cleanupInterval.unref();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Stop periodic cache cleanup (for testing or shutdown)
|
|
28
|
+
*/
|
|
29
|
+
stopCleanup() {
|
|
30
|
+
if (this.cleanupInterval) {
|
|
31
|
+
clearInterval(this.cleanupInterval);
|
|
32
|
+
this.cleanupInterval = undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Remove expired entries from cache
|
|
37
|
+
*/
|
|
38
|
+
cleanupExpiredCache() {
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
let cleaned = 0;
|
|
41
|
+
for (const [key, value] of this.scopeCache.entries()) {
|
|
42
|
+
if (value.expiresAt <= now) {
|
|
43
|
+
this.scopeCache.delete(key);
|
|
44
|
+
cleaned++;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (this.scopeCache.size > this.maxCacheSize) {
|
|
48
|
+
const entriesToRemove = this.scopeCache.size - this.maxCacheSize;
|
|
49
|
+
const entries = Array.from(this.scopeCache.entries());
|
|
50
|
+
entries.sort((a, b) => a[1].expiresAt - b[1].expiresAt);
|
|
51
|
+
for (let i = 0; i < entriesToRemove; i++) {
|
|
52
|
+
const entry = entries[i];
|
|
53
|
+
if (entry) {
|
|
54
|
+
this.scopeCache.delete(entry[0]);
|
|
55
|
+
cleaned++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (cleaned > 0) {
|
|
60
|
+
console.debug(`Cleaned ${cleaned} expired/old scope cache entries`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Register a custom scope checker
|
|
65
|
+
*/
|
|
66
|
+
register(checker) {
|
|
67
|
+
this.checkers.set(checker.provider, checker);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Check if a scope checker is available for a provider
|
|
71
|
+
*/
|
|
72
|
+
hasChecker(provider) {
|
|
73
|
+
return this.checkers.has(provider);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get scope checker for a provider
|
|
77
|
+
*/
|
|
78
|
+
getChecker(provider) {
|
|
79
|
+
return this.checkers.get(provider);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Check what scopes a token has (with caching and deduplication)
|
|
83
|
+
* @param provider - Provider name
|
|
84
|
+
* @param token - Access token
|
|
85
|
+
* @param cacheTTL - Cache TTL in seconds (default: 3600 = 1 hour)
|
|
86
|
+
*/
|
|
87
|
+
async checkScopes(provider, token, cacheTTL = 3600) {
|
|
88
|
+
const cacheKey = `${provider}:${this.hashToken(token)}`;
|
|
89
|
+
const cached = this.scopeCache.get(cacheKey);
|
|
90
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
91
|
+
return cached.scopes;
|
|
92
|
+
}
|
|
93
|
+
const pending = this.pendingChecks.get(cacheKey);
|
|
94
|
+
if (pending) {
|
|
95
|
+
return pending;
|
|
96
|
+
}
|
|
97
|
+
const checker = this.checkers.get(provider);
|
|
98
|
+
if (!checker) {
|
|
99
|
+
throw new Error(`No scope checker registered for provider: ${provider}`);
|
|
100
|
+
}
|
|
101
|
+
const checkPromise = (async () => {
|
|
102
|
+
try {
|
|
103
|
+
const scopes = await checker.check(token);
|
|
104
|
+
this.scopeCache.set(cacheKey, {
|
|
105
|
+
scopes,
|
|
106
|
+
expiresAt: Date.now() + cacheTTL * 1000,
|
|
107
|
+
});
|
|
108
|
+
return scopes;
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
this.pendingChecks.delete(cacheKey);
|
|
112
|
+
}
|
|
113
|
+
})();
|
|
114
|
+
this.pendingChecks.set(cacheKey, checkPromise);
|
|
115
|
+
return checkPromise;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Validate if a token is still valid
|
|
119
|
+
*/
|
|
120
|
+
async validateToken(provider, token) {
|
|
121
|
+
const checker = this.checkers.get(provider);
|
|
122
|
+
if (!checker || !checker.validate) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
return await checker.validate(token);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get complete token information
|
|
129
|
+
*/
|
|
130
|
+
async getTokenInfo(provider, token) {
|
|
131
|
+
const checker = this.checkers.get(provider);
|
|
132
|
+
if (!checker) {
|
|
133
|
+
throw new Error(`No scope checker registered for provider: ${provider}`);
|
|
134
|
+
}
|
|
135
|
+
const [scopes, valid] = await Promise.all([
|
|
136
|
+
checker.check(token),
|
|
137
|
+
checker.validate ? checker.validate(token) : Promise.resolve(true),
|
|
138
|
+
]);
|
|
139
|
+
return {
|
|
140
|
+
valid,
|
|
141
|
+
scopes,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Clear cached scopes
|
|
146
|
+
*/
|
|
147
|
+
clearCache(provider) {
|
|
148
|
+
if (provider) {
|
|
149
|
+
for (const key of this.scopeCache.keys()) {
|
|
150
|
+
if (key.startsWith(`${provider}:`)) {
|
|
151
|
+
this.scopeCache.delete(key);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
this.scopeCache.clear();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Hash token for cache key (SHA-256, first 16 chars)
|
|
161
|
+
*/
|
|
162
|
+
hashToken(token) {
|
|
163
|
+
return createHash('sha256').update(token).digest('hex').substring(0, 16);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=scope-checkers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope-checkers.js","sourceRoot":"","sources":["../../src/oauth/scope-checkers.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mondaydotcomorg/atp-providers",
|
|
3
|
+
"version": "0.17.14",
|
|
4
|
+
"description": "Built-in providers for Agent Tool Protocol (cache, auth, audit)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"clean": "rm -rf dist",
|
|
17
|
+
"lint": "tsc --noEmit",
|
|
18
|
+
"test": "vitest run"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@mondaydotcomorg/atp-protocol": "0.17.14"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"typescript": "^5.7.2"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mondaydotcomorg/atp-providers",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "packages/providers/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"targets": {
|
|
7
|
+
"build": {
|
|
8
|
+
"executor": "nx:run-commands",
|
|
9
|
+
"outputs": ["{workspaceRoot}/packages/providers/dist"],
|
|
10
|
+
"options": {
|
|
11
|
+
"command": "tsc --build packages/providers/tsconfig.json",
|
|
12
|
+
"cwd": "{workspaceRoot}"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"clean": {
|
|
16
|
+
"executor": "nx:run-commands",
|
|
17
|
+
"options": {
|
|
18
|
+
"command": "rm -rf packages/providers/dist"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"lint": {
|
|
22
|
+
"executor": "nx:run-commands",
|
|
23
|
+
"options": {
|
|
24
|
+
"command": "cd packages/providers && npm run lint"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"tags": [
|
|
29
|
+
"atp-core"
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { AuditEvent, AuditFilter, AuditSink } from '@mondaydotcomorg/atp-protocol';
|
|
2
|
+
import { appendFile, readFile, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* JSONL (JSON Lines) audit sink
|
|
8
|
+
* Writes audit events to a file, one JSON object per line
|
|
9
|
+
* Simple, append-only, easy to parse with standard tools
|
|
10
|
+
*/
|
|
11
|
+
export class JSONLAuditSink implements AuditSink {
|
|
12
|
+
name = 'jsonl';
|
|
13
|
+
private filePath: string;
|
|
14
|
+
private sanitizeSecrets: boolean;
|
|
15
|
+
private buffer: AuditEvent[] = [];
|
|
16
|
+
private flushInterval: NodeJS.Timeout | null = null;
|
|
17
|
+
private batchSize: number;
|
|
18
|
+
|
|
19
|
+
constructor(options: {
|
|
20
|
+
filePath: string;
|
|
21
|
+
sanitizeSecrets?: boolean;
|
|
22
|
+
batchSize?: number;
|
|
23
|
+
flushIntervalMs?: number;
|
|
24
|
+
}) {
|
|
25
|
+
this.filePath = options.filePath;
|
|
26
|
+
this.sanitizeSecrets = options.sanitizeSecrets ?? true;
|
|
27
|
+
this.batchSize = options.batchSize || 10;
|
|
28
|
+
|
|
29
|
+
const dir = dirname(this.filePath);
|
|
30
|
+
if (!existsSync(dir)) {
|
|
31
|
+
mkdir(dir, { recursive: true }).catch((err) => {
|
|
32
|
+
console.error(`Failed to create audit directory: ${err.message}`);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (options.flushIntervalMs) {
|
|
37
|
+
this.flushInterval = setInterval(() => {
|
|
38
|
+
if (this.buffer.length > 0) {
|
|
39
|
+
this.flush().catch((err) => {
|
|
40
|
+
console.error(`Failed to flush audit buffer: ${err.message}`);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}, options.flushIntervalMs);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async write(event: AuditEvent): Promise<void> {
|
|
48
|
+
const sanitized = this.sanitizeSecrets ? this.sanitizeEvent(event) : event;
|
|
49
|
+
const line = JSON.stringify(sanitized) + '\n';
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
await appendFile(this.filePath, line, 'utf8');
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error(`Failed to write audit event: ${(error as Error).message}`);
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async writeBatch(events: AuditEvent[]): Promise<void> {
|
|
60
|
+
const sanitized = this.sanitizeSecrets ? events.map((e) => this.sanitizeEvent(e)) : events;
|
|
61
|
+
|
|
62
|
+
const lines = sanitized.map((e) => JSON.stringify(e)).join('\n') + '\n';
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
await appendFile(this.filePath, lines, 'utf8');
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(`Failed to write audit batch: ${(error as Error).message}`);
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async query(filter: AuditFilter): Promise<AuditEvent[]> {
|
|
73
|
+
try {
|
|
74
|
+
const content = await readFile(this.filePath, 'utf8');
|
|
75
|
+
const lines = content.split('\n').filter((line) => line.trim());
|
|
76
|
+
const events: AuditEvent[] = lines.map((line) => JSON.parse(line));
|
|
77
|
+
|
|
78
|
+
return events
|
|
79
|
+
.filter((event) => {
|
|
80
|
+
if (filter.clientId && event.clientId !== filter.clientId) return false;
|
|
81
|
+
if (filter.userId && event.userId !== filter.userId) return false;
|
|
82
|
+
if (filter.from && event.timestamp < filter.from) return false;
|
|
83
|
+
if (filter.to && event.timestamp > filter.to) return false;
|
|
84
|
+
if (filter.resource && event.resource !== filter.resource) return false;
|
|
85
|
+
|
|
86
|
+
if (filter.eventType) {
|
|
87
|
+
const types = Array.isArray(filter.eventType) ? filter.eventType : [filter.eventType];
|
|
88
|
+
if (!types.includes(event.eventType)) return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (filter.status) {
|
|
92
|
+
const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
|
|
93
|
+
if (!statuses.includes(event.status)) return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (filter.minRiskScore && (event.riskScore || 0) < filter.minRiskScore) return false;
|
|
97
|
+
|
|
98
|
+
return true;
|
|
99
|
+
})
|
|
100
|
+
.slice(filter.offset || 0, (filter.offset || 0) + (filter.limit || 100));
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async disconnect(): Promise<void> {
|
|
110
|
+
if (this.flushInterval) {
|
|
111
|
+
clearInterval(this.flushInterval);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (this.buffer.length > 0) {
|
|
115
|
+
await this.flush();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private async flush(): Promise<void> {
|
|
120
|
+
if (this.buffer.length === 0) return;
|
|
121
|
+
|
|
122
|
+
await this.writeBatch([...this.buffer]);
|
|
123
|
+
this.buffer = [];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private sanitizeEvent(event: AuditEvent): AuditEvent {
|
|
127
|
+
const sanitized = { ...event };
|
|
128
|
+
|
|
129
|
+
if (sanitized.code) {
|
|
130
|
+
sanitized.code = this.sanitizeString(sanitized.code);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (sanitized.input) {
|
|
134
|
+
sanitized.input = this.sanitizeObject(sanitized.input);
|
|
135
|
+
}
|
|
136
|
+
if (sanitized.output) {
|
|
137
|
+
sanitized.output = this.sanitizeObject(sanitized.output);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return sanitized;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private sanitizeString(str: string): string {
|
|
144
|
+
const patterns = [
|
|
145
|
+
/api[_-]?key/gi,
|
|
146
|
+
/secret/gi,
|
|
147
|
+
/token/gi,
|
|
148
|
+
/password/gi,
|
|
149
|
+
/bearer/gi,
|
|
150
|
+
/authorization/gi,
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
for (const pattern of patterns) {
|
|
154
|
+
str = str.replace(
|
|
155
|
+
new RegExp(`(${pattern.source})\\s*[:=]\\s*['\"]?([^'\"\\s]+)`, 'gi'),
|
|
156
|
+
'$1: [REDACTED]'
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return str;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private sanitizeObject(obj: unknown): unknown {
|
|
164
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
165
|
+
return obj;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (Array.isArray(obj)) {
|
|
169
|
+
return obj.map((item) => this.sanitizeObject(item));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const sanitized: Record<string, unknown> = {};
|
|
173
|
+
const secretPatterns = ['key', 'secret', 'token', 'password', 'bearer', 'auth'];
|
|
174
|
+
|
|
175
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
176
|
+
const lowerKey = key.toLowerCase();
|
|
177
|
+
|
|
178
|
+
if (secretPatterns.some((pattern) => lowerKey.includes(pattern))) {
|
|
179
|
+
sanitized[key] = '[REDACTED]';
|
|
180
|
+
} else if (typeof value === 'object') {
|
|
181
|
+
sanitized[key] = this.sanitizeObject(value);
|
|
182
|
+
} else {
|
|
183
|
+
sanitized[key] = value;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return sanitized;
|
|
188
|
+
}
|
|
189
|
+
}
|