fix(gateway): require API key auth even in AUTH_MODE=local - #474
fix(gateway): require API key auth even in AUTH_MODE=local#474erichanwang wants to merge 1 commit into
Conversation
|
Running this branch in production on a self-hosted TLS deployment, and it fixes a second problem beyond the one in the description — worth recording, because that problem makes multi-user mode unusable on its own. The web half also repairs browser→gateway auth in
|
The gateway's AuthUser extractor fell back to an unconditional local-admin auth in AUTH_MODE=local whenever no valid API key was presented — so any bare request with no credentials at all was authenticated as full owner. That defeats the point of binding the gateway to loopback: anything else on the host could still reach every route (vault pair/status, cache invalidate, approvals) with zero credentials. validate_local is now removed; local mode rejects a request the API key check already failed rather than auto-trusting it. The web app's own gateway calls previously relied on exactly this bypass (sending no Authorization header), so getGatewayFetchOptions now fetches/provisions the caller's real API key (via the existing ensureApiKey-backed action) and attaches it as a Bearer token. Fixes onecli#263.
1da015a to
39bb74e
Compare
fixes #263
changes
This is a two-sided fix — a gateway-side hole, and a web-side dependency on that hole that had to be closed first.
Gateway (
apps/gateway/src/auth.rs):AuthUser::from_request_partstriesvalidate_api_key(checksAuthorization: Bearer oc_...against the DB) and only falls back tovalidate_request→validate_localwhen no such key is presented.validate_localthen unconditionally authenticated the caller aslocal-admin— full owner — with no credential of any kind. Every gateway route (vault pair/status, cache invalidate, pending approvals, approval decisions) goes through this same extractor, so inAUTH_MODE=local(the shipped single-user-dev default) any bare request reaching the gateway's loopback port was authenticated as the admin.validate_localis removed; local mode now rejects a request that already failed the API-key check rather than auto-trusting it — that's the only credential path in local mode (there's no login/session mechanism to fall back to).Web (
apps/web/src/lib/gateway-auth.ts): the web app's own gateway calls relied on exactly this bypass —getGatewayFetchOptionssentheaders: {}. Closing the gateway hole without fixing this would have broken vault pairing, cache invalidation, and approvals for every user.getGatewayFetchOptionsnow fetches/provisions the caller's real API key via the existingensureApiKey-backed server action (the same one the API Keys settings page uses) and attaches it asAuthorization: Bearer oc_.... This works unchanged underoauthmode too — sending a valid key alongside the session cookie just gives the gateway a stronger credential to check first.Trust model, before → after:
AUTH_MODE=local+ noAuthorizationheader → authenticated aslocal-admin(full owner), unconditionally.AUTH_MODE=local+ no validAuthorization: Bearer oc_...→401 Unauthorized. The web app now always sends a valid key. A human hitting the gateway directly (curl, no web UI) gets their key from the existing API Keys settings page (ensureApiKey) — no functionality removed, just no longer free for the taking.Updated two stale comments in
apps/web/src/lib/api/{cache,approvals}.tsthat described the old "edition-aware, no need to borrow an API key" design, which no longer matches.verification
cargo fmt --manifest-path apps/gateway/Cargo.toml --checkcargo test --manifest-path apps/gateway/Cargo.toml auth::— 4 passed, including new regression testvalidate_request_local_mode_rejects_bare_request(bare request in local mode →Err)pnpm --filter web check-typespnpm --filter web lintgit diff --checkpnpm check(full monorepo lint + check-types + format:check, also ran again via the pre-commit hook on commit)