Repository navigation
feat(embed): add GitHub Actions OIDC as primary auth path - #36
Conversation
Adds a GitHub Actions OIDC verifier (src/lib/github-oidc.ts) that verifies a workflow-minted JWT against GitHub's JWKS and authorizes on repository_owner. The embed-upload route now prefers OIDC and falls back to the static PROJECT_EMBED_UPLOAD_TOKEN bearer. Why: the static bearer requires gh secret set on every new codeseys-embed repo. With OIDC, any future repo under our owner can upload with zero per-repo configuration — they just request permissions: id-token: write and the runtime token broker mints an audience-scoped JWT. Implementation notes: - JWKS cached 1h in-process via a module-level cache; CF Worker also caches the JWKS fetch via cf.cacheTtl. - Audience pinned to https://codeseys.io to scope tokens to this service (workflows minting tokens for other audiences won't pass). - Authorization currently checks repository_owner === 'baladithyab'. Future hardening hooks documented in github-oidc.ts (require job_workflow_ref to come from web-embed-workflows, etc.). - 16 unit tests with synthesized RS256 tokens cover happy path + every rejection branch (issuer / audience / nbf / exp / kid / alg / signature). 163 tests total (was 147).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3f55edc22f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| // Find the signing key. | ||
| let jwks = await getGithubJwks(false) |
There was a problem hiding this comment.
When the JWKS cache is cold or expired and GitHub's JWKS request rejects or returns a non-2xx response, getGithubJwks() throws, but this await is outside any try/catch. That means an otherwise expected auth dependency outage bubbles out of PUT /api/embed-upload as an unhandled 500 instead of the typed 503 auth failure this verifier returns for other JWKS problems, breaking OIDC uploads with no controlled JSON error.
Useful? React with 👍 / 👎.
Two TS errors caught by astro check:
1. The `cf: { cacheTtl }` RequestInit extension is CF-Workers-only and
our type model doesn't have it; the @ts-expect-error suppression
was unused after the type sharpened. Drop the hint.
2. crypto.subtle.verify wants an ArrayBuffer-backed BufferSource, not
a Uint8Array<ArrayBufferLike>. Use .slice().buffer to get a fresh
ArrayBuffer (TextEncoder + atob both can produce Shared-backed
buffers under recent lib.dom.d.ts).
16 OIDC tests still pass.
🚀 Preview deployedURL: https://798030dc-codeseys-personal-website.codeseys.workers.dev Updated on every push to this PR. |
Summary
Replaces the static `PROJECT_EMBED_UPLOAD_TOKEN` shared bearer with GitHub Actions OIDC as the primary auth path on `/api/embed-upload`. Static bearer remains as fallback for manual uploads / repos without OIDC support.
Why now: the static bearer requires `gh secret set PROJECT_EMBED_UPLOAD_TOKEN` on every new repo we want to embed. After today's discovery audits, we'll have at least 4 codeseys-embed repos (CSE 160, CSE 101 source, CSCI 585 source, web-embed-workflows). Adding a 5th, 6th, etc. should be zero-config. OIDC achieves that — any GitHub Actions workflow under our owner can mint an audience-scoped JWT that the Worker verifies against GitHub's JWKS.
What's new
Token shape & verification
A workflow OIDC token is a 3-segment RS256 JWT with these claims (subset):
```
iss: https://token.actions.githubusercontent.com
aud: https://codeseys.io (matches our pinned EXPECTED_AUDIENCE)
sub: repo:/:ref:refs/heads/
repository: /
repository_owner: baladithyab ← we authorize on this
job_workflow_ref: //.github/workflows/@
ref: refs/heads/
sha:
actor:
exp: now + 5min
```
We verify the JWT signature against `https://token.actions.githubusercontent.com/.well-known/jwks\` and check `repository_owner === 'baladithyab'`.
Future hardening hooks (commented out, not in this PR)
Companion change
`baladithyab/web-embed-workflows#` updates `static-passthrough.yml` to mint and present OIDC tokens. Caller workflows need `permissions: id-token: write`.
Tests
Rollout
🤖 Generated with the help of Claude