workos 0.10.1 → 0.11.0
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 +20 -1
- package/dist/bin.js +108 -20
- package/dist/bin.js.map +1 -1
- package/dist/commands/claim.d.ts +11 -0
- package/dist/commands/claim.js +111 -0
- package/dist/commands/claim.js.map +1 -0
- package/dist/commands/debug.d.ts +16 -0
- package/dist/commands/debug.js +350 -0
- package/dist/commands/debug.js.map +1 -0
- package/dist/commands/env.js +11 -4
- package/dist/commands/env.js.map +1 -1
- package/dist/lib/__test-helpers__/mock-unclaimed-env-api-error.d.ts +7 -0
- package/dist/lib/__test-helpers__/mock-unclaimed-env-api-error.js +12 -0
- package/dist/lib/__test-helpers__/mock-unclaimed-env-api-error.js.map +1 -0
- package/dist/lib/agent-interface.js +17 -3
- package/dist/lib/agent-interface.js.map +1 -1
- package/dist/lib/config-store.d.ts +26 -3
- package/dist/lib/config-store.js +82 -0
- package/dist/lib/config-store.js.map +1 -1
- package/dist/lib/credential-proxy.d.ts +10 -1
- package/dist/lib/credential-proxy.js +96 -38
- package/dist/lib/credential-proxy.js.map +1 -1
- package/dist/lib/env-writer.d.ts +1 -0
- package/dist/lib/env-writer.js.map +1 -1
- package/dist/lib/installer-core.d.ts +3 -3
- package/dist/lib/resolve-install-credentials.d.ts +11 -0
- package/dist/lib/resolve-install-credentials.js +53 -0
- package/dist/lib/resolve-install-credentials.js.map +1 -0
- package/dist/lib/run-with-core.js +5 -0
- package/dist/lib/run-with-core.js.map +1 -1
- package/dist/lib/unclaimed-env-api.d.ts +42 -0
- package/dist/lib/unclaimed-env-api.js +139 -0
- package/dist/lib/unclaimed-env-api.js.map +1 -0
- package/dist/lib/unclaimed-env-provision.d.ts +24 -0
- package/dist/lib/unclaimed-env-provision.js +77 -0
- package/dist/lib/unclaimed-env-provision.js.map +1 -0
- package/dist/lib/unclaimed-warning.d.ts +15 -0
- package/dist/lib/unclaimed-warning.js +74 -0
- package/dist/lib/unclaimed-warning.js.map +1 -0
- package/dist/utils/box.d.ts +5 -0
- package/dist/utils/box.js +14 -0
- package/dist/utils/box.js.map +1 -0
- package/dist/utils/help-json.js +15 -0
- package/dist/utils/help-json.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Lightweight HTTP proxy that injects credentials
|
|
2
|
+
* Lightweight HTTP proxy that injects credentials into upstream requests.
|
|
3
3
|
* Includes lazy token refresh - refreshes proactively when token is expiring soon.
|
|
4
4
|
*/
|
|
5
5
|
export interface RefreshConfig {
|
|
@@ -34,3 +34,12 @@ export interface CredentialProxyHandle {
|
|
|
34
34
|
* Start the credential injector proxy with optional lazy refresh.
|
|
35
35
|
*/
|
|
36
36
|
export declare function startCredentialProxy(options: CredentialProxyOptions): Promise<CredentialProxyHandle>;
|
|
37
|
+
/**
|
|
38
|
+
* Start a lightweight proxy that injects claim token headers for unclaimed environments.
|
|
39
|
+
* No refresh logic — claim tokens are assumed valid for the duration of an install session.
|
|
40
|
+
*/
|
|
41
|
+
export declare function startClaimTokenProxy(options: {
|
|
42
|
+
upstreamUrl: string;
|
|
43
|
+
claimToken: string;
|
|
44
|
+
clientId: string;
|
|
45
|
+
}): Promise<CredentialProxyHandle>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Lightweight HTTP proxy that injects credentials
|
|
2
|
+
* Lightweight HTTP proxy that injects credentials into upstream requests.
|
|
3
3
|
* Includes lazy token refresh - refreshes proactively when token is expiring soon.
|
|
4
4
|
*/
|
|
5
5
|
import http from 'node:http';
|
|
@@ -9,6 +9,38 @@ import { logInfo, logError, logWarn } from '../utils/debug.js';
|
|
|
9
9
|
import { getCredentials, updateTokens } from './credentials.js';
|
|
10
10
|
import { analytics } from '../utils/analytics.js';
|
|
11
11
|
import { refreshAccessToken } from './token-refresh-client.js';
|
|
12
|
+
// Hop-by-hop headers that must not be forwarded by proxies (RFC 7230 §6.1)
|
|
13
|
+
const HOP_BY_HOP_HEADERS = new Set([
|
|
14
|
+
'connection',
|
|
15
|
+
'keep-alive',
|
|
16
|
+
'proxy-authenticate',
|
|
17
|
+
'proxy-authorization',
|
|
18
|
+
'te',
|
|
19
|
+
'trailer',
|
|
20
|
+
'transfer-encoding',
|
|
21
|
+
'upgrade',
|
|
22
|
+
]);
|
|
23
|
+
/** Copy headers, excluding hop-by-hop headers */
|
|
24
|
+
function filterHeaders(headers) {
|
|
25
|
+
const out = {};
|
|
26
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
27
|
+
if (!HOP_BY_HOP_HEADERS.has(key.toLowerCase()) && value !== undefined) {
|
|
28
|
+
out[key] = value;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
/** Build the upstream path, stripping the `beta` query param (unsupported by WorkOS LLM gateway) */
|
|
34
|
+
function buildUpstreamPath(reqUrl, upstream) {
|
|
35
|
+
const requestPath = reqUrl || '/';
|
|
36
|
+
const basePath = upstream.pathname.replace(/\/$/, '');
|
|
37
|
+
const fullPath = basePath + requestPath;
|
|
38
|
+
const upstreamUrl = new URL(fullPath, upstream.origin);
|
|
39
|
+
const searchParams = new URLSearchParams(upstreamUrl.search);
|
|
40
|
+
searchParams.delete('beta');
|
|
41
|
+
const queryString = searchParams.toString();
|
|
42
|
+
return upstreamUrl.pathname + (queryString ? `?${queryString}` : '');
|
|
43
|
+
}
|
|
12
44
|
// Module-level state for lazy refresh
|
|
13
45
|
let refreshPromise = null;
|
|
14
46
|
let refreshConfig = null;
|
|
@@ -178,37 +210,11 @@ async function handleRequest(req, res, upstream, useHttps, thresholdMs) {
|
|
|
178
210
|
}));
|
|
179
211
|
return;
|
|
180
212
|
}
|
|
181
|
-
// Build upstream request
|
|
182
|
-
|
|
183
|
-
const requestPath = req.url || '/';
|
|
184
|
-
const basePath = upstream.pathname.replace(/\/$/, ''); // Remove trailing slash
|
|
185
|
-
const fullPath = basePath + requestPath;
|
|
186
|
-
const upstreamUrl = new URL(fullPath, upstream.origin);
|
|
187
|
-
const headers = {};
|
|
188
|
-
// Copy headers, excluding hop-by-hop headers
|
|
189
|
-
const hopByHop = new Set([
|
|
190
|
-
'connection',
|
|
191
|
-
'keep-alive',
|
|
192
|
-
'proxy-authenticate',
|
|
193
|
-
'proxy-authorization',
|
|
194
|
-
'te',
|
|
195
|
-
'trailer',
|
|
196
|
-
'transfer-encoding',
|
|
197
|
-
'upgrade',
|
|
198
|
-
]);
|
|
199
|
-
for (const [key, value] of Object.entries(req.headers)) {
|
|
200
|
-
if (!hopByHop.has(key.toLowerCase()) && value !== undefined) {
|
|
201
|
-
headers[key] = value;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
// Inject credentials
|
|
213
|
+
// Build upstream request
|
|
214
|
+
const headers = filterHeaders(req.headers);
|
|
205
215
|
headers['authorization'] = `Bearer ${creds.accessToken}`;
|
|
206
216
|
headers['host'] = upstream.host;
|
|
207
|
-
|
|
208
|
-
const searchParams = new URLSearchParams(upstreamUrl.search);
|
|
209
|
-
searchParams.delete('beta');
|
|
210
|
-
const queryString = searchParams.toString();
|
|
211
|
-
const finalPath = upstreamUrl.pathname + (queryString ? `?${queryString}` : '');
|
|
217
|
+
const finalPath = buildUpstreamPath(req.url, upstream);
|
|
212
218
|
const requestOptions = {
|
|
213
219
|
hostname: upstream.hostname,
|
|
214
220
|
port: upstream.port || (useHttps ? 443 : 80),
|
|
@@ -219,14 +225,7 @@ async function handleRequest(req, res, upstream, useHttps, thresholdMs) {
|
|
|
219
225
|
};
|
|
220
226
|
const transport = useHttps ? https : http;
|
|
221
227
|
const proxyReq = transport.request(requestOptions, (proxyRes) => {
|
|
222
|
-
|
|
223
|
-
const responseHeaders = {};
|
|
224
|
-
for (const [key, value] of Object.entries(proxyRes.headers)) {
|
|
225
|
-
if (!hopByHop.has(key.toLowerCase()) && value !== undefined) {
|
|
226
|
-
responseHeaders[key] = value;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
res.writeHead(proxyRes.statusCode || 500, responseHeaders);
|
|
228
|
+
res.writeHead(proxyRes.statusCode || 500, filterHeaders(proxyRes.headers));
|
|
230
229
|
proxyRes.pipe(res);
|
|
231
230
|
});
|
|
232
231
|
proxyReq.on('error', (err) => {
|
|
@@ -268,6 +267,65 @@ async function handleRequest(req, res, upstream, useHttps, thresholdMs) {
|
|
|
268
267
|
// Stream request body
|
|
269
268
|
req.pipe(proxyReq);
|
|
270
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* Start a lightweight proxy that injects claim token headers for unclaimed environments.
|
|
272
|
+
* No refresh logic — claim tokens are assumed valid for the duration of an install session.
|
|
273
|
+
*/
|
|
274
|
+
export async function startClaimTokenProxy(options) {
|
|
275
|
+
const upstream = new URL(options.upstreamUrl);
|
|
276
|
+
const useHttps = upstream.protocol === 'https:';
|
|
277
|
+
const server = http.createServer(async (req, res) => {
|
|
278
|
+
const headers = filterHeaders(req.headers);
|
|
279
|
+
headers['x-workos-claim-token'] = options.claimToken;
|
|
280
|
+
headers['x-workos-client-id'] = options.clientId;
|
|
281
|
+
headers['host'] = upstream.host;
|
|
282
|
+
const finalPath = buildUpstreamPath(req.url, upstream);
|
|
283
|
+
const transport = useHttps ? https : http;
|
|
284
|
+
const proxyReq = transport.request({
|
|
285
|
+
hostname: upstream.hostname,
|
|
286
|
+
port: upstream.port || (useHttps ? 443 : 80),
|
|
287
|
+
path: finalPath,
|
|
288
|
+
method: req.method,
|
|
289
|
+
headers,
|
|
290
|
+
timeout: 120_000,
|
|
291
|
+
}, (proxyRes) => {
|
|
292
|
+
res.writeHead(proxyRes.statusCode || 500, filterHeaders(proxyRes.headers));
|
|
293
|
+
proxyRes.pipe(res);
|
|
294
|
+
});
|
|
295
|
+
proxyReq.on('error', (err) => {
|
|
296
|
+
logError('[claim-token-proxy] Upstream error:', err.message);
|
|
297
|
+
if (!res.headersSent) {
|
|
298
|
+
res.writeHead(502, { 'Content-Type': 'application/json' });
|
|
299
|
+
res.end(JSON.stringify({ error: 'proxy_error', message: err.message }));
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
proxyReq.on('timeout', () => {
|
|
303
|
+
proxyReq.destroy();
|
|
304
|
+
if (!res.headersSent) {
|
|
305
|
+
res.writeHead(504, { 'Content-Type': 'application/json' });
|
|
306
|
+
res.end(JSON.stringify({ error: 'upstream_timeout', message: 'Upstream server timed out' }));
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
req.pipe(proxyReq);
|
|
310
|
+
});
|
|
311
|
+
const port = await new Promise((resolve, reject) => {
|
|
312
|
+
server.once('error', (err) => reject(err));
|
|
313
|
+
server.listen(0, '127.0.0.1', () => {
|
|
314
|
+
const addr = server.address();
|
|
315
|
+
if (addr && typeof addr === 'object')
|
|
316
|
+
resolve(addr.port);
|
|
317
|
+
else
|
|
318
|
+
reject(new Error('Failed to get server address'));
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
const url = `http://127.0.0.1:${port}`;
|
|
322
|
+
logInfo(`[claim-token-proxy] Started on ${url}, forwarding to ${options.upstreamUrl}`);
|
|
323
|
+
return {
|
|
324
|
+
port,
|
|
325
|
+
url,
|
|
326
|
+
stop: async () => stopServer(server),
|
|
327
|
+
};
|
|
328
|
+
}
|
|
271
329
|
function stopServer(server) {
|
|
272
330
|
return new Promise((resolve, reject) => {
|
|
273
331
|
// Set a timeout for graceful shutdown
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential-proxy.js","sourceRoot":"","sources":["../../src/lib/credential-proxy.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Lightweight HTTP proxy that injects credentials from file into requests.\n * Includes lazy token refresh - refreshes proactively when token is expiring soon.\n */\n\nimport http from 'node:http';\nimport https from 'node:https';\nimport { URL } from 'node:url';\nimport { logInfo, logError, logWarn } from '../utils/debug.js';\nimport { getCredentials, updateTokens, type Credentials } from './credentials.js';\nimport { analytics } from '../utils/analytics.js';\nimport { refreshAccessToken } from './token-refresh-client.js';\n\nexport interface RefreshConfig {\n /** AuthKit domain for refresh endpoint */\n authkitDomain: string;\n /** OAuth client ID */\n clientId: string;\n /** Threshold in ms - refresh when token expires within this window (default: 60000 = 1 min) */\n refreshThresholdMs?: number;\n /** Callback when refresh succeeds */\n onRefreshSuccess?: () => void;\n /** Callback when refresh fails permanently (token expired, invalid_grant) */\n onRefreshExpired?: () => void;\n}\n\nexport interface CredentialProxyOptions {\n /** Upstream URL to forward requests to */\n upstreamUrl: string;\n /** Optional: specific port to bind (default: random) */\n port?: number;\n /** Optional: refresh configuration for lazy token refresh */\n refresh?: RefreshConfig;\n}\n\nexport interface CredentialProxyHandle {\n /** Port the proxy is listening on */\n port: number;\n /** Full URL for the proxy (e.g., http://localhost:54321) */\n url: string;\n /** Stop the proxy server */\n stop: () => Promise<void>;\n}\n\n// Module-level state for lazy refresh\nlet refreshPromise: Promise<void> | null = null;\nlet refreshConfig: RefreshConfig | null = null;\nlet consecutiveFailures = 0;\nconst MAX_CONSECUTIVE_FAILURES = 3;\n\n/**\n * Perform token refresh, updating credentials file.\n * Returns true if refresh succeeded.\n */\nasync function doRefresh(): Promise<boolean> {\n if (!refreshConfig) {\n logError('[credential-proxy] No refresh config available');\n return false;\n }\n\n const { authkitDomain, clientId, onRefreshSuccess, onRefreshExpired } = refreshConfig;\n const startTime = Date.now();\n\n logInfo('[credential-proxy] Starting token refresh...');\n\n analytics.capture('installer.token.refresh', {\n action: 'refresh_attempt',\n trigger: 'lazy',\n });\n\n const result = await refreshAccessToken(authkitDomain, clientId);\n\n if (result.success && result.accessToken && result.expiresAt) {\n // Update credentials file atomically\n updateTokens(result.accessToken, result.expiresAt, result.refreshToken);\n\n consecutiveFailures = 0;\n const durationMs = Date.now() - startTime;\n\n logInfo(\n `[credential-proxy] Token refreshed in ${durationMs}ms, expires: ${new Date(result.expiresAt).toISOString()}`,\n );\n\n analytics.capture('installer.token.refresh', {\n action: 'refresh_success',\n duration_ms: durationMs,\n token_rotated: !!result.refreshToken,\n });\n\n onRefreshSuccess?.();\n return true;\n }\n\n consecutiveFailures++;\n\n logError(`[credential-proxy] Refresh failed: ${result.error}`);\n\n analytics.capture('installer.token.refresh', {\n action: 'refresh_failure',\n error_type: result.errorType || 'unknown',\n error_message: result.error || 'Unknown error',\n consecutive_failures: consecutiveFailures,\n });\n\n // Handle permanent failure\n if (result.errorType === 'invalid_grant' || consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {\n logError('[credential-proxy] Refresh token expired or too many failures');\n onRefreshExpired?.();\n }\n\n return false;\n}\n\n/**\n * Ensure we have valid credentials, refreshing if needed.\n * Uses a promise-based lock to prevent concurrent refreshes.\n *\n * @returns Credentials to use for request, or null if unavailable\n */\nasync function ensureValidCredentials(thresholdMs: number): Promise<Credentials | null> {\n const creds = getCredentials();\n\n if (!creds?.accessToken) {\n return null;\n }\n\n // No refresh token = can't refresh, just use what we have\n if (!creds.refreshToken || !refreshConfig) {\n return creds;\n }\n\n const timeUntilExpiry = creds.expiresAt - Date.now();\n\n if (timeUntilExpiry <= 0) {\n // Token expired - must wait for refresh\n logWarn('[credential-proxy] Token expired, waiting for refresh...');\n\n if (!refreshPromise) {\n refreshPromise = doRefresh()\n .then(() => {})\n .finally(() => {\n refreshPromise = null;\n });\n }\n\n await refreshPromise;\n return getCredentials(); // Return fresh credentials\n }\n\n if (timeUntilExpiry < thresholdMs) {\n // Token expiring soon - trigger background refresh, but use current token\n logInfo(`[credential-proxy] Token expires in ${Math.round(timeUntilExpiry / 1000)}s, triggering refresh`);\n\n if (!refreshPromise) {\n refreshPromise = doRefresh()\n .then(() => {})\n .finally(() => {\n refreshPromise = null;\n });\n }\n // Don't await - fire and forget, use current (still valid) token\n }\n\n return creds;\n}\n\n/**\n * Start the credential injector proxy with optional lazy refresh.\n */\nexport async function startCredentialProxy(options: CredentialProxyOptions): Promise<CredentialProxyHandle> {\n const upstream = new URL(options.upstreamUrl);\n const useHttps = upstream.protocol === 'https:';\n const thresholdMs = options.refresh?.refreshThresholdMs ?? 60_000;\n\n // Store refresh config for lazy refresh\n refreshConfig = options.refresh ?? null;\n consecutiveFailures = 0;\n\n const server = http.createServer(async (req, res) => {\n await handleRequest(req, res, upstream, useHttps, thresholdMs);\n });\n\n // Find available port\n const port = await new Promise<number>((resolve, reject) => {\n const tryPort = options.port ?? 0; // 0 = random available port\n let attempts = 0;\n const maxAttempts = 10;\n\n const tryListen = (p: number) => {\n server.once('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE' && attempts < maxAttempts) {\n attempts++;\n tryListen(0); // Try random port\n } else {\n reject(err);\n }\n });\n\n server.listen(p, '127.0.0.1', () => {\n const addr = server.address();\n if (addr && typeof addr === 'object') {\n resolve(addr.port);\n } else {\n reject(new Error('Failed to get server address'));\n }\n });\n };\n\n tryListen(tryPort);\n });\n\n const url = `http://127.0.0.1:${port}`;\n logInfo(`[credential-proxy] Started on ${url}, forwarding to ${options.upstreamUrl}`);\n if (refreshConfig) {\n logInfo(`[credential-proxy] Lazy refresh enabled, threshold: ${thresholdMs}ms`);\n }\n\n // Telemetry for proxy start\n analytics.capture('installer.proxy', {\n action: 'start',\n port,\n refresh_enabled: !!refreshConfig,\n });\n\n return {\n port,\n url,\n stop: async () => {\n // Clear refresh state\n refreshConfig = null;\n refreshPromise = null;\n consecutiveFailures = 0;\n await stopServer(server);\n },\n };\n}\n\nasync function handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n upstream: URL,\n useHttps: boolean,\n thresholdMs: number,\n): Promise<void> {\n // Get valid credentials, potentially triggering refresh\n const creds = await ensureValidCredentials(thresholdMs);\n\n if (!creds?.accessToken) {\n logError('[credential-proxy] No credentials available');\n res.writeHead(401, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n error: 'credentials_unavailable',\n message: 'Not authenticated. Run `workos auth login` first.',\n }),\n );\n return;\n }\n\n // Build upstream request options\n // Concatenate paths properly - URL() would replace the base path with absolute paths\n const requestPath = req.url || '/';\n const basePath = upstream.pathname.replace(/\\/$/, ''); // Remove trailing slash\n const fullPath = basePath + requestPath;\n const upstreamUrl = new URL(fullPath, upstream.origin);\n\n const headers: http.OutgoingHttpHeaders = {};\n\n // Copy headers, excluding hop-by-hop headers\n const hopByHop = new Set([\n 'connection',\n 'keep-alive',\n 'proxy-authenticate',\n 'proxy-authorization',\n 'te',\n 'trailer',\n 'transfer-encoding',\n 'upgrade',\n ]);\n\n for (const [key, value] of Object.entries(req.headers)) {\n if (!hopByHop.has(key.toLowerCase()) && value !== undefined) {\n headers[key] = value;\n }\n }\n\n // Inject credentials\n headers['authorization'] = `Bearer ${creds.accessToken}`;\n headers['host'] = upstream.host;\n\n // Strip beta=true query param - WorkOS LLM gateway doesn't support it\n const searchParams = new URLSearchParams(upstreamUrl.search);\n searchParams.delete('beta');\n const queryString = searchParams.toString();\n const finalPath = upstreamUrl.pathname + (queryString ? `?${queryString}` : '');\n\n const requestOptions: http.RequestOptions = {\n hostname: upstream.hostname,\n port: upstream.port || (useHttps ? 443 : 80),\n path: finalPath,\n method: req.method,\n headers,\n timeout: 120_000, // 2 minute timeout\n };\n\n const transport = useHttps ? https : http;\n\n const proxyReq = transport.request(requestOptions, (proxyRes) => {\n // Copy response headers\n const responseHeaders: http.OutgoingHttpHeaders = {};\n for (const [key, value] of Object.entries(proxyRes.headers)) {\n if (!hopByHop.has(key.toLowerCase()) && value !== undefined) {\n responseHeaders[key] = value;\n }\n }\n\n res.writeHead(proxyRes.statusCode || 500, responseHeaders);\n proxyRes.pipe(res);\n });\n\n proxyReq.on('error', (err) => {\n logError('[credential-proxy] Upstream error:', err.message);\n\n if (!res.headersSent) {\n if ((err as NodeJS.ErrnoException).code === 'ECONNREFUSED') {\n res.writeHead(502, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n error: 'upstream_unavailable',\n message: 'Could not connect to upstream server',\n }),\n );\n } else if ((err as NodeJS.ErrnoException).code === 'ETIMEDOUT') {\n res.writeHead(504, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n error: 'upstream_timeout',\n message: 'Upstream server timed out',\n }),\n );\n } else {\n res.writeHead(502, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n error: 'proxy_error',\n message: err.message,\n }),\n );\n }\n }\n });\n\n proxyReq.on('timeout', () => {\n proxyReq.destroy();\n if (!res.headersSent) {\n res.writeHead(504, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n error: 'upstream_timeout',\n message: 'Upstream server timed out',\n }),\n );\n }\n });\n\n // Stream request body\n req.pipe(proxyReq);\n}\n\nfunction stopServer(server: http.Server): Promise<void> {\n return new Promise((resolve, reject) => {\n // Set a timeout for graceful shutdown\n const timeout = setTimeout(() => {\n logInfo('[credential-proxy] Force closing after timeout');\n server.closeAllConnections?.();\n resolve();\n }, 5000);\n\n server.close((err) => {\n clearTimeout(timeout);\n if (err) {\n logError('[credential-proxy] Error stopping server:', err);\n reject(err);\n } else {\n logInfo('[credential-proxy] Stopped');\n resolve();\n }\n });\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"credential-proxy.js","sourceRoot":"","sources":["../../src/lib/credential-proxy.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Lightweight HTTP proxy that injects credentials into upstream requests.\n * Includes lazy token refresh - refreshes proactively when token is expiring soon.\n */\n\nimport http from 'node:http';\nimport https from 'node:https';\nimport { URL } from 'node:url';\nimport { logInfo, logError, logWarn } from '../utils/debug.js';\nimport { getCredentials, updateTokens, type Credentials } from './credentials.js';\nimport { analytics } from '../utils/analytics.js';\nimport { refreshAccessToken } from './token-refresh-client.js';\n\nexport interface RefreshConfig {\n /** AuthKit domain for refresh endpoint */\n authkitDomain: string;\n /** OAuth client ID */\n clientId: string;\n /** Threshold in ms - refresh when token expires within this window (default: 60000 = 1 min) */\n refreshThresholdMs?: number;\n /** Callback when refresh succeeds */\n onRefreshSuccess?: () => void;\n /** Callback when refresh fails permanently (token expired, invalid_grant) */\n onRefreshExpired?: () => void;\n}\n\nexport interface CredentialProxyOptions {\n /** Upstream URL to forward requests to */\n upstreamUrl: string;\n /** Optional: specific port to bind (default: random) */\n port?: number;\n /** Optional: refresh configuration for lazy token refresh */\n refresh?: RefreshConfig;\n}\n\nexport interface CredentialProxyHandle {\n /** Port the proxy is listening on */\n port: number;\n /** Full URL for the proxy (e.g., http://localhost:54321) */\n url: string;\n /** Stop the proxy server */\n stop: () => Promise<void>;\n}\n\n// Hop-by-hop headers that must not be forwarded by proxies (RFC 7230 §6.1)\nconst HOP_BY_HOP_HEADERS = new Set([\n 'connection',\n 'keep-alive',\n 'proxy-authenticate',\n 'proxy-authorization',\n 'te',\n 'trailer',\n 'transfer-encoding',\n 'upgrade',\n]);\n\n/** Copy headers, excluding hop-by-hop headers */\nfunction filterHeaders(headers: Record<string, string | string[] | undefined>): http.OutgoingHttpHeaders {\n const out: http.OutgoingHttpHeaders = {};\n for (const [key, value] of Object.entries(headers)) {\n if (!HOP_BY_HOP_HEADERS.has(key.toLowerCase()) && value !== undefined) {\n out[key] = value;\n }\n }\n return out;\n}\n\n/** Build the upstream path, stripping the `beta` query param (unsupported by WorkOS LLM gateway) */\nfunction buildUpstreamPath(reqUrl: string | undefined, upstream: URL): string {\n const requestPath = reqUrl || '/';\n const basePath = upstream.pathname.replace(/\\/$/, '');\n const fullPath = basePath + requestPath;\n const upstreamUrl = new URL(fullPath, upstream.origin);\n const searchParams = new URLSearchParams(upstreamUrl.search);\n searchParams.delete('beta');\n const queryString = searchParams.toString();\n return upstreamUrl.pathname + (queryString ? `?${queryString}` : '');\n}\n\n// Module-level state for lazy refresh\nlet refreshPromise: Promise<void> | null = null;\nlet refreshConfig: RefreshConfig | null = null;\nlet consecutiveFailures = 0;\nconst MAX_CONSECUTIVE_FAILURES = 3;\n\n/**\n * Perform token refresh, updating credentials file.\n * Returns true if refresh succeeded.\n */\nasync function doRefresh(): Promise<boolean> {\n if (!refreshConfig) {\n logError('[credential-proxy] No refresh config available');\n return false;\n }\n\n const { authkitDomain, clientId, onRefreshSuccess, onRefreshExpired } = refreshConfig;\n const startTime = Date.now();\n\n logInfo('[credential-proxy] Starting token refresh...');\n\n analytics.capture('installer.token.refresh', {\n action: 'refresh_attempt',\n trigger: 'lazy',\n });\n\n const result = await refreshAccessToken(authkitDomain, clientId);\n\n if (result.success && result.accessToken && result.expiresAt) {\n // Update credentials file atomically\n updateTokens(result.accessToken, result.expiresAt, result.refreshToken);\n\n consecutiveFailures = 0;\n const durationMs = Date.now() - startTime;\n\n logInfo(\n `[credential-proxy] Token refreshed in ${durationMs}ms, expires: ${new Date(result.expiresAt).toISOString()}`,\n );\n\n analytics.capture('installer.token.refresh', {\n action: 'refresh_success',\n duration_ms: durationMs,\n token_rotated: !!result.refreshToken,\n });\n\n onRefreshSuccess?.();\n return true;\n }\n\n consecutiveFailures++;\n\n logError(`[credential-proxy] Refresh failed: ${result.error}`);\n\n analytics.capture('installer.token.refresh', {\n action: 'refresh_failure',\n error_type: result.errorType || 'unknown',\n error_message: result.error || 'Unknown error',\n consecutive_failures: consecutiveFailures,\n });\n\n // Handle permanent failure\n if (result.errorType === 'invalid_grant' || consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {\n logError('[credential-proxy] Refresh token expired or too many failures');\n onRefreshExpired?.();\n }\n\n return false;\n}\n\n/**\n * Ensure we have valid credentials, refreshing if needed.\n * Uses a promise-based lock to prevent concurrent refreshes.\n *\n * @returns Credentials to use for request, or null if unavailable\n */\nasync function ensureValidCredentials(thresholdMs: number): Promise<Credentials | null> {\n const creds = getCredentials();\n\n if (!creds?.accessToken) {\n return null;\n }\n\n // No refresh token = can't refresh, just use what we have\n if (!creds.refreshToken || !refreshConfig) {\n return creds;\n }\n\n const timeUntilExpiry = creds.expiresAt - Date.now();\n\n if (timeUntilExpiry <= 0) {\n // Token expired - must wait for refresh\n logWarn('[credential-proxy] Token expired, waiting for refresh...');\n\n if (!refreshPromise) {\n refreshPromise = doRefresh()\n .then(() => {})\n .finally(() => {\n refreshPromise = null;\n });\n }\n\n await refreshPromise;\n return getCredentials(); // Return fresh credentials\n }\n\n if (timeUntilExpiry < thresholdMs) {\n // Token expiring soon - trigger background refresh, but use current token\n logInfo(`[credential-proxy] Token expires in ${Math.round(timeUntilExpiry / 1000)}s, triggering refresh`);\n\n if (!refreshPromise) {\n refreshPromise = doRefresh()\n .then(() => {})\n .finally(() => {\n refreshPromise = null;\n });\n }\n // Don't await - fire and forget, use current (still valid) token\n }\n\n return creds;\n}\n\n/**\n * Start the credential injector proxy with optional lazy refresh.\n */\nexport async function startCredentialProxy(options: CredentialProxyOptions): Promise<CredentialProxyHandle> {\n const upstream = new URL(options.upstreamUrl);\n const useHttps = upstream.protocol === 'https:';\n const thresholdMs = options.refresh?.refreshThresholdMs ?? 60_000;\n\n // Store refresh config for lazy refresh\n refreshConfig = options.refresh ?? null;\n consecutiveFailures = 0;\n\n const server = http.createServer(async (req, res) => {\n await handleRequest(req, res, upstream, useHttps, thresholdMs);\n });\n\n // Find available port\n const port = await new Promise<number>((resolve, reject) => {\n const tryPort = options.port ?? 0; // 0 = random available port\n let attempts = 0;\n const maxAttempts = 10;\n\n const tryListen = (p: number) => {\n server.once('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE' && attempts < maxAttempts) {\n attempts++;\n tryListen(0); // Try random port\n } else {\n reject(err);\n }\n });\n\n server.listen(p, '127.0.0.1', () => {\n const addr = server.address();\n if (addr && typeof addr === 'object') {\n resolve(addr.port);\n } else {\n reject(new Error('Failed to get server address'));\n }\n });\n };\n\n tryListen(tryPort);\n });\n\n const url = `http://127.0.0.1:${port}`;\n logInfo(`[credential-proxy] Started on ${url}, forwarding to ${options.upstreamUrl}`);\n if (refreshConfig) {\n logInfo(`[credential-proxy] Lazy refresh enabled, threshold: ${thresholdMs}ms`);\n }\n\n // Telemetry for proxy start\n analytics.capture('installer.proxy', {\n action: 'start',\n port,\n refresh_enabled: !!refreshConfig,\n });\n\n return {\n port,\n url,\n stop: async () => {\n // Clear refresh state\n refreshConfig = null;\n refreshPromise = null;\n consecutiveFailures = 0;\n await stopServer(server);\n },\n };\n}\n\nasync function handleRequest(\n req: http.IncomingMessage,\n res: http.ServerResponse,\n upstream: URL,\n useHttps: boolean,\n thresholdMs: number,\n): Promise<void> {\n // Get valid credentials, potentially triggering refresh\n const creds = await ensureValidCredentials(thresholdMs);\n\n if (!creds?.accessToken) {\n logError('[credential-proxy] No credentials available');\n res.writeHead(401, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n error: 'credentials_unavailable',\n message: 'Not authenticated. Run `workos auth login` first.',\n }),\n );\n return;\n }\n\n // Build upstream request\n const headers = filterHeaders(req.headers);\n headers['authorization'] = `Bearer ${creds.accessToken}`;\n headers['host'] = upstream.host;\n const finalPath = buildUpstreamPath(req.url, upstream);\n\n const requestOptions: http.RequestOptions = {\n hostname: upstream.hostname,\n port: upstream.port || (useHttps ? 443 : 80),\n path: finalPath,\n method: req.method,\n headers,\n timeout: 120_000, // 2 minute timeout\n };\n\n const transport = useHttps ? https : http;\n\n const proxyReq = transport.request(requestOptions, (proxyRes) => {\n res.writeHead(proxyRes.statusCode || 500, filterHeaders(proxyRes.headers));\n proxyRes.pipe(res);\n });\n\n proxyReq.on('error', (err) => {\n logError('[credential-proxy] Upstream error:', err.message);\n\n if (!res.headersSent) {\n if ((err as NodeJS.ErrnoException).code === 'ECONNREFUSED') {\n res.writeHead(502, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n error: 'upstream_unavailable',\n message: 'Could not connect to upstream server',\n }),\n );\n } else if ((err as NodeJS.ErrnoException).code === 'ETIMEDOUT') {\n res.writeHead(504, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n error: 'upstream_timeout',\n message: 'Upstream server timed out',\n }),\n );\n } else {\n res.writeHead(502, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n error: 'proxy_error',\n message: err.message,\n }),\n );\n }\n }\n });\n\n proxyReq.on('timeout', () => {\n proxyReq.destroy();\n if (!res.headersSent) {\n res.writeHead(504, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n error: 'upstream_timeout',\n message: 'Upstream server timed out',\n }),\n );\n }\n });\n\n // Stream request body\n req.pipe(proxyReq);\n}\n\n/**\n * Start a lightweight proxy that injects claim token headers for unclaimed environments.\n * No refresh logic — claim tokens are assumed valid for the duration of an install session.\n */\nexport async function startClaimTokenProxy(options: {\n upstreamUrl: string;\n claimToken: string;\n clientId: string;\n}): Promise<CredentialProxyHandle> {\n const upstream = new URL(options.upstreamUrl);\n const useHttps = upstream.protocol === 'https:';\n\n const server = http.createServer(async (req, res) => {\n const headers = filterHeaders(req.headers);\n headers['x-workos-claim-token'] = options.claimToken;\n headers['x-workos-client-id'] = options.clientId;\n headers['host'] = upstream.host;\n const finalPath = buildUpstreamPath(req.url, upstream);\n\n const transport = useHttps ? https : http;\n\n const proxyReq = transport.request(\n {\n hostname: upstream.hostname,\n port: upstream.port || (useHttps ? 443 : 80),\n path: finalPath,\n method: req.method,\n headers,\n timeout: 120_000,\n },\n (proxyRes) => {\n res.writeHead(proxyRes.statusCode || 500, filterHeaders(proxyRes.headers));\n proxyRes.pipe(res);\n },\n );\n\n proxyReq.on('error', (err) => {\n logError('[claim-token-proxy] Upstream error:', err.message);\n if (!res.headersSent) {\n res.writeHead(502, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'proxy_error', message: err.message }));\n }\n });\n\n proxyReq.on('timeout', () => {\n proxyReq.destroy();\n if (!res.headersSent) {\n res.writeHead(504, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'upstream_timeout', message: 'Upstream server timed out' }));\n }\n });\n\n req.pipe(proxyReq);\n });\n\n const port = await new Promise<number>((resolve, reject) => {\n server.once('error', (err) => reject(err));\n server.listen(0, '127.0.0.1', () => {\n const addr = server.address();\n if (addr && typeof addr === 'object') resolve(addr.port);\n else reject(new Error('Failed to get server address'));\n });\n });\n\n const url = `http://127.0.0.1:${port}`;\n logInfo(`[claim-token-proxy] Started on ${url}, forwarding to ${options.upstreamUrl}`);\n\n return {\n port,\n url,\n stop: async () => stopServer(server),\n };\n}\n\nfunction stopServer(server: http.Server): Promise<void> {\n return new Promise((resolve, reject) => {\n // Set a timeout for graceful shutdown\n const timeout = setTimeout(() => {\n logInfo('[credential-proxy] Force closing after timeout');\n server.closeAllConnections?.();\n resolve();\n }, 5000);\n\n server.close((err) => {\n clearTimeout(timeout);\n if (err) {\n logError('[credential-proxy] Error stopping server:', err);\n reject(err);\n } else {\n logInfo('[credential-proxy] Stopped');\n resolve();\n }\n });\n });\n}\n"]}
|
package/dist/lib/env-writer.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-writer.js","sourceRoot":"","sources":["../../src/lib/env-writer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEtD,MAAM,2BAA2B,GAAG,CAAC,YAAY,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC;AAE3E;;;;GAIG;AACH,SAAS,eAAe,CAAC,UAAkB;IACzC,MAAM,aAAa,GAAG,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAErD,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAC/B,aAAa,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;QAC7C,OAAO;IACT,CAAC;IAED,MAAM,OAAO,GAAG,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IACrD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAE7D,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,2BAA2B,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QACrE,OAAO;IACT,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACrD,aAAa,CAAC,aAAa,EAAE,GAAG,OAAO,GAAG,SAAS,cAAc,CAAC,CAAC;AACrE,CAAC;
|
|
1
|
+
{"version":3,"file":"env-writer.js","sourceRoot":"","sources":["../../src/lib/env-writer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEtD,MAAM,2BAA2B,GAAG,CAAC,YAAY,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC;AAE3E;;;;GAIG;AACH,SAAS,eAAe,CAAC,UAAkB;IACzC,MAAM,aAAa,GAAG,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAErD,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAC/B,aAAa,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;QAC7C,OAAO;IACT,CAAC;IAED,MAAM,OAAO,GAAG,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IACrD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAE7D,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,2BAA2B,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QACrE,OAAO;IACT,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACrD,aAAa,CAAC,aAAa,EAAE,GAAG,OAAO,GAAG,SAAS,cAAc,CAAC,CAAC;AACrE,CAAC;AAWD;;;;GAIG;AACH,SAAS,sBAAsB;IAC7B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IACjC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC9B,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAClF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,UAAkB,EAAE,OAAyB;IACzE,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAE/C,+BAA+B;IAC/B,IAAI,WAAW,GAA2B,EAAE,CAAC;IAC7C,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC/C,WAAW,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC;IAED,iDAAiD;IACjD,MAAM,MAAM,GAAG,EAAE,GAAG,WAAW,EAAE,GAAG,OAAO,EAAE,CAAC;IAE9C,2CAA2C;IAC3C,IAAI,CAAC,MAAM,CAAC,sBAAsB,EAAE,CAAC;QACnC,MAAM,CAAC,sBAAsB,GAAG,sBAAsB,EAAE,CAAC;IAC3D,CAAC;IAED,aAAa;IACb,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;SACnC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC;SACxC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,eAAe,CAAC,UAAU,CAAC,CAAC;IAE5B,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC,CAAC;AACzC,CAAC","sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { parseEnvFile } from '../utils/env-parser.js';\n\nconst ENV_LOCAL_COVERING_PATTERNS = ['.env.local', '.env*.local', '.env*'];\n\n/**\n * Ensure .env.local is in .gitignore.\n * Creates .gitignore if it doesn't exist.\n * No-ops if a covering pattern is already present.\n */\nfunction ensureGitignore(installDir: string): void {\n const gitignorePath = join(installDir, '.gitignore');\n\n if (!existsSync(gitignorePath)) {\n writeFileSync(gitignorePath, '.env.local\\n');\n return;\n }\n\n const content = readFileSync(gitignorePath, 'utf-8');\n const lines = content.split('\\n').map((line) => line.trim());\n\n if (lines.some((line) => ENV_LOCAL_COVERING_PATTERNS.includes(line))) {\n return;\n }\n\n const separator = content.endsWith('\\n') ? '' : '\\n';\n writeFileSync(gitignorePath, `${content}${separator}.env.local\\n`);\n}\n\ninterface EnvVars {\n WORKOS_API_KEY?: string;\n WORKOS_CLIENT_ID: string;\n WORKOS_REDIRECT_URI?: string;\n NEXT_PUBLIC_WORKOS_REDIRECT_URI?: string;\n WORKOS_COOKIE_PASSWORD?: string;\n WORKOS_CLAIM_TOKEN?: string;\n}\n\n/**\n * Generate a cryptographically secure cookie password.\n * Returns 32-char hex string (16 random bytes).\n * Uses Web Crypto API available in Node.js 20+\n */\nfunction generateCookiePassword(): string {\n const array = new Uint8Array(16);\n crypto.getRandomValues(array);\n return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\n/**\n * Write environment variables to .env.local before agent runs.\n * Merges with existing .env.local if present (new vars take precedence).\n * Auto-generates WORKOS_COOKIE_PASSWORD if not provided.\n */\nexport function writeEnvLocal(installDir: string, envVars: Partial<EnvVars>): void {\n const envPath = join(installDir, '.env.local');\n\n // Read existing env if present\n let existingEnv: Record<string, string> = {};\n if (existsSync(envPath)) {\n const content = readFileSync(envPath, 'utf-8');\n existingEnv = parseEnvFile(content);\n }\n\n // Merge with new vars (new vars take precedence)\n const merged = { ...existingEnv, ...envVars };\n\n // Generate cookie password if not provided\n if (!merged.WORKOS_COOKIE_PASSWORD) {\n merged.WORKOS_COOKIE_PASSWORD = generateCookiePassword();\n }\n\n // Write back\n const content = Object.entries(merged)\n .map(([key, value]) => `${key}=${value}`)\n .join('\\n');\n\n ensureGitignore(installDir);\n\n writeFileSync(envPath, content + '\\n');\n}\n"]}
|
|
@@ -399,13 +399,13 @@ export declare const installerMachine: import("xstate").StateMachine<InstallerMa
|
|
|
399
399
|
type: "emitComplete";
|
|
400
400
|
params: unknown;
|
|
401
401
|
}, {
|
|
402
|
-
type: "
|
|
402
|
+
type: "hasCredentials";
|
|
403
403
|
params: unknown;
|
|
404
404
|
} | {
|
|
405
|
-
type: "
|
|
405
|
+
type: "shouldSkipAuth";
|
|
406
406
|
params: unknown;
|
|
407
407
|
} | {
|
|
408
|
-
type: "
|
|
408
|
+
type: "gitIsClean";
|
|
409
409
|
params: unknown;
|
|
410
410
|
} | {
|
|
411
411
|
type: "hasIntegration";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve credentials for install flow.
|
|
3
|
+
* Priority: existing creds (env var, --api-key, active env) -> unclaimed env provisioning -> login fallback.
|
|
4
|
+
*
|
|
5
|
+
* The installer needs both API credentials (for WorkOS API calls) AND gateway auth
|
|
6
|
+
* (for the LLM agent). This function ensures both are available:
|
|
7
|
+
* - Unclaimed env: API key + claim token (claim token proxy handles gateway)
|
|
8
|
+
* - Logged-in user: API key + OAuth token (credential proxy handles gateway)
|
|
9
|
+
* - Direct mode: not handled here (resolved in agent-interface.ts via ANTHROPIC_API_KEY)
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveInstallCredentials(apiKey: string | undefined, installDir: string | undefined, skipAuth: boolean | undefined, authenticate: () => Promise<unknown>): Promise<void>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve credentials for install flow.
|
|
3
|
+
* Priority: existing creds (env var, --api-key, active env) -> unclaimed env provisioning -> login fallback.
|
|
4
|
+
*
|
|
5
|
+
* The installer needs both API credentials (for WorkOS API calls) AND gateway auth
|
|
6
|
+
* (for the LLM agent). This function ensures both are available:
|
|
7
|
+
* - Unclaimed env: API key + claim token (claim token proxy handles gateway)
|
|
8
|
+
* - Logged-in user: API key + OAuth token (credential proxy handles gateway)
|
|
9
|
+
* - Direct mode: not handled here (resolved in agent-interface.ts via ANTHROPIC_API_KEY)
|
|
10
|
+
*/
|
|
11
|
+
export async function resolveInstallCredentials(apiKey, installDir, skipAuth, authenticate) {
|
|
12
|
+
// Explicit API key from env var or flag — user handles gateway auth separately
|
|
13
|
+
const envApiKey = process.env.WORKOS_API_KEY;
|
|
14
|
+
if (envApiKey)
|
|
15
|
+
return;
|
|
16
|
+
if (apiKey)
|
|
17
|
+
return;
|
|
18
|
+
try {
|
|
19
|
+
const { getActiveEnvironment, isUnclaimedEnvironment } = await import('./config-store.js');
|
|
20
|
+
const { hasCredentials } = await import('./credentials.js');
|
|
21
|
+
const activeEnv = getActiveEnvironment();
|
|
22
|
+
if (activeEnv?.apiKey) {
|
|
23
|
+
// Has API key — but does it have gateway auth?
|
|
24
|
+
if (isUnclaimedEnvironment(activeEnv)) {
|
|
25
|
+
// Unclaimed with claim token — claim token proxy will handle gateway
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (hasCredentials()) {
|
|
29
|
+
// Has OAuth tokens — credential proxy will handle gateway
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// Has API key but no gateway auth — need to log in
|
|
33
|
+
if (!skipAuth)
|
|
34
|
+
await authenticate();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// No existing credentials — try unclaimed env provisioning
|
|
38
|
+
const { tryProvisionUnclaimedEnv } = await import('./unclaimed-env-provision.js');
|
|
39
|
+
const dir = installDir ?? process.cwd();
|
|
40
|
+
const provisioned = await tryProvisionUnclaimedEnv({ installDir: dir });
|
|
41
|
+
if (!provisioned) {
|
|
42
|
+
// Unclaimed env provisioning failed — fall back to login
|
|
43
|
+
if (!skipAuth)
|
|
44
|
+
await authenticate();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
const { logError } = await import('../utils/debug.js');
|
|
49
|
+
logError('[resolve-install-credentials] Failed:', error instanceof Error ? error.message : String(error));
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=resolve-install-credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-install-credentials.js","sourceRoot":"","sources":["../../src/lib/resolve-install-credentials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,MAA0B,EAC1B,UAA8B,EAC9B,QAA6B,EAC7B,YAAoC;IAEpC,+EAA+E;IAC/E,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAC7C,IAAI,SAAS;QAAE,OAAO;IACtB,IAAI,MAAM;QAAE,OAAO;IAEnB,IAAI,CAAC;QACH,MAAM,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;QAC3F,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;QAC5D,MAAM,SAAS,GAAG,oBAAoB,EAAE,CAAC;QAEzC,IAAI,SAAS,EAAE,MAAM,EAAE,CAAC;YACtB,+CAA+C;YAC/C,IAAI,sBAAsB,CAAC,SAAS,CAAC,EAAE,CAAC;gBACtC,qEAAqE;gBACrE,OAAO;YACT,CAAC;YACD,IAAI,cAAc,EAAE,EAAE,CAAC;gBACrB,0DAA0D;gBAC1D,OAAO;YACT,CAAC;YACD,mDAAmD;YACnD,IAAI,CAAC,QAAQ;gBAAE,MAAM,YAAY,EAAE,CAAC;YACpC,OAAO;QACT,CAAC;QAED,2DAA2D;QAC3D,MAAM,EAAE,wBAAwB,EAAE,GAAG,MAAM,MAAM,CAAC,8BAA8B,CAAC,CAAC;QAClF,MAAM,GAAG,GAAG,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QACxC,MAAM,WAAW,GAAG,MAAM,wBAAwB,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;QACxE,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,yDAAyD;YACzD,IAAI,CAAC,QAAQ;gBAAE,MAAM,YAAY,EAAE,CAAC;QACtC,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;QACvD,QAAQ,CAAC,uCAAuC,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1G,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["/**\n * Resolve credentials for install flow.\n * Priority: existing creds (env var, --api-key, active env) -> unclaimed env provisioning -> login fallback.\n *\n * The installer needs both API credentials (for WorkOS API calls) AND gateway auth\n * (for the LLM agent). This function ensures both are available:\n * - Unclaimed env: API key + claim token (claim token proxy handles gateway)\n * - Logged-in user: API key + OAuth token (credential proxy handles gateway)\n * - Direct mode: not handled here (resolved in agent-interface.ts via ANTHROPIC_API_KEY)\n */\nexport async function resolveInstallCredentials(\n apiKey: string | undefined,\n installDir: string | undefined,\n skipAuth: boolean | undefined,\n authenticate: () => Promise<unknown>,\n): Promise<void> {\n // Explicit API key from env var or flag — user handles gateway auth separately\n const envApiKey = process.env.WORKOS_API_KEY;\n if (envApiKey) return;\n if (apiKey) return;\n\n try {\n const { getActiveEnvironment, isUnclaimedEnvironment } = await import('./config-store.js');\n const { hasCredentials } = await import('./credentials.js');\n const activeEnv = getActiveEnvironment();\n\n if (activeEnv?.apiKey) {\n // Has API key — but does it have gateway auth?\n if (isUnclaimedEnvironment(activeEnv)) {\n // Unclaimed with claim token — claim token proxy will handle gateway\n return;\n }\n if (hasCredentials()) {\n // Has OAuth tokens — credential proxy will handle gateway\n return;\n }\n // Has API key but no gateway auth — need to log in\n if (!skipAuth) await authenticate();\n return;\n }\n\n // No existing credentials — try unclaimed env provisioning\n const { tryProvisionUnclaimedEnv } = await import('./unclaimed-env-provision.js');\n const dir = installDir ?? process.cwd();\n const provisioned = await tryProvisionUnclaimedEnv({ installDir: dir });\n if (!provisioned) {\n // Unclaimed env provisioning failed — fall back to login\n if (!skipAuth) await authenticate();\n }\n } catch (error) {\n const { logError } = await import('../utils/debug.js');\n logError('[resolve-install-credentials] Failed:', error instanceof Error ? error.message : String(error));\n throw error;\n }\n}\n"]}
|
|
@@ -175,6 +175,11 @@ export async function runWithCore(options) {
|
|
|
175
175
|
const machineWithActors = installerMachine.provide({
|
|
176
176
|
actors: {
|
|
177
177
|
checkAuthentication: fromPromise(async () => {
|
|
178
|
+
// Check for active environment with credentials (covers unclaimed environments)
|
|
179
|
+
const activeEnv = getActiveEnvironment();
|
|
180
|
+
if (activeEnv?.clientId && activeEnv?.apiKey) {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
178
183
|
const token = getAccessToken();
|
|
179
184
|
if (!token) {
|
|
180
185
|
// This should rarely happen since bin.ts handles auth first
|