@vymalo/opencode-oauth2 0.14.1 → 0.15.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/dist/cache.d.ts +14 -3
- package/dist/cache.js +32 -86
- package/dist/cache.js.map +1 -1
- package/dist/config.d.ts +3 -37
- package/dist/config.js +39 -136
- package/dist/config.js.map +1 -1
- package/dist/lib.d.ts +2 -2
- package/dist/lib.js +1 -1
- package/dist/lib.js.map +1 -1
- package/dist/model-discovery.d.ts +2 -2
- package/dist/model-discovery.js +1 -1
- package/dist/opencode.d.ts +1 -1
- package/dist/opencode.js +1 -1
- package/dist/opencode.js.map +1 -1
- package/dist/plugin.d.ts +4 -2
- package/dist/plugin.js +62 -31
- package/dist/plugin.js.map +1 -1
- package/dist/scheduler.d.ts +1 -1
- package/dist/types.d.ts +1 -7
- package/package.json +3 -2
- package/dist/logging.d.ts +0 -13
- package/dist/logging.js +0 -64
- package/dist/logging.js.map +0 -1
- package/dist/oauth/browser.d.ts +0 -1
- package/dist/oauth/browser.js +0 -33
- package/dist/oauth/browser.js.map +0 -1
- package/dist/oauth/client.d.ts +0 -56
- package/dist/oauth/client.js +0 -424
- package/dist/oauth/client.js.map +0 -1
- package/dist/oauth/device-code.d.ts +0 -28
- package/dist/oauth/device-code.js +0 -247
- package/dist/oauth/device-code.js.map +0 -1
- package/dist/oauth/discovery.d.ts +0 -8
- package/dist/oauth/discovery.js +0 -38
- package/dist/oauth/discovery.js.map +0 -1
- package/dist/oauth/http-utils.d.ts +0 -33
- package/dist/oauth/http-utils.js +0 -118
- package/dist/oauth/http-utils.js.map +0 -1
- package/dist/oauth/local-callback.d.ts +0 -10
- package/dist/oauth/local-callback.js +0 -84
- package/dist/oauth/local-callback.js.map +0 -1
- package/dist/oauth/pkce.d.ts +0 -5
- package/dist/oauth/pkce.js +0 -17
- package/dist/oauth/pkce.js.map +0 -1
- package/dist/oauth/subject-token.d.ts +0 -20
- package/dist/oauth/subject-token.js +0 -81
- package/dist/oauth/subject-token.js.map +0 -1
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
import { toTokenSet } from "./client.js";
|
|
2
|
-
import { readResponseBodyPreview as readResponsePreviewShared, scrubSecrets } from "./http-utils.js";
|
|
3
|
-
import { generatePkcePair } from "./pkce.js";
|
|
4
|
-
const DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
5
|
-
const DEFAULT_POLL_INTERVAL_SECONDS = 5;
|
|
6
|
-
const SLOW_DOWN_INCREMENT_SECONDS = 5;
|
|
7
|
-
const ERROR_BODY_PREVIEW_CHARS = 500;
|
|
8
|
-
// Cap on the polling interval itself, not on the number of retries. We never
|
|
9
|
-
// hard-stop on transient transport errors — a VPN flap or DNS hiccup mid-flow
|
|
10
|
-
// should not abort an in-progress device-code session. The `expires_in`
|
|
11
|
-
// deadline bounds the overall wait. The cap keeps the interval from growing
|
|
12
|
-
// without bound while we wait for transient conditions to clear.
|
|
13
|
-
const MAX_POLL_INTERVAL_SECONDS = 60;
|
|
14
|
-
function defaultSleep(ms) {
|
|
15
|
-
return new Promise((resolve) => {
|
|
16
|
-
setTimeout(resolve, ms);
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
async function readResponseBodyPreview(response) {
|
|
20
|
-
return readResponsePreviewShared(response, ERROR_BODY_PREVIEW_CHARS);
|
|
21
|
-
}
|
|
22
|
-
function parseDeviceAuthorizationResponse(payload) {
|
|
23
|
-
if (!payload || typeof payload !== "object") {
|
|
24
|
-
throw new Error("device authorization response is not a JSON object");
|
|
25
|
-
}
|
|
26
|
-
const record = payload;
|
|
27
|
-
const deviceCode = record.device_code;
|
|
28
|
-
const userCode = record.user_code;
|
|
29
|
-
const verificationUri = record.verification_uri;
|
|
30
|
-
const expiresIn = record.expires_in;
|
|
31
|
-
if (typeof deviceCode !== "string" || deviceCode.length === 0) {
|
|
32
|
-
throw new Error("device authorization response is missing device_code");
|
|
33
|
-
}
|
|
34
|
-
if (typeof userCode !== "string" || userCode.length === 0) {
|
|
35
|
-
throw new Error("device authorization response is missing user_code");
|
|
36
|
-
}
|
|
37
|
-
if (typeof verificationUri !== "string" || verificationUri.length === 0) {
|
|
38
|
-
throw new Error("device authorization response is missing verification_uri");
|
|
39
|
-
}
|
|
40
|
-
if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) {
|
|
41
|
-
throw new Error("device authorization response is missing a valid expires_in");
|
|
42
|
-
}
|
|
43
|
-
const verificationUriComplete = typeof record.verification_uri_complete === "string" && record.verification_uri_complete.length > 0 ? record.verification_uri_complete : undefined;
|
|
44
|
-
const interval = typeof record.interval === "number" && Number.isFinite(record.interval) && record.interval > 0 ? Math.ceil(record.interval) : undefined;
|
|
45
|
-
return {
|
|
46
|
-
device_code: deviceCode,
|
|
47
|
-
user_code: userCode,
|
|
48
|
-
verification_uri: verificationUri,
|
|
49
|
-
verification_uri_complete: verificationUriComplete,
|
|
50
|
-
expires_in: expiresIn,
|
|
51
|
-
interval
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
export async function acquireTokenViaDeviceCode(options) {
|
|
55
|
-
const fetchImpl = options.fetchImpl ?? fetch;
|
|
56
|
-
const sleep = options.sleep ?? defaultSleep;
|
|
57
|
-
const now = options.now ?? Date.now;
|
|
58
|
-
const { logger, serverId } = options;
|
|
59
|
-
// PKCE for the device flow (RFC 8628 + RFC 7636). Keycloak enforces this when
|
|
60
|
-
// the client's "Proof Key for Code Exchange Code Challenge Method" is set, and
|
|
61
|
-
// rejects the device-authorization request with
|
|
62
|
-
// `invalid_request: Missing parameter: code_challenge_method` otherwise. On by
|
|
63
|
-
// default — providers that don't require PKCE ignore it — and opt-out via the
|
|
64
|
-
// `pkce` server option for non-compliant IdPs. Mirrors the authorization_code
|
|
65
|
-
// flow.
|
|
66
|
-
const usePkce = options.pkce !== false;
|
|
67
|
-
const pkce = usePkce ? generatePkcePair() : undefined;
|
|
68
|
-
// Step 1: request a device code.
|
|
69
|
-
const deviceAuthBody = new URLSearchParams({
|
|
70
|
-
client_id: options.clientId,
|
|
71
|
-
scope: options.scopes.join(" ")
|
|
72
|
-
});
|
|
73
|
-
if (pkce) {
|
|
74
|
-
deviceAuthBody.set("code_challenge", pkce.challenge);
|
|
75
|
-
deviceAuthBody.set("code_challenge_method", "S256");
|
|
76
|
-
}
|
|
77
|
-
if (options.clientSecret) {
|
|
78
|
-
deviceAuthBody.set("client_secret", options.clientSecret);
|
|
79
|
-
}
|
|
80
|
-
const deviceAuthController = new AbortController();
|
|
81
|
-
const deviceAuthTimeout = setTimeout(() => deviceAuthController.abort(), options.timeoutMs);
|
|
82
|
-
let deviceAuthResponse;
|
|
83
|
-
try {
|
|
84
|
-
deviceAuthResponse = await fetchImpl(options.deviceAuthorizationEndpoint, {
|
|
85
|
-
method: "POST",
|
|
86
|
-
headers: {
|
|
87
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
88
|
-
Accept: "application/json"
|
|
89
|
-
},
|
|
90
|
-
body: deviceAuthBody,
|
|
91
|
-
signal: deviceAuthController.signal
|
|
92
|
-
});
|
|
93
|
-
} finally {
|
|
94
|
-
clearTimeout(deviceAuthTimeout);
|
|
95
|
-
}
|
|
96
|
-
if (!deviceAuthResponse.ok) {
|
|
97
|
-
const preview = await readResponseBodyPreview(deviceAuthResponse);
|
|
98
|
-
// Log the body separately at error-level so the logger's redaction filter
|
|
99
|
-
// can scrub matching keys (e.g. a verbose provider echoing `client_secret`
|
|
100
|
-
// back in the response). Never embed the body in error.message — callers
|
|
101
|
-
// log error.message verbatim, bypassing the redaction filter.
|
|
102
|
-
logger.error("oauth_device_authorization_failed", {
|
|
103
|
-
serverId,
|
|
104
|
-
status: deviceAuthResponse.status,
|
|
105
|
-
bodyPreview: preview ? scrubSecrets(preview) : undefined
|
|
106
|
-
});
|
|
107
|
-
throw new Error(`device authorization request failed (${deviceAuthResponse.status})`);
|
|
108
|
-
}
|
|
109
|
-
const deviceAuthPayload = await deviceAuthResponse.json();
|
|
110
|
-
const deviceAuth = parseDeviceAuthorizationResponse(deviceAuthPayload);
|
|
111
|
-
const verificationUri = deviceAuth.verification_uri_complete ?? deviceAuth.verification_uri;
|
|
112
|
-
// user_code is an ephemeral, single-use code with no value outside the active
|
|
113
|
-
// flow — that is the spec's intent (RFC 8628). Log it so operators can see the
|
|
114
|
-
// active code and forward it to the user if needed.
|
|
115
|
-
logger.info("oauth_device_code_issued", {
|
|
116
|
-
verificationUri,
|
|
117
|
-
userCode: deviceAuth.user_code,
|
|
118
|
-
expiresIn: deviceAuth.expires_in,
|
|
119
|
-
serverId
|
|
120
|
-
});
|
|
121
|
-
// Also surface to stderr so terminal users can see the code regardless of log
|
|
122
|
-
// routing. Mirrors the browser-fallback pattern in client.ts.
|
|
123
|
-
process.stderr.write(`\n[opencode-oauth2] device-code login for ${serverId}:\n visit: ${verificationUri}\n code: ${deviceAuth.user_code}\n (expires in ${deviceAuth.expires_in}s)\n\n`);
|
|
124
|
-
// Step 2: poll the token endpoint.
|
|
125
|
-
let intervalSeconds = deviceAuth.interval ?? DEFAULT_POLL_INTERVAL_SECONDS;
|
|
126
|
-
const deadlineMs = now() + deviceAuth.expires_in * 1e3;
|
|
127
|
-
let consecutiveTransientFailures = 0;
|
|
128
|
-
while (true) {
|
|
129
|
-
const remainingMs = deadlineMs - now();
|
|
130
|
-
if (remainingMs <= 0) {
|
|
131
|
-
throw new Error("device code expired before authorization completed");
|
|
132
|
-
}
|
|
133
|
-
await sleep(Math.min(intervalSeconds * 1e3, remainingMs));
|
|
134
|
-
const pollBody = new URLSearchParams({
|
|
135
|
-
grant_type: DEVICE_CODE_GRANT_TYPE,
|
|
136
|
-
device_code: deviceAuth.device_code,
|
|
137
|
-
client_id: options.clientId
|
|
138
|
-
});
|
|
139
|
-
if (pkce) {
|
|
140
|
-
pollBody.set("code_verifier", pkce.verifier);
|
|
141
|
-
}
|
|
142
|
-
if (options.clientSecret) {
|
|
143
|
-
pollBody.set("client_secret", options.clientSecret);
|
|
144
|
-
}
|
|
145
|
-
const pollController = new AbortController();
|
|
146
|
-
const pollTimeout = setTimeout(() => pollController.abort(), options.timeoutMs);
|
|
147
|
-
let pollResponse;
|
|
148
|
-
try {
|
|
149
|
-
pollResponse = await fetchImpl(options.tokenEndpoint, {
|
|
150
|
-
method: "POST",
|
|
151
|
-
headers: {
|
|
152
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
153
|
-
Accept: "application/json"
|
|
154
|
-
},
|
|
155
|
-
body: pollBody,
|
|
156
|
-
signal: pollController.signal
|
|
157
|
-
});
|
|
158
|
-
// Reset the failure counter on any HTTP response — even a non-2xx one
|
|
159
|
-
// is a sign the network round-trip is working; only thrown exceptions
|
|
160
|
-
// (network errors, timeouts) count as transient failures.
|
|
161
|
-
consecutiveTransientFailures = 0;
|
|
162
|
-
} catch (error) {
|
|
163
|
-
// TypeError from fetch typically means a programming/configuration
|
|
164
|
-
// error (malformed URL, unsupported scheme) that won't resolve on
|
|
165
|
-
// retry. Fail fast on those instead of burning the expires_in window.
|
|
166
|
-
// Everything else (AbortError from timeout, network errors, DNS
|
|
167
|
-
// failures) is treated as transient and triggers backoff per
|
|
168
|
-
// RFC 8628 §3.5.
|
|
169
|
-
if (error instanceof TypeError) {
|
|
170
|
-
logger.error("oauth_device_code_poll_failed", {
|
|
171
|
-
serverId,
|
|
172
|
-
error: error.message
|
|
173
|
-
});
|
|
174
|
-
throw error;
|
|
175
|
-
}
|
|
176
|
-
consecutiveTransientFailures++;
|
|
177
|
-
// Exponential backoff (capped) on transient transport errors. We do
|
|
178
|
-
// NOT hard-stop: VPN flaps, transient DNS/TLS outages, and similar
|
|
179
|
-
// short-lived disruptions are normal during a multi-minute device-code
|
|
180
|
-
// window. The `expires_in` deadline at the top of the loop is the
|
|
181
|
-
// sole termination condition.
|
|
182
|
-
intervalSeconds = Math.min(intervalSeconds + SLOW_DOWN_INCREMENT_SECONDS, MAX_POLL_INTERVAL_SECONDS);
|
|
183
|
-
logger.warn("oauth_device_code_poll_transient_error", {
|
|
184
|
-
serverId,
|
|
185
|
-
error: error instanceof Error ? error.message : String(error),
|
|
186
|
-
consecutiveFailures: consecutiveTransientFailures,
|
|
187
|
-
nextIntervalSeconds: intervalSeconds
|
|
188
|
-
});
|
|
189
|
-
continue;
|
|
190
|
-
} finally {
|
|
191
|
-
clearTimeout(pollTimeout);
|
|
192
|
-
}
|
|
193
|
-
if (pollResponse.ok) {
|
|
194
|
-
const payload = await pollResponse.json();
|
|
195
|
-
const token = toTokenSet(payload, { requireRefreshToken: true });
|
|
196
|
-
logger.info("oauth_device_code_success", {
|
|
197
|
-
serverId,
|
|
198
|
-
hasRefreshToken: true
|
|
199
|
-
});
|
|
200
|
-
return token;
|
|
201
|
-
}
|
|
202
|
-
if (pollResponse.status >= 400 && pollResponse.status < 500) {
|
|
203
|
-
let errorPayload = {};
|
|
204
|
-
const text = await readResponseBodyPreview(pollResponse);
|
|
205
|
-
try {
|
|
206
|
-
if (text.length > 0) {
|
|
207
|
-
errorPayload = JSON.parse(text);
|
|
208
|
-
}
|
|
209
|
-
} catch {}
|
|
210
|
-
const errorCode = errorPayload.error;
|
|
211
|
-
if (errorCode === "authorization_pending") {
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
if (errorCode === "slow_down") {
|
|
215
|
-
intervalSeconds += SLOW_DOWN_INCREMENT_SECONDS;
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
if (errorCode === "expired_token") {
|
|
219
|
-
throw new Error("device code expired before user completed authorization");
|
|
220
|
-
}
|
|
221
|
-
if (errorCode === "access_denied") {
|
|
222
|
-
throw new Error("device code authorization denied by user");
|
|
223
|
-
}
|
|
224
|
-
logger.error("oauth_device_code_poll_failed", {
|
|
225
|
-
serverId,
|
|
226
|
-
status: pollResponse.status,
|
|
227
|
-
errorCode: errorCode || undefined,
|
|
228
|
-
// Body preview goes here, not into the thrown error.message — callers
|
|
229
|
-
// log error.message verbatim and would bypass the logger's redaction.
|
|
230
|
-
// scrubSecrets masks token-shaped substrings the field-name-based
|
|
231
|
-
// logger redaction would otherwise miss.
|
|
232
|
-
bodyPreview: text ? scrubSecrets(text) : undefined
|
|
233
|
-
});
|
|
234
|
-
throw new Error(`device code token poll failed (${pollResponse.status})${errorCode ? `: ${errorCode}` : ""}`);
|
|
235
|
-
}
|
|
236
|
-
// 5xx — surface to caller; do not retry indefinitely on server errors.
|
|
237
|
-
const preview = await readResponseBodyPreview(pollResponse);
|
|
238
|
-
logger.error("oauth_device_code_poll_failed", {
|
|
239
|
-
serverId,
|
|
240
|
-
status: pollResponse.status,
|
|
241
|
-
bodyPreview: preview ? scrubSecrets(preview) : undefined
|
|
242
|
-
});
|
|
243
|
-
throw new Error(`device code token poll failed (${pollResponse.status})`);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
//# sourceMappingURL=device-code.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"mappings":"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","names":[],"sources":["../../src/oauth/device-code.ts"],"version":3,"file":"device-code.js","sourceRoot":""}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export interface OidcMetadata {
|
|
2
|
-
issuer: string;
|
|
3
|
-
authorization_endpoint?: string;
|
|
4
|
-
token_endpoint: string;
|
|
5
|
-
device_authorization_endpoint?: string;
|
|
6
|
-
jwks_uri?: string;
|
|
7
|
-
}
|
|
8
|
-
export declare function discoverOidcMetadata(issuer: string, fetchImpl?: typeof fetch, timeoutMs?: number): Promise<OidcMetadata>;
|
package/dist/oauth/discovery.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
function buildWellKnownUrl(issuer) {
|
|
2
|
-
const normalizedIssuer = issuer.endsWith("/") ? issuer : `${issuer}/`;
|
|
3
|
-
const url = new URL(".well-known/openid-configuration", normalizedIssuer);
|
|
4
|
-
return url.toString();
|
|
5
|
-
}
|
|
6
|
-
export async function discoverOidcMetadata(issuer, fetchImpl = fetch, timeoutMs = 15e3) {
|
|
7
|
-
const url = buildWellKnownUrl(issuer);
|
|
8
|
-
const controller = new AbortController();
|
|
9
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
10
|
-
try {
|
|
11
|
-
const response = await fetchImpl(url, {
|
|
12
|
-
method: "GET",
|
|
13
|
-
headers: { Accept: "application/json" },
|
|
14
|
-
signal: controller.signal
|
|
15
|
-
});
|
|
16
|
-
if (!response.ok) {
|
|
17
|
-
throw new Error(`OIDC discovery failed (${response.status})`);
|
|
18
|
-
}
|
|
19
|
-
const metadata = await response.json();
|
|
20
|
-
// Only the token_endpoint is universally required — every grant we support
|
|
21
|
-
// needs it. authorization_endpoint and device_authorization_endpoint are
|
|
22
|
-
// grant-specific; callers validate per their chosen flow.
|
|
23
|
-
if (!metadata.token_endpoint) {
|
|
24
|
-
throw new Error("OIDC metadata is missing token_endpoint");
|
|
25
|
-
}
|
|
26
|
-
return {
|
|
27
|
-
issuer: metadata.issuer ?? issuer,
|
|
28
|
-
authorization_endpoint: metadata.authorization_endpoint,
|
|
29
|
-
token_endpoint: metadata.token_endpoint,
|
|
30
|
-
device_authorization_endpoint: metadata.device_authorization_endpoint,
|
|
31
|
-
jwks_uri: metadata.jwks_uri
|
|
32
|
-
};
|
|
33
|
-
} finally {
|
|
34
|
-
clearTimeout(timeout);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
//# sourceMappingURL=discovery.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"mappings":"AAYA,SAAS,kBAAkB,QAAwB;CACjD,MAAM,mBAAmB,OAAO,SAAS,GAAG,IAAI,SAAS,GAAG,OAAO;CACnE,MAAM,MAAM,IAAI,IAAI,oCAAoC,gBAAgB;CACxE,OAAO,IAAI,SAAS;AACtB;AAEA,OAAO,eAAe,qBACpB,QACA,YAA0B,OAC1B,YAAY,MACW;CACvB,MAAM,MAAM,kBAAkB,MAAM;CACpC,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,UAAU,iBAAiB,WAAW,MAAM,GAAG,SAAS;CAE9D,IAAI;EACF,MAAM,WAAW,MAAM,UAAU,KAAK;GACpC,QAAQ;GACR,SAAS,EAAE,QAAQ,mBAAmB;GACtC,QAAQ,WAAW;EACrB,CAAC;EAED,IAAI,CAAC,SAAS,IAAI;GAChB,MAAM,IAAI,MAAM,0BAA0B,SAAS,OAAO,EAAE;EAC9D;EAEA,MAAM,WAAY,MAAM,SAAS,KAAK;;;;EAItC,IAAI,CAAC,SAAS,gBAAgB;GAC5B,MAAM,IAAI,MAAM,yCAAyC;EAC3D;EAEA,OAAO;GACL,QAAQ,SAAS,UAAU;GAC3B,wBAAwB,SAAS;GACjC,gBAAgB,SAAS;GACzB,+BAA+B,SAAS;GACxC,UAAU,SAAS;EACrB;CACF,UAAU;EACR,aAAa,OAAO;CACtB;AACF","names":[],"sources":["../../src/oauth/discovery.ts"],"version":3,"file":"discovery.js","sourceRoot":""}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Strip credentials (`user:pass@`) and the query string from a URL before
|
|
3
|
-
* including it in user-facing error messages or thrown exceptions. The query
|
|
4
|
-
* string can contain access tokens, session ids, or other secrets supplied by
|
|
5
|
-
* caller-controlled config — `baseURL` and `issuer` are not validated as
|
|
6
|
-
* credential-free.
|
|
7
|
-
*/
|
|
8
|
-
export declare function redactUrl(rawUrl: string): string;
|
|
9
|
-
/**
|
|
10
|
-
* Read at most `maxChars` characters from a `Response`'s body without
|
|
11
|
-
* buffering the rest. Cancels the underlying stream once the cap is reached so
|
|
12
|
-
* the network isn't drained on a huge error page.
|
|
13
|
-
*
|
|
14
|
-
* Returns an empty string if the body is unreadable or empty.
|
|
15
|
-
*/
|
|
16
|
-
export declare function readResponseBodyPreview(response: Response, maxChars?: number): Promise<string>;
|
|
17
|
-
/**
|
|
18
|
-
* Mask token/credential substrings inside an arbitrary text body before it
|
|
19
|
-
* lands in a structured log entry. Field-name-based redaction in upstream
|
|
20
|
-
* loggers only matches whole field NAMES — it does not scrub secrets embedded
|
|
21
|
-
* in arbitrary string VALUES (like an IdP error body that echoes back the
|
|
22
|
-
* client_secret it received).
|
|
23
|
-
*
|
|
24
|
-
* Catches:
|
|
25
|
-
* - JSON: `"access_token": "..."` and the other SECRET_FIELD_NAMES
|
|
26
|
-
* - form bodies: `client_secret=...`
|
|
27
|
-
* - Bearer/Basic auth headers
|
|
28
|
-
* - bare JWT-shaped strings
|
|
29
|
-
*
|
|
30
|
-
* Anything not matching stays intact, so error messages keep their diagnostic
|
|
31
|
-
* value (status code, error kind, descriptions, etc.).
|
|
32
|
-
*/
|
|
33
|
-
export declare function scrubSecrets(text: string): string;
|
package/dist/oauth/http-utils.js
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Strip credentials (`user:pass@`) and the query string from a URL before
|
|
3
|
-
* including it in user-facing error messages or thrown exceptions. The query
|
|
4
|
-
* string can contain access tokens, session ids, or other secrets supplied by
|
|
5
|
-
* caller-controlled config — `baseURL` and `issuer` are not validated as
|
|
6
|
-
* credential-free.
|
|
7
|
-
*/
|
|
8
|
-
export function redactUrl(rawUrl) {
|
|
9
|
-
try {
|
|
10
|
-
const url = new URL(rawUrl);
|
|
11
|
-
url.username = "";
|
|
12
|
-
url.password = "";
|
|
13
|
-
url.search = "";
|
|
14
|
-
url.hash = "";
|
|
15
|
-
return url.toString();
|
|
16
|
-
} catch {
|
|
17
|
-
// Not a parseable URL — strip anything that looks like a query and any
|
|
18
|
-
// userinfo separator, then return.
|
|
19
|
-
return rawUrl.replace(/\/\/[^/@]*@/, "//").replace(/\?.*$/, "").replace(/#.*$/, "");
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Read at most `maxChars` characters from a `Response`'s body without
|
|
24
|
-
* buffering the rest. Cancels the underlying stream once the cap is reached so
|
|
25
|
-
* the network isn't drained on a huge error page.
|
|
26
|
-
*
|
|
27
|
-
* Returns an empty string if the body is unreadable or empty.
|
|
28
|
-
*/
|
|
29
|
-
export async function readResponseBodyPreview(response, maxChars = 500) {
|
|
30
|
-
if (!response.body) {
|
|
31
|
-
// Some runtimes attach the body lazily; fall back to text() but still cap.
|
|
32
|
-
try {
|
|
33
|
-
const text = await response.text();
|
|
34
|
-
return text.slice(0, maxChars);
|
|
35
|
-
} catch {
|
|
36
|
-
return "";
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
const reader = response.body.getReader();
|
|
40
|
-
const decoder = new TextDecoder("utf-8", { fatal: false });
|
|
41
|
-
let collected = "";
|
|
42
|
-
try {
|
|
43
|
-
while (collected.length < maxChars) {
|
|
44
|
-
const { done, value } = await reader.read();
|
|
45
|
-
if (done) {
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
if (!value) {
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
collected += decoder.decode(value, { stream: true });
|
|
52
|
-
if (collected.length >= maxChars) {
|
|
53
|
-
collected = collected.slice(0, maxChars);
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
} catch {} finally {
|
|
58
|
-
try {
|
|
59
|
-
await reader.cancel();
|
|
60
|
-
} catch {}
|
|
61
|
-
}
|
|
62
|
-
return collected;
|
|
63
|
-
}
|
|
64
|
-
// Names of token/credential fields commonly echoed back by misbehaving IdPs in
|
|
65
|
-
// error responses. Used both for JSON-style ("name":"value") and form-style
|
|
66
|
-
// (name=value&...) substitution.
|
|
67
|
-
const SECRET_FIELD_NAMES = [
|
|
68
|
-
"access_token",
|
|
69
|
-
"refresh_token",
|
|
70
|
-
"id_token",
|
|
71
|
-
"client_secret",
|
|
72
|
-
"client_assertion",
|
|
73
|
-
"code",
|
|
74
|
-
"device_code",
|
|
75
|
-
"password",
|
|
76
|
-
"assertion",
|
|
77
|
-
"subject_token",
|
|
78
|
-
"actor_token"
|
|
79
|
-
];
|
|
80
|
-
const REDACTED = "[redacted]";
|
|
81
|
-
// Pre-built patterns so we don't re-compile on every call.
|
|
82
|
-
const SECRET_JSON_PATTERN = new RegExp(
|
|
83
|
-
// "name" : "value" (also handles escaped quotes inside value)
|
|
84
|
-
`("(?:${SECRET_FIELD_NAMES.join("|")})"\\s*:\\s*)"(?:\\\\.|[^"\\\\])*"`,
|
|
85
|
-
"gi"
|
|
86
|
-
);
|
|
87
|
-
const SECRET_FORM_PATTERN = new RegExp(
|
|
88
|
-
// name=value (terminated by & or end-of-string)
|
|
89
|
-
`(\\b(?:${SECRET_FIELD_NAMES.join("|")}))=([^&\\s]+)`,
|
|
90
|
-
"gi"
|
|
91
|
-
);
|
|
92
|
-
// Bearer / Basic prefixes in headers/messages, plus bare JWT-shaped strings.
|
|
93
|
-
const BEARER_PATTERN = /\b(Bearer|Basic)\s+([A-Za-z0-9._\-+/=]+)/g;
|
|
94
|
-
const JWT_PATTERN = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g;
|
|
95
|
-
/**
|
|
96
|
-
* Mask token/credential substrings inside an arbitrary text body before it
|
|
97
|
-
* lands in a structured log entry. Field-name-based redaction in upstream
|
|
98
|
-
* loggers only matches whole field NAMES — it does not scrub secrets embedded
|
|
99
|
-
* in arbitrary string VALUES (like an IdP error body that echoes back the
|
|
100
|
-
* client_secret it received).
|
|
101
|
-
*
|
|
102
|
-
* Catches:
|
|
103
|
-
* - JSON: `"access_token": "..."` and the other SECRET_FIELD_NAMES
|
|
104
|
-
* - form bodies: `client_secret=...`
|
|
105
|
-
* - Bearer/Basic auth headers
|
|
106
|
-
* - bare JWT-shaped strings
|
|
107
|
-
*
|
|
108
|
-
* Anything not matching stays intact, so error messages keep their diagnostic
|
|
109
|
-
* value (status code, error kind, descriptions, etc.).
|
|
110
|
-
*/
|
|
111
|
-
export function scrubSecrets(text) {
|
|
112
|
-
if (!text) {
|
|
113
|
-
return text;
|
|
114
|
-
}
|
|
115
|
-
return text.replace(SECRET_JSON_PATTERN, `$1"${REDACTED}"`).replace(SECRET_FORM_PATTERN, `$1=${REDACTED}`).replace(BEARER_PATTERN, `$1 ${REDACTED}`).replace(JWT_PATTERN, REDACTED);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
//# sourceMappingURL=http-utils.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"mappings":";;;;;;;AAOA,OAAO,SAAS,UAAU,QAAwB;CAChD,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,MAAM;EAC1B,IAAI,WAAW;EACf,IAAI,WAAW;EACf,IAAI,SAAS;EACb,IAAI,OAAO;EACX,OAAO,IAAI,SAAS;CACtB,QAAQ;;;EAGN,OAAO,OACJ,QAAQ,eAAe,IAAI,CAAC,CAC5B,QAAQ,SAAS,EAAE,CAAC,CACpB,QAAQ,QAAQ,EAAE;CACvB;AACF;;;;;;;;AASA,OAAO,eAAe,wBAAwB,UAAoB,WAAW,KAAsB;CACjG,IAAI,CAAC,SAAS,MAAM;;EAElB,IAAI;GACF,MAAM,OAAO,MAAM,SAAS,KAAK;GACjC,OAAO,KAAK,MAAM,GAAG,QAAQ;EAC/B,QAAQ;GACN,OAAO;EACT;CACF;CAEA,MAAM,SAAS,SAAS,KAAK,UAAU;CACvC,MAAM,UAAU,IAAI,YAAY,SAAS,EAAE,OAAO,MAAM,CAAC;CACzD,IAAI,YAAY;CAEhB,IAAI;EACF,OAAO,UAAU,SAAS,UAAU;GAClC,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;GAC1C,IAAI,MAAM;IACR;GACF;GACA,IAAI,CAAC,OAAO;IACV;GACF;GACA,aAAa,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;GACnD,IAAI,UAAU,UAAU,UAAU;IAChC,YAAY,UAAU,MAAM,GAAG,QAAQ;IACvC;GACF;EACF;CACF,QAAQ,CAER,UAAU;EACR,IAAI;GACF,MAAM,OAAO,OAAO;EACtB,QAAQ,CAER;CACF;CAEA,OAAO;AACT;;;;AAKA,MAAM,qBAAqB;CACzB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,MAAM,WAAW;;AAGjB,MAAM,sBAAsB,IAAI;;CAE9B,QAAQ,mBAAmB,KAAK,GAAG,EAAE;CACrC;AACF;AACA,MAAM,sBAAsB,IAAI;;CAE9B,UAAU,mBAAmB,KAAK,GAAG,EAAE;CACvC;AACF;;AAEA,MAAM,iBAAiB;AACvB,MAAM,cAAc;;;;;;;;;;;;;;;;;AAkBpB,OAAO,SAAS,aAAa,MAAsB;CACjD,IAAI,CAAC,MAAM;EACT,OAAO;CACT;CACA,OAAO,KACJ,QAAQ,qBAAqB,MAAM,SAAS,EAAE,CAAC,CAC/C,QAAQ,qBAAqB,MAAM,UAAU,CAAC,CAC9C,QAAQ,gBAAgB,MAAM,UAAU,CAAC,CACzC,QAAQ,aAAa,QAAQ;AAClC","names":[],"sources":["../../src/oauth/http-utils.ts"],"version":3,"file":"http-utils.js","sourceRoot":""}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export interface OAuthCallbackResult {
|
|
2
|
-
code: string;
|
|
3
|
-
state: string;
|
|
4
|
-
}
|
|
5
|
-
export interface LocalCallbackServer {
|
|
6
|
-
redirectUri: string;
|
|
7
|
-
waitForCode: (timeoutMs?: number) => Promise<OAuthCallbackResult>;
|
|
8
|
-
close: () => Promise<void>;
|
|
9
|
-
}
|
|
10
|
-
export declare function startLocalCallbackServer(callbackPath?: string, port?: number): Promise<LocalCallbackServer>;
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { createServer } from "node:http";
|
|
2
|
-
function writeHtml(response, statusCode, body) {
|
|
3
|
-
response.statusCode = statusCode;
|
|
4
|
-
response.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
5
|
-
response.end(body);
|
|
6
|
-
}
|
|
7
|
-
function parseCallback(request) {
|
|
8
|
-
if (!request.url) {
|
|
9
|
-
return undefined;
|
|
10
|
-
}
|
|
11
|
-
const url = new URL(request.url, "http://127.0.0.1");
|
|
12
|
-
const code = url.searchParams.get("code");
|
|
13
|
-
const state = url.searchParams.get("state");
|
|
14
|
-
if (!code || !state) {
|
|
15
|
-
return undefined;
|
|
16
|
-
}
|
|
17
|
-
return {
|
|
18
|
-
code,
|
|
19
|
-
state
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
export async function startLocalCallbackServer(callbackPath = "/oauth2/callback", port) {
|
|
23
|
-
let resolver;
|
|
24
|
-
let rejecter;
|
|
25
|
-
const promise = new Promise((resolve, reject) => {
|
|
26
|
-
resolver = resolve;
|
|
27
|
-
rejecter = reject;
|
|
28
|
-
});
|
|
29
|
-
const server = createServer((request, response) => {
|
|
30
|
-
const requestUrl = request.url ?? "";
|
|
31
|
-
const parsed = new URL(requestUrl, "http://127.0.0.1");
|
|
32
|
-
if (parsed.pathname !== callbackPath) {
|
|
33
|
-
writeHtml(response, 404, "<h1>Not Found</h1>");
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
const payload = parseCallback(request);
|
|
37
|
-
if (!payload) {
|
|
38
|
-
writeHtml(response, 400, "<h1>Invalid OAuth callback</h1>");
|
|
39
|
-
rejecter?.(new Error("invalid oauth callback payload"));
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
writeHtml(response, 200, "<h1>Login complete</h1><p>You can close this tab.</p>");
|
|
43
|
-
resolver?.(payload);
|
|
44
|
-
});
|
|
45
|
-
const listenPort = typeof port === "number" && port >= 0 ? port : 0;
|
|
46
|
-
await new Promise((resolve, reject) => {
|
|
47
|
-
server.once("error", reject);
|
|
48
|
-
server.listen(listenPort, "127.0.0.1", () => resolve());
|
|
49
|
-
});
|
|
50
|
-
const address = server.address();
|
|
51
|
-
if (!address || typeof address === "string") {
|
|
52
|
-
throw new Error("failed to allocate local callback port");
|
|
53
|
-
}
|
|
54
|
-
return {
|
|
55
|
-
redirectUri: `http://127.0.0.1:${address.port}${callbackPath}`,
|
|
56
|
-
waitForCode(timeoutMs = 12e4) {
|
|
57
|
-
return new Promise((resolve, reject) => {
|
|
58
|
-
const timeout = setTimeout(() => {
|
|
59
|
-
reject(new Error("timed out waiting for OAuth callback"));
|
|
60
|
-
}, timeoutMs);
|
|
61
|
-
promise.then((value) => {
|
|
62
|
-
clearTimeout(timeout);
|
|
63
|
-
resolve(value);
|
|
64
|
-
}).catch((error) => {
|
|
65
|
-
clearTimeout(timeout);
|
|
66
|
-
reject(error);
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
},
|
|
70
|
-
close() {
|
|
71
|
-
return new Promise((resolve, reject) => {
|
|
72
|
-
server.close((error) => {
|
|
73
|
-
if (error) {
|
|
74
|
-
reject(error);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
resolve();
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
//# sourceMappingURL=local-callback.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"mappings":"AAAA,SAAS,oBAA+D;AAaxE,SAAS,UAAU,UAA0B,YAAoB,MAAoB;CACnF,SAAS,aAAa;CACtB,SAAS,UAAU,gBAAgB,0BAA0B;CAC7D,SAAS,IAAI,IAAI;AACnB;AAEA,SAAS,cAAc,SAA2D;CAChF,IAAI,CAAC,QAAQ,KAAK;EAChB,OAAO;CACT;CAEA,MAAM,MAAM,IAAI,IAAI,QAAQ,KAAK,kBAAkB;CACnD,MAAM,OAAO,IAAI,aAAa,IAAI,MAAM;CACxC,MAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;CAE1C,IAAI,CAAC,QAAQ,CAAC,OAAO;EACnB,OAAO;CACT;CAEA,OAAO;EAAE;EAAM;CAAM;AACvB;AAEA,OAAO,eAAe,yBACpB,eAAe,oBACf,MAC8B;CAC9B,IAAI;CACJ,IAAI;CAEJ,MAAM,UAAU,IAAI,SAA8B,SAAS,WAAW;EACpE,WAAW;EACX,WAAW;CACb,CAAC;CAED,MAAM,SAAS,cAAc,SAAS,aAAa;EACjD,MAAM,aAAa,QAAQ,OAAO;EAClC,MAAM,SAAS,IAAI,IAAI,YAAY,kBAAkB;EAErD,IAAI,OAAO,aAAa,cAAc;GACpC,UAAU,UAAU,KAAK,oBAAoB;GAC7C;EACF;EAEA,MAAM,UAAU,cAAc,OAAO;EACrC,IAAI,CAAC,SAAS;GACZ,UAAU,UAAU,KAAK,iCAAiC;GAC1D,WAAW,IAAI,MAAM,gCAAgC,CAAC;GACtD;EACF;EAEA,UAAU,UAAU,KAAK,uDAAuD;EAChF,WAAW,OAAO;CACpB,CAAC;CAED,MAAM,aAAa,OAAO,SAAS,YAAY,QAAQ,IAAI,OAAO;CAClE,MAAM,IAAI,SAAe,SAAS,WAAW;EAC3C,OAAO,KAAK,SAAS,MAAM;EAC3B,OAAO,OAAO,YAAY,mBAAmB,QAAQ,CAAC;CACxD,CAAC;CAED,MAAM,UAAU,OAAO,QAAQ;CAC/B,IAAI,CAAC,WAAW,OAAO,YAAY,UAAU;EAC3C,MAAM,IAAI,MAAM,wCAAwC;CAC1D;CAEA,OAAO;EACL,aAAa,oBAAoB,QAAQ,OAAO;EAChD,YAAY,YAAY,MAAS;GAC/B,OAAO,IAAI,SAA8B,SAAS,WAAW;IAC3D,MAAM,UAAU,iBAAiB;KAC/B,OAAO,IAAI,MAAM,sCAAsC,CAAC;IAC1D,GAAG,SAAS;IAEZ,QACG,MAAM,UAAU;KACf,aAAa,OAAO;KACpB,QAAQ,KAAK;IACf,CAAC,CAAC,CACD,OAAO,UAAU;KAChB,aAAa,OAAO;KACpB,OAAO,KAAK;IACd,CAAC;GACL,CAAC;EACH;EACA,QAAQ;GACN,OAAO,IAAI,SAAe,SAAS,WAAW;IAC5C,OAAO,OAAO,UAAU;KACtB,IAAI,OAAO;MACT,OAAO,KAAK;MACZ;KACF;KACA,QAAQ;IACV,CAAC;GACH,CAAC;EACH;CACF;AACF","names":[],"sources":["../../src/oauth/local-callback.ts"],"version":3,"file":"local-callback.js","sourceRoot":""}
|
package/dist/oauth/pkce.d.ts
DELETED
package/dist/oauth/pkce.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
-
function toBase64Url(buffer) {
|
|
3
|
-
return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
4
|
-
}
|
|
5
|
-
export function generatePkcePair() {
|
|
6
|
-
const verifier = toBase64Url(randomBytes(32));
|
|
7
|
-
const challenge = toBase64Url(createHash("sha256").update(verifier).digest());
|
|
8
|
-
return {
|
|
9
|
-
verifier,
|
|
10
|
-
challenge
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
export function generateStateToken() {
|
|
14
|
-
return toBase64Url(randomBytes(24));
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
//# sourceMappingURL=pkce.js.map
|
package/dist/oauth/pkce.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"mappings":"AAAA,SAAS,YAAY,mBAAmB;AAExC,SAAS,YAAY,QAAwB;CAC3C,OAAO,OAAO,SAAS,QAAQ,CAAC,CAAC,QAAQ,OAAO,GAAG,CAAC,CAAC,QAAQ,OAAO,GAAG,CAAC,CAAC,QAAQ,QAAQ,EAAE;AAC7F;AAEA,OAAO,SAAS,mBAA4D;CAC1E,MAAM,WAAW,YAAY,YAAY,EAAE,CAAC;CAC5C,MAAM,YAAY,YAAY,WAAW,QAAQ,CAAC,CAAC,OAAO,QAAQ,CAAC,CAAC,OAAO,CAAC;CAE5E,OAAO;EAAE;EAAU;CAAU;AAC/B;AAEA,OAAO,SAAS,qBAA6B;CAC3C,OAAO,YAAY,YAAY,EAAE,CAAC;AACpC","names":[],"sources":["../../src/oauth/pkce.ts"],"version":3,"file":"pkce.js","sourceRoot":""}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { type SubjectTokenSource } from "../config.js";
|
|
2
|
-
export interface ResolveSubjectTokenOptions {
|
|
3
|
-
fetchImpl?: typeof fetch;
|
|
4
|
-
timeoutMs?: number;
|
|
5
|
-
/**
|
|
6
|
-
* Override process.env. Lets tests inject GHA-style env vars without
|
|
7
|
-
* mutating the real environment.
|
|
8
|
-
*/
|
|
9
|
-
env?: Record<string, string | undefined>;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Read the platform-supplied JWT that the plugin will present as the subject
|
|
13
|
-
* token (or assertion) for the `jwt_bearer` and `token_exchange` flows.
|
|
14
|
-
*
|
|
15
|
-
* Each source resolves fresh on every call — we never cache the JWT itself,
|
|
16
|
-
* only the OIDC access token it gets exchanged for. K8s projected SA tokens
|
|
17
|
-
* rotate; GHA OIDC tokens are short-lived (~10 min); both are cheap to
|
|
18
|
-
* re-fetch on demand.
|
|
19
|
-
*/
|
|
20
|
-
export declare function resolveSubjectToken(source: SubjectTokenSource, options?: ResolveSubjectTokenOptions): Promise<string>;
|