Compare commits
22 Commits
renovate/s
...
5a9848fd5f
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a9848fd5f | |||
| 712badb69d | |||
| c40dd38c99 | |||
| 98121d9954 | |||
| f340f8fd0d | |||
| d2fa9e46df | |||
| f770e4a785 | |||
| 013a45ab56 | |||
| 321b940398 | |||
| e21a8acc60 | |||
| f15e530130 | |||
| e533a3404e | |||
| a944da2204 | |||
| 012cfb96cd | |||
| e3e015852c | |||
| 59b4f7f28c | |||
| e212251da8 | |||
| f49c8cee85 | |||
| b34f1083a3 | |||
| b85668c233 | |||
| 45cbff7672 | |||
| 51b47dbfb0 |
@@ -5,11 +5,9 @@ status: completed
|
|||||||
type: bug
|
type: bug
|
||||||
priority: normal
|
priority: normal
|
||||||
created_at: 2026-03-22T10:51:30Z
|
created_at: 2026-03-22T10:51:30Z
|
||||||
updated_at: 2026-03-22T10:59:46Z
|
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.
|
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.
|
## 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.
|
||||||
|
|
||||||
Deployed to production via PR #86 merge on 2026-03-22.
|
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import urllib.request
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from fastapi import APIRouter, Request
|
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
|
|
||||||
from app.core.auth import _build_jwks_url, _extract_token, _get_jwks_client
|
|
||||||
from app.core.config import settings
|
|
||||||
from app.core.database import async_session
|
from app.core.database import async_session
|
||||||
|
|
||||||
router = APIRouter(tags=["health"])
|
router = APIRouter(tags=["health"])
|
||||||
@@ -27,45 +23,3 @@ async def health_check():
|
|||||||
async def root():
|
async def root():
|
||||||
"""Root endpoint."""
|
"""Root endpoint."""
|
||||||
return {"message": "Nuzlocke Tracker API", "docs": "/docs"}
|
return {"message": "Nuzlocke Tracker API", "docs": "/docs"}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/auth-debug")
|
|
||||||
async def auth_debug(request: Request):
|
|
||||||
"""Temporary diagnostic endpoint for auth debugging."""
|
|
||||||
result: dict = {}
|
|
||||||
|
|
||||||
# Config
|
|
||||||
result["supabase_url"] = settings.supabase_url
|
|
||||||
result["has_jwt_secret"] = bool(settings.supabase_jwt_secret)
|
|
||||||
result["jwks_url"] = (
|
|
||||||
_build_jwks_url(settings.supabase_url) if settings.supabase_url else None
|
|
||||||
)
|
|
||||||
|
|
||||||
# JWKS fetch
|
|
||||||
jwks_url = result["jwks_url"]
|
|
||||||
if jwks_url:
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(jwks_url, timeout=5) as resp:
|
|
||||||
result["jwks_status"] = resp.status
|
|
||||||
result["jwks_body"] = resp.read().decode()
|
|
||||||
except Exception as e:
|
|
||||||
result["jwks_fetch_error"] = str(e)
|
|
||||||
|
|
||||||
# JWKS client
|
|
||||||
client = _get_jwks_client()
|
|
||||||
result["jwks_client_exists"] = client is not None
|
|
||||||
|
|
||||||
# Token info (header only, no secrets)
|
|
||||||
token = _extract_token(request)
|
|
||||||
if token:
|
|
||||||
import jwt
|
|
||||||
|
|
||||||
try:
|
|
||||||
header = jwt.get_unverified_header(token)
|
|
||||||
result["token_header"] = header
|
|
||||||
except Exception as e:
|
|
||||||
result["token_header_error"] = str(e)
|
|
||||||
else:
|
|
||||||
result["token"] = "not provided"
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import logging
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
@@ -13,7 +12,6 @@ from app.core.database import get_session
|
|||||||
from app.models.nuzlocke_run import NuzlockeRun
|
from app.models.nuzlocke_run import NuzlockeRun
|
||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
_jwks_client: PyJWKClient | None = None
|
_jwks_client: PyJWKClient | None = None
|
||||||
|
|
||||||
|
|
||||||
@@ -26,21 +24,11 @@ class AuthUser:
|
|||||||
role: str | None = None
|
role: str | None = None
|
||||||
|
|
||||||
|
|
||||||
def _build_jwks_url(base_url: str) -> str:
|
|
||||||
"""Build the JWKS URL, adding /auth/v1 prefix for Supabase Cloud."""
|
|
||||||
base = base_url.rstrip("/")
|
|
||||||
if "/auth/v1" in base:
|
|
||||||
return f"{base}/.well-known/jwks.json"
|
|
||||||
# Supabase Cloud URLs need the /auth/v1 prefix;
|
|
||||||
# local GoTrue serves JWKS at root but uses HS256 fallback anyway.
|
|
||||||
return f"{base}/auth/v1/.well-known/jwks.json"
|
|
||||||
|
|
||||||
|
|
||||||
def _get_jwks_client() -> PyJWKClient | None:
|
def _get_jwks_client() -> PyJWKClient | None:
|
||||||
"""Get or create a cached JWKS client."""
|
"""Get or create a cached JWKS client."""
|
||||||
global _jwks_client
|
global _jwks_client
|
||||||
if _jwks_client is None and settings.supabase_url:
|
if _jwks_client is None and settings.supabase_url:
|
||||||
jwks_url = _build_jwks_url(settings.supabase_url)
|
jwks_url = f"{settings.supabase_url.rstrip('/')}/.well-known/jwks.json"
|
||||||
_jwks_client = PyJWKClient(jwks_url, cache_jwk_set=True, lifespan=300)
|
_jwks_client = PyJWKClient(jwks_url, cache_jwk_set=True, lifespan=300)
|
||||||
return _jwks_client
|
return _jwks_client
|
||||||
|
|
||||||
@@ -83,14 +71,12 @@ def _verify_jwt(token: str) -> dict | None:
|
|||||||
algorithms=["RS256", "ES256"],
|
algorithms=["RS256", "ES256"],
|
||||||
audience="authenticated",
|
audience="authenticated",
|
||||||
)
|
)
|
||||||
except jwt.InvalidTokenError as e:
|
except jwt.InvalidTokenError:
|
||||||
logger.warning("JWKS JWT validation failed: %s", e)
|
pass
|
||||||
except PyJWKClientError as e:
|
except PyJWKClientError:
|
||||||
logger.warning("JWKS client error: %s", e)
|
pass
|
||||||
except PyJWKSetError as e:
|
except PyJWKSetError:
|
||||||
logger.warning("JWKS set error: %s", e)
|
pass
|
||||||
else:
|
|
||||||
logger.warning("No JWKS client available (SUPABASE_URL not set?)")
|
|
||||||
return _verify_jwt_hs256(token)
|
return _verify_jwt_hs256(token)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
60
frontend/package-lock.json
generated
60
frontend/package-lock.json
generated
@@ -1389,9 +1389,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@supabase/auth-js": {
|
"node_modules/@supabase/auth-js": {
|
||||||
"version": "2.103.0",
|
"version": "2.99.3",
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.103.0.tgz",
|
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.99.3.tgz",
|
||||||
"integrity": "sha512-6zAanO6c+6gpHOlt5Lb9TlBBkJdZiUWkWCJKAxzkywBDcwaHlLJKXnjQGX6GyVCyKRR1e7sTq4re/yRTH6U/9A==",
|
"integrity": "sha512-vMEVLA1kGGYd/kdsJSwtjiFUZM1nGfrz2DWmgMBZtocV48qL+L2+4QpIkueXyBEumMQZFEyhz57i/5zGHjvdBw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.8.1"
|
"tslib": "2.8.1"
|
||||||
@@ -1401,9 +1401,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@supabase/functions-js": {
|
"node_modules/@supabase/functions-js": {
|
||||||
"version": "2.103.0",
|
"version": "2.99.3",
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.103.0.tgz",
|
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.99.3.tgz",
|
||||||
"integrity": "sha512-YrneV2NjskUkkmkZ2Jt2n3elBgbWzV4Y1M9MM370z2Zd5ZPFqFbY8KIoPwuNjtAGE9YrpKBxnbZqeF07BiN9Og==",
|
"integrity": "sha512-6tk2zrcBkzKaaBXPOG5nshn30uJNFGOH9LxOnE8i850eQmsX+jVm7vql9kTPyvUzEHwU4zdjSOkXS9M+9ukMVA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.8.1"
|
"tslib": "2.8.1"
|
||||||
@@ -1412,16 +1412,10 @@
|
|||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@supabase/phoenix": {
|
|
||||||
"version": "0.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.0.tgz",
|
|
||||||
"integrity": "sha512-RHSx8bHS02xwfHdAbX5Lpbo6PXbgyf7lTaXTlwtFDPwOIw64NnVRwFAXGojHhjtVYI+PEPNSWwkL90f4agN3bw==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@supabase/postgrest-js": {
|
"node_modules/@supabase/postgrest-js": {
|
||||||
"version": "2.103.0",
|
"version": "2.99.3",
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.103.0.tgz",
|
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.99.3.tgz",
|
||||||
"integrity": "sha512-rC3sRxYdPZymkp2CZR1MiNQgbOleD01bGsW8VxEKRR5nMkLZ1NgAS1QTQf78Wh30czFyk505ZYr9Od8/mWT2TA==",
|
"integrity": "sha512-8HxEf+zNycj7Z8+ONhhlu+7J7Ha+L6weyCtdEeK2mN5OWJbh6n4LPU4iuJ5UlCvvNnbSXMoutY7piITEEAgl2g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.8.1"
|
"tslib": "2.8.1"
|
||||||
@@ -1431,12 +1425,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@supabase/realtime-js": {
|
"node_modules/@supabase/realtime-js": {
|
||||||
"version": "2.103.0",
|
"version": "2.99.3",
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.103.0.tgz",
|
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.99.3.tgz",
|
||||||
"integrity": "sha512-gcPtXzZ6izyyBVf2of7K3dEt8CScPJn8VcSlQq6oWL9QoE1kqfQl0oFrOMHd5qrcADewxI7OxxosLB8W4XqtIQ==",
|
"integrity": "sha512-c1azgZ2nZPczbY5k5u5iFrk1InpxN81IvNE+UBAkjrBz3yc5ALLJNkeTQwbJZT4PZBuYXEzqYGLMuh9fdTtTMg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@supabase/phoenix": "^0.4.0",
|
"@types/phoenix": "^1.6.6",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"tslib": "2.8.1",
|
"tslib": "2.8.1",
|
||||||
"ws": "^8.18.2"
|
"ws": "^8.18.2"
|
||||||
@@ -1446,9 +1440,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@supabase/storage-js": {
|
"node_modules/@supabase/storage-js": {
|
||||||
"version": "2.103.0",
|
"version": "2.99.3",
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.103.0.tgz",
|
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.99.3.tgz",
|
||||||
"integrity": "sha512-DHmlvdAXwtOmZNbkIZi4lkobPR3XjIzoOgzoz5duMf6G+sDeY015YrzMJCnqdccuYr7X5x4yYuSwF//RoN2dvQ==",
|
"integrity": "sha512-lOfIm4hInNcd8x0i1LWphnLKxec42wwbjs+vhaVAvR801Vda0UAMbTooUY6gfqgQb8v29GofqKuQMMTAsl6w/w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"iceberg-js": "^0.8.1",
|
"iceberg-js": "^0.8.1",
|
||||||
@@ -1459,16 +1453,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@supabase/supabase-js": {
|
"node_modules/@supabase/supabase-js": {
|
||||||
"version": "2.103.0",
|
"version": "2.99.3",
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.103.0.tgz",
|
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.99.3.tgz",
|
||||||
"integrity": "sha512-j/6q5+LtXbR/YOLSLhy7Na74RD1cV2v+KwIIuuqMEjk1JpLEEyu0ynwDHpGoxMncDQl+R5FogaVqZm+85lZvtw==",
|
"integrity": "sha512-GuPbzoEaI51AkLw9VGhLNvnzw4PHbS3p8j2/JlvLeZNQMKwZw4aEYQIDBRtFwL5Nv7/275n9m4DHtakY8nCvgg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@supabase/auth-js": "2.103.0",
|
"@supabase/auth-js": "2.99.3",
|
||||||
"@supabase/functions-js": "2.103.0",
|
"@supabase/functions-js": "2.99.3",
|
||||||
"@supabase/postgrest-js": "2.103.0",
|
"@supabase/postgrest-js": "2.99.3",
|
||||||
"@supabase/realtime-js": "2.103.0",
|
"@supabase/realtime-js": "2.99.3",
|
||||||
"@supabase/storage-js": "2.103.0"
|
"@supabase/storage-js": "2.99.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
@@ -2032,6 +2026,12 @@
|
|||||||
"undici-types": "~7.18.0"
|
"undici-types": "~7.18.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/phoenix": {
|
||||||
|
"version": "1.6.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz",
|
||||||
|
"integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.2.14",
|
"version": "19.2.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
||||||
|
|||||||
Reference in New Issue
Block a user