From 7d3aa48f0a63ff48e51907131797375f86133fac Mon Sep 17 00:00:00 2001 From: Eric Sitarz Date: Thu, 1 Oct 2026 12:44:06 -0500 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=94=90=20publish=20via=20npm=20truste?= =?UTF-8?q?d=20publishing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Write tokens expire in 90 days. OIDC removes NPM_TOKEN. Node 24 ships the npm CLI that can exchange the OIDC token. --- .github/workflows/ci.yml | 2 +- .github/workflows/tag-and-publish.yml | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 656f42e3..de181ca9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: Install Dependencies run: npm i diff --git a/.github/workflows/tag-and-publish.yml b/.github/workflows/tag-and-publish.yml index 8db826ea..ed905436 100644 --- a/.github/workflows/tag-and-publish.yml +++ b/.github/workflows/tag-and-publish.yml @@ -2,6 +2,7 @@ name: Tag, Release, & Publish permissions: contents: write + id-token: write on: push: @@ -17,7 +18,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 registry-url: https://registry.npmjs.org - name: Install Dependencies @@ -105,6 +106,4 @@ jobs: - name: Publish to npm if: steps.autotagger.outputs.tagname != '' - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: npm publish --access public From 73a3a7e0224ac78bb374229ecb74d857b9ac6d18 Mon Sep 17 00:00:00 2001 From: Eric Sitarz Date: Fri, 2 Oct 2026 09:15:20 -0500 Subject: [PATCH 2/2] ci: check trusted publishing auth Exchange the GitHub OIDC token with npm on this branch only. The publish job still runs on master alone. --- .github/workflows/tag-and-publish.yml | 57 +++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/.github/workflows/tag-and-publish.yml b/.github/workflows/tag-and-publish.yml index ed905436..e6db9741 100644 --- a/.github/workflows/tag-and-publish.yml +++ b/.github/workflows/tag-and-publish.yml @@ -8,9 +8,66 @@ on: push: branches: - master + - ci/npm-trusted-publishing jobs: + verify-trusted-publishing: + if: github.ref == 'refs/heads/ci/npm-trusted-publishing' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 24 + registry-url: https://registry.npmjs.org + + - name: Check npm version + run: npm --version + + - name: Exchange OIDC token with npm + run: | + node --input-type=module <<'NODE' + const audience = "npm:registry.npmjs.org"; + const packageName = "ordercloud-javascript-sdk"; + const requestUrl = process.env.ACTIONS_ID_TOKEN_REQUEST_URL; + const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN; + if (!requestUrl || !requestToken) { + throw new Error("GitHub did not provide an OIDC token request endpoint."); + } + + const separator = requestUrl.includes("?") ? "&" : "?"; + const identityResponse = await fetch( + `${requestUrl}${separator}audience=${encodeURIComponent(audience)}`, + { headers: { Authorization: `bearer ${requestToken}` } } + ); + if (!identityResponse.ok) { + throw new Error(`GitHub OIDC request failed (${identityResponse.status}).`); + } + const { value: identityToken } = await identityResponse.json(); + if (!identityToken) { + throw new Error("GitHub OIDC response did not include an identity token."); + } + console.log(`::add-mask::${identityToken}`); + + const exchangeResponse = await fetch( + `https://registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/${encodeURIComponent(packageName)}`, + { method: "POST", headers: { Authorization: `Bearer ${identityToken}` } } + ); + const body = await exchangeResponse.json().catch(() => ({})); + if (body.token) console.log(`::add-mask::${body.token}`); + if (!exchangeResponse.ok || !body.token) { + const detail = { ...body }; + delete detail.token; + throw new Error( + `npm rejected the trusted publisher (${exchangeResponse.status}): ${JSON.stringify(detail)}` + ); + } + console.log(`Trusted publishing accepted. Publish token expires ${body.expires ?? "unknown"}.`); + NODE + build: + if: github.ref == 'refs/heads/master' runs-on: windows-latest # ubuntu throws error on build steps: - uses: actions/checkout@v4