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:
@@ -47,7 +47,12 @@ from app.models.procurement import (
|
||||
)
|
||||
from app.schemas.procurement import PMReceiptCreate
|
||||
from app.services.inventory_service import canonical_specs_hash
|
||||
from app.utils.errors import ForbiddenError, NotFoundError, ValidationError
|
||||
from app.utils.errors import (
|
||||
ForbiddenError,
|
||||
NotFoundError,
|
||||
UnauthorizedError,
|
||||
ValidationError,
|
||||
)
|
||||
from app.utils.serialization import to_iso
|
||||
|
||||
router = APIRouter(prefix="/pm", tags=["pm"])
|
||||
@@ -71,6 +76,8 @@ async def arrivals_scope(request: Request, db: AsyncSession = Depends(get_db)) -
|
||||
if request.headers.get("authorization"):
|
||||
ctx: UserContext | None = await get_optional_user(request, db)
|
||||
if ctx is not None:
|
||||
if ctx.role is None:
|
||||
raise ForbiddenError("نقش شما هنوز در سیستم تعیین نشده است")
|
||||
if ctx.role == "pm":
|
||||
if ctx.project_id is None:
|
||||
raise ForbiddenError("شما به هیچ پروژهای متصل نیستید (projects.pm_user_id)")
|
||||
@@ -79,6 +86,8 @@ async def arrivals_scope(request: Request, db: AsyncSession = Depends(get_db)) -
|
||||
return ArrivalScope(role="admin", user_id=ctx.user.id, project_id=None, display_name=ctx.display_name)
|
||||
raise ForbiddenError("این بخش فقط برای مدیر پروژه یا مدیر تدارکات است")
|
||||
# Dev fallback: X-User-Role: pm | admin (procurement_manager maps to admin)
|
||||
if not request.headers.get("x-user-role"):
|
||||
raise UnauthorizedError("احراز هویت لازم است — توکن Casdoor یا حالت توسعه را ارسال کنید")
|
||||
role = (request.headers.get("x-user-role") or "").strip().lower()
|
||||
if role == "procurement_manager":
|
||||
role = "admin"
|
||||
|
||||
Reference in New Issue
Block a user