From 6a7ca14e5a704541557cfeb23c656322d83f0c62 Mon Sep 17 00:00:00 2001 From: yoshi49535 Date: Thu, 27 Aug 2026 22:13:20 +0900 Subject: [PATCH] fix(e2e): adopt the provider's key defaults and the verifier's resource grammar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit auth.provider#282 made EdDSA the signing default, removed the HS256 fallback, and put a >=32-byte floor on the decoded secret; the suite's 19-char shared secret and implicit HS256 both stopped working. The E2E stays on HS256 rather than moving to a published JWKS: auth.policy-verifier#109 requires https for a JWKS URI with an http carve-out only for loopback, and here the verifier would reach the provider at http://provider:3000 — a compose service name, not loopback. Widening that carve-out to make a test pass would weaken the fix. The secret now has ONE definition, in the Makefile, interpolated into the containers and exported to the test processes. Two definitions is what broke first: tests/shared/oauthFlow.js fell back to the old literal, so every hand-minted ABAC token failed as a 401 that read like a policy failure. The fallback is gone — the suite now refuses to run without it. auth.policy-verifier#117 stopped rewriting the resource-type separator, so project:1.member:2 derives project.member rather than project_member; the fixture scope follows. Co-Authored-By: Claude Fable 5 --- Makefile | 7 +++++++ tests/abac/index.test.js | 9 +++++++-- tests/docker-compose.yml | 20 +++++++++++++++++--- tests/provider/clients.yaml | 2 +- tests/shared/oauthFlow.js | 20 +++++++++++++++++++- 5 files changed, 51 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 6c37cc1..9ab7e7e 100644 --- a/Makefile +++ b/Makefile @@ -40,6 +40,13 @@ build: setup cd repos/auth.proxy && pnpm install && pnpm run build cd repos/auth.policy-verifier && pnpm install && pnpm run build +# One definition of the shared HS256 secret, interpolated into the containers +# by docker compose and exported to the test processes, which mint their own +# tokens with it. Defining it twice is how the suite drifted before: the tests +# fell back to a stale literal and every negative case failed as a 401 that +# read like a policy failure. auth.provider#282 requires >=32 decoded bytes. +export OAUTH_JWT_SECRET := qmV+afsq/SMZ7hPGs9edVQDvPzNmjXemJNjqti181v0= + .PHONY: test-e2e test-e2e: build docker compose -f tests/docker-compose.yml up -d --build --wait diff --git a/tests/abac/index.test.js b/tests/abac/index.test.js index 5eeb46b..03706b7 100644 --- a/tests/abac/index.test.js +++ b/tests/abac/index.test.js @@ -41,14 +41,19 @@ function signToken(claims, options = {}) { /** Grant carrying `read:project`. */ let projectGrant; -/** Grant carrying only `read:project_member` — used for the deny case. */ +/** + * Grant carrying only `read:project.member` — used for the deny case, and for + * the nested-resource allow. The scope name tracks the resource type the + * verifier derives: auth.policy-verifier#117 stopped rewriting the `.` + * separator to `_`, so `project:1.member:2` now derives `project.member`. + */ let memberGrant; beforeAll(async () => { const session = await login(); expect(session.status).toBe(200); projectGrant = await codeFlow({ cookie: session.cookie, scope: 'openid read:project' }); - memberGrant = await codeFlow({ cookie: session.cookie, scope: 'openid read:project_member' }); + memberGrant = await codeFlow({ cookie: session.cookie, scope: 'openid read:project.member' }); }, 30_000); describe('ABAC: POST /verify with provider-issued access tokens', () => { diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index c6c8bef..237b278 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -25,12 +25,24 @@ services: - ./provider/clients.yaml:/home/node/templates/standalone/config/clients.yaml:ro - ./provider/users.yaml:/home/node/templates/standalone/config/users.yaml:ro environment: - - OAUTH_JWT_SECRET=test-secret-for-e2e + # auth.provider#282 made EdDSA the default and removed the HS256 + # fallback, so the algorithm must be named explicitly. The E2E stays on + # HS256 deliberately: the verifier requires https for a JWKS URI, with an + # http carve-out only for loopback (auth.policy-verifier#109), and here it + # would reach the provider at http://provider:3000 — a compose service + # name, not loopback. A JWKS-based E2E would fail the verifier's boot + # check, so the two services share a secret instead. + - OAUTH_JWT_ALGORITHM=HS256 + # #282 also put a >=32-byte floor on the decoded secret, measured at the + # smallest plausible reading. The value is defined once in the Makefile + # and interpolated here, because the ABAC tests mint tokens with the same + # secret — a second literal is how the two drifted apart before. + - OAUTH_JWT_SECRET=${OAUTH_JWT_SECRET:?run via make test-e2e} # auth.provider requires a canonical issuer and never derives one from the # request (auth.provider#266). It is an identifier, not a URL anything # fetches, so the E2E pins a fixed one both services agree on. - OAUTH_JWT_ISSUER=https://auth.e2e.test - - SESSION_SECRET=test-session-secret + - SESSION_SECRET=lO0QH09fuKSGuViZ9myJbH3jsgai99A2GpC3RYRuy6Y= - SESSION_SECURE=false # __Host- prefixed default requires secure=true; plain-HTTP E2E needs both overrides - SESSION_NAME=auth.session @@ -138,7 +150,9 @@ services: volumes: - ./abac/application.conf:/home/node/templates/standalone/config/application.conf:ro environment: - - OAUTH_JWT_SECRET=test-secret-for-e2e + # Same interpolated secret as the provider — under HS256 the verifier + # holds the shared secret it verifies with. + - OAUTH_JWT_SECRET=${OAUTH_JWT_SECRET:?run via make test-e2e} # auth.policy-verifier validates iss / aud / typ per RFC 9068 §4 and # refuses to boot without them (auth.policy-verifier#105). The issuer must # match what the provider stamps, and the audience must match what the diff --git a/tests/provider/clients.yaml b/tests/provider/clients.yaml index 50f0898..40bd669 100644 --- a/tests/provider/clients.yaml +++ b/tests/provider/clients.yaml @@ -26,7 +26,7 @@ e2e-app: - "openid" - "email" - "read:project" - - "read:project_member" + - "read:project.member" allowedAudiences: - "https://api.e2e.test" allowedGrantTypes: diff --git a/tests/shared/oauthFlow.js b/tests/shared/oauthFlow.js index 12568fa..219930c 100644 --- a/tests/shared/oauthFlow.js +++ b/tests/shared/oauthFlow.js @@ -20,7 +20,25 @@ export const VERIFIER_URL = process.env.VERIFIER_URL || 'http://localhost:3097'; export const ISSUER = process.env.OAUTH_JWT_ISSUER || 'https://auth.e2e.test'; export const AUDIENCE = process.env.OAUTH_JWT_AUDIENCE || 'https://api.e2e.test'; -export const JWT_SECRET = process.env.OAUTH_JWT_SECRET || 'test-secret-for-e2e'; +/** + * Must equal the containers' `OAUTH_JWT_SECRET` in tests/docker-compose.yml. + * The negative ABAC tests mint their own tokens with it, so a value that + * merely looks plausible produces 401s that read like a policy failure. + * There is deliberately no fallback: auth.provider#282 put a >=32-byte floor + * on the secret, and a default here would silently drift from compose again + * the next time the floor or the value changes. + */ +export const JWT_SECRET = requireEnv('OAUTH_JWT_SECRET'); + +function requireEnv(name) { + const value = process.env[name]; + if (!value) { + throw new Error( + `${name} is not set. Run the suite through \`make test-e2e\`, which exports it from tests/docker-compose.yml.`, + ); + } + return value; +} /** Marked `firstParty: true` in tests/provider/clients.yaml. */ export const CLIENT_ID = 'e2e-app';