Migrate connect to use SDK

This commit is contained in:
Jill Regan
2026-02-20 08:24:08 -05:00
parent ab44f9f69c
commit ffffc2db51
5 changed files with 471 additions and 59 deletions

175
package-lock.json generated
View File

@@ -9,6 +9,7 @@
"version": "3.1.0",
"license": "MIT",
"dependencies": {
"@1password/connect": "^1.4.2",
"@1password/op-js": "^0.1.11",
"@1password/sdk": "^0.4.0",
"@actions/core": "^1.10.1",
@@ -29,6 +30,19 @@
"typescript": "^5.4.2"
}
},
"node_modules/@1password/connect": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@1password/connect/-/connect-1.4.2.tgz",
"integrity": "sha512-CxcDQIr76nloWwGWRrmz/U7DuU65WKrN/yarq45LrC3L6b/pC7bZyskvougadG32fRwBieLJX143lTI8T1bAtQ==",
"license": "MIT",
"dependencies": {
"axios": "^1.10.0",
"debug": "^4.4.1",
"lodash.clonedeep": "^4.5.0",
"slugify": "^1.6.6",
"uuid": "^9.0.1"
}
},
"node_modules/@1password/eslint-config": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@1password/eslint-config/-/eslint-config-4.3.1.tgz",
@@ -1980,6 +1994,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@@ -2006,6 +2026,17 @@
"node": ">=4"
}
},
"node_modules/axios": {
"version": "1.13.5",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.11",
"form-data": "^4.0.5",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -2272,7 +2303,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
"integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -2517,6 +2547,18 @@
"dev": true,
"license": "MIT"
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
@@ -2650,10 +2692,9 @@
}
},
"node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"dev": true,
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -2736,6 +2777,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/detect-newline": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@@ -2799,7 +2849,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
@@ -2943,7 +2992,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -2953,7 +3001,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -2990,7 +3037,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
"integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@@ -3000,15 +3046,15 @@
}
},
"node_modules/es-set-tostringtag": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz",
"integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==",
"dev": true,
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"get-intrinsic": "^1.2.4",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.1"
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
@@ -3841,6 +3887,26 @@
"license": "ISC",
"peer": true
},
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/for-each": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
@@ -3851,6 +3917,22 @@
"is-callable": "^1.1.3"
}
},
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -3877,7 +3959,6 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -3950,7 +4031,6 @@
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz",
"integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
@@ -4107,7 +4187,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -4193,7 +4272,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -4206,7 +4284,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
@@ -4222,7 +4299,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@@ -5953,6 +6029,12 @@
"node": ">=8"
}
},
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
"license": "MIT"
},
"node_modules/lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -6151,7 +6233,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz",
"integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -6188,6 +6269,27 @@
"node": ">=8.6"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
@@ -6238,7 +6340,6 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/natural-compare": {
@@ -6724,6 +6825,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -7237,6 +7344,15 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/slugify": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz",
"integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==",
"license": "MIT",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -7968,6 +8084,19 @@
"punycode": "^2.1.0"
}
},
"node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/v8-to-istanbul": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",

View File

@@ -42,6 +42,7 @@
"dependencies": {
"@1password/op-js": "^0.1.11",
"@1password/sdk": "^0.4.0",
"@1password/connect": "^1.4.2",
"@actions/core": "^1.10.1",
"@actions/exec": "^1.1.1",
"@actions/tool-cache": "^2.0.2",

View File

@@ -26,13 +26,6 @@ const loadSecretsAction = async () => {
dotenv.config({ path: file });
}
const isConnect =
process.env[envConnectHost] && process.env[envConnectToken];
// If Connect is used, download and install the CLI
if (isConnect) {
await installCLI();
}
// Load secrets
await loadSecrets(shouldExportEnv);
} catch (error) {

View File

@@ -15,6 +15,7 @@ import {
envManagedVariables,
envServiceAccountToken,
} from "./constants";
import { OnePasswordConnect } from "@1password/connect";
jest.mock("@actions/core");
jest.mock("@actions/exec", () => ({
@@ -29,6 +30,7 @@ jest.mock("@1password/sdk", () => ({
validateSecretReference: jest.fn(),
},
}));
jest.mock("@1password/connect");
beforeEach(() => {
jest.clearAllMocks();
@@ -151,25 +153,47 @@ describe("extractSecret", () => {
});
describe("loadSecrets when using Connect", () => {
it("sets the client info and gets the executed output", async () => {
beforeEach(() => {
process.env[envConnectHost] = "https://connect.example";
process.env[envConnectToken] = "test-token";
process.env[envServiceAccountToken] = "";
Object.keys(process.env).forEach((key) => {
if (
typeof process.env[key] === "string" &&
process.env[key]?.startsWith("op://")
) {
delete process.env[key];
}
});
process.env.MY_SECRET = "op://vault/item/field";
(OnePasswordConnect as jest.Mock).mockReturnValue({
getItem: jest.fn().mockResolvedValue({
fields: [
{ label: "field", value: "resolved-via-connect", section: undefined },
],
sections: [],
}),
});
});
it("resolves ref via Connect SDK and exports secret", async () => {
await loadSecrets(true);
expect(setClientInfo).toHaveBeenCalledWith({
name: "1Password GitHub Action",
id: "GHA",
});
expect(exec.getExecOutput).toHaveBeenCalledWith('sh -c "op env ls"');
expect(core.exportVariable).toHaveBeenCalledWith(
"OP_MANAGED_VARIABLES",
"MOCK_SECRET",
"MY_SECRET",
"resolved-via-connect",
);
expect(core.exportVariable).toHaveBeenCalledWith(
envManagedVariables,
"MY_SECRET",
);
});
it("return early if no env vars with secrets found", async () => {
(exec.getExecOutput as jest.Mock).mockReturnValueOnce({ stdout: "" });
delete process.env.MY_SECRET;
await loadSecrets(true);
expect(exec.getExecOutput).toHaveBeenCalledWith('sh -c "op env ls"');
expect(core.exportVariable).not.toHaveBeenCalled();
});
@@ -177,7 +201,15 @@ describe("loadSecrets when using Connect", () => {
it("is called when shouldExportEnv is true", async () => {
await loadSecrets(true);
expect(core.exportVariable).toHaveBeenCalledTimes(1);
expect(core.exportVariable).toHaveBeenCalledTimes(2);
expect(core.exportVariable).toHaveBeenCalledWith(
"MY_SECRET",
"resolved-via-connect",
);
expect(core.exportVariable).toHaveBeenCalledWith(
envManagedVariables,
"MY_SECRET",
);
});
it("is not called when shouldExportEnv is false", async () => {

View File

@@ -1,7 +1,7 @@
import * as core from "@actions/core";
import * as exec from "@actions/exec";
import { read, setClientInfo, semverToInt } from "@1password/op-js";
import { read } from "@1password/op-js";
import { createClient, Secrets } from "@1password/sdk";
import { OnePasswordConnect, FullItem, OPConnect } from "@1password/connect";
import { version } from "../package.json";
import {
authErr,
@@ -11,6 +11,242 @@ import {
envManagedVariables,
} from "./constants";
// Types for parsed op ref
export interface ParsedOpRef {
vault: string;
item: string;
section: string | undefined;
field: string;
}
const parseOpRef = (ref: string): ParsedOpRef => {
// Safety check: refs are validated by validateSecretRefs before this runs
// this guards against parseOpRef being called directly with invalid input
if (!ref.startsWith("op://")) {
throw new Error(`Invalid op reference: ${ref}`);
}
const segments = ref
.slice("op://".length)
.split("/")
.map((s) => decodeURIComponent(s));
if (segments.length < 3 || segments.length > 4) {
throw new Error(
`Invalid op reference: use op://<vault>/<item>/<field> or op://<vault>/<item>/<section>/<field>. Got: ${ref}`,
);
}
const vault = segments[0] ?? "";
if (!vault) {
throw new Error(`Invalid op reference: vault is required`);
}
const item = segments[1] ?? "";
if (!item) {
throw new Error(`Invalid op reference: item is required`);
}
// Last segment is always the field
const field = segments[segments.length - 1] ?? "";
if (!field) {
throw new Error(`Invalid op reference: field is required`);
}
// Second to last segment is the section if it exists
let section: string | undefined;
if (segments.length === 4) {
section = segments[2];
if (!section) {
throw new Error(`Invalid op reference: section is required when using 4 path segments`);
}
}
return {
vault,
item,
field,
section,
};
}
const getSecretFromConnectItem = async (
client: OPConnect,
item: FullItem,
parsed: ParsedOpRef,
): Promise<string> => {
const sectionIds = parsed.section ? findSectionIdsByQuery(item.sections, parsed.section) : [];
const { fieldValue, fileId } = findMatchingFieldAndFile(item, parsed.field, sectionIds);
if (fieldValue !== undefined) {
return fieldValue;
}
// If a file was found, get the content of the file
if (fileId) {
const content = await client.getFileContent(
parsed.vault,
parsed.item,
fileId,
);
return content;
}
if (parsed.section) {
throw new Error(
`could not find field or file ${parsed.field} in section ${parsed.section} on item ${parsed.item} in vault ${parsed.vault}`,
);
}
throw new Error(
`could not find field or file ${parsed.field} on item ${parsed.item} in vault ${parsed.vault}`,
);
}
const findSectionIdsByQuery = (
sections: FullItem["sections"],
sectionQuery: string | undefined,
): string[] => {
// If no sections were returned with the item throw an error
if (!sections || sections.length === 0) {
throw new Error(
`section ${sectionQuery} could not be found in specified item`,
);
}
const ids = sections
.filter((s) => s.id === sectionQuery || s.label === sectionQuery)
.map((s) => s.id!)
.filter(Boolean);
// If no sections were found with the given query throw an error
if (ids.length === 0) {
throw new Error(
`section ${sectionQuery} could not be found in specified item`,
);
}
return ids;
};
const findMatchingFieldAndFile = (
item: FullItem,
fieldOrFileQuery: string,
sectionIds: string[],
): { fieldValue?: string; fileId?: string } => {
const errMultiple = `multiple fields ${fieldOrFileQuery} that match the provided reference have been found`;
const fields = item.fields ?? [];
const files = item.files ?? [];
const sectionFilter = sectionIds.length > 0;
let matchedField: (typeof fields)[0] | undefined;
let matchedFile: (typeof files)[0] | undefined;
if (sectionFilter) {
// Filter fields by section
const matchingFields = fields.filter((f) => {
const fieldIdOrLabelMatchesQuery = f.id === fieldOrFileQuery || f.label === fieldOrFileQuery;
const sectionId = f.section?.id;
const fieldSectionIsInRefSections = sectionId != null && sectionIds.includes(sectionId);
return fieldIdOrLabelMatchesQuery && fieldSectionIsInRefSections;
});
// If multiple fields match the query throw an error otherwise set first matching field
if (matchingFields.length > 1) {
throw new Error(errMultiple);
}
matchedField = matchingFields[0];
const matchingFiles = files.filter((f) => {
const fileIdOrNameMatchesQuery = f.id === fieldOrFileQuery || f.name === fieldOrFileQuery;
const sectionId = f.section?.id;
const fileSectionIsInRefSections = sectionId != null && sectionIds.includes(sectionId);
return fileIdOrNameMatchesQuery && fileSectionIsInRefSections;
});
// If multiple files match the query throw an error otherwise set first matching file
if (matchingFiles.length > 1) {
throw new Error(errMultiple);
}
matchedFile = matchingFiles[0];
} else {
let matchingFields = fields.filter((f) => {
const fieldIdOrLabelMatchesQuery = f.id === fieldOrFileQuery || f.label === fieldOrFileQuery;
const fieldHasNoSection = f.section?.id == null;
return fieldIdOrLabelMatchesQuery && fieldHasNoSection;
});
// If multiple fields match the query throw an error otherwise set first matching field
if (matchingFields.length > 1) {
throw new Error(errMultiple);
}
matchedField = matchingFields[0];
// If no field was found with no section, find a field in any section
if (!matchedField) {
const matchingFieldsInAnySection = fields.filter((f) => {
const fieldIdOrLabelMatchesQuery =f.id === fieldOrFileQuery || f.label === fieldOrFileQuery;
return fieldIdOrLabelMatchesQuery;
});
if (matchingFieldsInAnySection.length > 1) {
throw new Error(errMultiple);
}
matchedField = matchingFieldsInAnySection[0];
}
let matchingFiles = files.filter((f) => {
const fileIdOrNameMatchesQuery = f.id === fieldOrFileQuery || f.name === fieldOrFileQuery;
const fileHasNoSection = f.section?.id == null;
return fileIdOrNameMatchesQuery && fileHasNoSection;
});
// If multiple files match the query throw an error otherwise set first matching file
if (matchingFiles.length > 1) {
throw new Error(errMultiple);
}
matchedFile = matchingFiles[0];
// If no file was found with no section, find a file in any section
if (!matchedFile) {
const matchingFilesInAnySection = files.filter((f) => {
const fileIdOrNameMatchesQuery = f.id === fieldOrFileQuery || f.name === fieldOrFileQuery;
return fileIdOrNameMatchesQuery;
});
if (matchingFilesInAnySection.length > 1) {
throw new Error(errMultiple);
}
matchedFile = matchingFilesInAnySection[0];
}
}
if (matchedField && matchedFile) {
throw new Error(
`you cannot query fields/files that are identically named.`,
);
}
if (matchedField) {
if (matchedField.value === undefined || matchedField.value === null) {
throw new Error(
`field ${fieldOrFileQuery} has no value in specified item`,
);
}
return { fieldValue: matchedField.value };
}
if (matchedFile?.id) {
const fileId = matchedFile.id;
return { fileId };
}
return {};
}
export const validateAuth = (): void => {
const isConnect = process.env[envConnectHost] && process.env[envConnectToken];
const isServiceAccount = process.env[envServiceAccountToken];
@@ -105,29 +341,50 @@ export const extractSecret = (
}
};
// Connect loads secrets via the 1Password CLI
// Connect loads secrets via the Connect JS SDK
const loadSecretsViaConnect = async (
shouldExportEnv: boolean,
): Promise<void> => {
setClientInfo({
name: "1Password GitHub Action",
id: "GHA",
build: semverToInt(version),
});
// Load secrets from environment variables using 1Password CLI.
// Iterate over them to find 1Password references, extract the secret values,
// and make them available in the next steps either as step outputs or as environment variables.
const res = await exec.getExecOutput(`sh -c "op env ls"`);
if (res.stdout === "") {
const envs = getEnvVarNamesWithSecretRefs();
if (envs.length === 0) {
return;
}
const envs = res.stdout.replace(/\n+$/g, "").split(/\r?\n/);
for (const envName of envs) {
extractSecret(envName, shouldExportEnv);
validateSecretRefs(envs);
const host = process.env[envConnectHost];
const token = process.env[envConnectToken];
if (!host || !token) {
throw new Error(authErr);
}
// Authenticate with the Connect SDK
let client;
try {
client = OnePasswordConnect({
serverURL: host,
token,
});
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
throw new Error(`Connect authentication failed: ${message}`);
}
for (const envName of envs) {
const ref = process.env[envName];
if (!ref) {
continue;
}
// Parse the op ref and get the item from the Connect SDK
const parsed = parseOpRef(ref);
const item = await client.getItem(parsed.vault, parsed.item);
// Get the secret value from the item as Connect returns a full item object
const secretValue = await getSecretFromConnectItem(client, item, parsed);
setResolvedSecret(envName, secretValue, shouldExportEnv);
}
if (shouldExportEnv) {
core.exportVariable(envManagedVariables, envs.join());
}