diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 667578cd98..f591827f1c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,6 @@ concurrency: cancel-in-progress: true env: - DFCG_ARTIFACTS_ACCESS_TOKEN: ${{ secrets.DFCG_ARTIFACTS_ACCESS_TOKEN }} CMC_API_KEY: ${{ secrets.CMC_API_KEY }} APCA_API_KEY_ID: ${{ secrets.APCA_API_KEY_ID }} APCA_API_SECRET_KEY: ${{ secrets.APCA_API_SECRET_KEY }} @@ -56,6 +55,19 @@ jobs: steps: - uses: actions/checkout@v6 + # Mint a short-lived, contents:read token for the private + # dfcg-artifacts repo via the blocksense-ci-token-provider GitHub + # App, replacing the long-lived DFCG_ARTIFACTS_ACCESS_TOKEN PAT. + - name: Mint dfcg-artifacts token + id: dfcg-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.CI_TOKEN_PROVIDER_APP_ID }} + private-key: ${{ secrets.CI_TOKEN_PROVIDER_PRIVATE_KEY }} + owner: blocksense-network + repositories: dfcg-artifacts + permission-contents: read + - name: Install Nix uses: metacraft-labs/nixos-modules/.github/install-nix@main with: @@ -75,6 +87,8 @@ jobs: - name: Test all packages run: just test-ts + env: + DFCG_ARTIFACTS_ACCESS_TOKEN: ${{ steps.dfcg-token.outputs.token }} decoders-tests: timeout-minutes: 20 diff --git a/libs/ts/config-types/src/dfcg/artifacts/downloader.ts b/libs/ts/config-types/src/dfcg/artifacts/downloader.ts index 8570939576..f84867063a 100644 --- a/libs/ts/config-types/src/dfcg/artifacts/downloader.ts +++ b/libs/ts/config-types/src/dfcg/artifacts/downloader.ts @@ -82,19 +82,26 @@ export async function isTokenValid(): Promise { const octokit = new Octokit({ auth: GITHUB_TOKEN }); try { - await octokit.rest.users.getAuthenticated(); + // Probe access to the actual artifacts repo rather than `/user`. + // A GitHub App installation token (minted by the ci-token-provider + // App) is not a user, so `users.getAuthenticated()` returns 403 + // ("Resource not accessible by integration") even when the token can + // read the repo. `repos.get` only needs metadata:read and works for + // both App installation tokens and classic PATs — and it checks the + // thing we actually care about: can we reach dfcg-artifacts. + await octokit.rest.repos.get({ owner: OWNER, repo: REPO }); return true; } catch (error) { // Octokit can surface a RequestError from a differently-resolved copy of // `@octokit/request-error`, so `instanceof` is unreliable here. Fall back - // to the numeric `status` carried on the error so an invalid/expired token - // (401) makes us report the token as invalid — and callers skip — rather - // than crashing. + // to the numeric `status` carried on the error. A missing/expired/ + // unauthorized token (401/403/404) means we can't read the repo, so the + // callers skip rather than crash. const status = error instanceof RequestError ? error.status : (error as { status?: number } | null)?.status; - if (status === 401) { + if (status === 401 || status === 403 || status === 404) { return false; } throw error;