@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.
Files changed (58) hide show
  1. package/README.md +430 -0
  2. package/__tests__/oauth-integration.test.ts +457 -0
  3. package/__tests__/scope-checkers.test.ts +560 -0
  4. package/__tests__/token-expiration.test.ts +308 -0
  5. package/dist/audit/jsonl.d.ts +29 -0
  6. package/dist/audit/jsonl.d.ts.map +1 -0
  7. package/dist/audit/jsonl.js +163 -0
  8. package/dist/audit/jsonl.js.map +1 -0
  9. package/dist/audit/opentelemetry.d.ts +23 -0
  10. package/dist/audit/opentelemetry.d.ts.map +1 -0
  11. package/dist/audit/opentelemetry.js +240 -0
  12. package/dist/audit/opentelemetry.js.map +1 -0
  13. package/dist/audit/otel-metrics.d.ts +111 -0
  14. package/dist/audit/otel-metrics.d.ts.map +1 -0
  15. package/dist/audit/otel-metrics.js +115 -0
  16. package/dist/audit/otel-metrics.js.map +1 -0
  17. package/dist/auth/env.d.ts +21 -0
  18. package/dist/auth/env.d.ts.map +1 -0
  19. package/dist/auth/env.js +48 -0
  20. package/dist/auth/env.js.map +1 -0
  21. package/dist/cache/file.d.ts +47 -0
  22. package/dist/cache/file.d.ts.map +1 -0
  23. package/dist/cache/file.js +262 -0
  24. package/dist/cache/file.js.map +1 -0
  25. package/dist/cache/memory.d.ts +30 -0
  26. package/dist/cache/memory.d.ts.map +1 -0
  27. package/dist/cache/memory.js +81 -0
  28. package/dist/cache/memory.js.map +1 -0
  29. package/dist/cache/redis.d.ts +28 -0
  30. package/dist/cache/redis.d.ts.map +1 -0
  31. package/dist/cache/redis.js +124 -0
  32. package/dist/cache/redis.js.map +1 -0
  33. package/dist/index.d.ts +10 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +9 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/oauth/index.d.ts +2 -0
  38. package/dist/oauth/index.d.ts.map +1 -0
  39. package/dist/oauth/index.js +2 -0
  40. package/dist/oauth/index.js.map +1 -0
  41. package/dist/oauth/scope-checkers.d.ts +61 -0
  42. package/dist/oauth/scope-checkers.d.ts.map +1 -0
  43. package/dist/oauth/scope-checkers.js +166 -0
  44. package/dist/oauth/scope-checkers.js.map +1 -0
  45. package/package.json +26 -0
  46. package/project.json +31 -0
  47. package/src/audit/jsonl.ts +189 -0
  48. package/src/audit/opentelemetry.ts +286 -0
  49. package/src/audit/otel-metrics.ts +122 -0
  50. package/src/auth/env.ts +65 -0
  51. package/src/cache/file.ts +330 -0
  52. package/src/cache/memory.ts +105 -0
  53. package/src/cache/redis.ts +160 -0
  54. package/src/index.ts +32 -0
  55. package/src/oauth/index.ts +1 -0
  56. package/src/oauth/scope-checkers.ts +196 -0
  57. package/tsconfig.json +14 -0
  58. 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
+ }