offrouter-core 0.2.1 → 0.2.2

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 (139) hide show
  1. package/dist/audit.d.ts +137 -0
  2. package/dist/audit.d.ts.map +1 -0
  3. package/dist/audit.js +320 -0
  4. package/dist/audit.js.map +1 -0
  5. package/dist/auth/credential-chain.d.ts +84 -0
  6. package/dist/auth/credential-chain.d.ts.map +1 -0
  7. package/dist/auth/credential-chain.js +150 -0
  8. package/dist/auth/credential-chain.js.map +1 -0
  9. package/dist/auth/index.d.ts +10 -0
  10. package/dist/auth/index.d.ts.map +1 -0
  11. package/dist/auth/index.js +7 -0
  12. package/dist/auth/index.js.map +1 -0
  13. package/dist/auth/keychain.d.ts +60 -0
  14. package/dist/auth/keychain.d.ts.map +1 -0
  15. package/dist/auth/keychain.js +104 -0
  16. package/dist/auth/keychain.js.map +1 -0
  17. package/dist/auth/oauth-pkce.d.ts +138 -0
  18. package/dist/auth/oauth-pkce.d.ts.map +1 -0
  19. package/dist/auth/oauth-pkce.js +375 -0
  20. package/dist/auth/oauth-pkce.js.map +1 -0
  21. package/dist/auth/oauth-server.d.ts +36 -0
  22. package/dist/auth/oauth-server.d.ts.map +1 -0
  23. package/dist/auth/oauth-server.js +210 -0
  24. package/dist/auth/oauth-server.js.map +1 -0
  25. package/dist/config.d.ts +76 -0
  26. package/dist/config.d.ts.map +1 -0
  27. package/dist/config.js +355 -0
  28. package/dist/config.js.map +1 -0
  29. package/dist/delegation.d.ts +123 -0
  30. package/dist/delegation.d.ts.map +1 -0
  31. package/dist/delegation.js +455 -0
  32. package/dist/delegation.js.map +1 -0
  33. package/dist/index.d.ts +41 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +26 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/policy.d.ts +45 -0
  38. package/dist/policy.d.ts.map +1 -0
  39. package/dist/policy.js +318 -0
  40. package/dist/policy.js.map +1 -0
  41. package/{src/protocol.ts → dist/protocol.d.ts} +2 -2
  42. package/dist/protocol.d.ts.map +1 -0
  43. package/dist/protocol.js +6 -0
  44. package/dist/protocol.js.map +1 -0
  45. package/dist/providers/adapter.d.ts +137 -0
  46. package/dist/providers/adapter.d.ts.map +1 -0
  47. package/dist/providers/adapter.js +163 -0
  48. package/dist/providers/adapter.js.map +1 -0
  49. package/dist/providers/catalog-data.d.ts +128 -0
  50. package/dist/providers/catalog-data.d.ts.map +1 -0
  51. package/dist/providers/catalog-data.js +397 -0
  52. package/dist/providers/catalog-data.js.map +1 -0
  53. package/dist/providers/catalog.d.ts +63 -0
  54. package/dist/providers/catalog.d.ts.map +1 -0
  55. package/dist/providers/catalog.js +394 -0
  56. package/dist/providers/catalog.js.map +1 -0
  57. package/dist/providers/fake.d.ts +46 -0
  58. package/dist/providers/fake.d.ts.map +1 -0
  59. package/dist/providers/fake.js +234 -0
  60. package/dist/providers/fake.js.map +1 -0
  61. package/dist/providers/openai-compatible.d.ts +65 -0
  62. package/dist/providers/openai-compatible.d.ts.map +1 -0
  63. package/dist/providers/openai-compatible.js +434 -0
  64. package/dist/providers/openai-compatible.js.map +1 -0
  65. package/dist/proxy/responses-mapper.d.ts +366 -0
  66. package/dist/proxy/responses-mapper.d.ts.map +1 -0
  67. package/dist/proxy/responses-mapper.js +517 -0
  68. package/dist/proxy/responses-mapper.js.map +1 -0
  69. package/dist/proxy/responses-server.d.ts +29 -0
  70. package/dist/proxy/responses-server.d.ts.map +1 -0
  71. package/dist/proxy/responses-server.js +360 -0
  72. package/dist/proxy/responses-server.js.map +1 -0
  73. package/dist/router.d.ts +18 -0
  74. package/dist/router.d.ts.map +1 -0
  75. package/dist/router.js +296 -0
  76. package/dist/router.js.map +1 -0
  77. package/dist/schemas.d.ts +560 -0
  78. package/dist/schemas.d.ts.map +1 -0
  79. package/{src/schemas.ts → dist/schemas.js} +83 -103
  80. package/dist/schemas.js.map +1 -0
  81. package/dist/secrets.d.ts +112 -0
  82. package/dist/secrets.d.ts.map +1 -0
  83. package/dist/secrets.js +326 -0
  84. package/dist/secrets.js.map +1 -0
  85. package/dist/types.d.ts +117 -0
  86. package/dist/types.d.ts.map +1 -0
  87. package/dist/types.js +6 -0
  88. package/dist/types.js.map +1 -0
  89. package/dist/usage-file.d.ts +59 -0
  90. package/dist/usage-file.d.ts.map +1 -0
  91. package/dist/usage-file.js +316 -0
  92. package/dist/usage-file.js.map +1 -0
  93. package/dist/usage.d.ts +235 -0
  94. package/dist/usage.d.ts.map +1 -0
  95. package/dist/usage.js +517 -0
  96. package/dist/usage.js.map +1 -0
  97. package/package.json +7 -2
  98. package/src/audit.test.ts +0 -302
  99. package/src/audit.ts +0 -553
  100. package/src/auth/credential-chain.test.ts +0 -326
  101. package/src/auth/credential-chain.ts +0 -235
  102. package/src/auth/index.ts +0 -45
  103. package/src/auth/keychain.test.ts +0 -265
  104. package/src/auth/keychain.ts +0 -168
  105. package/src/auth/oauth-pkce.test.ts +0 -329
  106. package/src/auth/oauth-pkce.ts +0 -571
  107. package/src/auth/oauth-server.test.ts +0 -83
  108. package/src/auth/oauth-server.ts +0 -296
  109. package/src/config.test.ts +0 -479
  110. package/src/config.ts +0 -505
  111. package/src/delegation.test.ts +0 -368
  112. package/src/delegation.ts +0 -605
  113. package/src/index.ts +0 -280
  114. package/src/policy.test.ts +0 -634
  115. package/src/policy.ts +0 -420
  116. package/src/providers/adapter.test.ts +0 -293
  117. package/src/providers/adapter.ts +0 -328
  118. package/src/providers/catalog-data.test.ts +0 -258
  119. package/src/providers/catalog-data.ts +0 -498
  120. package/src/providers/catalog.test.ts +0 -84
  121. package/src/providers/catalog.ts +0 -483
  122. package/src/providers/fake.ts +0 -312
  123. package/src/providers/openai-compatible.test.ts +0 -366
  124. package/src/providers/openai-compatible.ts +0 -554
  125. package/src/proxy/responses-mapper.test.ts +0 -290
  126. package/src/proxy/responses-mapper.ts +0 -736
  127. package/src/proxy/responses-server.test.ts +0 -322
  128. package/src/proxy/responses-server.ts +0 -469
  129. package/src/router.test.ts +0 -699
  130. package/src/router.ts +0 -352
  131. package/src/schemas.test.ts +0 -291
  132. package/src/secrets.test.ts +0 -271
  133. package/src/secrets.ts +0 -461
  134. package/src/types.ts +0 -173
  135. package/src/usage-file.test.ts +0 -243
  136. package/src/usage-file.ts +0 -435
  137. package/src/usage.test.ts +0 -335
  138. package/src/usage.ts +0 -859
  139. package/tsconfig.json +0 -9
@@ -0,0 +1,375 @@
1
+ /**
2
+ * OAuth PKCE flow for OffRouter.
3
+ *
4
+ * Supports Anthropic and OpenAI/Codex only (opt-in, bring-your-own-client-id).
5
+ * Gemini/xAI/Z.ai are API-key only and do NOT implement OAuth.
6
+ *
7
+ * Never auto-triggers OAuth. Requires explicit user configuration.
8
+ * Tokens are stored in the SecretStore (keychain or file fallback).
9
+ */
10
+ import { randomBytes } from "node:crypto";
11
+ import { Redacted } from "./credential-chain.js";
12
+ import { setOAuthTokens } from "./keychain.js";
13
+ import { startCallbackServer } from "./oauth-server.js";
14
+ // ---------------------------------------------------------------------------
15
+ // PKCE code generation
16
+ // ---------------------------------------------------------------------------
17
+ const VERIFIER_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
18
+ /**
19
+ * Generate a random code verifier for PKCE (RFC 7636).
20
+ * Length is between 43 and 128 characters.
21
+ */
22
+ export function generateCodeVerifier() {
23
+ const length = 64 + (randomBytes(1)[0] % 32); // 64-95 chars
24
+ const bytes = randomBytes(length);
25
+ let verifier = "";
26
+ for (let i = 0; i < length; i++) {
27
+ verifier += VERIFIER_CHARS[bytes[i] % VERIFIER_CHARS.length];
28
+ }
29
+ return verifier;
30
+ }
31
+ /**
32
+ * Generate a S256 code challenge from a verifier (RFC 7636).
33
+ * Uses Web Crypto API (available in Node >= 22, Workers, browsers).
34
+ */
35
+ export async function generateCodeChallenge(verifier) {
36
+ const encoder = new TextEncoder();
37
+ const data = encoder.encode(verifier);
38
+ const digest = await crypto.subtle.digest("SHA-256", data);
39
+ const base64 = btoa(String.fromCharCode(...new Uint8Array(digest)));
40
+ return base64
41
+ .replace(/\+/g, "-")
42
+ .replace(/\//g, "_")
43
+ .replace(/=+$/, "");
44
+ }
45
+ /**
46
+ * OAuth PKCE configurations for supported providers.
47
+ * Only Anthropic and OpenAI/Codex are supported.
48
+ * Gemini/xAI/Z.ai are API-key only.
49
+ */
50
+ export const PKCE_CONFIGS = {
51
+ anthropic: {
52
+ authorizeUrl: "https://console.anthropic.com/oauth/authorize",
53
+ tokenUrl: "https://console.anthropic.com/oauth/token",
54
+ scopes: ["api", "offline_access"],
55
+ clientIdEnvVar: "OFFROUTER_ANTHROPIC_CLIENT_ID",
56
+ // No default client id; users must bring their own.
57
+ },
58
+ openai: {
59
+ authorizeUrl: "https://auth.openai.com/oauth/authorize",
60
+ tokenUrl: "https://auth.openai.com/oauth/token",
61
+ scopes: ["offline_access"],
62
+ clientIdEnvVar: "OFFROUTER_OPENAI_CLIENT_ID",
63
+ // Note: reuses Codex client id; documented as fragile.
64
+ },
65
+ };
66
+ /**
67
+ * Returns true if the provider supports OAuth (opt-in).
68
+ */
69
+ export function isOAuthProvider(providerId) {
70
+ return providerId in PKCE_CONFIGS;
71
+ }
72
+ // ---------------------------------------------------------------------------
73
+ // OAuth session store (in-memory, out-of-band — NOT embedded in state)
74
+ // ---------------------------------------------------------------------------
75
+ /** Time-to-live for a stored OAuth session (10 minutes). */
76
+ const SESSION_TTL_MS = 10 * 60 * 1000;
77
+ /** Module-level session store: nonce → session. */
78
+ const sessions = new Map();
79
+ /**
80
+ * Retrieve and delete a session by nonce (one-shot consume).
81
+ * Returns null if the nonce is unknown, already consumed, or expired.
82
+ */
83
+ export function consumeOAuthSession(nonce) {
84
+ const session = sessions.get(nonce);
85
+ if (!session)
86
+ return null;
87
+ sessions.delete(nonce);
88
+ if (Date.now() - session.createdAt > SESSION_TTL_MS) {
89
+ return null; // expired
90
+ }
91
+ return session;
92
+ }
93
+ /**
94
+ * Cancel / discard a session without consuming it.
95
+ */
96
+ export function cancelOAuthSession(nonce) {
97
+ sessions.delete(nonce);
98
+ }
99
+ /**
100
+ * Create an OAuth state string that encodes provider metadata.
101
+ * Used to validate the callback matches the initiated flow.
102
+ */
103
+ export function createOAuthState(provider) {
104
+ const nonce = randomBytes(16).toString("hex");
105
+ return JSON.stringify({ provider, nonce });
106
+ }
107
+ /**
108
+ * Start an OAuth PKCE flow for the given provider.
109
+ * Returns the authorization URL and state for redirect.
110
+ * Never auto-triggers; requires explicit user configuration.
111
+ */
112
+ export async function startOAuthFlow(provider, options) {
113
+ const config = PKCE_CONFIGS[provider];
114
+ if (!config) {
115
+ throw new Error(`OAuth not supported for provider: ${provider}. ` +
116
+ `OAuth is only available for anthropic and openai providers.`);
117
+ }
118
+ const clientId = options.clientId ??
119
+ (config.clientIdEnvVar ? process.env[config.clientIdEnvVar] : undefined);
120
+ if (!clientId) {
121
+ throw new Error(`Client ID required for ${provider} OAuth. ` +
122
+ `Set the ${config.clientIdEnvVar ?? "client id"} environment variable ` +
123
+ `or pass it explicitly.`);
124
+ }
125
+ const codeVerifier = generateCodeVerifier();
126
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
127
+ const state = createOAuthState(provider);
128
+ const params = new URLSearchParams({
129
+ response_type: "code",
130
+ client_id: clientId,
131
+ redirect_uri: options.redirectUri,
132
+ code_challenge: codeChallenge,
133
+ code_challenge_method: "S256",
134
+ state,
135
+ scope: config.scopes.join(" "),
136
+ });
137
+ const authorizeUrl = `${config.authorizeUrl}?${params.toString()}`;
138
+ // Store the verifier OOB so it never appears in the caller-visible state
139
+ const parsed = JSON.parse(state);
140
+ sessions.set(parsed.nonce, {
141
+ codeVerifier,
142
+ redirectUri: options.redirectUri,
143
+ createdAt: Date.now(),
144
+ });
145
+ return {
146
+ authorizeUrl,
147
+ state, // clean — provider + nonce only, no secrets
148
+ };
149
+ }
150
+ /**
151
+ * Exchange an authorization code for tokens.
152
+ * Called after the user completes the OAuth flow in their browser.
153
+ */
154
+ export async function exchangeCodeForTokens(provider, code, statePayload, options) {
155
+ const config = PKCE_CONFIGS[provider];
156
+ if (!config) {
157
+ throw new Error(`OAuth not supported for provider: ${provider}`);
158
+ }
159
+ let stateData;
160
+ try {
161
+ stateData = JSON.parse(statePayload);
162
+ }
163
+ catch {
164
+ throw new Error("Invalid OAuth state payload");
165
+ }
166
+ if (!stateData.nonce) {
167
+ throw new Error("Missing nonce in OAuth state");
168
+ }
169
+ const session = consumeOAuthSession(stateData.nonce);
170
+ if (!session) {
171
+ throw new Error("OAuth session expired, already consumed, or not found. Please restart the OAuth flow.");
172
+ }
173
+ if (!session.codeVerifier) {
174
+ throw new Error("Missing code verifier in OAuth session");
175
+ }
176
+ const clientId = config.clientIdEnvVar
177
+ ? (process.env[config.clientIdEnvVar] ?? "")
178
+ : "";
179
+ const tokenParams = new URLSearchParams({
180
+ grant_type: "authorization_code",
181
+ code,
182
+ redirect_uri: session.redirectUri ?? "http://127.0.0.1:8765/callback",
183
+ client_id: clientId,
184
+ code_verifier: session.codeVerifier,
185
+ });
186
+ const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
187
+ const response = await fetchImpl(config.tokenUrl, {
188
+ method: "POST",
189
+ headers: {
190
+ "content-type": "application/x-www-form-urlencoded",
191
+ },
192
+ body: tokenParams.toString(),
193
+ });
194
+ if (!response.ok) {
195
+ const body = await response.text().catch(() => "");
196
+ throw new Error(`Token exchange failed for ${provider}: HTTP ${response.status} ${body}`);
197
+ }
198
+ const json = (await response.json());
199
+ if (!json.access_token) {
200
+ throw new Error(`Token exchange for ${provider} returned no access_token`);
201
+ }
202
+ // Store tokens in secret store (structured)
203
+ if (options.store) {
204
+ const obtainedAt = Date.now();
205
+ await setOAuthTokens(provider, {
206
+ accessToken: json.access_token,
207
+ refreshToken: json.refresh_token,
208
+ expiresAt: json.expires_in
209
+ ? obtainedAt + json.expires_in * 1000
210
+ : undefined,
211
+ scope: json.scope,
212
+ obtainedAt,
213
+ }, options.store);
214
+ }
215
+ return {
216
+ accessToken: new Redacted(json.access_token),
217
+ refreshToken: json.refresh_token
218
+ ? new Redacted(json.refresh_token)
219
+ : undefined,
220
+ expiresIn: json.expires_in,
221
+ scope: json.scope,
222
+ };
223
+ }
224
+ // ---------------------------------------------------------------------------
225
+ // Login orchestration (callback server + browser + token exchange)
226
+ // ---------------------------------------------------------------------------
227
+ /** Default loopback ports for supported providers. */
228
+ const DEFAULT_CALLBACK_PORTS = {
229
+ anthropic: 1456,
230
+ openai: 1455,
231
+ };
232
+ /**
233
+ * Return the default callback port for a provider, or 0 (OS-assigned).
234
+ */
235
+ export function defaultCallbackPort(provider) {
236
+ return DEFAULT_CALLBACK_PORTS[provider] ?? 0;
237
+ }
238
+ /**
239
+ * Resolve the client id for a provider from process env (or a provided env
240
+ * override). Returns undefined if the env var is not set.
241
+ */
242
+ export function resolveClientId(provider, env = process.env) {
243
+ const config = PKCE_CONFIGS[provider];
244
+ if (!config?.clientIdEnvVar)
245
+ return undefined;
246
+ return env[config.clientIdEnvVar] ?? undefined;
247
+ }
248
+ /**
249
+ * Run the full OAuth PKCE login flow:
250
+ * 1. Start a local callback server on 127.0.0.1
251
+ * 2. Build the authorize URL with PKCE challenge + state
252
+ * 3. Notify caller (onAuthorizeUrl) and attempt browser open
253
+ * 4. Wait for the provider callback (code + state)
254
+ * 5. Exchange code for tokens
255
+ * 6. Store structured tokens in the secret store
256
+ * 7. Clean up the server
257
+ */
258
+ export async function runOAuthLogin(opts) {
259
+ const config = PKCE_CONFIGS[opts.provider];
260
+ if (!config) {
261
+ throw new Error(`OAuth not supported for provider: ${opts.provider}. ` +
262
+ `OAuth is only available for anthropic and openai providers.`);
263
+ }
264
+ const clientId = opts.clientId ?? resolveClientId(opts.provider) ?? undefined;
265
+ if (!clientId) {
266
+ const envHint = config.clientIdEnvVar
267
+ ? `Set the ${config.clientIdEnvVar} environment variable or pass it explicitly.`
268
+ : "A client id is required for this provider.";
269
+ throw new Error(`Client ID required for ${opts.provider} OAuth. ${envHint}`);
270
+ }
271
+ // 1. Start callback server
272
+ const port = opts.port ?? defaultCallbackPort(opts.provider);
273
+ const server = await startCallbackServer(port);
274
+ try {
275
+ const redirectUri = server.url;
276
+ // 2. Build the authorize URL with PKCE
277
+ const flow = await startOAuthFlow(opts.provider, {
278
+ redirectUri,
279
+ clientId,
280
+ });
281
+ // 3. Notify and attempt to open browser
282
+ opts.onAuthorizeUrl?.(flow.authorizeUrl);
283
+ if (opts.openBrowser) {
284
+ try {
285
+ await opts.openBrowser(flow.authorizeUrl);
286
+ }
287
+ catch {
288
+ // Best-effort only.
289
+ }
290
+ }
291
+ // 4. Wait for callback (validates expected state)
292
+ const { code } = await server.waitForCode({
293
+ expectedState: flow.state,
294
+ timeoutMs: opts.timeoutMs,
295
+ signal: opts.signal,
296
+ });
297
+ // 5. Exchange code for tokens (also stores them)
298
+ const tokens = await exchangeCodeForTokens(opts.provider, code, flow.state, {
299
+ store: opts.store,
300
+ fetch: opts.fetch,
301
+ });
302
+ // 6. Tokens are already stored by exchangeCodeForTokens.
303
+ // Compute expiresAt from expiresIn for the result.
304
+ const expiresAt = tokens.expiresIn
305
+ ? Date.now() + tokens.expiresIn * 1000
306
+ : undefined;
307
+ return {
308
+ provider: opts.provider,
309
+ redirectUri,
310
+ expiresAt,
311
+ scope: tokens.scope,
312
+ };
313
+ }
314
+ finally {
315
+ // 7. Clean up
316
+ await server.close();
317
+ }
318
+ }
319
+ /**
320
+ * Refresh an OAuth token using a refresh token.
321
+ * Updates the stored tokens on success.
322
+ */
323
+ export async function refreshTokens(provider, refreshToken, store, options) {
324
+ const config = PKCE_CONFIGS[provider];
325
+ if (!config) {
326
+ throw new Error(`OAuth not supported for provider: ${provider}`);
327
+ }
328
+ if (!store) {
329
+ throw new Error("SecretStore is required for token refresh");
330
+ }
331
+ const clientId = config.clientIdEnvVar
332
+ ? (process.env[config.clientIdEnvVar] ?? "")
333
+ : "";
334
+ const params = new URLSearchParams({
335
+ grant_type: "refresh_token",
336
+ refresh_token: refreshToken,
337
+ client_id: clientId,
338
+ });
339
+ const fetchImpl = options?.fetch ?? globalThis.fetch.bind(globalThis);
340
+ const response = await fetchImpl(config.tokenUrl, {
341
+ method: "POST",
342
+ headers: {
343
+ "content-type": "application/x-www-form-urlencoded",
344
+ },
345
+ body: params.toString(),
346
+ });
347
+ if (!response.ok) {
348
+ const body = await response.text().catch(() => "");
349
+ throw new Error(`Token refresh failed for ${provider}: HTTP ${response.status} ${body}`);
350
+ }
351
+ const json = (await response.json());
352
+ if (!json.access_token) {
353
+ throw new Error(`Token refresh for ${provider} returned no access_token`);
354
+ }
355
+ // Update stored tokens (structured)
356
+ const obtainedAt = Date.now();
357
+ await setOAuthTokens(provider, {
358
+ accessToken: json.access_token,
359
+ refreshToken: json.refresh_token,
360
+ expiresAt: json.expires_in
361
+ ? obtainedAt + json.expires_in * 1000
362
+ : undefined,
363
+ scope: json.scope,
364
+ obtainedAt,
365
+ }, store);
366
+ return {
367
+ accessToken: new Redacted(json.access_token),
368
+ refreshToken: json.refresh_token
369
+ ? new Redacted(json.refresh_token)
370
+ : undefined,
371
+ expiresIn: json.expires_in,
372
+ scope: json.scope,
373
+ };
374
+ }
375
+ //# sourceMappingURL=oauth-pkce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-pkce.js","sourceRoot":"","sources":["../../src/auth/oauth-pkce.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ export interface CallbackServerOptions {
2
+ /**
3
+ * State value expected from the provider. Validated in waitForCode against
4
+ * the state returned by the redirect; a mismatch rejects the promise.
5
+ * May also be supplied per-call to waitForCode.
6
+ */
7
+ expectedState?: string;
8
+ }
9
+ export interface WaitForCodeOptions {
10
+ /** Overrides the server-level expectedState for this call. */
11
+ expectedState?: string;
12
+ /** How long to wait for a callback before rejecting (default 5 minutes). */
13
+ timeoutMs?: number;
14
+ /** Optional abort signal to cancel the wait early. */
15
+ signal?: AbortSignal;
16
+ }
17
+ export interface CallbackCode {
18
+ code: string;
19
+ state: string;
20
+ }
21
+ export interface CallbackServerHandle {
22
+ /** Full redirect_uri to register with the provider (http://127.0.0.1:PORT/callback). */
23
+ url: string;
24
+ /** Actual bound port (useful when 0 was requested). */
25
+ port: number;
26
+ /** Resolve with {code, state} on a valid callback; reject on timeout/state mismatch. */
27
+ waitForCode(options?: WaitForCodeOptions): Promise<CallbackCode>;
28
+ /** Stop listening and free the port. Idempotent. */
29
+ close(): Promise<void>;
30
+ }
31
+ /**
32
+ * Start a loopback HTTP server that captures an OAuth authorization code.
33
+ * Pass port 0 to let the OS choose a free port (read handle.port afterwards).
34
+ */
35
+ export declare function startCallbackServer(port: number, options?: CallbackServerOptions): Promise<CallbackServerHandle>;
36
+ //# sourceMappingURL=oauth-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-server.d.ts","sourceRoot":"","sources":["../../src/auth/oauth-server.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB;IACnC,wFAAwF;IACxF,GAAG,EAAE,MAAM,CAAC;IACZ,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,wFAAwF;IACxF,WAAW,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACjE,oDAAoD;IACpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,oBAAoB,CAAC,CA6K/B"}
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Local OAuth callback server for OffRouter's live PKCE login flow.
3
+ *
4
+ * Binds to 127.0.0.1 ONLY (never 0.0.0.0) so the provider can redirect back
5
+ * to this machine without exposing the callback to the network. The server
6
+ * captures the authorization `code` + `state` from the redirect and hands them
7
+ * to the caller; it never touches tokens, secrets, or the network beyond the
8
+ * single loopback HTTP socket.
9
+ */
10
+ import { createServer } from "node:http";
11
+ import { URL } from "node:url";
12
+ /** Loopback-only hostname. Hardcoded to prevent accidental 0.0.0.0 binds. */
13
+ const LOOPBACK_HOST = "127.0.0.1";
14
+ /** Path the provider redirects to (http://127.0.0.1:PORT/callback). */
15
+ const CALLBACK_PATH = "/callback";
16
+ /**
17
+ * Start a loopback HTTP server that captures an OAuth authorization code.
18
+ * Pass port 0 to let the OS choose a free port (read handle.port afterwards).
19
+ */
20
+ export function startCallbackServer(port, options = {}) {
21
+ return new Promise((resolve, reject) => {
22
+ const serverLevelState = options.expectedState;
23
+ // Deferred for the first valid callback (has a `code` param).
24
+ let captured = null;
25
+ const waiters = [];
26
+ const httpServer = createServer((req, res) => {
27
+ handleCallback(req, res);
28
+ });
29
+ function handleCallback(req, res) {
30
+ // Only respond to GET on the callback path; everything else 404s.
31
+ if (req.method !== "GET") {
32
+ res.writeHead(405, { "content-type": "text/plain; charset=utf-8" });
33
+ res.end("Method Not Allowed");
34
+ return;
35
+ }
36
+ let parsedUrl;
37
+ try {
38
+ parsedUrl = new URL(req.url ?? "/", `http://${LOOPBACK_HOST}`);
39
+ }
40
+ catch {
41
+ res.writeHead(400, { "content-type": "text/plain; charset=utf-8" });
42
+ res.end("Bad Request");
43
+ return;
44
+ }
45
+ const pathname = parsedUrl.pathname;
46
+ const code = parsedUrl.searchParams.get("code");
47
+ const state = parsedUrl.searchParams.get("state");
48
+ const error = parsedUrl.searchParams.get("error");
49
+ // OAuth error from the provider (e.g. user denied consent).
50
+ if (error) {
51
+ res.writeHead(400, { "content-type": "text/html; charset=utf-8" });
52
+ res.end(authorizationDeniedPage(error));
53
+ return;
54
+ }
55
+ if (pathname !== CALLBACK_PATH) {
56
+ res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
57
+ res.end("Not Found");
58
+ return;
59
+ }
60
+ // Missing code -> provider returned nothing usable.
61
+ if (!code) {
62
+ res.writeHead(400, { "content-type": "text/html; charset=utf-8" });
63
+ res.end(missingCodePage());
64
+ return;
65
+ }
66
+ // Capture the first valid callback exactly once.
67
+ const callback = { code, state: state ?? "" };
68
+ if (!captured) {
69
+ captured = callback;
70
+ // Resolve any pending waiters that accept this callback.
71
+ const pending = waiters.splice(0, waiters.length);
72
+ for (const w of pending) {
73
+ w.resolve(callback);
74
+ }
75
+ }
76
+ // Always show the user a friendly page; security validation happens in
77
+ // waitForCode (state may not be known at request time).
78
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
79
+ res.end(successPage());
80
+ }
81
+ httpServer.on("error", (err) => {
82
+ reject(new Error(`Failed to start OAuth callback server on 127.0.0.1:${port}: ${err.message}`, { cause: err }));
83
+ });
84
+ httpServer.listen(port, LOOPBACK_HOST, () => {
85
+ const address = httpServer.address();
86
+ const boundPort = address && typeof address === "object" ? address.port : port;
87
+ const redirectUrl = `http://${LOOPBACK_HOST}:${boundPort}${CALLBACK_PATH}`;
88
+ const handle = {
89
+ url: redirectUrl,
90
+ port: boundPort,
91
+ waitForCode(waitOpts = {}) {
92
+ const expected = waitOpts.expectedState !== undefined
93
+ ? waitOpts.expectedState
94
+ : serverLevelState;
95
+ const timeoutMs = waitOpts.timeoutMs ?? 5 * 60 * 1000;
96
+ return new Promise((resolveWait, rejectWait) => {
97
+ let settled = false;
98
+ const settle = (outcome, isReject) => {
99
+ if (settled)
100
+ return;
101
+ settled = true;
102
+ clearTimeout(timer);
103
+ waitOpts.signal?.removeEventListener("abort", onAbort);
104
+ if (isReject) {
105
+ rejectWait(outcome);
106
+ }
107
+ else {
108
+ resolveWait(outcome);
109
+ }
110
+ };
111
+ const onAbort = () => {
112
+ settle(new Error("OAuth callback wait was aborted."), true);
113
+ };
114
+ const timer = setTimeout(() => {
115
+ settle(new Error("OAuth login timed out waiting for callback."), true);
116
+ }, timeoutMs);
117
+ if (waitOpts.signal?.aborted) {
118
+ settle(new Error("OAuth callback wait was aborted."), true);
119
+ return;
120
+ }
121
+ waitOpts.signal?.addEventListener("abort", onAbort);
122
+ const resolveWith = (callback) => {
123
+ if (expected !== undefined && callback.state !== expected) {
124
+ settle(new Error(`OAuth state mismatch: expected "${redactState(expected)}" ` +
125
+ `but received "${redactState(callback.state)}". ` +
126
+ `This may indicate a CSRF attempt or a stale login tab.`), true);
127
+ return;
128
+ }
129
+ settle(callback, false);
130
+ };
131
+ if (captured) {
132
+ resolveWith(captured);
133
+ return;
134
+ }
135
+ waiters.push({ resolve: resolveWith, reject: rejectWait });
136
+ });
137
+ },
138
+ close() {
139
+ return new Promise((resolveClose) => {
140
+ httpServer.close(() => resolveClose());
141
+ // close() waits for in-flight connections; closeAllConnections is
142
+ // available on Node 18+ and speeds shutdown without dropping the
143
+ // response the user's browser is mid-receiving.
144
+ // (Not called here to keep the success page delivery intact.)
145
+ });
146
+ },
147
+ };
148
+ resolve(handle);
149
+ });
150
+ });
151
+ }
152
+ // ---------------------------------------------------------------------------
153
+ // Static HTML pages (ASCII-only, no secret material)
154
+ // ---------------------------------------------------------------------------
155
+ function successPage() {
156
+ return [
157
+ "<!doctype html>",
158
+ "<html lang=\"en\">",
159
+ "<head><meta charset=\"utf-8\"><title>OffRouter login</title></head>",
160
+ "<body>",
161
+ "<h1>OffRouter login authorized</h1>",
162
+ "<p>You can close this tab and return to your terminal.</p>",
163
+ "</body>",
164
+ "</html>",
165
+ "",
166
+ ].join("\n");
167
+ }
168
+ function authorizationDeniedPage(error) {
169
+ const safe = escapeHtml(error);
170
+ return [
171
+ "<!doctype html>",
172
+ "<html lang=\"en\">",
173
+ "<head><meta charset=\"utf-8\"><title>OffRouter login</title></head>",
174
+ "<body>",
175
+ "<h1>OffRouter login not completed</h1>",
176
+ `<p>The provider reported: ${safe}</p>`,
177
+ "<p>You can close this tab and try again from your terminal.</p>",
178
+ "</body>",
179
+ "</html>",
180
+ "",
181
+ ].join("\n");
182
+ }
183
+ function missingCodePage() {
184
+ return [
185
+ "<!doctype html>",
186
+ "<html lang=\"en\">",
187
+ "<head><meta charset=\"utf-8\"><title>OffRouter login</title></head>",
188
+ "<body>",
189
+ "<h1>OffRouter login incomplete</h1>",
190
+ "<p>The callback did not include an authorization code.</p>",
191
+ "</body>",
192
+ "</html>",
193
+ "",
194
+ ].join("\n");
195
+ }
196
+ function escapeHtml(value) {
197
+ return value
198
+ .replace(/&/g, "&amp;")
199
+ .replace(/</g, "&lt;")
200
+ .replace(/>/g, "&gt;")
201
+ .replace(/"/g, "&quot;")
202
+ .replace(/'/g, "&#39;");
203
+ }
204
+ /** Truncate a state token for error messages so it is never echoed in full. */
205
+ function redactState(state) {
206
+ if (state.length <= 8)
207
+ return "***";
208
+ return `${state.slice(0, 4)}...${state.slice(-4)}`;
209
+ }
210
+ //# sourceMappingURL=oauth-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-server.js","sourceRoot":"","sources":["../../src/auth/oauth-server.ts"],"names":[],"mappings":"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"}