diff --git a/.beans/nuzlocke-tracker-26my--crash-show-owner-info-in-admin-pages.md b/.beans/nuzlocke-tracker-26my--crash-show-owner-info-in-admin-pages.md index 631d490..1f6d2a0 100644 --- a/.beans/nuzlocke-tracker-26my--crash-show-owner-info-in-admin-pages.md +++ b/.beans/nuzlocke-tracker-26my--crash-show-owner-info-in-admin-pages.md @@ -5,7 +5,7 @@ status: completed type: bug priority: high created_at: 2026-03-22T09:41:57Z -updated_at: 2026-03-22T09:45:28Z +updated_at: 2026-03-22T09:45:38Z parent: nuzlocke-tracker-bw1m blocking: - nuzlocke-tracker-2fp1 diff --git a/.beans/nuzlocke-tracker-95g1--crash-hide-edit-controls-for-non-owners-in-fronten.md b/.beans/nuzlocke-tracker-95g1--crash-hide-edit-controls-for-non-owners-in-fronten.md new file mode 100644 index 0000000..36ca8c6 --- /dev/null +++ b/.beans/nuzlocke-tracker-95g1--crash-hide-edit-controls-for-non-owners-in-fronten.md @@ -0,0 +1,28 @@ +--- +# nuzlocke-tracker-95g1 +title: 'Crash: Hide edit controls for non-owners in frontend' +status: completed +type: bug +priority: high +created_at: 2026-03-22T09:41:57Z +updated_at: 2026-03-22T09:46:59Z +parent: nuzlocke-tracker-bw1m +blocking: + - nuzlocke-tracker-i2va +--- + +Bean was found in 'in-progress' status on startup but no agent was running. +This likely indicates a crash or unexpected termination. + +Manual review required before retrying. + +Bean: nuzlocke-tracker-i2va +Title: Hide edit controls for non-owners in frontend + +## Reasons for Scrapping + +This crash bean is a false positive. The original task (nuzlocke-tracker-i2va) was already completed and merged to `develop` before this crash bean was created: +- Commit `3bd24fc`: fix: hide edit controls for non-owners in frontend +- Commit `118dbca`: chore: mark bean nuzlocke-tracker-i2va as completed + +No additional work required. diff --git a/.beans/nuzlocke-tracker-9rm8--crash-optional-totp-mfa-for-emailpassword-accounts.md b/.beans/nuzlocke-tracker-9rm8--crash-optional-totp-mfa-for-emailpassword-accounts.md index 725c4d8..6b1f14b 100644 --- a/.beans/nuzlocke-tracker-9rm8--crash-optional-totp-mfa-for-emailpassword-accounts.md +++ b/.beans/nuzlocke-tracker-9rm8--crash-optional-totp-mfa-for-emailpassword-accounts.md @@ -1,11 +1,11 @@ --- # nuzlocke-tracker-9rm8 title: 'Crash: Optional TOTP MFA for email/password accounts' -status: scrapped +status: completed type: bug priority: high created_at: 2026-03-22T09:41:57Z -updated_at: 2026-03-22T09:46:14Z +updated_at: 2026-03-22T09:46:30Z parent: nuzlocke-tracker-bw1m blocking: - nuzlocke-tracker-f2hs diff --git a/.beans/nuzlocke-tracker-snft--support-es256-ecc-p-256-jwt-keys-in-backend-auth.md b/.beans/nuzlocke-tracker-snft--support-es256-ecc-p-256-jwt-keys-in-backend-auth.md new file mode 100644 index 0000000..dd0ea72 --- /dev/null +++ b/.beans/nuzlocke-tracker-snft--support-es256-ecc-p-256-jwt-keys-in-backend-auth.md @@ -0,0 +1,13 @@ +--- +# nuzlocke-tracker-snft +title: Support ES256 (ECC P-256) JWT keys in backend auth +status: completed +type: bug +priority: normal +created_at: 2026-03-22T10:51:30Z +updated_at: 2026-03-22T10:52:46Z +--- + +Backend JWKS verification only accepts RS256 algorithm, but Supabase JWT key was switched to ECC P-256 (ES256). This causes 401 errors on all authenticated requests. Fix: accept both RS256 and ES256 in the algorithms list, and update tests accordingly. + +## Summary of Changes\n\nAdded ES256 to the accepted JWT algorithms in `_verify_jwt()` so ECC P-256 keys from Supabase are verified correctly alongside RSA keys. Added corresponding test with EC key fixtures. diff --git a/.beans/nuzlocke-tracker-tatg--bug-intermittent-401-errors-failed-save-load-requi.md b/.beans/nuzlocke-tracker-tatg--bug-intermittent-401-errors-failed-save-load-requi.md index 724f1b0..7a8cd6a 100644 --- a/.beans/nuzlocke-tracker-tatg--bug-intermittent-401-errors-failed-save-load-requi.md +++ b/.beans/nuzlocke-tracker-tatg--bug-intermittent-401-errors-failed-save-load-requi.md @@ -5,7 +5,7 @@ status: completed type: bug priority: high created_at: 2026-03-21T21:50:48Z -updated_at: 2026-03-22T09:01:42Z +updated_at: 2026-03-22T09:44:54Z --- ## Problem diff --git a/backend/src/app/core/auth.py b/backend/src/app/core/auth.py index 4446a3f..77fc491 100644 --- a/backend/src/app/core/auth.py +++ b/backend/src/app/core/auth.py @@ -60,7 +60,7 @@ def _verify_jwt_hs256(token: str) -> dict | None: def _verify_jwt(token: str) -> dict | None: - """Verify JWT using JWKS (RS256), falling back to HS256 shared secret.""" + """Verify JWT using JWKS (RS256/ES256), falling back to HS256 shared secret.""" client = _get_jwks_client() if client: try: @@ -68,7 +68,7 @@ def _verify_jwt(token: str) -> dict | None: return jwt.decode( token, signing_key.key, - algorithms=["RS256"], + algorithms=["RS256", "ES256"], audience="authenticated", ) except jwt.InvalidTokenError: diff --git a/backend/tests/test_auth.py b/backend/tests/test_auth.py index a37cb78..a49fc78 100644 --- a/backend/tests/test_auth.py +++ b/backend/tests/test_auth.py @@ -4,7 +4,7 @@ from uuid import UUID import jwt import pytest -from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.asymmetric import ec, rsa from httpx import ASGITransport, AsyncClient from app.core.auth import AuthUser, get_current_user, require_admin, require_auth @@ -73,6 +73,55 @@ def mock_jwks_client(rsa_key_pair): return mock_client +@pytest.fixture(scope="module") +def ec_key_pair(): + """Generate EC P-256 key pair for testing.""" + private_key = ec.generate_private_key(ec.SECP256R1()) + public_key = private_key.public_key() + return private_key, public_key + + +@pytest.fixture +def valid_es256_token(ec_key_pair): + """Generate a valid ES256 JWT token.""" + private_key, _ = ec_key_pair + payload = { + "sub": "user-456", + "email": "ec-user@example.com", + "role": "authenticated", + "aud": "authenticated", + "exp": int(time.time()) + 3600, + } + return jwt.encode(payload, private_key, algorithm="ES256") + + +@pytest.fixture +def mock_jwks_client_ec(ec_key_pair): + """Create a mock JWKS client that returns our test EC public key.""" + _, public_key = ec_key_pair + mock_client = MagicMock() + mock_signing_key = MagicMock() + mock_signing_key.key = public_key + mock_client.get_signing_key_from_jwt.return_value = mock_signing_key + return mock_client + + +async def test_get_current_user_valid_es256_token( + valid_es256_token, mock_jwks_client_ec +): + """Test get_current_user works with ES256 (ECC P-256) tokens.""" + with patch("app.core.auth._get_jwks_client", return_value=mock_jwks_client_ec): + + class MockRequest: + headers = {"Authorization": f"Bearer {valid_es256_token}"} + + user = get_current_user(MockRequest()) + assert user is not None + assert user.id == "user-456" + assert user.email == "ec-user@example.com" + assert user.role == "authenticated" + + async def test_get_current_user_valid_token(valid_token, mock_jwks_client): """Test get_current_user returns user for valid token.""" with patch("app.core.auth._get_jwks_client", return_value=mock_jwks_client):