feat: add role-based sidebar link visibility control
Deploy Dashboard (Dev) / deploy-dev (push) Successful in 1m37s
Deploy Dashboard (Dev) / deploy-dev (push) Successful in 1m37s
This commit is contained in:
@@ -31,7 +31,7 @@ def create_refresh_token(data: dict):
|
||||
return jwt.encode(to_encode, config.SECRET_KEY, algorithm=config.ALGORITHM)
|
||||
|
||||
async def get_current_user(token: str = Depends(oauth2_scheme)):
|
||||
from rbac import User, get_pages
|
||||
from rbac import User, get_pages, get_sidebar_links
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
@@ -48,10 +48,11 @@ async def get_current_user(token: str = Depends(oauth2_scheme)):
|
||||
except jwt.PyJWTError:
|
||||
raise credentials_exception
|
||||
pages = get_pages(username, role)
|
||||
return User(username=username, role=role, pages=pages)
|
||||
sidebar_links = get_sidebar_links(username, role)
|
||||
return User(username=username, role=role, pages=pages, sidebar_links=sidebar_links)
|
||||
|
||||
async def get_current_user_ws(token: str):
|
||||
from rbac import User, get_pages
|
||||
from rbac import User, get_pages, get_sidebar_links
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
@@ -67,7 +68,8 @@ async def get_current_user_ws(token: str):
|
||||
except jwt.PyJWTError:
|
||||
raise credentials_exception
|
||||
pages = get_pages(username, role)
|
||||
return User(username=username, role=role, pages=pages)
|
||||
sidebar_links = get_sidebar_links(username, role)
|
||||
return User(username=username, role=role, pages=pages, sidebar_links=sidebar_links)
|
||||
|
||||
# TOTP Persistence
|
||||
AUTH_FILE = config.VOLUME_ROOT + "/docker/nas-dashboard/auth.json"
|
||||
|
||||
@@ -16,9 +16,9 @@ ROLE_GROUP_MAP = {
|
||||
|
||||
DEFAULT_RBAC = {
|
||||
"role_defaults": {
|
||||
"admin": {"pages": "*"},
|
||||
"member": {"pages": ["dashboard", "docker", "files", "openclaw", "chat-digest"]},
|
||||
"viewer": {"pages": ["dashboard"]},
|
||||
"admin": {"pages": "*", "sidebar_links": "*"},
|
||||
"member": {"pages": ["dashboard", "docker", "files", "openclaw", "chat-digest"], "sidebar_links": "*"},
|
||||
"viewer": {"pages": ["dashboard"], "sidebar_links": ["dashboard"]},
|
||||
},
|
||||
"user_overrides": {},
|
||||
}
|
||||
@@ -28,6 +28,7 @@ class User(BaseModel):
|
||||
username: str
|
||||
role: str
|
||||
pages: list | str # "*" or list of page ids
|
||||
sidebar_links: list | str = "*" # "*" or list of sidebar link labels/ids
|
||||
|
||||
|
||||
def load_rbac() -> dict:
|
||||
@@ -61,6 +62,14 @@ def get_pages(username: str, role: str) -> list | str:
|
||||
return rbac.get("role_defaults", {}).get(role, {}).get("pages", [])
|
||||
|
||||
|
||||
def get_sidebar_links(username: str, role: str) -> list | str:
|
||||
rbac = load_rbac()
|
||||
overrides = rbac.get("user_overrides", {})
|
||||
if username in overrides and "sidebar_links" in overrides[username]:
|
||||
return overrides[username]["sidebar_links"]
|
||||
return rbac.get("role_defaults", {}).get(role, {}).get("sidebar_links", "*")
|
||||
|
||||
|
||||
def get_sidebar_order(username: str) -> dict | None:
|
||||
rbac = load_rbac()
|
||||
overrides = rbac.get("user_overrides", {})
|
||||
|
||||
@@ -156,6 +156,7 @@ async def read_users_me(current_user = Depends(auth.get_current_user)):
|
||||
"username": current_user.username,
|
||||
"role": current_user.role,
|
||||
"pages": current_user.pages,
|
||||
"sidebar_links": current_user.sidebar_links,
|
||||
"has_2fa": bool(totp_secret),
|
||||
}
|
||||
|
||||
@@ -411,11 +412,17 @@ async def rbac_update_role(role: str, request: Request, current_user = Depends(a
|
||||
from rbac import load_rbac, save_rbac
|
||||
body = await request.json()
|
||||
pages = body.get("pages")
|
||||
sidebar_links = body.get("sidebar_links")
|
||||
if pages is None:
|
||||
raise HTTPException(status_code=400, detail="Missing pages field")
|
||||
if pages != "*" and not isinstance(pages, list):
|
||||
raise HTTPException(status_code=400, detail="pages must be '*' or a list")
|
||||
if sidebar_links is not None and sidebar_links != "*" and not isinstance(sidebar_links, list):
|
||||
raise HTTPException(status_code=400, detail="sidebar_links must be '*' or a list")
|
||||
rbac = load_rbac()
|
||||
rbac.setdefault("role_defaults", {})[role] = {"pages": pages}
|
||||
role_config = {"pages": pages}
|
||||
if sidebar_links is not None:
|
||||
role_config["sidebar_links"] = sidebar_links
|
||||
rbac.setdefault("role_defaults", {})[role] = role_config
|
||||
save_rbac(rbac)
|
||||
return {"ok": True}
|
||||
|
||||
Reference in New Issue
Block a user