--- phase: "07.4" plan: "01" type: execute wave: 0 depends_on: [] files_modified: - backend/tests/test_auth_fgp.py autonomous: true requirements: - FGP-CONCERN # tracked in .planning/codebase/CONCERNS.md §"No Token Fingerprint / Token Binding" must_haves: truths: - "test_auth_fgp.py exists with exactly 4 test functions" - "All 4 tests are decorated with @pytest.mark.xfail(strict=False, reason='not implemented yet')" - "pytest -v reports all 4 as XFAIL — no failures, no errors" - "No production source files are modified" artifacts: - path: "backend/tests/test_auth_fgp.py" provides: "Wave 0 xfail stubs for FGP-01..FGP-04" contains: "test_fgp_match_returns_200" key_links: - from: "backend/tests/test_auth_fgp.py" to: "backend/tests/test_auth_deps.py" via: "imports FakeRedis, copies make_test_app / auth_client / _create_user patterns" pattern: "from tests.test_auth_api import FakeRedis" --- Create the Wave 0 test scaffold for Phase 7.4: a new test file `backend/tests/test_auth_fgp.py` containing 4 xfail stubs covering the four fingerprint behaviours (FGP-01..FGP-04). No production code is touched in this plan. Purpose: Establishes the Nyquist test harness before any production code changes. Follows the xfail(strict=False) Wave 0 convention established in Phases 7.2 and 7.3. Output: `backend/tests/test_auth_fgp.py` with 4 stubs; full test suite still passes with zero failures. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/07.4-security-token-fingerprinting-token-binding-inserted/07.4-CONTEXT.md @.planning/phases/07.4-security-token-fingerprinting-token-binding-inserted/07.4-RESEARCH.md @.planning/phases/07.4-security-token-fingerprinting-token-binding-inserted/07.4-PATTERNS.md From backend/tests/test_auth_deps.py (full harness to copy): Imports: import uuid import pytest import pytest_asyncio from httpx import ASGITransport, AsyncClient from fastapi import FastAPI, Depends from sqlalchemy.ext.asyncio import AsyncSession from tests.test_auth_api import FakeRedis make_test_app(): Creates minimal FastAPI app with /test/me route wired to get_current_user. Sets app.state.redis = FakeRedis() so NBF check and fgp check can access request.app.state.redis. auth_client fixture: @pytest_asyncio.fixture async def auth_client(db_session: AsyncSession): app = make_test_app() app.dependency_overrides[get_db] = lambda: db_session async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as c: yield c app.dependency_overrides.clear() _create_user helper (no decorator — plain async function): Inserts minimal User row; returns User ORM object. Fields: id=uuid.uuid4(), handle, email, password_hash, role, is_active=True Test function pattern: @pytest.mark.asyncio async def test_name(auth_client, db_session): from services.auth import create_access_token user = await _create_user(db_session) token = create_access_token(str(user.id), "user") resp = await auth_client.get("/test/me", headers={"Authorization": f"Bearer {token}"}) assert resp.status_code == 200 conftest _patch_es256_test_keys is autouse=True at session scope — no explicit reference needed in this file. From backend/tests/test_auth_es256.py (xfail stub pattern for Phase 7.3): @pytest.mark.xfail(strict=False, reason="not implemented yet") @pytest.mark.asyncio async def test_stub_name(...): pytest.xfail("not implemented yet") Task 1: Create test_auth_fgp.py with 4 xfail stubs (FGP-01..FGP-04) backend/tests/test_auth_fgp.py - backend/tests/test_auth_deps.py — copy the full harness (make_test_app, auth_client fixture, _create_user helper, imports block). This is the structural template; copy verbatim then modify test functions only. - backend/tests/test_auth_es256.py — xfail stub decoration pattern used in Phase 7.3. Stubs use @pytest.mark.xfail(strict=False, reason="not implemented yet") and body is only pytest.xfail("not implemented yet"). - .planning/phases/07.4-security-token-fingerprinting-token-binding-inserted/07.4-RESEARCH.md §"Test Case Structure" — exact test names and behaviour descriptions for all 4 tests. - FGP-01 test_fgp_match_returns_200: issues token with user_agent="Mozilla/5.0" accept_lang="en", sends request with matching headers — expects 200 - FGP-02 test_fgp_mismatch_returns_401: issues token with user_agent="Mozilla/5.0" accept_lang="en", sends request with user_agent="different-agent" — expects 401 and detail="Token fingerprint mismatch" - FGP-03 test_no_fgp_claim_allowed: crafts token manually without "fgp" key in payload — expects 200 (migration grace) - FGP-04 test_missing_headers_empty_string_binding: issues token with no user_agent/accept_lang args (defaults to ""), sends request with no User-Agent/Accept-Language headers — expects 200 Create backend/tests/test_auth_fgp.py. Structure: 1. Module docstring explaining Phase 7.4 fgp test coverage (FGP-01..04). 2. Imports: copy the exact imports block from test_auth_deps.py. Add these additional imports needed for FGP-03 (manual token craft): `import base64`, `import time`, `import jwt as _jwt`, and `from config import settings`. 3. Copy make_test_app() verbatim from test_auth_deps.py (includes app.state.redis = FakeRedis()). Do NOT add a /test/admin route — not needed here. 4. Copy auth_client fixture verbatim from test_auth_deps.py. 5. Copy _create_user helper verbatim from test_auth_deps.py. 6. Write 4 test functions in this order: - test_fgp_match_returns_200 (FGP-01) - test_fgp_mismatch_returns_401 (FGP-02) - test_no_fgp_claim_allowed (FGP-03) - test_missing_headers_empty_string_binding (FGP-04) Each test function: - Decorated with @pytest.mark.xfail(strict=False, reason="not implemented yet") ABOVE @pytest.mark.asyncio - Decorated with @pytest.mark.asyncio - Accepts (auth_client, db_session) as parameters - Body is a single line: pytest.xfail("not implemented yet") Do NOT write any assertion logic, token construction, or request code inside the stubs. The stub body is ONLY the pytest.xfail() call. Implementation comes in Wave 1 (Plan 07.4-02). 7. No production files (services/auth.py, deps/auth.py, api/auth.py) may be modified in this task. cd /Users/nik/Documents/Progamming/document_scanner/backend && pytest tests/test_auth_fgp.py -v 2>&1 | tail -20 - backend/tests/test_auth_fgp.py exists - pytest tests/test_auth_fgp.py -v shows exactly 4 tests, all reported as XFAIL - Zero FAILED, zero ERROR entries in the output - pytest -v (full suite) still passes with zero failures ## Trust Boundaries | Boundary | Description | |----------|-------------| | test harness → test DB | xfail stubs create no DB state; no trust boundary crossed in Wave 0 | ## STRIDE Threat Register | Threat ID | Category | Component | Disposition | Mitigation Plan | |-----------|----------|-----------|-------------|-----------------| | T-07.4-SC | Tampering | npm/pip/cargo installs | accept | No new packages installed in this plan; stdlib only | After this plan completes: - `pytest tests/test_auth_fgp.py -v` shows 4 XFAIL - `pytest -v` (full suite) exits 0 with the same pass count as before this plan (411+) plus 4 new XFAIL - No changes to backend/services/auth.py, backend/deps/auth.py, or backend/api/auth.py - `backend/tests/test_auth_fgp.py` exists with 4 test stubs (FGP-01..04) - All 4 stubs use `@pytest.mark.xfail(strict=False, reason="not implemented yet")` - Full test suite passes with zero failures; 4 new XFAIL added - No production code modified Create `.planning/phases/07.4-security-token-fingerprinting-token-binding-inserted/07.4-01-SUMMARY.md` when done