54cb94c30f
TokenPayload gains optional wid (workspace_id) and role claims; create_access_token accepts them as keyword-only args and only encodes them when provided. Existing tokens and existing callers keep working unchanged — the contract that protected requests must carry wid is enforced by middleware (PR4 T4.6/T4.7), not by the JWT type system. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
53 lines
1.7 KiB
Python
53 lines
1.7 KiB
Python
"""JWT carries wid + role claims (Stage 0 PR4).
|
|
|
|
Tokens issued after PR4 must include `wid` (workspace_id) and `role`
|
|
(owner/admin/member) so the AuthMiddleware can resolve the active
|
|
workspace without a DB hit. Legacy token compatibility lives in
|
|
:mod:`test_legacy_token_compat`.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from uuid import uuid4
|
|
|
|
import jwt
|
|
import pytest
|
|
|
|
from app.gateway.auth import create_access_token, decode_token
|
|
from app.gateway.auth.config import get_auth_config
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _stable_jwt_secret(monkeypatch):
|
|
"""Pin a deterministic JWT secret across tests in this module."""
|
|
monkeypatch.setenv("AUTH_JWT_SECRET", "test-secret-key-for-jwt-testing-minimum-32-chars")
|
|
yield
|
|
|
|
|
|
def test_jwt_includes_wid_and_role() -> None:
|
|
"""create_access_token records wid + role in the encoded payload."""
|
|
user_id = str(uuid4())
|
|
workspace_id = str(uuid4())
|
|
|
|
token = create_access_token(user_id, workspace_id=workspace_id, role="owner")
|
|
|
|
raw = jwt.decode(token, get_auth_config().jwt_secret, algorithms=["HS256"])
|
|
assert raw["sub"] == user_id
|
|
assert raw["wid"] == workspace_id
|
|
assert raw["role"] == "owner"
|
|
|
|
|
|
def test_decode_round_trip_keeps_wid_and_role() -> None:
|
|
"""decode_token returns a TokenPayload exposing wid + role attributes."""
|
|
user_id = str(uuid4())
|
|
workspace_id = str(uuid4())
|
|
|
|
token = create_access_token(user_id, workspace_id=workspace_id, role="member")
|
|
payload = decode_token(token)
|
|
|
|
# decode_token returns TokenError on failure — must be the success branch here.
|
|
assert hasattr(payload, "wid"), f"got {payload!r}"
|
|
assert payload.sub == user_id
|
|
assert payload.wid == workspace_id
|
|
assert payload.role == "member"
|