@mondaydotcomorg/atp-protocol 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 +241 -0
- package/dist/auth.d.ts +173 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +202 -0
- package/dist/auth.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth.d.ts +63 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +5 -0
- package/dist/oauth.js.map +1 -0
- package/dist/providers.d.ts +167 -0
- package/dist/providers.d.ts.map +1 -0
- package/dist/providers.js +33 -0
- package/dist/providers.js.map +1 -0
- package/dist/schemas.d.ts +6 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +51 -0
- package/dist/schemas.js.map +1 -0
- package/dist/types.d.ts +489 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +88 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +76 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +129 -0
- package/dist/validation.js.map +1 -0
- package/package.json +41 -0
- package/src/auth.ts +404 -0
- package/src/index.ts +6 -0
- package/src/oauth.ts +74 -0
- package/src/providers.ts +227 -0
- package/src/schemas.ts +55 -0
- package/src/types.ts +547 -0
- package/src/validation.ts +162 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input validation utilities for ExecutionConfig and other types
|
|
3
|
+
*/
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
/**
|
|
6
|
+
* Maximum allowed code size (1MB)
|
|
7
|
+
*/
|
|
8
|
+
export const MAX_CODE_SIZE = 1000000;
|
|
9
|
+
export class ConfigValidationError extends Error {
|
|
10
|
+
field;
|
|
11
|
+
value;
|
|
12
|
+
constructor(message, field, value) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.field = field;
|
|
15
|
+
this.value = value;
|
|
16
|
+
this.name = 'ConfigValidationError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class SecurityViolationError extends Error {
|
|
20
|
+
violations;
|
|
21
|
+
constructor(message, violations) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.violations = violations;
|
|
24
|
+
this.name = 'SecurityViolationError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Sanitizes input string by removing control characters and normalizing whitespace
|
|
29
|
+
*/
|
|
30
|
+
export function sanitizeInput(input, maxLength = MAX_CODE_SIZE) {
|
|
31
|
+
if (typeof input !== 'string') {
|
|
32
|
+
return '';
|
|
33
|
+
}
|
|
34
|
+
let sanitized = input.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, '');
|
|
35
|
+
sanitized = sanitized.replace(/[\u200B-\u200D\uFEFF]/g, '');
|
|
36
|
+
sanitized = sanitized.replace(/\n{10,}/g, '\n\n\n');
|
|
37
|
+
if (sanitized.length > maxLength) {
|
|
38
|
+
sanitized = sanitized.substring(0, maxLength);
|
|
39
|
+
}
|
|
40
|
+
return sanitized;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Frames user code in a secure execution context to prevent injection attacks
|
|
44
|
+
* Similar to SQL parameterized queries - treats user code as data within a safe boundary
|
|
45
|
+
*/
|
|
46
|
+
export function frameCodeExecution(userCode) {
|
|
47
|
+
const cleaned = sanitizeInput(userCode);
|
|
48
|
+
return `
|
|
49
|
+
(async function __user_code_context__() {
|
|
50
|
+
"use strict";
|
|
51
|
+
${cleaned}
|
|
52
|
+
})();
|
|
53
|
+
`.trim();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Zod schema for ExecutionConfig validation
|
|
57
|
+
*/
|
|
58
|
+
export const executionConfigSchema = z.object({
|
|
59
|
+
timeout: z
|
|
60
|
+
.number({
|
|
61
|
+
invalid_type_error: 'timeout must be a number',
|
|
62
|
+
})
|
|
63
|
+
.positive('timeout must be positive')
|
|
64
|
+
.max(300000, 'timeout cannot exceed 300000ms (5 minutes)')
|
|
65
|
+
.optional(),
|
|
66
|
+
maxMemory: z
|
|
67
|
+
.number({
|
|
68
|
+
invalid_type_error: 'maxMemory must be a number',
|
|
69
|
+
})
|
|
70
|
+
.positive('maxMemory must be positive')
|
|
71
|
+
.max(512 * 1024 * 1024, 'maxMemory cannot exceed 512MB')
|
|
72
|
+
.optional(),
|
|
73
|
+
maxLLMCalls: z
|
|
74
|
+
.number({
|
|
75
|
+
invalid_type_error: 'maxLLMCalls must be a number',
|
|
76
|
+
})
|
|
77
|
+
.nonnegative('maxLLMCalls cannot be negative')
|
|
78
|
+
.max(1000, 'maxLLMCalls cannot exceed 1000')
|
|
79
|
+
.optional(),
|
|
80
|
+
allowedAPIs: z
|
|
81
|
+
.array(z.string().refine((val) => val.trim().length > 0, {
|
|
82
|
+
message: 'allowedAPIs must contain non-empty strings',
|
|
83
|
+
}))
|
|
84
|
+
.optional(),
|
|
85
|
+
allowLLMCalls: z
|
|
86
|
+
.boolean({
|
|
87
|
+
invalid_type_error: 'allowLLMCalls must be a boolean',
|
|
88
|
+
})
|
|
89
|
+
.optional(),
|
|
90
|
+
progressCallback: z.function().optional(),
|
|
91
|
+
customLLMHandler: z.function().optional(),
|
|
92
|
+
clientServices: z.any().optional(),
|
|
93
|
+
provenanceMode: z.any().optional(),
|
|
94
|
+
securityPolicies: z.array(z.any()).optional(),
|
|
95
|
+
provenanceHints: z.array(z.string()).optional(),
|
|
96
|
+
});
|
|
97
|
+
/**
|
|
98
|
+
* Validates ExecutionConfig parameters using Zod
|
|
99
|
+
*/
|
|
100
|
+
export function validateExecutionConfig(config) {
|
|
101
|
+
try {
|
|
102
|
+
executionConfigSchema.parse(config);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (error instanceof z.ZodError) {
|
|
106
|
+
const errors = error.errors.map((err) => err.message);
|
|
107
|
+
throw new ConfigValidationError(`Invalid ExecutionConfig: ${errors.join(', ')}`, 'ExecutionConfig', config);
|
|
108
|
+
}
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Validates client ID format
|
|
114
|
+
*/
|
|
115
|
+
export function validateClientId(clientId) {
|
|
116
|
+
if (typeof clientId !== 'string') {
|
|
117
|
+
throw new ConfigValidationError('clientId must be a string', 'clientId', clientId);
|
|
118
|
+
}
|
|
119
|
+
if (clientId.trim().length === 0) {
|
|
120
|
+
throw new ConfigValidationError('clientId cannot be empty', 'clientId', clientId);
|
|
121
|
+
}
|
|
122
|
+
if (clientId.length > 256) {
|
|
123
|
+
throw new ConfigValidationError('clientId cannot exceed 256 characters', 'clientId', clientId);
|
|
124
|
+
}
|
|
125
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(clientId)) {
|
|
126
|
+
throw new ConfigValidationError('clientId can only contain alphanumeric characters, dashes, and underscores', 'clientId', clientId);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mondaydotcomorg/atp-protocol",
|
|
3
|
+
"version": "0.17.14",
|
|
4
|
+
"description": "Core protocol types and interfaces for Agent Tool Protocol",
|
|
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
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"dev": "tsc -p tsconfig.json --watch",
|
|
21
|
+
"clean": "rm -rf dist *.tsbuildinfo",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"lint": "tsc --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"agent",
|
|
27
|
+
"protocol",
|
|
28
|
+
"types",
|
|
29
|
+
"ai",
|
|
30
|
+
"llm"
|
|
31
|
+
],
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@mondaydotcomorg/atp-provenance": "0.17.14",
|
|
35
|
+
"zod": "^3.23.8"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"typescript": "^5.3.3",
|
|
39
|
+
"vitest": "^1.2.1"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/auth.ts
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication and credential management types for Agent Tool Protocol
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Supported authentication schemes
|
|
7
|
+
*/
|
|
8
|
+
export type AuthScheme = 'apiKey' | 'bearer' | 'basic' | 'oauth2' | 'custom' | 'composite';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Base authentication configuration
|
|
12
|
+
*/
|
|
13
|
+
export interface BaseAuthConfig {
|
|
14
|
+
scheme: AuthScheme;
|
|
15
|
+
/** Environment variable name to read credentials from */
|
|
16
|
+
envVar?: string;
|
|
17
|
+
/** Direct credential value (not recommended for production) */
|
|
18
|
+
value?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Credential source: 'server' for server-level env vars (default), 'user' for user-scoped OAuth
|
|
21
|
+
*/
|
|
22
|
+
source?: 'server' | 'user';
|
|
23
|
+
/**
|
|
24
|
+
* OAuth provider name for user-scoped credentials (e.g., 'github', 'google')
|
|
25
|
+
* Required when source='user'. Used to look up user's OAuth token from AuthProvider.
|
|
26
|
+
* Note: This is different from the 'provider' field which is for runtime credential providers.
|
|
27
|
+
*/
|
|
28
|
+
oauthProvider?: string;
|
|
29
|
+
/** Runtime credential provider function name */
|
|
30
|
+
provider?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* API Key authentication (in header or query param)
|
|
35
|
+
*/
|
|
36
|
+
export interface APIKeyAuthConfig extends BaseAuthConfig {
|
|
37
|
+
scheme: 'apiKey';
|
|
38
|
+
/** Where to send the API key */
|
|
39
|
+
in: 'header' | 'query';
|
|
40
|
+
/** Parameter/header name */
|
|
41
|
+
name: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Bearer token authentication
|
|
46
|
+
*/
|
|
47
|
+
export interface BearerAuthConfig extends BaseAuthConfig {
|
|
48
|
+
scheme: 'bearer';
|
|
49
|
+
/** Optional bearer format (e.g., 'JWT') */
|
|
50
|
+
bearerFormat?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* HTTP Basic authentication
|
|
55
|
+
*/
|
|
56
|
+
export interface BasicAuthConfig extends BaseAuthConfig {
|
|
57
|
+
scheme: 'basic';
|
|
58
|
+
/** Username (can use envVar for dynamic value) */
|
|
59
|
+
username?: string;
|
|
60
|
+
/** Username environment variable */
|
|
61
|
+
usernameEnvVar?: string;
|
|
62
|
+
/** Password environment variable */
|
|
63
|
+
passwordEnvVar?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* OAuth2 authentication with automatic token refresh
|
|
68
|
+
*/
|
|
69
|
+
export interface OAuth2AuthConfig extends BaseAuthConfig {
|
|
70
|
+
scheme: 'oauth2';
|
|
71
|
+
/** OAuth2 flow type */
|
|
72
|
+
flow: 'clientCredentials' | 'authorizationCode' | 'implicit' | 'password';
|
|
73
|
+
/** Token endpoint URL */
|
|
74
|
+
tokenUrl: string;
|
|
75
|
+
/** Authorization endpoint (for authorizationCode/implicit) */
|
|
76
|
+
authorizationUrl?: string;
|
|
77
|
+
/** Client ID */
|
|
78
|
+
clientId?: string;
|
|
79
|
+
/** Client ID environment variable */
|
|
80
|
+
clientIdEnvVar?: string;
|
|
81
|
+
/** Client secret environment variable */
|
|
82
|
+
clientSecretEnvVar?: string;
|
|
83
|
+
/** Scopes required */
|
|
84
|
+
scopes?: string[];
|
|
85
|
+
/** Refresh token environment variable (for token refresh) */
|
|
86
|
+
refreshTokenEnvVar?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Custom authentication with arbitrary headers
|
|
91
|
+
*/
|
|
92
|
+
export interface CustomAuthConfig extends BaseAuthConfig {
|
|
93
|
+
scheme: 'custom';
|
|
94
|
+
/** Custom headers to inject */
|
|
95
|
+
headers: Record<string, string>;
|
|
96
|
+
/** Environment variables to use for header values */
|
|
97
|
+
headerEnvVars?: Record<string, string>;
|
|
98
|
+
/** Query parameters to inject */
|
|
99
|
+
queryParams?: Record<string, string>;
|
|
100
|
+
/** Environment variables to use for query parameter values */
|
|
101
|
+
queryParamEnvVars?: Record<string, string>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Composite authentication - combines multiple auth mechanisms
|
|
106
|
+
* Useful for APIs that require multiple credentials (e.g., projectId + apiKey + secret)
|
|
107
|
+
*/
|
|
108
|
+
export interface CompositeAuthConfig extends BaseAuthConfig {
|
|
109
|
+
scheme: 'composite';
|
|
110
|
+
/**
|
|
111
|
+
* Multiple credentials to combine
|
|
112
|
+
* Example: { projectId: { envVar: 'PROJECT_ID' }, apiKey: { envVar: 'API_KEY' }, secret: { envVar: 'API_SECRET' } }
|
|
113
|
+
*/
|
|
114
|
+
credentials: Record<string, CredentialConfig>;
|
|
115
|
+
/** How to inject credentials: 'header', 'query', or 'both' */
|
|
116
|
+
injectAs?: 'header' | 'query' | 'both';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Individual credential configuration for composite auth
|
|
121
|
+
*/
|
|
122
|
+
export interface CredentialConfig {
|
|
123
|
+
/** Environment variable to read from */
|
|
124
|
+
envVar?: string;
|
|
125
|
+
/** Direct value (not recommended) */
|
|
126
|
+
value?: string;
|
|
127
|
+
/** Header name if injecting as header */
|
|
128
|
+
headerName?: string;
|
|
129
|
+
/** Query param name if injecting as query */
|
|
130
|
+
queryParamName?: string;
|
|
131
|
+
/** Whether this credential is required */
|
|
132
|
+
required?: boolean;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Union type of all auth configurations
|
|
137
|
+
*/
|
|
138
|
+
export type AuthConfig =
|
|
139
|
+
| APIKeyAuthConfig
|
|
140
|
+
| BearerAuthConfig
|
|
141
|
+
| BasicAuthConfig
|
|
142
|
+
| OAuth2AuthConfig
|
|
143
|
+
| CustomAuthConfig
|
|
144
|
+
| CompositeAuthConfig;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Runtime credential provider
|
|
148
|
+
* Allows dynamic credential resolution at runtime
|
|
149
|
+
*/
|
|
150
|
+
export interface CredentialProvider {
|
|
151
|
+
name: string;
|
|
152
|
+
/** Resolves credentials dynamically */
|
|
153
|
+
resolve: () => Promise<Credentials> | Credentials;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Resolved credentials ready to be injected into requests
|
|
158
|
+
*/
|
|
159
|
+
export interface Credentials {
|
|
160
|
+
headers?: Record<string, string>;
|
|
161
|
+
queryParams?: Record<string, string>;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Credential resolver - resolves auth config to actual credentials
|
|
166
|
+
*/
|
|
167
|
+
export class CredentialResolver {
|
|
168
|
+
private providers: Map<string, CredentialProvider> = new Map();
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Registers a runtime credential provider
|
|
172
|
+
*/
|
|
173
|
+
registerProvider(provider: CredentialProvider): void {
|
|
174
|
+
this.providers.set(provider.name, provider);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Resolves auth configuration to credentials
|
|
179
|
+
*/
|
|
180
|
+
async resolve(authConfig: AuthConfig): Promise<Credentials> {
|
|
181
|
+
if (authConfig.provider) {
|
|
182
|
+
const provider = this.providers.get(authConfig.provider);
|
|
183
|
+
if (!provider) {
|
|
184
|
+
throw new Error(`Credential provider '${authConfig.provider}' not found`);
|
|
185
|
+
}
|
|
186
|
+
return await provider.resolve();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
switch (authConfig.scheme) {
|
|
190
|
+
case 'apiKey':
|
|
191
|
+
return this.resolveAPIKey(authConfig);
|
|
192
|
+
case 'bearer':
|
|
193
|
+
return this.resolveBearer(authConfig);
|
|
194
|
+
case 'basic':
|
|
195
|
+
return this.resolveBasic(authConfig);
|
|
196
|
+
case 'oauth2':
|
|
197
|
+
return this.resolveOAuth2(authConfig);
|
|
198
|
+
case 'custom':
|
|
199
|
+
return this.resolveCustom(authConfig);
|
|
200
|
+
case 'composite':
|
|
201
|
+
return this.resolveComposite(authConfig);
|
|
202
|
+
default:
|
|
203
|
+
throw new Error(`Unsupported auth scheme: ${(authConfig as any).scheme}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private resolveAPIKey(config: APIKeyAuthConfig): Credentials {
|
|
208
|
+
const value = this.getValue(config);
|
|
209
|
+
if (!value) {
|
|
210
|
+
throw new Error(`API key not provided for '${config.name}'`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (config.in === 'header') {
|
|
214
|
+
return { headers: { [config.name]: value } };
|
|
215
|
+
} else {
|
|
216
|
+
return { queryParams: { [config.name]: value } };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private resolveBearer(config: BearerAuthConfig): Credentials {
|
|
221
|
+
const token = this.getValue(config);
|
|
222
|
+
if (!token) {
|
|
223
|
+
throw new Error('Bearer token not provided');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
headers: {
|
|
228
|
+
Authorization: `Bearer ${token}`,
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private resolveBasic(config: BasicAuthConfig): Credentials {
|
|
234
|
+
const username = config.usernameEnvVar ? process.env[config.usernameEnvVar] : config.username;
|
|
235
|
+
const password = config.passwordEnvVar
|
|
236
|
+
? process.env[config.passwordEnvVar]
|
|
237
|
+
: this.getValue(config);
|
|
238
|
+
|
|
239
|
+
if (!username || !password) {
|
|
240
|
+
throw new Error('Basic auth username and password not provided');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const credentials = Buffer.from(`${username}:${password}`).toString('base64');
|
|
244
|
+
return {
|
|
245
|
+
headers: {
|
|
246
|
+
Authorization: `Basic ${credentials}`,
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private async resolveOAuth2(config: OAuth2AuthConfig): Promise<Credentials> {
|
|
252
|
+
const clientId = config.clientIdEnvVar ? process.env[config.clientIdEnvVar] : config.clientId;
|
|
253
|
+
const clientSecret = config.clientSecretEnvVar
|
|
254
|
+
? process.env[config.clientSecretEnvVar]
|
|
255
|
+
: undefined;
|
|
256
|
+
|
|
257
|
+
if (!clientId || !clientSecret) {
|
|
258
|
+
throw new Error('OAuth2 client credentials not provided');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (config.flow === 'clientCredentials') {
|
|
262
|
+
const token = await this.fetchOAuth2Token(
|
|
263
|
+
config.tokenUrl,
|
|
264
|
+
clientId,
|
|
265
|
+
clientSecret,
|
|
266
|
+
config.scopes
|
|
267
|
+
);
|
|
268
|
+
return {
|
|
269
|
+
headers: {
|
|
270
|
+
Authorization: `Bearer ${token}`,
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const token = this.getValue(config);
|
|
276
|
+
if (token) {
|
|
277
|
+
return {
|
|
278
|
+
headers: {
|
|
279
|
+
Authorization: `Bearer ${token}`,
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
throw new Error(`OAuth2 flow '${config.flow}' requires manual token setup`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private resolveCustom(config: CustomAuthConfig): Credentials {
|
|
288
|
+
const headers: Record<string, string> = {};
|
|
289
|
+
const queryParams: Record<string, string> = {};
|
|
290
|
+
|
|
291
|
+
Object.assign(headers, config.headers);
|
|
292
|
+
|
|
293
|
+
if (config.headerEnvVars) {
|
|
294
|
+
for (const [headerName, envVar] of Object.entries(config.headerEnvVars)) {
|
|
295
|
+
const value = process.env[envVar];
|
|
296
|
+
if (value) {
|
|
297
|
+
headers[headerName] = value;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (config.queryParams) {
|
|
303
|
+
Object.assign(queryParams, config.queryParams);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (config.queryParamEnvVars) {
|
|
307
|
+
for (const [paramName, envVar] of Object.entries(config.queryParamEnvVars)) {
|
|
308
|
+
const value = process.env[envVar];
|
|
309
|
+
if (value) {
|
|
310
|
+
queryParams[paramName] = value;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
317
|
+
queryParams: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private resolveComposite(config: CompositeAuthConfig): Credentials {
|
|
322
|
+
const headers: Record<string, string> = {};
|
|
323
|
+
const queryParams: Record<string, string> = {};
|
|
324
|
+
|
|
325
|
+
for (const [credName, credConfig] of Object.entries(config.credentials)) {
|
|
326
|
+
const value = credConfig.envVar ? process.env[credConfig.envVar] : credConfig.value;
|
|
327
|
+
|
|
328
|
+
if (!value) {
|
|
329
|
+
if (credConfig.required !== false) {
|
|
330
|
+
throw new Error(`Required credential '${credName}' not provided`);
|
|
331
|
+
}
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const injectAs = config.injectAs || 'header';
|
|
336
|
+
|
|
337
|
+
if ((injectAs === 'header' || injectAs === 'both') && credConfig.headerName) {
|
|
338
|
+
headers[credConfig.headerName] = value;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if ((injectAs === 'query' || injectAs === 'both') && credConfig.queryParamName) {
|
|
342
|
+
queryParams[credConfig.queryParamName] = value;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (!credConfig.headerName && !credConfig.queryParamName) {
|
|
346
|
+
if (injectAs === 'query' || injectAs === 'both') {
|
|
347
|
+
queryParams[credName] = value;
|
|
348
|
+
} else {
|
|
349
|
+
headers[`X-${credName}`] = value;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
356
|
+
queryParams: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Gets credential value from config (env var or direct value)
|
|
362
|
+
*/
|
|
363
|
+
private getValue(config: BaseAuthConfig): string | undefined {
|
|
364
|
+
if (config.envVar) {
|
|
365
|
+
return process.env[config.envVar];
|
|
366
|
+
}
|
|
367
|
+
return config.value;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Fetches OAuth2 token using client credentials flow
|
|
372
|
+
*/
|
|
373
|
+
private async fetchOAuth2Token(
|
|
374
|
+
tokenUrl: string,
|
|
375
|
+
clientId: string,
|
|
376
|
+
clientSecret: string,
|
|
377
|
+
scopes?: string[]
|
|
378
|
+
): Promise<string> {
|
|
379
|
+
const params = new URLSearchParams({
|
|
380
|
+
grant_type: 'client_credentials',
|
|
381
|
+
client_id: clientId,
|
|
382
|
+
client_secret: clientSecret,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
if (scopes && scopes.length > 0) {
|
|
386
|
+
params.append('scope', scopes.join(' '));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const response = await fetch(tokenUrl, {
|
|
390
|
+
method: 'POST',
|
|
391
|
+
headers: {
|
|
392
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
393
|
+
},
|
|
394
|
+
body: params.toString(),
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
if (!response.ok) {
|
|
398
|
+
throw new Error(`OAuth2 token fetch failed: ${response.statusText}`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const data = (await response.json()) as { access_token: string };
|
|
402
|
+
return data.access_token;
|
|
403
|
+
}
|
|
404
|
+
}
|
package/src/index.ts
ADDED
package/src/oauth.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth and scope checking interfaces for Agent Tool Protocol
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Scope checker interface
|
|
7
|
+
* Checks what OAuth scopes a token has for a given provider
|
|
8
|
+
*/
|
|
9
|
+
export interface ScopeChecker {
|
|
10
|
+
/** Provider name (e.g., 'github', 'google', 'microsoft') */
|
|
11
|
+
provider: string;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check what scopes a token has
|
|
15
|
+
* @param token - Access token to check
|
|
16
|
+
* @returns Array of scope strings (e.g., ['repo', 'read:user'])
|
|
17
|
+
*/
|
|
18
|
+
check(token: string): Promise<string[]>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Validate if a token is still valid (optional)
|
|
22
|
+
* @param token - Access token to validate
|
|
23
|
+
* @returns true if valid, false if expired/revoked
|
|
24
|
+
*/
|
|
25
|
+
validate?(token: string): Promise<boolean>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Token information returned by providers
|
|
30
|
+
*/
|
|
31
|
+
export interface TokenInfo {
|
|
32
|
+
/** Whether the token is valid */
|
|
33
|
+
valid: boolean;
|
|
34
|
+
|
|
35
|
+
/** OAuth scopes the token has */
|
|
36
|
+
scopes: string[];
|
|
37
|
+
|
|
38
|
+
/** Token expiration timestamp (milliseconds since epoch) */
|
|
39
|
+
expiresAt?: number;
|
|
40
|
+
|
|
41
|
+
/** User identifier from the provider */
|
|
42
|
+
userId?: string;
|
|
43
|
+
|
|
44
|
+
/** Additional provider-specific data */
|
|
45
|
+
metadata?: Record<string, unknown>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Scope filtering configuration
|
|
50
|
+
*/
|
|
51
|
+
export interface ScopeFilteringConfig {
|
|
52
|
+
/** Enable scope-based filtering */
|
|
53
|
+
enabled: boolean;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Filtering mode:
|
|
57
|
+
* - 'eager': Filter tools at /api/definitions based on user's scopes
|
|
58
|
+
* - 'lazy': Return all tools, validate scopes only at execution time
|
|
59
|
+
*/
|
|
60
|
+
mode: 'eager' | 'lazy';
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Cache TTL for scope checks in seconds
|
|
64
|
+
* Default: 3600 (1 hour)
|
|
65
|
+
*/
|
|
66
|
+
cacheTTL?: number;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Fail behavior when scope checker not available for a provider
|
|
70
|
+
* - 'allow': Allow all tools (no filtering)
|
|
71
|
+
* - 'deny': Hide all tools requiring scopes
|
|
72
|
+
*/
|
|
73
|
+
fallback?: 'allow' | 'deny';
|
|
74
|
+
}
|