Files
ERP/pm/backend/app/routers/auth.py
T
MMD 2747f9d4bb chore(pm): vendor the Aramesh/PM platform under pm/ (squash of Aramesh/PM @ 91f3271)
The PM panel (PmPanelPage -> iframe /pm/) needs the project-management
platform served at /pm/ on the ERP domain. Source: org repo Aramesh/PM,
branch feature/project-management-platform. Update via:
git subtree pull --prefix=pm aramesh-pm feature/project-management-platform --squash
2026-10-07 15:14:20 +03:30

99 lines
3.7 KiB
Python

"""Local auth + role/scope introspection.
`POST /auth/login` is the real entry point; `GET /auth/me` re-reads the caller's
role/permissions/scope (used after a role or assignment change, and by the
frontend to self-heal a session stored before RBAC existed). The `/auth/dev-*`
endpoints implement the dashboard role switcher and only exist while
`PM_DEV_ROLE_SWITCH` is enabled.
"""
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.authz import build_context, dev_login_usernames, dev_switch_enabled
from app.db import get_db
from app.dev_accounts import demo_account, ensure_demo_assignments
from app.errors import AuthError, NotFoundError
from app.models import PMUser, ProjectAssignment
from app.security import authenticate, create_token, get_current_user
from app.serializers import user_payload
router = APIRouter(prefix="/auth", tags=["auth"])
class LoginIn(BaseModel):
username: str
password: str
@router.post("/login")
async def login(payload: LoginIn, db: AsyncSession = Depends(get_db)):
user = await authenticate(db, payload.username, payload.password)
if user is None:
raise AuthError("نام کاربری یا رمز عبور اشتباه است")
ctx = await build_context(db, user)
return {"data": {"token": create_token(user.id), "user": user_payload(ctx)}}
@router.get("/me")
async def me(user: PMUser = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
return {"data": user_payload(await build_context(db, user))}
# --- development role switcher -------------------------------------------------
def _require_dev_switch() -> None:
if not dev_switch_enabled():
raise NotFoundError("تغییر نقش فقط در فاز توسعه فعال است")
@router.get("/dev-roles")
async def dev_roles(db: AsyncSession = Depends(get_db)):
"""Demo identities offered by the dashboard role picker (dev only)."""
_require_dev_switch()
# projects only exist after the first pull — self-heal the demo scope here
await ensure_demo_assignments(db)
data = []
for username in dev_login_usernames():
account = demo_account(username)
if account is None:
continue
user = await db.scalar(select(PMUser).where(PMUser.username == account.username))
if user is None:
continue
projects = await db.scalar(
select(func.count()).where(ProjectAssignment.user_id == user.id)
)
data.append(
{
"username": user.username,
"display_name": user.display_name,
"role": account.role.value,
"project_count": projects or 0,
"all_projects": account.share is None,
}
)
return {"data": data}
class DevLoginIn(BaseModel):
username: str
@router.post("/dev-login")
async def dev_login(payload: DevLoginIn, db: AsyncSession = Depends(get_db)):
"""Issue a token for a demo account — dev role switching without a password.
Restricted to the configured demo usernames, never arbitrary users.
"""
_require_dev_switch()
if payload.username not in dev_login_usernames():
raise AuthError("این حساب در فهرست حساب‌های توسعه نیست")
user = await db.scalar(select(PMUser).where(PMUser.username == payload.username))
if user is None:
raise AuthError("حساب نمایشی ساخته نشده است؛ سرور را دوباره راه‌اندازی کنید")
ctx = await build_context(db, user)
return {"data": {"token": create_token(user.id), "user": user_payload(ctx)}}