Import Digest, NTLM, OAuth 1.0, and folder-level auth from Postman collections (#667)

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Gregory Schier
2026-09-15 13:18:34 -07:00
committed by GitHub
co-authored by Claude Opus 5
parent a822cc93d4
commit 31d55440c1
6 changed files with 474 additions and 13 deletions
+66 -2
View File
@@ -16,6 +16,20 @@ const POSTMAN_2_1_0_SCHEMA = "https://schema.getpostman.com/json/collection/v2.1
const POSTMAN_2_0_0_SCHEMA = "https://schema.getpostman.com/json/collection/v2.0.0/collection.json"; const POSTMAN_2_0_0_SCHEMA = "https://schema.getpostman.com/json/collection/v2.0.0/collection.json";
const VALID_SCHEMAS = [POSTMAN_2_0_0_SCHEMA, POSTMAN_2_1_0_SCHEMA]; const VALID_SCHEMAS = [POSTMAN_2_0_0_SCHEMA, POSTMAN_2_1_0_SCHEMA];
// Both products happen to spell these the same way, but the names are written
// out so anything Yaak's oauth1 plugin can't offer as a select option lands on
// the default instead of being passed straight through.
const OAUTH1_SIGNATURE_METHODS: Record<string, string> = {
"HMAC-SHA1": "HMAC-SHA1",
"HMAC-SHA256": "HMAC-SHA256",
"HMAC-SHA512": "HMAC-SHA512",
"RSA-SHA1": "RSA-SHA1",
"RSA-SHA256": "RSA-SHA256",
"RSA-SHA512": "RSA-SHA512",
PLAINTEXT: "PLAINTEXT",
};
const DEFAULT_OAUTH1_SIGNATURE_METHOD = "HMAC-SHA1";
type AtLeast<T, K extends keyof T> = Partial<T> & Pick<T, K>; type AtLeast<T, K extends keyof T> = Partial<T> & Pick<T, K>;
interface ExportResources { interface ExportResources {
@@ -98,6 +112,7 @@ export function convertPostman(contents: string): ImportPluginResponse | undefin
id: generateId("folder"), id: generateId("folder"),
name: v.name, name: v.name,
folderId, folderId,
...importAuth(v.auth),
}; };
trackSourceKey(folder.id, v, "item"); trackSourceKey(folder.id, v, "item");
exportResources.folders.push(folder); exportResources.folders.push(folder);
@@ -222,8 +237,6 @@ function convertUrl(rawUrl: unknown): Pick<HttpRequest, "url" | "urlParameters">
v += `#${url.hash}`; v += `#${url.hash}`;
} }
// TODO: Implement url.variables (path variables)
return { url: v, urlParameters: params }; return { url: v, urlParameters: params };
} }
@@ -274,6 +287,31 @@ function importAuth(rawAuth: unknown): Pick<HttpRequest, "authentication" | "aut
}; };
} }
if ("digest" in auth && authType === "digest") {
const d = pmArrayToObj(auth.digest);
return {
authenticationType: "digest",
authentication: {
username: d.username != null ? String(d.username) : undefined,
password: d.password != null ? String(d.password) : undefined,
realm: d.realm != null ? String(d.realm) : undefined,
},
};
}
if ("ntlm" in auth && authType === "ntlm") {
const n = pmArrayToObj(auth.ntlm);
return {
authenticationType: "windows",
authentication: {
username: n.username != null ? String(n.username) : undefined,
password: n.password != null ? String(n.password) : undefined,
domain: n.domain != null ? String(n.domain) : undefined,
workstation: n.workstation != null ? String(n.workstation) : undefined,
},
};
}
if ("awsv4" in auth && authType === "awsv4") { if ("awsv4" in auth && authType === "awsv4") {
const a = pmArrayToObj(auth.awsv4); const a = pmArrayToObj(auth.awsv4);
return { return {
@@ -317,6 +355,32 @@ function importAuth(rawAuth: unknown): Pick<HttpRequest, "authentication" | "aut
}; };
} }
if ("oauth1" in auth && authType === "oauth1") {
const o = pmArrayToObj(auth.oauth1);
const signatureMethod =
o.signatureMethod != null
? String(o.signatureMethod).toUpperCase()
: DEFAULT_OAUTH1_SIGNATURE_METHOD;
return {
authenticationType: "oauth1",
authentication: {
signatureMethod:
OAUTH1_SIGNATURE_METHODS[signatureMethod] ?? DEFAULT_OAUTH1_SIGNATURE_METHOD,
consumerKey: o.consumerKey != null ? String(o.consumerKey) : undefined,
consumerSecret: o.consumerSecret != null ? String(o.consumerSecret) : undefined,
tokenKey: o.token != null ? String(o.token) : undefined,
tokenSecret: o.tokenSecret != null ? String(o.tokenSecret) : undefined,
privateKey: o.privateKey != null ? String(o.privateKey) : undefined,
callback: o.callback != null ? String(o.callback) : undefined,
verifier: o.verifier != null ? String(o.verifier) : undefined,
timestamp: o.timestamp != null ? String(o.timestamp) : undefined,
nonce: o.nonce != null ? String(o.nonce) : undefined,
version: o.version != null ? String(o.version) : undefined,
realm: o.realm != null ? String(o.realm) : undefined,
},
};
}
if ("oauth2" in auth && authType === "oauth2") { if ("oauth2" in auth && authType === "oauth2") {
const o = pmArrayToObj(auth.oauth2); const o = pmArrayToObj(auth.oauth2);
+248
View File
@@ -738,6 +738,254 @@
} }
}, },
"response": [] "response": []
},
{
"name": "Digest",
"request": {
"auth": {
"type": "digest",
"digest": [
{
"key": "algorithm",
"value": "MD5",
"type": "string"
},
{
"key": "username",
"value": "digest-user",
"type": "string"
},
{
"key": "password",
"value": "digest-pass",
"type": "string"
},
{
"key": "realm",
"value": "testrealm@yaak.app",
"type": "string"
},
{
"key": "nonce",
"value": "dcd98b7102dd2f0e8b11d0f600bfb0c093",
"type": "string"
},
{
"key": "nonceCount",
"value": "00000001",
"type": "string"
},
{
"key": "clientNonce",
"value": "0a4f113b",
"type": "string"
},
{
"key": "opaque",
"value": "5ccc069c403ebaf9f0171e9517f40e41",
"type": "string"
},
{
"key": "qop",
"value": "auth",
"type": "string"
},
{
"key": "disableRetryRequest",
"value": true,
"type": "boolean"
}
]
},
"method": "GET",
"header": [],
"url": {
"raw": "https://yaak.app/x/echo",
"protocol": "https",
"host": ["yaak", "app"],
"path": ["x", "echo"]
}
},
"response": []
},
{
"name": "NTLM",
"request": {
"auth": {
"type": "ntlm",
"ntlm": [
{
"key": "username",
"value": "ntlm-user",
"type": "string"
},
{
"key": "password",
"value": "ntlm-pass",
"type": "string"
},
{
"key": "domain",
"value": "CORP",
"type": "string"
},
{
"key": "workstation",
"value": "WORKSTATION-01",
"type": "string"
},
{
"key": "disableRetryRequest",
"value": true,
"type": "boolean"
}
]
},
"method": "GET",
"header": [],
"url": {
"raw": "https://yaak.app/x/echo",
"protocol": "https",
"host": ["yaak", "app"],
"path": ["x", "echo"]
}
},
"response": []
},
{
"name": "OAuth 1",
"request": {
"auth": {
"type": "oauth1",
"oauth1": [
{
"key": "consumerKey",
"value": "consumer-key",
"type": "string"
},
{
"key": "consumerSecret",
"value": "consumer-secret",
"type": "string"
},
{
"key": "token",
"value": "access-token",
"type": "string"
},
{
"key": "tokenSecret",
"value": "token-secret",
"type": "string"
},
{
"key": "signatureMethod",
"value": "HMAC-SHA256",
"type": "string"
},
{
"key": "timestamp",
"value": "1700000000",
"type": "string"
},
{
"key": "nonce",
"value": "9e6dfada256c",
"type": "string"
},
{
"key": "version",
"value": "1.0",
"type": "string"
},
{
"key": "realm",
"value": "yaak.app",
"type": "string"
},
{
"key": "callback",
"value": "https://yaak.app/x/callback",
"type": "string"
},
{
"key": "verifier",
"value": "verifier-123",
"type": "string"
},
{
"key": "addParamsToHeader",
"value": true,
"type": "boolean"
},
{
"key": "addEmptyParamsToSign",
"value": false,
"type": "boolean"
},
{
"key": "includeBodyHash",
"value": false,
"type": "boolean"
}
]
},
"method": "GET",
"header": [],
"url": {
"raw": "https://yaak.app/x/echo",
"protocol": "https",
"host": ["yaak", "app"],
"path": ["x", "echo"]
}
},
"response": []
},
{
"name": "Folder With Auth",
"item": [
{
"name": "Folder Child Inherit",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "https://yaak.app/x/echo",
"protocol": "https",
"host": ["yaak", "app"],
"path": ["x", "echo"]
}
},
"response": []
},
{
"name": "Folder Child No Auth",
"request": {
"auth": {
"type": "noauth"
},
"method": "GET",
"header": [],
"url": {
"raw": "https://yaak.app/x/echo",
"protocol": "https",
"host": ["yaak", "app"],
"path": ["x", "echo"]
}
},
"response": []
}
],
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "folder-token",
"type": "string"
}
]
}
} }
], ],
"auth": { "auth": {
+115 -1
View File
@@ -297,9 +297,123 @@
"headerPrefix": "Bearer", "headerPrefix": "Bearer",
"location": "header" "location": "header"
} }
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_10",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": null,
"name": "Digest",
"method": "GET",
"url": "https://yaak.app/x/echo",
"urlParameters": [],
"body": {},
"bodyType": null,
"sortPriority": 10,
"headers": [],
"authenticationType": "digest",
"authentication": {
"username": "digest-user",
"password": "digest-pass",
"realm": "testrealm@yaak.app"
}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_11",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": null,
"name": "NTLM",
"method": "GET",
"url": "https://yaak.app/x/echo",
"urlParameters": [],
"body": {},
"bodyType": null,
"sortPriority": 11,
"headers": [],
"authenticationType": "windows",
"authentication": {
"username": "ntlm-user",
"password": "ntlm-pass",
"domain": "CORP",
"workstation": "WORKSTATION-01"
}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_12",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": null,
"name": "OAuth 1",
"method": "GET",
"url": "https://yaak.app/x/echo",
"urlParameters": [],
"body": {},
"bodyType": null,
"sortPriority": 12,
"headers": [],
"authenticationType": "oauth1",
"authentication": {
"signatureMethod": "HMAC-SHA256",
"consumerKey": "consumer-key",
"consumerSecret": "consumer-secret",
"tokenKey": "access-token",
"tokenSecret": "token-secret",
"callback": "https://yaak.app/x/callback",
"verifier": "verifier-123",
"timestamp": "1700000000",
"nonce": "9e6dfada256c",
"version": "1.0",
"realm": "yaak.app"
}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_13",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": "GENERATE_ID::FOLDER_0",
"name": "Folder Child Inherit",
"method": "GET",
"url": "https://yaak.app/x/echo",
"urlParameters": [],
"body": {},
"bodyType": null,
"sortPriority": 14,
"headers": [],
"authenticationType": null,
"authentication": {}
},
{
"model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_14",
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"folderId": "GENERATE_ID::FOLDER_0",
"name": "Folder Child No Auth",
"method": "GET",
"url": "https://yaak.app/x/echo",
"urlParameters": [],
"body": {},
"bodyType": null,
"sortPriority": 15,
"headers": [],
"authenticationType": "none",
"authentication": {}
} }
], ],
"folders": [] "folders": [
{
"model": "folder",
"sortPriority": 13,
"workspaceId": "GENERATE_ID::WORKSPACE_0",
"id": "GENERATE_ID::FOLDER_0",
"name": "Folder With Auth",
"folderId": null,
"authenticationType": "bearer",
"authentication": {
"token": "folder-token"
}
}
]
}, },
"sourceKeys": { "sourceKeys": {
"GENERATE_ID::WORKSPACE_0": "collection:9e6dfada-256c-49ea-a38f-7d1b05b7ca2d" "GENERATE_ID::WORKSPACE_0": "collection:9e6dfada-256c-49ea-a38f-7d1b05b7ca2d"
+13 -9
View File
@@ -23,9 +23,9 @@
"httpRequests": [ "httpRequests": [
{ {
"model": "http_request", "model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_10", "id": "GENERATE_ID::HTTP_REQUEST_15",
"workspaceId": "GENERATE_ID::WORKSPACE_1", "workspaceId": "GENERATE_ID::WORKSPACE_1",
"folderId": "GENERATE_ID::FOLDER_1", "folderId": "GENERATE_ID::FOLDER_2",
"name": "Request 1", "name": "Request 1",
"method": "GET", "method": "GET",
"url": "", "url": "",
@@ -39,9 +39,9 @@
}, },
{ {
"model": "http_request", "model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_11", "id": "GENERATE_ID::HTTP_REQUEST_16",
"workspaceId": "GENERATE_ID::WORKSPACE_1", "workspaceId": "GENERATE_ID::WORKSPACE_1",
"folderId": "GENERATE_ID::FOLDER_0", "folderId": "GENERATE_ID::FOLDER_1",
"name": "Request 2", "name": "Request 2",
"method": "GET", "method": "GET",
"url": "", "url": "",
@@ -55,7 +55,7 @@
}, },
{ {
"model": "http_request", "model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_12", "id": "GENERATE_ID::HTTP_REQUEST_17",
"workspaceId": "GENERATE_ID::WORKSPACE_1", "workspaceId": "GENERATE_ID::WORKSPACE_1",
"folderId": null, "folderId": null,
"name": "Request 3", "name": "Request 3",
@@ -75,17 +75,21 @@
"model": "folder", "model": "folder",
"sortPriority": 0, "sortPriority": 0,
"workspaceId": "GENERATE_ID::WORKSPACE_1", "workspaceId": "GENERATE_ID::WORKSPACE_1",
"id": "GENERATE_ID::FOLDER_0", "id": "GENERATE_ID::FOLDER_1",
"name": "Top Folder", "name": "Top Folder",
"folderId": null "folderId": null,
"authenticationType": null,
"authentication": {}
}, },
{ {
"model": "folder", "model": "folder",
"sortPriority": 1, "sortPriority": 1,
"workspaceId": "GENERATE_ID::WORKSPACE_1", "workspaceId": "GENERATE_ID::WORKSPACE_1",
"id": "GENERATE_ID::FOLDER_1", "id": "GENERATE_ID::FOLDER_2",
"name": "Nested Folder", "name": "Nested Folder",
"folderId": "GENERATE_ID::FOLDER_0" "folderId": "GENERATE_ID::FOLDER_1",
"authenticationType": null,
"authentication": {}
} }
] ]
}, },
+1 -1
View File
@@ -31,7 +31,7 @@
"httpRequests": [ "httpRequests": [
{ {
"model": "http_request", "model": "http_request",
"id": "GENERATE_ID::HTTP_REQUEST_13", "id": "GENERATE_ID::HTTP_REQUEST_18",
"workspaceId": "GENERATE_ID::WORKSPACE_2", "workspaceId": "GENERATE_ID::WORKSPACE_2",
"folderId": null, "folderId": null,
"name": "Form URL", "name": "Form URL",
@@ -123,6 +123,37 @@ describe("importer-postman", () => {
expect(keyOf(before, before?.resources.workspaces[0]?.id)).toBe("collection:collection-id"); expect(keyOf(before, before?.resources.workspaces[0]?.id)).toBe("collection:collection-id");
}); });
test("Falls back to the default OAuth 1 signature method for unrecognized ones", () => {
const result = convertPostman(
JSON.stringify({
info: {
name: "OAuth 1 Signature",
schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
},
item: [
{
name: "Request",
request: {
method: "GET",
url: "https://yaak.app",
auth: {
type: "oauth1",
oauth1: [{ key: "signatureMethod", value: "HMAC-SHA384" }],
},
},
},
],
}),
);
expect(result?.resources.httpRequests).toEqual([
expect.objectContaining({
authenticationType: "oauth1",
authentication: { signatureMethod: "HMAC-SHA1" },
}),
]);
});
test("Omits keys for items the collection never identified", () => { test("Omits keys for items the collection never identified", () => {
const result = convertPostman( const result = convertPostman(
JSON.stringify({ JSON.stringify({