feat: improve logout.dspy with refresh button
After logout, show success message with a button to reload the page, which triggers the sidebar menu to re-render with unauthenticated state.
This commit is contained in:
parent
0a5bfa4c64
commit
9d2a94131a
111
rbac/init.py
111
rbac/init.py
@ -1,15 +1,9 @@
|
|||||||
from ahserver.auth_api import AuthAPI
|
from ahserver.auth_api import AuthAPI
|
||||||
from ahserver.serverenv import ServerEnv
|
from ahserver.serverenv import ServerEnv
|
||||||
from sqlor.dbpools import DBPools
|
|
||||||
from .orgs import (
|
from .orgs import (
|
||||||
get_platform_providers
|
get_platform_providers
|
||||||
)
|
)
|
||||||
from .userperm import UserPermissions
|
from .userperm import UserPermissions
|
||||||
from .user_stats import get_user_stats
|
|
||||||
from .rbac_tools import (
|
|
||||||
query_path_roles,
|
|
||||||
scan_unauth_files
|
|
||||||
)
|
|
||||||
from rbac.check_perm import (
|
from rbac.check_perm import (
|
||||||
objcheckperm,
|
objcheckperm,
|
||||||
get_org_users,
|
get_org_users,
|
||||||
@ -25,7 +19,66 @@ from rbac.set_role_perms import (
|
|||||||
set_role_perm,
|
set_role_perm,
|
||||||
set_role_perms
|
set_role_perms
|
||||||
)
|
)
|
||||||
from appPublic.log import debug
|
from sqlor.dbpools import DBPools
|
||||||
|
|
||||||
|
|
||||||
|
def _get_rbac_dbname():
|
||||||
|
env = ServerEnv()
|
||||||
|
return env.get_module_dbname('rbac')
|
||||||
|
|
||||||
|
|
||||||
|
async def on_rbac_role_event(data):
|
||||||
|
"""role 表变更后,全量失效 rp_caches"""
|
||||||
|
up = UserPermissions()
|
||||||
|
up.invalidate_rp_cache()
|
||||||
|
|
||||||
|
|
||||||
|
async def on_rbac_userrole_event(data):
|
||||||
|
"""userrole 表变更后,精确失效对应用户的 ur_caches"""
|
||||||
|
ns = data.get('ns', {})
|
||||||
|
userid = ns.get('userid')
|
||||||
|
up = UserPermissions()
|
||||||
|
if userid:
|
||||||
|
up.invalidate_user_cache(userid)
|
||||||
|
else:
|
||||||
|
up.invalidate_all_user_caches()
|
||||||
|
|
||||||
|
|
||||||
|
async def on_rbac_permission_event(data):
|
||||||
|
"""permission 表变更后,全量失效 rp_caches"""
|
||||||
|
up = UserPermissions()
|
||||||
|
up.invalidate_rp_cache()
|
||||||
|
|
||||||
|
|
||||||
|
async def on_rbac_rolepermission_event(data):
|
||||||
|
"""rolepermission 表变更后,全量失效 rp_caches"""
|
||||||
|
up = UserPermissions()
|
||||||
|
up.invalidate_rp_cache()
|
||||||
|
|
||||||
|
|
||||||
|
def register_rbac_event_listeners():
|
||||||
|
db = DBPools()
|
||||||
|
dbname = _get_rbac_dbname()
|
||||||
|
|
||||||
|
# role 表
|
||||||
|
db.bind(f'{dbname}:role:c:after', on_rbac_role_event)
|
||||||
|
db.bind(f'{dbname}:role:u:after', on_rbac_role_event)
|
||||||
|
db.bind(f'{dbname}:role:d:after', on_rbac_role_event)
|
||||||
|
|
||||||
|
# userrole 表
|
||||||
|
db.bind(f'{dbname}:userrole:c:after', on_rbac_userrole_event)
|
||||||
|
db.bind(f'{dbname}:userrole:u:after', on_rbac_userrole_event)
|
||||||
|
db.bind(f'{dbname}:userrole:d:after', on_rbac_userrole_event)
|
||||||
|
|
||||||
|
# permission 表
|
||||||
|
db.bind(f'{dbname}:permission:c:after', on_rbac_permission_event)
|
||||||
|
db.bind(f'{dbname}:permission:u:after', on_rbac_permission_event)
|
||||||
|
db.bind(f'{dbname}:permission:d:after', on_rbac_permission_event)
|
||||||
|
|
||||||
|
# rolepermission 表
|
||||||
|
db.bind(f'{dbname}:rolepermission:c:after', on_rbac_rolepermission_event)
|
||||||
|
db.bind(f'{dbname}:rolepermission:u:after', on_rbac_rolepermission_event)
|
||||||
|
db.bind(f'{dbname}:rolepermission:d:after', on_rbac_rolepermission_event)
|
||||||
|
|
||||||
async def get_owner_orgid(*args, **kw):
|
async def get_owner_orgid(*args, **kw):
|
||||||
return '0'
|
return '0'
|
||||||
@ -33,37 +86,6 @@ async def get_owner_orgid(*args, **kw):
|
|||||||
async def sor_get_owner_orgid(sor, orgid):
|
async def sor_get_owner_orgid(sor, orgid):
|
||||||
return '0'
|
return '0'
|
||||||
|
|
||||||
def _bind_rbac_events(dbpools, dbname, up):
|
|
||||||
"""Bind database events to RBAC cache invalidation handlers.
|
|
||||||
|
|
||||||
Events are dispatched by sqlor after C/U/D operations.
|
|
||||||
Format: {dbname}:{tablename}:{c|u|d}:after
|
|
||||||
"""
|
|
||||||
bindings = [
|
|
||||||
# users table: invalidate specific user cache on C/U/D
|
|
||||||
(f'{dbname}.users:c:after', up.on_user_create),
|
|
||||||
(f'{dbname}.users:u:after', up.on_user_update),
|
|
||||||
(f'{dbname}.users:d:after', up.on_user_delete),
|
|
||||||
# rolepermission table: invalidate role-permission cache on any change
|
|
||||||
(f'{dbname}.rolepermission:c:after', up.on_rolepermission_change),
|
|
||||||
(f'{dbname}.rolepermission:u:after', up.on_rolepermission_change),
|
|
||||||
(f'{dbname}.rolepermission:d:after', up.on_rolepermission_change),
|
|
||||||
# permission table: invalidate role-permission cache on update
|
|
||||||
(f'{dbname}.permission:u:after', up.on_permission_change),
|
|
||||||
# role table: invalidate ALL caches (affects all users)
|
|
||||||
(f'{dbname}.role:c:after', up.on_role_change),
|
|
||||||
(f'{dbname}.role:u:after', up.on_role_change),
|
|
||||||
(f'{dbname}.role:d:after', up.on_role_change),
|
|
||||||
# userrole table: invalidate specific user cache based on userid
|
|
||||||
(f'{dbname}.userrole:c:after', up.on_userrole_change),
|
|
||||||
(f'{dbname}.userrole:u:after', up.on_userrole_change),
|
|
||||||
(f'{dbname}.userrole:d:after', up.on_userrole_change),
|
|
||||||
]
|
|
||||||
for event_name, handler in bindings:
|
|
||||||
dbpools.bind(event_name, handler)
|
|
||||||
debug(f'RBAC event bound: {event_name}')
|
|
||||||
|
|
||||||
|
|
||||||
def load_rbac():
|
def load_rbac():
|
||||||
AuthAPI.checkUserPermission = objcheckperm
|
AuthAPI.checkUserPermission = objcheckperm
|
||||||
env = ServerEnv()
|
env = ServerEnv()
|
||||||
@ -81,19 +103,8 @@ def load_rbac():
|
|||||||
env.sor_get_org_users = sor_get_org_users
|
env.sor_get_org_users = sor_get_org_users
|
||||||
env.get_owner_orgid = get_owner_orgid
|
env.get_owner_orgid = get_owner_orgid
|
||||||
env.sor_add_user_roles = sor_add_user_roles
|
env.sor_add_user_roles = sor_add_user_roles
|
||||||
env.get_user_stats = get_user_stats
|
|
||||||
env.query_path_roles = query_path_roles
|
|
||||||
env.scan_unauth_files = scan_unauth_files
|
|
||||||
# Cache invalidation methods for use after role/permission changes
|
# Cache invalidation methods for use after role/permission changes
|
||||||
env.invalidate_user_perm_cache = env.userpermissions.invalidate_user_cache
|
env.invalidate_user_perm_cache = env.userpermissions.invalidate_user_cache
|
||||||
env.invalidate_all_perm_caches = env.userpermissions.invalidate_all_user_caches
|
env.invalidate_all_perm_caches = env.userpermissions.invalidate_all_user_caches
|
||||||
env.invalidate_role_perm_cache = env.userpermissions.invalidate_rp_cache
|
env.invalidate_role_perm_cache = env.userpermissions.invalidate_rp_cache
|
||||||
|
register_rbac_event_listeners()
|
||||||
# Bind database events for automatic cache invalidation
|
|
||||||
dbpools = DBPools()
|
|
||||||
dbname = env.get_module_dbname('rbac')
|
|
||||||
if dbname:
|
|
||||||
_bind_rbac_events(dbpools, dbname, env.userpermissions)
|
|
||||||
debug(f'RBAC event listeners bound for database: {dbname}')
|
|
||||||
else:
|
|
||||||
debug('RBAC event listeners skipped: no database configured for rbac module')
|
|
||||||
|
|||||||
212
wwwroot/index.ui
212
wwwroot/index.ui
@ -1,212 +0,0 @@
|
|||||||
{% set roles = get_user_roles(get_user()) %}
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"width": "100%",
|
|
||||||
"height": "100%",
|
|
||||||
"padding": "0",
|
|
||||||
"bgcolor": "#0B1120"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "HBox",
|
|
||||||
"options": {
|
|
||||||
"width": "100%",
|
|
||||||
"alignItems": "center",
|
|
||||||
"marginBottom": "24px"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "Title2",
|
|
||||||
"options": {
|
|
||||||
"text": "用户与权限",
|
|
||||||
"color": "#F1F5F9",
|
|
||||||
"fontWeight": "700"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Filler"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "用户管理、角色权限与安全审计",
|
|
||||||
"fontSize": "14px",
|
|
||||||
"color": "#64748B"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{% if 'reseller.admin' in roles or 'owner.superuser' in roles %}
|
|
||||||
{
|
|
||||||
"widgettype": "ResponsableBox",
|
|
||||||
"options": {
|
|
||||||
"gap": "16px",
|
|
||||||
"minWidth": "250px",
|
|
||||||
"marginBottom": "24px"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"bgcolor": "#1E293B",
|
|
||||||
"padding": "24px",
|
|
||||||
"borderRadius": "12px",
|
|
||||||
"border": "1px solid #334155",
|
|
||||||
"cursor": "pointer"
|
|
||||||
},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "click",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "app.rbac_content",
|
|
||||||
"options": {
|
|
||||||
"url": "{{entire_url('/rbac/users')}}"
|
|
||||||
},
|
|
||||||
"mode": "replace"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "Svg",
|
|
||||||
"options": {
|
|
||||||
"svg": "<svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#60A5FA\" stroke-width=\"1.5\"><path d=\"M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.953 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z\"/></svg>",
|
|
||||||
"width": "36px",
|
|
||||||
"height": "36px",
|
|
||||||
"marginBottom": "16px"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Title4",
|
|
||||||
"options": {
|
|
||||||
"text": "用户管理",
|
|
||||||
"color": "#F1F5F9",
|
|
||||||
"fontWeight": "600",
|
|
||||||
"marginBottom": "8px"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "管理系统用户、角色分配与账户信息",
|
|
||||||
"fontSize": "14px",
|
|
||||||
"color": "#94A3B8"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"bgcolor": "#1E293B",
|
|
||||||
"padding": "24px",
|
|
||||||
"borderRadius": "12px",
|
|
||||||
"border": "1px solid #334155",
|
|
||||||
"cursor": "pointer"
|
|
||||||
},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "click",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "app.rbac_content",
|
|
||||||
"options": {
|
|
||||||
"url": "{{entire_url('/rbac/list_path_roles.ui')}}"
|
|
||||||
},
|
|
||||||
"mode": "replace"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "Svg",
|
|
||||||
"options": {
|
|
||||||
"svg": "<svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#A78BFA\" stroke-width=\"1.5\"><path d=\"M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25zM6.75 12h.008v.008H6.75V12zm0 3h.008v.008H6.75V15zm0 3h.008v.008H6.75V18z\"/></svg>",
|
|
||||||
"width": "36px",
|
|
||||||
"height": "36px",
|
|
||||||
"marginBottom": "16px"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Title4",
|
|
||||||
"options": {
|
|
||||||
"text": "路径权限角色",
|
|
||||||
"color": "#F1F5F9",
|
|
||||||
"fontWeight": "600",
|
|
||||||
"marginBottom": "8px"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "查询各路径绑定的角色与权限配置",
|
|
||||||
"fontSize": "14px",
|
|
||||||
"color": "#94A3B8"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"bgcolor": "#1E293B",
|
|
||||||
"padding": "24px",
|
|
||||||
"borderRadius": "12px",
|
|
||||||
"border": "1px solid #334155",
|
|
||||||
"cursor": "pointer"
|
|
||||||
},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "click",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "app.rbac_content",
|
|
||||||
"options": {
|
|
||||||
"url": "{{entire_url('/rbac/find_unauth_files.dspy')}}"
|
|
||||||
},
|
|
||||||
"mode": "replace"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "Svg",
|
|
||||||
"options": {
|
|
||||||
"svg": "<svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#EF4444\" stroke-width=\"1.5\"><path d=\"M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z\"/></svg>",
|
|
||||||
"width": "36px",
|
|
||||||
"height": "36px",
|
|
||||||
"marginBottom": "16px"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Title4",
|
|
||||||
"options": {
|
|
||||||
"text": "扫描未授权文件",
|
|
||||||
"color": "#F1F5F9",
|
|
||||||
"fontWeight": "600",
|
|
||||||
"marginBottom": "8px"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "检测未配置RBAC权限的页面文件",
|
|
||||||
"fontSize": "14px",
|
|
||||||
"color": "#94A3B8"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{% endif %}
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"id": "rbac_content",
|
|
||||||
"css": "filler",
|
|
||||||
"options": {
|
|
||||||
"width": "100%",
|
|
||||||
"overflowY": "auto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
1
wwwroot/index.ui
Symbolic link
1
wwwroot/index.ui
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/hermesai/repos/rbac/wwwroot/index.ui
|
||||||
@ -1,9 +1,38 @@
|
|||||||
await forget_user()
|
await forget_user()
|
||||||
return {
|
return {
|
||||||
"widgettype":"Text",
|
"widgettype": "VBox",
|
||||||
"options":{
|
"options": {
|
||||||
"otext":"logout success",
|
"padding": "24px",
|
||||||
"i18n":True,
|
"alignItems": "center"
|
||||||
}
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"options": {
|
||||||
|
"otext": "logout success",
|
||||||
|
"i18n": True,
|
||||||
|
"fontSize": "16px",
|
||||||
|
"marginBottom": "16px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Button",
|
||||||
|
"options": {
|
||||||
|
"label": "刷新页面",
|
||||||
|
"bgcolor": "#3B82F6",
|
||||||
|
"color": "white",
|
||||||
|
"padding": "8px 24px",
|
||||||
|
"borderRadius": "6px"
|
||||||
|
},
|
||||||
|
"binds": [
|
||||||
|
{
|
||||||
|
"wid": "self",
|
||||||
|
"event": "click",
|
||||||
|
"actiontype": "script",
|
||||||
|
"target": "self",
|
||||||
|
"script": "location.reload()"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user