4 Commits

Author SHA1 Message Date
Renovate Bot
7ccf743339 chore(deps): update dependency @supabase/supabase-js to v2.103.0
All checks were successful
CI / backend-tests (pull_request) Successful in 29s
CI / frontend-tests (pull_request) Successful in 27s
2026-04-09 07:02:31 +00:00
d8fec0e5d7 fix:add debugging endpoint for auth issues
All checks were successful
CI / backend-tests (push) Successful in 30s
CI / frontend-tests (push) Successful in 28s
2026-03-22 12:15:25 +01:00
c9b09b8250 fix: fix supabase auth url
All checks were successful
CI / backend-tests (push) Successful in 30s
CI / frontend-tests (push) Successful in 30s
2026-03-22 12:10:03 +01:00
fde1867863 fix: add logging to debug auth issues
All checks were successful
CI / backend-tests (push) Successful in 29s
CI / frontend-tests (push) Successful in 28s
2026-03-22 12:01:28 +01:00
4 changed files with 101 additions and 39 deletions

View File

@@ -5,9 +5,11 @@ status: completed
type: bug
priority: normal
created_at: 2026-03-22T10:51:30Z
updated_at: 2026-03-22T10:52:46Z
updated_at: 2026-03-22T10:59: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.
Deployed to production via PR #86 merge on 2026-03-22.

View File

@@ -1,6 +1,10 @@
from fastapi import APIRouter
import urllib.request
from fastapi import APIRouter, Request
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
router = APIRouter(tags=["health"])
@@ -23,3 +27,45 @@ async def health_check():
async def root():
"""Root endpoint."""
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

View File

@@ -1,3 +1,4 @@
import logging
from dataclasses import dataclass
from uuid import UUID
@@ -12,6 +13,7 @@ from app.core.database import get_session
from app.models.nuzlocke_run import NuzlockeRun
from app.models.user import User
logger = logging.getLogger(__name__)
_jwks_client: PyJWKClient | None = None
@@ -24,11 +26,21 @@ class AuthUser:
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:
"""Get or create a cached JWKS client."""
global _jwks_client
if _jwks_client is None and settings.supabase_url:
jwks_url = f"{settings.supabase_url.rstrip('/')}/.well-known/jwks.json"
jwks_url = _build_jwks_url(settings.supabase_url)
_jwks_client = PyJWKClient(jwks_url, cache_jwk_set=True, lifespan=300)
return _jwks_client
@@ -71,12 +83,14 @@ def _verify_jwt(token: str) -> dict | None:
algorithms=["RS256", "ES256"],
audience="authenticated",
)
except jwt.InvalidTokenError:
pass
except PyJWKClientError:
pass
except PyJWKSetError:
pass
except jwt.InvalidTokenError as e:
logger.warning("JWKS JWT validation failed: %s", e)
except PyJWKClientError as e:
logger.warning("JWKS client error: %s", e)
except PyJWKSetError as e:
logger.warning("JWKS set error: %s", e)
else:
logger.warning("No JWKS client available (SUPABASE_URL not set?)")
return _verify_jwt_hs256(token)

View File

@@ -1389,9 +1389,9 @@
"license": "MIT"
},
"node_modules/@supabase/auth-js": {
"version": "2.99.3",
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.99.3.tgz",
"integrity": "sha512-vMEVLA1kGGYd/kdsJSwtjiFUZM1nGfrz2DWmgMBZtocV48qL+L2+4QpIkueXyBEumMQZFEyhz57i/5zGHjvdBw==",
"version": "2.103.0",
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.103.0.tgz",
"integrity": "sha512-6zAanO6c+6gpHOlt5Lb9TlBBkJdZiUWkWCJKAxzkywBDcwaHlLJKXnjQGX6GyVCyKRR1e7sTq4re/yRTH6U/9A==",
"license": "MIT",
"dependencies": {
"tslib": "2.8.1"
@@ -1401,9 +1401,9 @@
}
},
"node_modules/@supabase/functions-js": {
"version": "2.99.3",
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.99.3.tgz",
"integrity": "sha512-6tk2zrcBkzKaaBXPOG5nshn30uJNFGOH9LxOnE8i850eQmsX+jVm7vql9kTPyvUzEHwU4zdjSOkXS9M+9ukMVA==",
"version": "2.103.0",
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.103.0.tgz",
"integrity": "sha512-YrneV2NjskUkkmkZ2Jt2n3elBgbWzV4Y1M9MM370z2Zd5ZPFqFbY8KIoPwuNjtAGE9YrpKBxnbZqeF07BiN9Og==",
"license": "MIT",
"dependencies": {
"tslib": "2.8.1"
@@ -1412,10 +1412,16 @@
"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": {
"version": "2.99.3",
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.99.3.tgz",
"integrity": "sha512-8HxEf+zNycj7Z8+ONhhlu+7J7Ha+L6weyCtdEeK2mN5OWJbh6n4LPU4iuJ5UlCvvNnbSXMoutY7piITEEAgl2g==",
"version": "2.103.0",
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.103.0.tgz",
"integrity": "sha512-rC3sRxYdPZymkp2CZR1MiNQgbOleD01bGsW8VxEKRR5nMkLZ1NgAS1QTQf78Wh30czFyk505ZYr9Od8/mWT2TA==",
"license": "MIT",
"dependencies": {
"tslib": "2.8.1"
@@ -1425,12 +1431,12 @@
}
},
"node_modules/@supabase/realtime-js": {
"version": "2.99.3",
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.99.3.tgz",
"integrity": "sha512-c1azgZ2nZPczbY5k5u5iFrk1InpxN81IvNE+UBAkjrBz3yc5ALLJNkeTQwbJZT4PZBuYXEzqYGLMuh9fdTtTMg==",
"version": "2.103.0",
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.103.0.tgz",
"integrity": "sha512-gcPtXzZ6izyyBVf2of7K3dEt8CScPJn8VcSlQq6oWL9QoE1kqfQl0oFrOMHd5qrcADewxI7OxxosLB8W4XqtIQ==",
"license": "MIT",
"dependencies": {
"@types/phoenix": "^1.6.6",
"@supabase/phoenix": "^0.4.0",
"@types/ws": "^8.18.1",
"tslib": "2.8.1",
"ws": "^8.18.2"
@@ -1440,9 +1446,9 @@
}
},
"node_modules/@supabase/storage-js": {
"version": "2.99.3",
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.99.3.tgz",
"integrity": "sha512-lOfIm4hInNcd8x0i1LWphnLKxec42wwbjs+vhaVAvR801Vda0UAMbTooUY6gfqgQb8v29GofqKuQMMTAsl6w/w==",
"version": "2.103.0",
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.103.0.tgz",
"integrity": "sha512-DHmlvdAXwtOmZNbkIZi4lkobPR3XjIzoOgzoz5duMf6G+sDeY015YrzMJCnqdccuYr7X5x4yYuSwF//RoN2dvQ==",
"license": "MIT",
"dependencies": {
"iceberg-js": "^0.8.1",
@@ -1453,16 +1459,16 @@
}
},
"node_modules/@supabase/supabase-js": {
"version": "2.99.3",
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.99.3.tgz",
"integrity": "sha512-GuPbzoEaI51AkLw9VGhLNvnzw4PHbS3p8j2/JlvLeZNQMKwZw4aEYQIDBRtFwL5Nv7/275n9m4DHtakY8nCvgg==",
"version": "2.103.0",
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.103.0.tgz",
"integrity": "sha512-j/6q5+LtXbR/YOLSLhy7Na74RD1cV2v+KwIIuuqMEjk1JpLEEyu0ynwDHpGoxMncDQl+R5FogaVqZm+85lZvtw==",
"license": "MIT",
"dependencies": {
"@supabase/auth-js": "2.99.3",
"@supabase/functions-js": "2.99.3",
"@supabase/postgrest-js": "2.99.3",
"@supabase/realtime-js": "2.99.3",
"@supabase/storage-js": "2.99.3"
"@supabase/auth-js": "2.103.0",
"@supabase/functions-js": "2.103.0",
"@supabase/postgrest-js": "2.103.0",
"@supabase/realtime-js": "2.103.0",
"@supabase/storage-js": "2.103.0"
},
"engines": {
"node": ">=20.0.0"
@@ -2026,12 +2032,6 @@
"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": {
"version": "19.2.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",