Compare commits
4 Commits
ce9d08963f
...
renovate/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed3af0f1fc | ||
| d8fec0e5d7 | |||
| c9b09b8250 | |||
| fde1867863 |
@@ -5,9 +5,11 @@ 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: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.
|
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,6 +1,10 @@
|
|||||||
from fastapi import APIRouter
|
import urllib.request
|
||||||
|
|
||||||
|
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"])
|
||||||
@@ -23,3 +27,45 @@ 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,3 +1,4 @@
|
|||||||
|
import logging
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from uuid import UUID
|
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.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
|
||||||
|
|
||||||
|
|
||||||
@@ -24,11 +26,21 @@ 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 = 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)
|
_jwks_client = PyJWKClient(jwks_url, cache_jwk_set=True, lifespan=300)
|
||||||
return _jwks_client
|
return _jwks_client
|
||||||
|
|
||||||
@@ -71,12 +83,14 @@ def _verify_jwt(token: str) -> dict | None:
|
|||||||
algorithms=["RS256", "ES256"],
|
algorithms=["RS256", "ES256"],
|
||||||
audience="authenticated",
|
audience="authenticated",
|
||||||
)
|
)
|
||||||
except jwt.InvalidTokenError:
|
except jwt.InvalidTokenError as e:
|
||||||
pass
|
logger.warning("JWKS JWT validation failed: %s", e)
|
||||||
except PyJWKClientError:
|
except PyJWKClientError as e:
|
||||||
pass
|
logger.warning("JWKS client error: %s", e)
|
||||||
except PyJWKSetError:
|
except PyJWKSetError as e:
|
||||||
pass
|
logger.warning("JWKS set error: %s", e)
|
||||||
|
else:
|
||||||
|
logger.warning("No JWKS client available (SUPABASE_URL not set?)")
|
||||||
return _verify_jwt_hs256(token)
|
return _verify_jwt_hs256(token)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
96
frontend/package-lock.json
generated
96
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.99.3",
|
"version": "2.105.4",
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.99.3.tgz",
|
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.105.4.tgz",
|
||||||
"integrity": "sha512-vMEVLA1kGGYd/kdsJSwtjiFUZM1nGfrz2DWmgMBZtocV48qL+L2+4QpIkueXyBEumMQZFEyhz57i/5zGHjvdBw==",
|
"integrity": "sha512-Ejfa37M5xoIwoxVebxRahnwubPo8g22qkXQ4p50+N9MIvU9UZoN+A8dwVPtczzGf8oV/YXN80ZPxK4aWXuSN/A==",
|
||||||
"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.99.3",
|
"version": "2.105.4",
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.99.3.tgz",
|
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.105.4.tgz",
|
||||||
"integrity": "sha512-6tk2zrcBkzKaaBXPOG5nshn30uJNFGOH9LxOnE8i850eQmsX+jVm7vql9kTPyvUzEHwU4zdjSOkXS9M+9ukMVA==",
|
"integrity": "sha512-JVNKbBft3Qkja+WlGaE026AJ2AH9K0UTsxsfvEIHgd4zFrBor4BYRCrYFrv9IDsvVqkF72wKDsODJl5GY/C4tA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.8.1"
|
"tslib": "2.8.1"
|
||||||
@@ -1412,10 +1412,16 @@
|
|||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@supabase/phoenix": {
|
||||||
|
"version": "0.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.2.tgz",
|
||||||
|
"integrity": "sha512-YSAGnmDAfuleFCVt3CeurQZAhxRfXWeZIIkwp7NhYzQ1UwW6ePSnzsFAiUm/mbCkfoCf70QQHKW/K6RKh52a4A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@supabase/postgrest-js": {
|
"node_modules/@supabase/postgrest-js": {
|
||||||
"version": "2.99.3",
|
"version": "2.105.4",
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.99.3.tgz",
|
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.105.4.tgz",
|
||||||
"integrity": "sha512-8HxEf+zNycj7Z8+ONhhlu+7J7Ha+L6weyCtdEeK2mN5OWJbh6n4LPU4iuJ5UlCvvNnbSXMoutY7piITEEAgl2g==",
|
"integrity": "sha512-SppIyLo/kTwIlz1qpv2HN1EQqBg0GVktrDDFsXygYROha3MgVn4rT7p5EjFHFqXQm2rdRGb/BI7bc+jr10m91w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.8.1"
|
"tslib": "2.8.1"
|
||||||
@@ -1425,24 +1431,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@supabase/realtime-js": {
|
"node_modules/@supabase/realtime-js": {
|
||||||
"version": "2.99.3",
|
"version": "2.105.4",
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.99.3.tgz",
|
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.105.4.tgz",
|
||||||
"integrity": "sha512-c1azgZ2nZPczbY5k5u5iFrk1InpxN81IvNE+UBAkjrBz3yc5ALLJNkeTQwbJZT4PZBuYXEzqYGLMuh9fdTtTMg==",
|
"integrity": "sha512-6ov6c59+8D9h7q4M4Gy/uDJlC0Akxl9/714Y+6vJ+Sijuc16TS/p5DwhfRCLNcIhNiej1gEt+CQUwsjiPt4PxQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/phoenix": "^1.6.6",
|
"@supabase/phoenix": "^0.4.2",
|
||||||
"@types/ws": "^8.18.1",
|
"tslib": "2.8.1"
|
||||||
"tslib": "2.8.1",
|
|
||||||
"ws": "^8.18.2"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@supabase/storage-js": {
|
"node_modules/@supabase/storage-js": {
|
||||||
"version": "2.99.3",
|
"version": "2.105.4",
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.99.3.tgz",
|
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.105.4.tgz",
|
||||||
"integrity": "sha512-lOfIm4hInNcd8x0i1LWphnLKxec42wwbjs+vhaVAvR801Vda0UAMbTooUY6gfqgQb8v29GofqKuQMMTAsl6w/w==",
|
"integrity": "sha512-Jx+pzMP1Whjof2PWHoVBUA75/p7PQE9CqKBzn1oXVyJDOggMLSH2OzVWwsXYaxEpdC1K/KltwmOX44nL3LHl9g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"iceberg-js": "^0.8.1",
|
"iceberg-js": "^0.8.1",
|
||||||
@@ -1453,16 +1457,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@supabase/supabase-js": {
|
"node_modules/@supabase/supabase-js": {
|
||||||
"version": "2.99.3",
|
"version": "2.105.4",
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.99.3.tgz",
|
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.105.4.tgz",
|
||||||
"integrity": "sha512-GuPbzoEaI51AkLw9VGhLNvnzw4PHbS3p8j2/JlvLeZNQMKwZw4aEYQIDBRtFwL5Nv7/275n9m4DHtakY8nCvgg==",
|
"integrity": "sha512-cEnx+k49knU+qdIP7rXwR6fqEXPHZs+74xFK1R0S8MgQ7v9tbePVdGxvO03n3bPympMdJWVLadARBfU4TgNHCQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@supabase/auth-js": "2.99.3",
|
"@supabase/auth-js": "2.105.4",
|
||||||
"@supabase/functions-js": "2.99.3",
|
"@supabase/functions-js": "2.105.4",
|
||||||
"@supabase/postgrest-js": "2.99.3",
|
"@supabase/postgrest-js": "2.105.4",
|
||||||
"@supabase/realtime-js": "2.99.3",
|
"@supabase/realtime-js": "2.105.4",
|
||||||
"@supabase/storage-js": "2.99.3"
|
"@supabase/storage-js": "2.105.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
@@ -2021,17 +2025,12 @@
|
|||||||
"version": "25.5.0",
|
"version": "25.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
|
||||||
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
|
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"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",
|
||||||
@@ -2057,15 +2056,6 @@
|
|||||||
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
|
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/ws": {
|
|
||||||
"version": "8.18.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
|
||||||
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@ungap/structured-clone": {
|
"node_modules/@ungap/structured-clone": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
|
||||||
@@ -4855,6 +4845,7 @@
|
|||||||
"version": "7.18.2",
|
"version": "7.18.2",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/unified": {
|
"node_modules/unified": {
|
||||||
@@ -5203,27 +5194,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
|
||||||
"version": "8.19.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
|
||||||
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bufferutil": "^4.0.1",
|
|
||||||
"utf-8-validate": ">=5.0.2"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bufferutil": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"utf-8-validate": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/xml-name-validator": {
|
"node_modules/xml-name-validator": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
|
||||||
|
|||||||
Reference in New Issue
Block a user