@rigkit/provider-freestyle 0.2.8 → 0.2.10
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/package.json +6 -6
- package/src/host-auth.test.ts +125 -9
- package/src/host-auth.ts +191 -6
- package/src/version.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigkit/provider-freestyle",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,16 +17,16 @@
|
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"zod": "^4",
|
|
20
|
-
"@rigkit/sdk": "0.2.
|
|
21
|
-
"@rigkit/engine": "0.2.
|
|
22
|
-
"@rigkit/provider-cmux": "0.2.
|
|
20
|
+
"@rigkit/sdk": "0.2.10",
|
|
21
|
+
"@rigkit/engine": "0.2.10",
|
|
22
|
+
"@rigkit/provider-cmux": "0.2.10"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"freestyle": "^0.1.
|
|
25
|
+
"freestyle": "^0.1.52"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/bun": "latest",
|
|
29
|
-
"freestyle": "^0.1.
|
|
29
|
+
"freestyle": "^0.1.52",
|
|
30
30
|
"typescript": "latest"
|
|
31
31
|
},
|
|
32
32
|
"publishConfig": {
|
package/src/host-auth.test.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type {
|
|
|
8
8
|
WorkflowEvent,
|
|
9
9
|
} from "@rigkit/engine";
|
|
10
10
|
import { FREESTYLE_PROVIDER_ID, freestyle, freestyleProviderPlugin } from "./index.ts";
|
|
11
|
-
import { createFreestyleProxyFetch } from "./host-auth.ts";
|
|
11
|
+
import { createFreestyleProxyFetch, createFreestyleSdkFetch } from "./host-auth.ts";
|
|
12
12
|
import type { FreestyleRuntime } from "./provider.ts";
|
|
13
13
|
import { RIGKIT_PROVIDER_FREESTYLE_VERSION } from "./version.ts";
|
|
14
14
|
|
|
@@ -293,6 +293,102 @@ describe("Freestyle provider host auth", () => {
|
|
|
293
293
|
});
|
|
294
294
|
|
|
295
295
|
describe("Freestyle provider proxy fetch", () => {
|
|
296
|
+
test("logs a replayable API-key fetch with the Freestyle API key redacted", async () => {
|
|
297
|
+
const sdkFetch = createFreestyleSdkFetch(testFetch(async () =>
|
|
298
|
+
Response.json({
|
|
299
|
+
code: "INTERNAL_ERROR",
|
|
300
|
+
message: "Internal server error",
|
|
301
|
+
}, { status: 500, statusText: "Internal Server Error" })
|
|
302
|
+
));
|
|
303
|
+
|
|
304
|
+
const messages = await captureConsoleError(async () => {
|
|
305
|
+
const response = await sdkFetch("https://api.freestyle.sh/v1/vms", {
|
|
306
|
+
method: "POST",
|
|
307
|
+
headers: {
|
|
308
|
+
Authorization: "Bearer real-api-key",
|
|
309
|
+
"Content-Type": "application/json",
|
|
310
|
+
},
|
|
311
|
+
body: JSON.stringify({
|
|
312
|
+
image: "ubuntu-24.04",
|
|
313
|
+
apiKey: "body-api-key",
|
|
314
|
+
}),
|
|
315
|
+
});
|
|
316
|
+
expect(response.status).toBe(500);
|
|
317
|
+
await response.text();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
expect(messages).toHaveLength(1);
|
|
321
|
+
expect(messages[0]).toContain('await fetch("https://api.freestyle.sh/v1/vms", {');
|
|
322
|
+
expect(messages[0]).toContain('"Authorization": "Bearer <redacted FREESTYLE_API_KEY>"');
|
|
323
|
+
expect(messages[0]).toContain('"image": "ubuntu-24.04"');
|
|
324
|
+
expect(messages[0]).toContain('"apiKey": "[redacted]"');
|
|
325
|
+
expect(messages[0]).toContain('Response: 500 Internal Server Error');
|
|
326
|
+
expect(messages[0]).not.toContain("real-api-key");
|
|
327
|
+
expect(messages[0]).not.toContain("body-api-key");
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test("logs the original replayable request when a background request fails through the proxy", async () => {
|
|
331
|
+
const proxyFetch = createFreestyleProxyFetch({
|
|
332
|
+
dashboardUrl: "https://dash.freestyle.sh",
|
|
333
|
+
accessToken: "stack-access-token",
|
|
334
|
+
teamId: "team_123",
|
|
335
|
+
fetch: testFetch(async (resource, init) => {
|
|
336
|
+
const url = resourceUrl(resource);
|
|
337
|
+
expect(url.href).toBe("https://dash.freestyle.sh/api/proxy/request");
|
|
338
|
+
const body = JSON.parse(String(init?.body));
|
|
339
|
+
if (body.data.path === "v1/vms") {
|
|
340
|
+
return Response.json({
|
|
341
|
+
requestId: "ri_test_123",
|
|
342
|
+
status: "pending",
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
if (body.data.path === "auth/v1/background-requests/ri_test_123") {
|
|
346
|
+
return Response.json({
|
|
347
|
+
code: "INTERNAL_ERROR",
|
|
348
|
+
message: "Internal server error",
|
|
349
|
+
accessToken: "should-redact",
|
|
350
|
+
}, { status: 500, statusText: "Internal Server Error" });
|
|
351
|
+
}
|
|
352
|
+
return Response.json({ error: "unexpected request", body }, { status: 500 });
|
|
353
|
+
}),
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const first = await proxyFetch("https://api.freestyle.sh/v1/vms", {
|
|
357
|
+
method: "POST",
|
|
358
|
+
headers: {
|
|
359
|
+
Authorization: "Bearer rigkit-browser-auth",
|
|
360
|
+
"Content-Type": "application/json",
|
|
361
|
+
},
|
|
362
|
+
body: JSON.stringify({
|
|
363
|
+
image: "ubuntu-24.04",
|
|
364
|
+
}),
|
|
365
|
+
});
|
|
366
|
+
expect(first.status).toBe(202);
|
|
367
|
+
|
|
368
|
+
const messages = await captureConsoleError(async () => {
|
|
369
|
+
const failed = await proxyFetch("https://api.freestyle.sh/auth/v1/background-requests/ri_test_123", {
|
|
370
|
+
method: "GET",
|
|
371
|
+
headers: {
|
|
372
|
+
Authorization: "Bearer rigkit-browser-auth",
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
expect(failed.status).toBe(500);
|
|
376
|
+
await failed.text();
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
expect(messages).toHaveLength(1);
|
|
380
|
+
expect(messages[0]).toContain("Freestyle background request ri_test_123 failed. Original API request:");
|
|
381
|
+
expect(messages[0]).toContain('await fetch("https://api.freestyle.sh/v1/vms", {');
|
|
382
|
+
expect(messages[0]).toContain('method: "POST"');
|
|
383
|
+
expect(messages[0]).toContain('"Authorization": "Bearer <redacted FREESTYLE_API_KEY>"');
|
|
384
|
+
expect(messages[0]).toContain('"image": "ubuntu-24.04"');
|
|
385
|
+
expect(messages[0]).toContain('Response: 500 Internal Server Error');
|
|
386
|
+
expect(messages[0]).toContain('"accessToken":"[redacted]"');
|
|
387
|
+
expect(messages[0]).not.toContain("stack-access-token");
|
|
388
|
+
expect(messages[0]).not.toContain("rigkit-browser-auth");
|
|
389
|
+
expect(messages[0]).not.toContain("should-redact");
|
|
390
|
+
});
|
|
391
|
+
|
|
296
392
|
test("preserves Freestyle background request semantics through the browser-auth proxy", async () => {
|
|
297
393
|
const proxyFetch = createFreestyleProxyFetch({
|
|
298
394
|
dashboardUrl: "https://dash.freestyle.sh",
|
|
@@ -356,13 +452,16 @@ describe("Freestyle provider proxy fetch", () => {
|
|
|
356
452
|
),
|
|
357
453
|
});
|
|
358
454
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
455
|
+
let response: Response | undefined;
|
|
456
|
+
await captureConsoleError(async () => {
|
|
457
|
+
response = await proxyFetch("https://api.freestyle.sh/v1/vms", {
|
|
458
|
+
method: "POST",
|
|
459
|
+
body: "{}",
|
|
460
|
+
});
|
|
362
461
|
});
|
|
363
462
|
|
|
364
|
-
expect(response
|
|
365
|
-
await expect(response
|
|
463
|
+
expect(response?.status).toBe(500);
|
|
464
|
+
await expect(response?.json()).resolves.toEqual({
|
|
366
465
|
code: "INTERNAL_ERROR",
|
|
367
466
|
message: "VM setup failed",
|
|
368
467
|
details: {
|
|
@@ -389,10 +488,13 @@ describe("Freestyle provider proxy fetch", () => {
|
|
|
389
488
|
),
|
|
390
489
|
});
|
|
391
490
|
|
|
392
|
-
|
|
491
|
+
let response: Response | undefined;
|
|
492
|
+
await captureConsoleError(async () => {
|
|
493
|
+
response = await proxyFetch("https://api.freestyle.sh/v1/vms");
|
|
494
|
+
});
|
|
393
495
|
|
|
394
|
-
expect(response
|
|
395
|
-
await expect(response
|
|
496
|
+
expect(response?.status).toBe(500);
|
|
497
|
+
await expect(response?.json()).resolves.toEqual({
|
|
396
498
|
code: "INTERNAL_ERROR",
|
|
397
499
|
message: "Internal server error",
|
|
398
500
|
requestId: "req_123",
|
|
@@ -475,6 +577,20 @@ function resourceUrl(resource: Parameters<typeof fetch>[0]): URL {
|
|
|
475
577
|
return new URL(resource.url);
|
|
476
578
|
}
|
|
477
579
|
|
|
580
|
+
async function captureConsoleError(action: () => Promise<void>): Promise<string[]> {
|
|
581
|
+
const previous = console.error;
|
|
582
|
+
const messages: string[] = [];
|
|
583
|
+
console.error = (...args: unknown[]) => {
|
|
584
|
+
messages.push(args.map((arg) => String(arg)).join(" "));
|
|
585
|
+
};
|
|
586
|
+
try {
|
|
587
|
+
await action();
|
|
588
|
+
} finally {
|
|
589
|
+
console.error = previous;
|
|
590
|
+
}
|
|
591
|
+
return messages;
|
|
592
|
+
}
|
|
593
|
+
|
|
478
594
|
function setEnv(name: string, value: string | undefined): void {
|
|
479
595
|
if (value === undefined) {
|
|
480
596
|
delete process.env[name];
|
package/src/host-auth.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { RIGKIT_PROVIDER_FREESTYLE_VERSION } from "./version.ts";
|
|
|
8
8
|
|
|
9
9
|
const DEFAULT_STACK_API_URL = "https://api.stack-auth.com";
|
|
10
10
|
const DEFAULT_STACK_APP_URL = "https://dash.freestyle.sh";
|
|
11
|
+
const DEFAULT_FREESTYLE_API_URL = "https://api.freestyle.sh";
|
|
11
12
|
const DEFAULT_STACK_PROJECT_ID = "0edf478c-f123-46fb-818f-34c0024a9f35";
|
|
12
13
|
const DEFAULT_STACK_PUBLISHABLE_CLIENT_KEY = "pck_h2aft7g9pqjzrkdnzs199h1may5wjtdtdxeex7m2wzp1r";
|
|
13
14
|
const DEFAULT_CLI_AUTH_TIMEOUT_MILLIS = 10 * 60 * 1000;
|
|
@@ -112,10 +113,15 @@ export function createFreestyleProxyFetch(input: {
|
|
|
112
113
|
}): typeof fetch {
|
|
113
114
|
const fetchFn = input.fetch ?? globalThis.fetch;
|
|
114
115
|
const dashboardUrl = trimTrailingSlash(input.dashboardUrl);
|
|
116
|
+
const backgroundRequests = new Map<string, string>();
|
|
115
117
|
|
|
116
118
|
const proxyFetch = async (resource: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]) => {
|
|
117
119
|
const url = resourceUrl(resource);
|
|
118
120
|
const path = `${url.pathname}${url.search}`.replace(/^\/+/, "");
|
|
121
|
+
const freestyleRequestInit: RequestInit = {
|
|
122
|
+
...init,
|
|
123
|
+
headers: withRigkitHeaders(init?.headers),
|
|
124
|
+
};
|
|
119
125
|
const proxyResponse = await fetchFn(`${dashboardUrl}/api/proxy/request`, {
|
|
120
126
|
method: "POST",
|
|
121
127
|
headers: withRigkitHeaders({
|
|
@@ -126,8 +132,8 @@ export function createFreestyleProxyFetch(input: {
|
|
|
126
132
|
accessToken: input.accessToken,
|
|
127
133
|
teamId: input.teamId,
|
|
128
134
|
path,
|
|
129
|
-
method: init
|
|
130
|
-
headers: Object.fromEntries(
|
|
135
|
+
method: resolveRequestMethod(resource, init),
|
|
136
|
+
headers: Object.fromEntries(new Headers(freestyleRequestInit.headers).entries()),
|
|
131
137
|
body: init?.body ? String(init.body) : undefined,
|
|
132
138
|
},
|
|
133
139
|
}),
|
|
@@ -135,6 +141,13 @@ export function createFreestyleProxyFetch(input: {
|
|
|
135
141
|
|
|
136
142
|
if (!proxyResponse.ok) {
|
|
137
143
|
const errorText = await proxyResponse.text();
|
|
144
|
+
await logFreestyleApiRequestFailure({
|
|
145
|
+
backgroundRequests,
|
|
146
|
+
resource,
|
|
147
|
+
init: freestyleRequestInit,
|
|
148
|
+
response: proxyResponse,
|
|
149
|
+
responseText: errorText,
|
|
150
|
+
});
|
|
138
151
|
const normalized = normalizeProxyError(errorText, proxyResponse.status);
|
|
139
152
|
return new Response(normalized.body, {
|
|
140
153
|
status: proxyResponse.status,
|
|
@@ -146,6 +159,9 @@ export function createFreestyleProxyFetch(input: {
|
|
|
146
159
|
const data = await proxyResponse.json();
|
|
147
160
|
if (isBackgroundRequestPending(data)) {
|
|
148
161
|
const requestId = backgroundRequestId(data);
|
|
162
|
+
if (requestId) {
|
|
163
|
+
backgroundRequests.set(requestId, formatReplayableFetchRequest(resource, freestyleRequestInit));
|
|
164
|
+
}
|
|
149
165
|
return Response.json(data, {
|
|
150
166
|
status: 202,
|
|
151
167
|
headers: {
|
|
@@ -162,11 +178,24 @@ export function createFreestyleProxyFetch(input: {
|
|
|
162
178
|
}
|
|
163
179
|
|
|
164
180
|
export function createFreestyleSdkFetch(fetchFn: typeof fetch = globalThis.fetch): typeof fetch {
|
|
165
|
-
const
|
|
166
|
-
|
|
181
|
+
const backgroundRequests = new Map<string, string>();
|
|
182
|
+
const rigkitFetch = (async (resource, init) => {
|
|
183
|
+
const requestInit: RequestInit = {
|
|
167
184
|
...init,
|
|
168
185
|
headers: withRigkitHeaders(init?.headers),
|
|
169
|
-
}
|
|
186
|
+
};
|
|
187
|
+
const response = await fetchFn(resource, requestInit);
|
|
188
|
+
await rememberBackgroundRequest(backgroundRequests, resource, requestInit, response);
|
|
189
|
+
if (!response.ok) {
|
|
190
|
+
await logFreestyleApiRequestFailure({
|
|
191
|
+
backgroundRequests,
|
|
192
|
+
resource,
|
|
193
|
+
init: requestInit,
|
|
194
|
+
response,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return response;
|
|
198
|
+
}) as typeof fetch;
|
|
170
199
|
return Object.assign(rigkitFetch, {
|
|
171
200
|
preconnect: fetchFn.preconnect?.bind(fetchFn) ?? (() => {}),
|
|
172
201
|
}) as typeof fetch;
|
|
@@ -269,6 +298,162 @@ function withRigkitHeaders(headers: HeadersInit | undefined): Headers {
|
|
|
269
298
|
return next;
|
|
270
299
|
}
|
|
271
300
|
|
|
301
|
+
async function rememberBackgroundRequest(
|
|
302
|
+
backgroundRequests: Map<string, string>,
|
|
303
|
+
resource: Parameters<typeof fetch>[0],
|
|
304
|
+
init: RequestInit,
|
|
305
|
+
response: Response,
|
|
306
|
+
): Promise<void> {
|
|
307
|
+
if (response.status !== 202) return;
|
|
308
|
+
const requestId = await responseBackgroundRequestId(response);
|
|
309
|
+
if (!requestId) return;
|
|
310
|
+
backgroundRequests.set(requestId, formatReplayableFetchRequest(resource, init));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function logFreestyleApiRequestFailure(input: {
|
|
314
|
+
backgroundRequests: Map<string, string>;
|
|
315
|
+
resource: Parameters<typeof fetch>[0];
|
|
316
|
+
init: RequestInit;
|
|
317
|
+
response: Response;
|
|
318
|
+
responseText?: string;
|
|
319
|
+
}): Promise<void> {
|
|
320
|
+
if (isFreestyleBackgroundLogRequest(input.resource)) return;
|
|
321
|
+
|
|
322
|
+
const requestId = backgroundRequestIdFromResource(input.resource);
|
|
323
|
+
const replayRequest = requestId ? input.backgroundRequests.get(requestId) : undefined;
|
|
324
|
+
const responseSummary = await formatResponseSummary(input.response, input.responseText);
|
|
325
|
+
const heading = requestId
|
|
326
|
+
? `Freestyle background request ${requestId} failed. Original API request:`
|
|
327
|
+
: "Freestyle API request failed. Replay request:";
|
|
328
|
+
const request = replayRequest ?? formatReplayableFetchRequest(input.resource, input.init);
|
|
329
|
+
console.error(`${heading}\n${request}\n${responseSummary}`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async function responseBackgroundRequestId(response: Response): Promise<string | undefined> {
|
|
333
|
+
const header = response.headers.get("x-freestyle-background-request-id");
|
|
334
|
+
if (header) return header;
|
|
335
|
+
const data = await response.clone().json().catch(() => undefined);
|
|
336
|
+
return backgroundRequestId(data);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function backgroundRequestIdFromResource(resource: Parameters<typeof fetch>[0]): string | undefined {
|
|
340
|
+
const path = resourceUrl(resource).pathname;
|
|
341
|
+
const match = path.match(/\/auth\/v1\/background-requests\/([^/]+)$/);
|
|
342
|
+
return match?.[1] ? decodeURIComponent(match[1]) : undefined;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function isFreestyleBackgroundLogRequest(resource: Parameters<typeof fetch>[0]): boolean {
|
|
346
|
+
return resourceUrl(resource).pathname === "/observability/v1/logs";
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function formatResponseSummary(response: Response, responseText?: string): Promise<string> {
|
|
350
|
+
const text = responseText ?? await response.clone().text().catch(() => "");
|
|
351
|
+
const status = [response.status, response.statusText].filter(Boolean).join(" ");
|
|
352
|
+
const redactedBody = formatRedactedResponseBody(text);
|
|
353
|
+
return [
|
|
354
|
+
`Response: ${status}`,
|
|
355
|
+
...(redactedBody ? [`Response body: ${redactedBody}`] : []),
|
|
356
|
+
].join("\n");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function formatRedactedResponseBody(text: string): string | undefined {
|
|
360
|
+
if (!text) return undefined;
|
|
361
|
+
try {
|
|
362
|
+
return JSON.stringify(redactSensitiveFields(JSON.parse(text)));
|
|
363
|
+
} catch {
|
|
364
|
+
return text;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function formatReplayableFetchRequest(resource: Parameters<typeof fetch>[0], init: RequestInit): string {
|
|
369
|
+
const lines = [
|
|
370
|
+
`await fetch(${JSON.stringify(resourceUrl(resource).href)}, {`,
|
|
371
|
+
` method: ${JSON.stringify(resolveRequestMethod(resource, init))},`,
|
|
372
|
+
];
|
|
373
|
+
const headers = replayableHeaders(resource, init);
|
|
374
|
+
if (Object.keys(headers).length > 0) {
|
|
375
|
+
lines.push(` headers: ${indentContinuation(JSON.stringify(headers, null, 2), 2)},`);
|
|
376
|
+
}
|
|
377
|
+
const body = replayableBody(init.body);
|
|
378
|
+
if (body) {
|
|
379
|
+
lines.push(` body: ${indentContinuation(body, 2)},`);
|
|
380
|
+
}
|
|
381
|
+
lines.push("});");
|
|
382
|
+
return lines.join("\n");
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function replayableHeaders(resource: Parameters<typeof fetch>[0], init: RequestInit): Record<string, string> {
|
|
386
|
+
const headers = resource instanceof Request ? new Headers(resource.headers) : new Headers();
|
|
387
|
+
new Headers(init.headers).forEach((value, key) => {
|
|
388
|
+
headers.set(key, value);
|
|
389
|
+
});
|
|
390
|
+
const result: Record<string, string> = {};
|
|
391
|
+
headers.forEach((value, key) => {
|
|
392
|
+
result[displayHeaderName(key)] = redactHeaderValue(key, value);
|
|
393
|
+
});
|
|
394
|
+
return result;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function resolveRequestMethod(resource: Parameters<typeof fetch>[0], init?: RequestInit): string {
|
|
398
|
+
return init?.method ?? (resource instanceof Request ? resource.method : "GET");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function replayableBody(body: BodyInit | null | undefined): string | undefined {
|
|
402
|
+
if (body === undefined || body === null) return undefined;
|
|
403
|
+
if (typeof body === "string") {
|
|
404
|
+
try {
|
|
405
|
+
const parsed = JSON.parse(body) as unknown;
|
|
406
|
+
return `JSON.stringify(${JSON.stringify(redactSensitiveFields(parsed), null, 2)})`;
|
|
407
|
+
} catch {
|
|
408
|
+
return JSON.stringify(body);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (body instanceof URLSearchParams) {
|
|
412
|
+
return `new URLSearchParams(${JSON.stringify(body.toString())})`;
|
|
413
|
+
}
|
|
414
|
+
return JSON.stringify(String(body));
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function indentContinuation(value: string, spaces: number): string {
|
|
418
|
+
const lines = value.split("\n");
|
|
419
|
+
const indent = " ".repeat(spaces);
|
|
420
|
+
return [lines[0], ...lines.slice(1).map((line) => `${indent}${line}`)].join("\n");
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function displayHeaderName(name: string): string {
|
|
424
|
+
const normalized = name.toLowerCase();
|
|
425
|
+
return {
|
|
426
|
+
authorization: "Authorization",
|
|
427
|
+
"content-type": "Content-Type",
|
|
428
|
+
"user-agent": "User-Agent",
|
|
429
|
+
"x-freestyle-identity-access-token": "X-Freestyle-Identity-Access-Token",
|
|
430
|
+
[RIGKIT_HEADER]: RIGKIT_HEADER,
|
|
431
|
+
[RIGKIT_VERSION_HEADER]: RIGKIT_VERSION_HEADER,
|
|
432
|
+
}[normalized] ?? name;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function redactHeaderValue(name: string, value: string): string {
|
|
436
|
+
if (name.toLowerCase() === "authorization" && /^Bearer\s+/i.test(value)) {
|
|
437
|
+
return "Bearer <redacted FREESTYLE_API_KEY>";
|
|
438
|
+
}
|
|
439
|
+
return isSensitiveFieldName(name) ? "[redacted]" : value;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function redactSensitiveFields(value: unknown): unknown {
|
|
443
|
+
if (Array.isArray(value)) return value.map(redactSensitiveFields);
|
|
444
|
+
if (!value || typeof value !== "object") return value;
|
|
445
|
+
const next: Record<string, unknown> = {};
|
|
446
|
+
for (const [key, field] of Object.entries(value)) {
|
|
447
|
+
next[key] = isSensitiveFieldName(key) ? "[redacted]" : redactSensitiveFields(field);
|
|
448
|
+
}
|
|
449
|
+
return next;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function isSensitiveFieldName(name: string): boolean {
|
|
453
|
+
return /authorization|api[-_]?key|access[-_]?token|refresh[-_]?token|password|secret|credential|cookie/i
|
|
454
|
+
.test(name);
|
|
455
|
+
}
|
|
456
|
+
|
|
272
457
|
async function resolveStackAccessToken(input: {
|
|
273
458
|
config: StackAuthConfig;
|
|
274
459
|
storage: ProviderStorage;
|
|
@@ -512,7 +697,7 @@ function saveStackAuthState(storage: ProviderStorage, key: string, state: StackA
|
|
|
512
697
|
}
|
|
513
698
|
|
|
514
699
|
function resourceUrl(resource: Parameters<typeof fetch>[0]): URL {
|
|
515
|
-
if (typeof resource === "string") return new URL(resource);
|
|
700
|
+
if (typeof resource === "string") return new URL(resource, DEFAULT_FREESTYLE_API_URL);
|
|
516
701
|
if (resource instanceof URL) return resource;
|
|
517
702
|
return new URL(resource.url);
|
|
518
703
|
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const RIGKIT_PROVIDER_FREESTYLE_VERSION = "0.2.
|
|
1
|
+
export const RIGKIT_PROVIDER_FREESTYLE_VERSION = "0.2.10";
|