From 3c30f68c5505d074692dafc955deceedd5249af4 Mon Sep 17 00:00:00 2001 From: Jeroen van der Merwe Date: Wed, 11 Mar 2026 09:21:05 +0200 Subject: [PATCH 1/2] Implement authentication extraction from headers when using the cURL importer --- plugins/importer-curl/src/index.ts | 105 +++++++++++++++++++++++++++-- 1 file changed, 99 insertions(+), 6 deletions(-) diff --git a/plugins/importer-curl/src/index.ts b/plugins/importer-curl/src/index.ts index d5aced69e..9ff798179 100644 --- a/plugins/importer-curl/src/index.ts +++ b/plugins/importer-curl/src/index.ts @@ -181,6 +181,88 @@ export function convertCurl(rawData: string) { }; } +interface ExtractedAuthentication { + authenticationType: string | null; + authentication: Record; + filteredHeaders: HttpUrlParameter[]; // headers without authorization +} + +function extractAuthenticationFromHeaders( + headers: HttpUrlParameter[], +): ExtractedAuthentication { + const authorizationHeaderIndex = headers.findIndex( + (h) => h.name.toLowerCase() === "authorization", + ); + + if (authorizationHeaderIndex === -1) { + return { + authenticationType: null, + authentication: {}, + filteredHeaders: headers, + }; + } + + const authorizationHeader = headers[authorizationHeaderIndex]; + if (authorizationHeader == null) { + return { + authenticationType: null, + authentication: {}, + filteredHeaders: headers, + }; + } + + const value = authorizationHeader.value.trim(); + const spaceIndex = value.indexOf(" "); + + if (spaceIndex <= 0) { + return { + authenticationType: null, + authentication: {}, + filteredHeaders: headers, + }; + } + + const scheme = value.slice(0, spaceIndex).toLowerCase(); + const token = value.slice(spaceIndex + 1); + + // Bearer authentication (RFC 6750) + if (scheme === "bearer") { + const filteredHeaders = headers.filter((_, i) => i !== authorizationHeaderIndex); + return { + authenticationType: "bearer", + authentication: { token, prefix: "Bearer" }, + filteredHeaders, + }; + } + + // Basic authentication (RFC 7617) + if (scheme === "basic") { + try { + const decoded = Buffer.from(token, "base64").toString(); + const colonIndex = decoded.indexOf(":"); + if (colonIndex > 0) { + const filteredHeaders = headers.filter((_, i) => i !== authorizationHeaderIndex); + return { + authenticationType: "basic", + authentication: { + username: decoded.slice(0, colonIndex), + password: decoded.slice(colonIndex + 1), + }, + filteredHeaders, + }; + } + } catch { + // Invalid base64, keep header as-is + } + } + + return { + authenticationType: null, + authentication: {}, + filteredHeaders: headers, + }; +} + function importCommand(parseEntries: string[], workspaceId: string) { // ~~~~~~~~~~~~~~~~~~~~~ // // Collect all the flags // @@ -323,8 +405,19 @@ function importCommand(parseEntries: string[], workspaceId: string) { }); } + // Extract authentication from Authorization headers (Bearer/Basic) + const { + authenticationType: extractedAuthenticationType, + authentication: extractedAuthentication, + filteredHeaders, + } = extractAuthenticationFromHeaders(headers); + + // Use extracted authentication from header if found, otherwise fall back to -u/--user parsing + const finalAuthenticationType = extractedAuthenticationType || authenticationType; + const finalAuthentication = extractedAuthenticationType ? extractedAuthentication : authentication; + // Body (Text or Blob) - const contentTypeHeader = headers.find((header) => header.name.toLowerCase() === "content-type"); + const contentTypeHeader = filteredHeaders.find((header) => header.name.toLowerCase() === "content-type"); const mimeType = contentTypeHeader ? contentTypeHeader.value.split(";")[0]?.trim() : null; // Extract boundary from Content-Type header for multipart parsing @@ -398,7 +491,7 @@ function importCommand(parseEntries: string[], workspaceId: string) { value: decodeURIComponent(parameter.value || ""), })), }; - headers.push({ + filteredHeaders.push({ name: "Content-Type", value: "application/x-www-form-urlencoded", enabled: true, @@ -419,7 +512,7 @@ function importCommand(parseEntries: string[], workspaceId: string) { form: formDataParams, }; if (mimeType == null) { - headers.push({ + filteredHeaders.push({ name: "Content-Type", value: "multipart/form-data", enabled: true, @@ -442,9 +535,9 @@ function importCommand(parseEntries: string[], workspaceId: string) { urlParameters, url, method, - headers, - authentication, - authenticationType, + headers: filteredHeaders, + authentication: finalAuthentication, + authenticationType: finalAuthenticationType, body, bodyType, folderId: null, From fbc9630f067a11ccec923da9c3757e2b5559ba37 Mon Sep 17 00:00:00 2001 From: Jeroen van der Merwe Date: Wed, 11 Mar 2026 14:37:09 +0200 Subject: [PATCH 2/2] Add tests for authorization header handling in cURL importer --- plugins/importer-curl/tests/index.test.ts | 123 ++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/plugins/importer-curl/tests/index.test.ts b/plugins/importer-curl/tests/index.test.ts index c9e2085f6..1e3a36311 100644 --- a/plugins/importer-curl/tests/index.test.ts +++ b/plugins/importer-curl/tests/index.test.ts @@ -332,6 +332,129 @@ describe("importer-curl", () => { }); }); + test("Imports Bearer token from Authorization header", () => { + expect( + convertCurl('curl -H "Authorization: Bearer token123" https://yaak.app'), + ).toEqual({ + resources: { + workspaces: [baseWorkspace()], + httpRequests: [ + baseRequest({ + url: "https://yaak.app", + authenticationType: "bearer", + authentication: { + token: "token123", + prefix: "Bearer", + }, + headers: [], + }), + ], + }, + }); + }); + + test("Imports Basic auth from Authorization header (base64 decoded)", () => { + expect( + convertCurl('curl -H "Authorization: Basic dXNlcjpwYXNzd29yZA==" https://yaak.app'), + ).toEqual({ + resources: { + workspaces: [baseWorkspace()], + httpRequests: [ + baseRequest({ + url: "https://yaak.app", + authenticationType: "basic", + authentication: { + username: "user", + password: "password", + }, + headers: [], + }), + ], + }, + }); + }); + + test("Authorization header takes precedence over -u flag", () => { + expect( + convertCurl('curl -u admin:secret -H "Authorization: Bearer token123" https://yaak.app'), + ).toEqual({ + resources: { + workspaces: [baseWorkspace()], + httpRequests: [ + baseRequest({ + url: "https://yaak.app", + authenticationType: "bearer", + authentication: { + token: "token123", + prefix: "Bearer", + }, + headers: [], + }), + ], + }, + }); + }); + + test("Authorization header extraction is case-insensitive", () => { + expect( + convertCurl('curl -H "authorization: bearer lowercaseToken" https://yaak.app'), + ).toEqual({ + resources: { + workspaces: [baseWorkspace()], + httpRequests: [ + baseRequest({ + url: "https://yaak.app", + authenticationType: "bearer", + authentication: { + token: "lowercaseToken", + prefix: "Bearer", + }, + headers: [], + }), + ], + }, + }); + }); + + test("Preserves other headers when extracting Authorization", () => { + expect( + convertCurl('curl -H "Authorization: Bearer token123" -H "X-Custom: value" https://yaak.app'), + ).toEqual({ + resources: { + workspaces: [baseWorkspace()], + httpRequests: [ + baseRequest({ + url: "https://yaak.app", + authenticationType: "bearer", + authentication: { + token: "token123", + prefix: "Bearer", + }, + headers: [{ name: "X-Custom", value: "value", enabled: true }], + }), + ], + }, + }); + }); + + test("Invalid base64 in Basic auth keeps header in headers", () => { + expect( + convertCurl('curl -H "Authorization: Basic not-valid-base64!!!" https://yaak.app'), + ).toEqual({ + resources: { + workspaces: [baseWorkspace()], + httpRequests: [ + baseRequest({ + url: "https://yaak.app", + headers: [ + { name: "Authorization", value: "Basic not-valid-base64!!!", enabled: true }, + ], + }), + ], + }, + }); + }); + test("Imports cookie as header", () => { expect(convertCurl('curl --cookie "foo=bar" https://yaak.app')).toEqual({ resources: {