feat: add callback handler and group query API for client auth flow

- Add rl_handle_callback() to init.py: handles Volcengine H5 auth callback,
  queries vendor for result, registers rl_org_group mapping
- Add rl_query_groups() to init.py: client API to query authenticated
  group_ids for an org
- Add wwwroot/api/rl_callback.dspy endpoint (no auth required for vendor POST)
- Add wwwroot/api/rl_query_groups.dspy endpoint (login required)
- Remove deprecated rl_app_user_* files (no longer used)
- Update scripts/load_path.py: rl_callback -> any role, rl_query_groups -> logined
This commit is contained in:
yumoqing 2026-05-28 16:57:04 +08:00
parent 3ad9b2bb46
commit af65c307f8
7 changed files with 161 additions and 79 deletions

View File

@ -643,6 +643,126 @@ async def rl_sync_asset_status_user(org_id, asset_id, downapp_id):
return {"success": True, "status": status, "url": url}
async def rl_handle_callback(byted_token, project_name="default"):
"""
Callback handler: Volcengine POSTs here after H5 auth completes.
Looks up local group by byted_token, queries vendor for result,
then registers rl_org_group mapping.
"""
dbname = _get_dbname()
db = DBPools()
# 1. Find local group by byted_token
async with db.sqlorContext(dbname) as sor:
recs = await sor.R("rl_asset_group", {"byted_token": byted_token})
if not recs:
return {"success": False, "message": "未找到对应的认证会话"}
rec = recs[0]
local_group_id = rec.id
org_id = rec.org_id
# Already processed?
if rec.status == "active" and rec.vendor_group_id:
debug(f"callback already processed for group {local_group_id}")
return {
"success": True,
"local_group_id": local_group_id,
"vendor_group_id": rec.vendor_group_id,
"message": "已处理",
}
# 2. Get vendor keys
async with db.sqlorContext(dbname) as sor:
vrecs = await sor.R("rl_vendor_config", {"vendor": "volcengine"})
if not vrecs:
return {"success": False, "message": "供应商配置不存在"}
vrec = vrecs[0]
if vrec.status != "active":
return {"success": False, "message": "供应商服务已停用"}
env = ServerEnv()
ak = env.password_decode(vrec.ak)
sk = env.password_decode(vrec.sk)
# 3. Query vendor for result
client = _get_client("volcengine", ak, sk)
result = client.get_visual_validate_result(byted_token, project_name)
if "error" in result:
error(f"callback query vendor error: {result}")
return {"success": False, "message": result.get("error", "查询失败")}
vendor_group_id = result.get("GroupId", "")
if not vendor_group_id:
return {"success": False, "message": "尚未完成认证或认证失败"}
# 4. Register rl_org_group mapping
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
mapping_id = getID()
async with db.sqlorContext(dbname) as sor:
existing = await sor.R("rl_org_group", {
"org_id": org_id,
"vendor": "volcengine",
})
if existing:
await sor.U("rl_org_group", {
"vendor_group_id": vendor_group_id,
"local_group_id": local_group_id,
"update_time": now,
}, {"id": existing[0].id})
else:
await sor.I("rl_org_group", {
"id": mapping_id,
"org_id": org_id,
"vendor": "volcengine",
"vendor_group_id": vendor_group_id,
"local_group_id": local_group_id,
"status": "active",
"create_time": now,
})
# 5. Update rl_asset_group status
async with db.sqlorContext(dbname) as sor:
await sor.U("rl_asset_group", {
"vendor_group_id": vendor_group_id,
"status": "active",
"update_time": now,
}, {"id": local_group_id})
debug(f"callback processed: org={org_id}, group={local_group_id}, "
f"vendor_group={vendor_group_id}")
return {
"success": True,
"local_group_id": local_group_id,
"vendor_group_id": vendor_group_id,
}
async def rl_query_groups(org_id):
"""
Client API: Query authenticated group_ids for an org.
Returns list of (local_group_id, vendor_group_id, status) mappings.
"""
dbname = _get_dbname()
db = DBPools()
async with db.sqlorContext(dbname) as sor:
recs = await sor.R("rl_org_group", {"org_id": org_id})
if not recs:
return {"success": True, "groups": []}
groups = []
for r in recs:
groups.append({
"local_group_id": r.local_group_id,
"vendor_group_id": r.vendor_group_id,
"vendor": r.vendor,
"status": r.status,
"create_time": getattr(r, "create_time", ""),
})
return {"success": True, "groups": groups}
# ============================================================
# Module loader
# ============================================================
@ -664,5 +784,7 @@ def load_reallife_asset():
g.rl_check_validate_and_map = rl_check_validate_and_map
g.rl_upload_user = rl_upload_user
g.rl_sync_asset_status_user = rl_sync_asset_status_user
g.rl_handle_callback = rl_handle_callback
g.rl_query_groups = rl_query_groups
return True

View File

@ -21,7 +21,9 @@ python = os.path.join(sage_root, "py3/bin/python")
set_perm = os.path.join(sage_root, "set_role_perm.py")
# Permission definitions
paths_any = [] # No login required
paths_any = [
"/reallife_asset/api/rl_callback.dspy", # Volcengine callback - no auth
]
paths_logined = [
"/reallife_asset",
"/reallife_asset/index.ui",
@ -50,12 +52,7 @@ paths_logined = [
"/reallife_asset/api/rl_verify.dspy",
"/reallife_asset/api/rl_upload.dspy",
"/reallife_asset/api/rl_status.dspy",
# Ops management CRUD
"/reallife_asset/api/rl_app_user_create.dspy",
"/reallife_asset/api/rl_app_user_update.dspy",
"/reallife_asset/api/rl_app_user_delete.dspy",
"/reallife_asset/rl_app_user_list",
"/reallife_asset/rl_app_user_list/index.ui",
"/reallife_asset/api/rl_query_groups.dspy",
# Vendor Config CRUD
"/reallife_asset/api/rl_vendor_config_create.dspy",
"/reallife_asset/api/rl_vendor_config_update.dspy",

View File

@ -1,32 +0,0 @@
# Create app record
org_id = params_kw.get('org_id', (await get_userorgid()) or '0')
id = params_kw.get('id', getID())
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Decrypt keys if provided by form? No, usually Ops enters plain text, we encrypt.
ak = params_kw.get('ak', '')
sk = params_kw.get('sk', '')
env = ServerEnv()
if ak: ak = env.password_encode(ak)
if sk: sk = env.password_encode(sk)
data = {
"id": id,
"org_id": org_id,
"downapp_id": params_kw.get('downapp_id', ''),
"vendor": params_kw.get('vendor', 'volcengine'),
"status": params_kw.get('status', 'pending'),
"ak": ak,
"sk": sk,
"callback_url": params_kw.get('callback_url', ''),
"remark": params_kw.get('remark', ''),
"create_time": now,
"update_time": now
}
db = DBPools()
dbname = get_module_dbname('reallife_asset')
async with db.sqlorContext(dbname) as sor:
await sor.I("rl_app_user", data)
return {"success": True, "id": id}

View File

@ -1,9 +0,0 @@
id = params_kw.get('id', '')
if not id: return {"success": False, "message": "id required"}
db = DBPools()
dbname = get_module_dbname('reallife_asset')
async with db.sqlorContext(dbname) as sor:
await sor.D("rl_app_user", {"id": id})
return {"success": True}

View File

@ -1,31 +0,0 @@
id = params_kw.get('id', '')
if not id: return {"success": False, "message": "id required"}
db = DBPools()
dbname = get_module_dbname('reallife_asset')
async with db.sqlorContext(dbname) as sor:
recs = await sor.R("rl_app_user", {"id": id})
if not recs: return {"success": False, "message": "Not found"}
# Prepare update data
upd = {}
for k in ['status', 'remark', 'callback_url', 'vendor']:
if params_kw.get(k):
upd[k] = params_kw.get(k)
# Handle keys encryption
ak = params_kw.get('ak', None)
sk = params_kw.get('sk', None)
if ak is not None:
env = ServerEnv()
upd['ak'] = env.password_encode(ak)
if sk is not None:
env = ServerEnv()
upd['sk'] = env.password_encode(sk)
upd['update_time'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
async with db.sqlorContext(dbname) as sor:
await sor.U("rl_app_user", upd, {"id": id})
return {"success": True}

View File

@ -0,0 +1,31 @@
import json
# Volcengine callback POSTs JSON body with BytedToken and result info.
# The callback URL is configured when calling CreateVisualValidateSession.
# Typical payload: {"BytedToken": "...", "ReqUUID": "...", "Status": "..."}
# Try to parse JSON body first
body_str = http_request.get("body", "") or ""
byted_token = ""
project_name = "default"
try:
body = json.loads(body_str) if body_str else {}
byted_token = body.get("BytedToken", "")
# Also check alternative field names
if not byted_token:
byted_token = body.get("byted_token", "")
if not byted_token:
byted_token = body.get("Token", "")
except:
byted_token = ""
# Fallback: check query params
if not byted_token:
byted_token = params_kw.get("BytedToken", params_kw.get("byted_token", ""))
if not byted_token:
return {"success": False, "message": "缺少 BytedToken 参数"}
result = await rl_handle_callback(byted_token, project_name)
return result

View File

@ -0,0 +1,4 @@
org_id = (await get_userorgid()) or '0'
result = await rl_query_groups(org_id)
return result