收敛用户角色并共享项目库
- 后端限制系统只保留默认 admin 管理员,新建用户固定为标注员,并拒绝观察员或额外管理员角色。 - 将项目、帧、媒体解析、AI 标注、任务、Dashboard 和导出接口改为共享项目库访问,标注员具备同等项目管理和标注能力。 - 前端用户管理移除角色选择和观察员入口,只展示唯一管理员与标注员状态。 - 更新后端/前端测试,覆盖唯一 admin、旧 viewer 归一为标注员、用户删除和共享项目库访问。 - 同步更新 AGENTS 与 doc 文档中的角色权限、共享项目库和测试计划说明。
This commit is contained in:
@@ -18,6 +18,7 @@ from schemas import LoginResponse, UserOut
|
||||
router = APIRouter(prefix="/api/auth", tags=["Auth"])
|
||||
password_context = CryptContext(schemes=["pbkdf2_sha256"], deprecated="auto")
|
||||
bearer_scheme = HTTPBearer(auto_error=False)
|
||||
SUPPORTED_ROLES = {"admin", "annotator"}
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
@@ -50,9 +51,26 @@ def create_access_token(user: User, expires_delta: timedelta | None = None) -> s
|
||||
|
||||
|
||||
def ensure_default_admin(db: Session) -> User:
|
||||
"""Create the default development admin if the user table is empty."""
|
||||
"""Create and enforce the single default administrator account."""
|
||||
existing = db.query(User).filter(User.username == settings.default_admin_username).first()
|
||||
if existing:
|
||||
changed = False
|
||||
if existing.role != "admin":
|
||||
existing.role = "admin"
|
||||
changed = True
|
||||
if not existing.is_active:
|
||||
existing.is_active = 1
|
||||
changed = True
|
||||
extra_admins = db.query(User).filter(
|
||||
User.role == "admin",
|
||||
User.id != existing.id,
|
||||
).all()
|
||||
for user in extra_admins:
|
||||
user.role = "annotator"
|
||||
changed = True
|
||||
if changed:
|
||||
db.commit()
|
||||
db.refresh(existing)
|
||||
return existing
|
||||
user = User(
|
||||
username=settings.default_admin_username,
|
||||
@@ -63,6 +81,31 @@ def ensure_default_admin(db: Session) -> User:
|
||||
db.add(user)
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
extra_admins = db.query(User).filter(
|
||||
User.role == "admin",
|
||||
User.id != user.id,
|
||||
).all()
|
||||
if extra_admins:
|
||||
for extra_user in extra_admins:
|
||||
extra_user.role = "annotator"
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
return user
|
||||
|
||||
|
||||
def normalize_user_role(db: Session, user: User) -> User:
|
||||
"""Keep legacy accounts within the current two-role policy."""
|
||||
desired_role = "admin" if user.username == settings.default_admin_username else "annotator"
|
||||
changed = False
|
||||
if user.role != desired_role:
|
||||
user.role = desired_role
|
||||
changed = True
|
||||
if user.username == settings.default_admin_username and not user.is_active:
|
||||
user.is_active = 1
|
||||
changed = True
|
||||
if changed:
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
return user
|
||||
|
||||
|
||||
@@ -92,6 +135,8 @@ def get_current_user(
|
||||
) from exc
|
||||
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if user:
|
||||
user = normalize_user_role(db, user)
|
||||
if not user or not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
@@ -110,7 +155,7 @@ def require_admin(current_user: User = Depends(get_current_user)) -> User:
|
||||
|
||||
def require_editor(current_user: User = Depends(get_current_user)) -> User:
|
||||
"""Require a user role that can modify segmentation data."""
|
||||
if current_user.role not in {"admin", "annotator"}:
|
||||
if current_user.role not in SUPPORTED_ROLES:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Edit permission required")
|
||||
return current_user
|
||||
|
||||
@@ -143,6 +188,8 @@ def login(payload: LoginRequest, db: Session = Depends(get_db)) -> dict:
|
||||
"""Authenticate a user and return a signed JWT."""
|
||||
ensure_default_admin(db)
|
||||
user = db.query(User).filter(User.username == payload.username).first()
|
||||
if user:
|
||||
user = normalize_user_role(db, user)
|
||||
if not user or not user.is_active or not verify_password(payload.password, user.password_hash):
|
||||
write_audit_log(
|
||||
db,
|
||||
|
||||
Reference in New Issue
Block a user