auth: G3.2 hub JWKS verifier — humans can sign in to the plane (SSO #14)
The human path refused every token in production (503 human_signin_not_ready) because no verifier existed. api/app/hub_jwt.py verifies hub access tokens against account.windyword.ai's JWKS: - RS256 only (closes alg:none and HS256-with-public-key confusion) - iss must be "windy-identity" — what hub ACCESS tokens carry (observed live); id_tokens (discovery-URL issuer) are not accepted as bearers - aud optional today, must name Windy Git when present; hub_require_aud flips it mandatory once the hub emits it. PyJWT's own aud check is off on purpose: it rejects ANY aud-bearing token when no audience is given. - type must be human; identity = windy_identity_id, never sub (per-row id) - production verifies even if require_verified_jwt is off 11 behavioral tests sign real RS256 tokens with a local key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -308,12 +308,13 @@ def test_g36_trust_client_never_soft_allows():
|
||||
|
||||
def test_g36_unverified_human_jwt_is_refused_in_production():
|
||||
"""I-8 applied to ourselves: an unverified JWT is an authentication bypass,
|
||||
not a shortcut. Until G3.2's JWKS verifier exists, production refuses."""
|
||||
not a shortcut. G3.2's verifier now exists; behavioral proof that forged,
|
||||
expired, mis-issued and mis-audienced tokens are refused lives in
|
||||
test_hub_jwt.py. Here: the gate defaults closed."""
|
||||
from api.app.config import Settings
|
||||
|
||||
assert Settings().require_verified_jwt is True
|
||||
src = (ROOT / "api" / "app" / "auth.py").read_text()
|
||||
assert "human_signin_not_ready" in src
|
||||
assert Settings().hub_issuers == ["windy-identity"]
|
||||
|
||||
|
||||
def test_no_auth_bypass_env_var_anywhere():
|
||||
|
||||
Reference in New Issue
Block a user