22 Commits

Author SHA1 Message Date
5a9848fd5f Merge pull request 'develop' (#87) from develop into main
Reviewed-on: #87
2026-03-22 11:54:20 +01:00
712badb69d Merge pull request 'Release: MFA, JWKS auth, run ownership, and dependency updates' (#79) from develop into main
Reviewed-on: #79
2026-03-22 11:42:58 +01:00
c40dd38c99 Merge pull request 'update beans and postgres mount path' (#73) from develop into main
Reviewed-on: #73
2026-03-21 12:52:48 +01:00
98121d9954 Merge pull request 'Release: fix TypeScript build errors blocking deploy' (#72) from develop into main
Reviewed-on: #72
2026-03-21 12:27:49 +01:00
f340f8fd0d Merge pull request 'Release: auth system, admin RBAC, and production Supabase setup' (#70) from develop into main
Reviewed-on: #70
2026-03-21 12:21:07 +01:00
d2fa9e46df Merge pull request 'develop' (#56) from develop into main
Reviewed-on: #56
2026-03-20 20:02:22 +01:00
f770e4a785 Merge pull request 'develop' (#45) from develop into main
Reviewed-on: #45
2026-03-20 15:16:00 +01:00
013a45ab56 Merge pull request 'Allow multiple games per region in Custom genlocke' (#34) from develop into main
Reviewed-on: #34
2026-03-17 13:35:27 +01:00
321b940398 Merge pull request 'Fix FK violations when pruning stale routes' (#32) from develop into main
Reviewed-on: #32
2026-02-21 17:56:57 +01:00
e21a8acc60 Merge pull request 'Housekeeping: archive beans, add seed pruning' (#31) from develop into main
Reviewed-on: #31
2026-02-21 17:46:57 +01:00
f15e530130 Merge pull request 'Release: test infrastructure, rules overhaul, and design refresh' (#30) from develop into main
Reviewed-on: #30
2026-02-21 16:58:14 +01:00
e533a3404e Merge pull request 'develop' (#25) from develop into main
Reviewed-on: TheFurya/nuzlocke-tracker#25
2026-02-16 21:19:57 +01:00
a944da2204 Merge pull request 'develop' (#24) from develop into main
Reviewed-on: TheFurya/nuzlocke-tracker#24
2026-02-14 11:05:17 +01:00
012cfb96cd Merge pull request 'develop' (#21) from develop into main
Reviewed-on: TheFurya/nuzlocke-tracker#21
2026-02-14 10:01:41 +01:00
e3e015852c Merge pull request 'develop' (#19) from develop into main
Reviewed-on: TheFurya/nuzlocke-tracker#19
2026-02-13 09:32:47 +01:00
59b4f7f28c Merge pull request 'Complete Game Data Cleanup epic' (#16) from develop into main
Reviewed-on: TheFurya/nuzlocke-tracker#16
2026-02-11 15:34:25 +01:00
e212251da8 Merge pull request 'Fix route ordering' (#15) from develop into main
Reviewed-on: TheFurya/nuzlocke-tracker#15
2026-02-11 15:24:11 +01:00
f49c8cee85 Merge pull request 'Remove old Go fetch-pokeapi tool, update README for import-pokedb (#13)' (#14) from develop into main
Reviewed-on: TheFurya/nuzlocke-tracker#14
2026-02-11 13:57:09 +01:00
b34f1083a3 Merge pull request 'Update README.md' (#12) from develop into main
Reviewed-on: TheFurya/nuzlocke-tracker#12
2026-02-11 13:49:04 +01:00
b85668c233 Merge pull request 'Update bean' (#11) from develop into main
Reviewed-on: TheFurya/nuzlocke-tracker#11
2026-02-11 13:43:16 +01:00
45cbff7672 Merge pull request 'Fix webp sprites not loading in production nginx' (#10) from develop into main
Reviewed-on: TheFurya/nuzlocke-tracker#10
2026-02-11 13:25:14 +01:00
51b47dbfb0 Merge pull request 'develop' (#9) from develop into main
Reviewed-on: TheFurya/nuzlocke-tracker#9
2026-02-11 13:05:12 +01:00
5 changed files with 18 additions and 80 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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)

View File

@@ -13,7 +13,7 @@
"@dnd-kit/utilities": "3.2.2", "@dnd-kit/utilities": "3.2.2",
"@supabase/supabase-js": "^2.99.3", "@supabase/supabase-js": "^2.99.3",
"@tailwindcss/typography": "^0.5.19", "@tailwindcss/typography": "^0.5.19",
"@tanstack/react-query": "5.97.0", "@tanstack/react-query": "5.94.5",
"react": "19.2.4", "react": "19.2.4",
"react-dom": "19.2.4", "react-dom": "19.2.4",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
@@ -1817,9 +1817,9 @@
} }
}, },
"node_modules/@tanstack/query-core": { "node_modules/@tanstack/query-core": {
"version": "5.97.0", "version": "5.94.5",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.97.0.tgz", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.94.5.tgz",
"integrity": "sha512-QdpLP5VzVMgo4VtaPppRA2W04UFjIqX+bxke/ZJhE5cfd5UPkRzqIAJQt9uXkQJjqE8LBOMbKv7f8HCsZltXlg==", "integrity": "sha512-Vx1JJiBURW/wdNGP45afjrqn0LfxYwL7K/bSrQvNRtyLGF1bxQPgUXCpzscG29e+UeFOh9hz1KOVala0N+bZiA==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"type": "github", "type": "github",
@@ -1827,12 +1827,12 @@
} }
}, },
"node_modules/@tanstack/react-query": { "node_modules/@tanstack/react-query": {
"version": "5.97.0", "version": "5.94.5",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.97.0.tgz", "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.94.5.tgz",
"integrity": "sha512-y4So4eGcQoK2WVMAcDNZE9ofB/p5v1OlKvtc1F3uqHwrtifobT7q+ZnXk2mRkc8E84HKYSlAE9z6HXl2V0+ySQ==", "integrity": "sha512-1wmrxKFkor+q8l+ygdHmv0Sq5g84Q3p4xvuJ7AdSIAhQQ7udOt+ZSZ19g1Jea3mHqtlTslLGJsmC4vHFgP0P3A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@tanstack/query-core": "5.97.0" "@tanstack/query-core": "5.94.5"
}, },
"funding": { "funding": {
"type": "github", "type": "github",

View File

@@ -21,7 +21,7 @@
"@dnd-kit/utilities": "3.2.2", "@dnd-kit/utilities": "3.2.2",
"@supabase/supabase-js": "^2.99.3", "@supabase/supabase-js": "^2.99.3",
"@tailwindcss/typography": "^0.5.19", "@tailwindcss/typography": "^0.5.19",
"@tanstack/react-query": "5.97.0", "@tanstack/react-query": "5.94.5",
"react": "19.2.4", "react": "19.2.4",
"react-dom": "19.2.4", "react-dom": "19.2.4",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",