fix(auth): Casdoor JWT verification and 401 semantics; add /auth/login password-grant endpoint

- verify kid from the JWT header (pyjwt decode() returns claims, not the
  header — the old lookup was always None and rejected every token)
- convert JWK entries to crypto keys explicitly (pyjwt 2.13 dropped dict
  keys) and fetch the JWKS from /.well-known/jwks
- 401 for missing/invalid/expired/wrong-issuer/wrong-audience tokens
  (was 403/400); 403 stays for valid-but-roleless users; 503 when Casdoor
  is unreachable; new CASDOOR_ISSUER setting separates the in-network
  endpoint from the browser-facing issuer
- dev-header fallback in inventory/pm scopes now requires an explicit
  X-User-Role header (no auth info at all -> 401)
- new POST /api/v1/auth/login: password grant against Casdoor, local JWT
  verification, auto-provisioning, returns token + profile with the role
  resolved from the app users table
This commit is contained in:
ali_mohaghegh
2026-08-29 14:43:49 +03:30
parent 06a9a654a5
commit 3cd76d335f
7 changed files with 202 additions and 38 deletions
+14
View File
@@ -49,3 +49,17 @@ class ForbiddenError(AppError):
def __init__(self, message: str, field: str | None = None):
super().__init__(403, "FORBIDDEN", message, field)
class UnauthorizedError(AppError):
"""401 UNAUTHORIZED — missing, invalid, expired or untrusted credentials."""
def __init__(self, message: str, field: str | None = None):
super().__init__(401, "UNAUTHORIZED", message, field)
class ServiceUnavailableError(AppError):
"""503 UPSTREAM_UNAVAILABLE — a dependent service (Casdoor/MinIO) is down."""
def __init__(self, message: str, field: str | None = None):
super().__init__(503, "UPSTREAM_UNAVAILABLE", message, field)