Squashed 'pm/' content from commit 91f3271
git-subtree-dir: pm git-subtree-split: 91f3271d866fa8ee471cfb6793e334b83e6d2b6e
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
"""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)}}
|
||||
Reference in New Issue
Block a user