新增
This commit is contained in:
+41
-33
@@ -44,11 +44,15 @@ from desktop_router import router as desktop_router
|
||||
from link_cards_router import router as link_cards_router, UPLOAD_DIR as LINK_CARD_UPLOAD_DIR
|
||||
from auth.dependencies import (
|
||||
get_current_user,
|
||||
require_accounts_write,
|
||||
require_admin,
|
||||
require_accounts_cookie,
|
||||
require_accounts_create,
|
||||
require_accounts_delete,
|
||||
require_accounts_start,
|
||||
require_accounts_stop,
|
||||
require_accounts_update,
|
||||
require_messages_write,
|
||||
require_rules_write,
|
||||
require_write,
|
||||
require_system_logs_clear,
|
||||
)
|
||||
from auth.account_limits import ensure_can_add_account
|
||||
from auth.scopes import (
|
||||
@@ -61,7 +65,7 @@ from auth.scopes import (
|
||||
received_logs_for_user,
|
||||
system_logs_for_user,
|
||||
)
|
||||
from auth.roles import has_permission, is_admin
|
||||
from auth.roles import has_global_scope, has_permission, is_admin
|
||||
from auth.permissions import LOGS_READ, RECEIVED_MESSAGES_READ, SYSTEM_LOGS_READ
|
||||
from auth.passwords import hash_password
|
||||
from rpa_engine.batch_start import BatchStartQueue
|
||||
@@ -1413,7 +1417,7 @@ async def get_account_options(
|
||||
Account.reply_cooldown_seconds,
|
||||
Account.quota_disabled,
|
||||
)
|
||||
if not is_admin(user.role):
|
||||
if not has_global_scope(user.role):
|
||||
stmt = stmt.where(Account.owner_id == user.id)
|
||||
|
||||
rows = (await db.execute(stmt.order_by(Account.id.asc()))).all()
|
||||
@@ -1632,9 +1636,9 @@ async def update_account(
|
||||
account_id: int,
|
||||
body: AccountUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user: User = Depends(require_accounts_write),
|
||||
user: User = Depends(require_accounts_update),
|
||||
):
|
||||
account = await get_owned_account(db, user, account_id, write=True)
|
||||
account = await get_owned_account(db, user, account_id, write=True, write_permission="accounts.update")
|
||||
follow_config_changed = bool(
|
||||
{"follow_welcome_enabled", "follow_welcome_content"}
|
||||
& set(body.model_fields_set)
|
||||
@@ -1700,7 +1704,7 @@ async def get_reply_queue_summaries(
|
||||
allowed_ids = await owned_account_ids(db, user)
|
||||
worker_entries = list(manager.workers.items())
|
||||
else:
|
||||
if is_admin(user.role):
|
||||
if has_global_scope(user.role):
|
||||
allowed_ids = set(requested_ids)
|
||||
elif requested_ids:
|
||||
owned_result = await db.execute(
|
||||
@@ -1793,7 +1797,7 @@ async def send_account_queued_reply_now(
|
||||
user: User = Depends(require_messages_write),
|
||||
):
|
||||
"""将指定任务原子移入紧急队列,并把它后面的普通任务前移一槽。"""
|
||||
await get_owned_account(db, user, account_id, write=True)
|
||||
await get_owned_account(db, user, account_id, write=True, write_permission="messages.write")
|
||||
worker = manager.workers.get(account_id)
|
||||
service = worker._im_service if worker else None
|
||||
if not worker or not worker.is_running or not service or not service._running:
|
||||
@@ -1888,7 +1892,7 @@ async def get_account_cookie(
|
||||
account_id: int,
|
||||
purpose: Optional[str] = None,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user: User = Depends(get_current_user),
|
||||
user: User = Depends(require_accounts_cookie),
|
||||
):
|
||||
"""Read a Cookie for management or for legacy desktop login clients.
|
||||
|
||||
@@ -1921,7 +1925,7 @@ async def get_account_cookie(
|
||||
async def get_desktop_login_credential(
|
||||
account_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user: User = Depends(get_current_user),
|
||||
user: User = Depends(require_accounts_cookie),
|
||||
):
|
||||
"""Return credentials only when the selected Douyin identity is verified.
|
||||
|
||||
@@ -1945,7 +1949,7 @@ async def update_account_cookie(
|
||||
account_id: int,
|
||||
body: AccountCookieUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user: User = Depends(require_accounts_write),
|
||||
user: User = Depends(require_accounts_cookie),
|
||||
):
|
||||
# Validate first: malformed input must not take a healthy hosted account
|
||||
# offline. Filesystem and database mutations happen only after the worker
|
||||
@@ -1960,7 +1964,7 @@ async def update_account_cookie(
|
||||
# preparations. Keep the lock until the new Cookie and cleared identity
|
||||
# are committed so no worker can start in the stop/commit gap.
|
||||
async with manager.preparation_lock(account_id):
|
||||
account = await get_owned_account(db, user, account_id, write=True)
|
||||
account = await get_owned_account(db, user, account_id, write=True, write_permission="accounts.cookie")
|
||||
await _release_db_connection(db)
|
||||
await batch_start_queue.cancel_account(account_id)
|
||||
await manager.stop_worker(account_id)
|
||||
@@ -1994,13 +1998,13 @@ async def update_account_cookie(
|
||||
async def delete_account_cookie(
|
||||
account_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user: User = Depends(require_accounts_write),
|
||||
user: User = Depends(require_accounts_cookie),
|
||||
):
|
||||
# Deleting credentials uses the same preparation lock as starting a
|
||||
# worker, preventing a new worker from appearing after stop_worker but
|
||||
# before the cleared credentials are committed.
|
||||
async with manager.preparation_lock(account_id):
|
||||
account = await get_owned_account(db, user, account_id, write=True)
|
||||
account = await get_owned_account(db, user, account_id, write=True, write_permission="accounts.cookie")
|
||||
await _release_db_connection(db)
|
||||
await batch_start_queue.cancel_account(account_id)
|
||||
await manager.stop_worker(account_id)
|
||||
@@ -2023,7 +2027,7 @@ async def delete_account_cookie(
|
||||
async def create_account(
|
||||
account_in: AccountCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user: User = Depends(require_accounts_write),
|
||||
user: User = Depends(require_accounts_create),
|
||||
):
|
||||
cookie_data = (account_in.cookie_data or "").strip()
|
||||
standard_json_str = None
|
||||
@@ -2061,9 +2065,9 @@ async def create_account(
|
||||
async def delete_account(
|
||||
account_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user: User = Depends(require_accounts_write),
|
||||
user: User = Depends(require_accounts_delete),
|
||||
):
|
||||
await get_owned_account(db, user, account_id, write=True)
|
||||
await get_owned_account(db, user, account_id, write=True, write_permission="accounts.delete")
|
||||
# 停止运行中的任务
|
||||
await _release_db_connection(db)
|
||||
await batch_start_queue.cancel_account(account_id)
|
||||
@@ -2079,7 +2083,7 @@ async def delete_account(
|
||||
async def validate_account_credential(
|
||||
account_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user: User = Depends(get_current_user),
|
||||
user: User = Depends(require_accounts_cookie),
|
||||
):
|
||||
account = await get_owned_account(db, user, account_id)
|
||||
|
||||
@@ -2096,9 +2100,9 @@ async def validate_account_credential(
|
||||
async def reset_account_credentials(
|
||||
account_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user: User = Depends(require_accounts_write),
|
||||
user: User = Depends(require_accounts_cookie),
|
||||
):
|
||||
await get_owned_account(db, user, account_id, write=True)
|
||||
await get_owned_account(db, user, account_id, write=True, write_permission="accounts.cookie")
|
||||
await _release_db_connection(db)
|
||||
await batch_start_queue.cancel_account(account_id)
|
||||
account = await _reset_account_credentials(account_id, db)
|
||||
@@ -2251,9 +2255,11 @@ async def start_account_rpa(
|
||||
account_id: int,
|
||||
body: StartAccountRequest = StartAccountRequest(),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user: User = Depends(require_accounts_write),
|
||||
user: User = Depends(require_accounts_start),
|
||||
):
|
||||
account = await get_owned_account(db, user, account_id, write=True)
|
||||
account = await get_owned_account(
|
||||
db, user, account_id, write=True, write_permission="accounts.start"
|
||||
)
|
||||
# Cancelling waits for an in-flight queued start, and the preparation lock
|
||||
# waits for whichever start owns this account. Neither may keep a pooled
|
||||
# connection checked out while it waits.
|
||||
@@ -2267,7 +2273,7 @@ async def start_account_rpa(
|
||||
async def submit_account_start_batch(
|
||||
body: BatchStartRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user: User = Depends(require_accounts_write),
|
||||
user: User = Depends(require_accounts_start),
|
||||
):
|
||||
requested_ids = list(
|
||||
dict.fromkeys(int(value) for value in body.account_ids if int(value) > 0)
|
||||
@@ -2280,7 +2286,7 @@ async def submit_account_start_batch(
|
||||
# The submit path needs only ids and the disabled flag. Do not hydrate
|
||||
# every account's large cookie/session/QR columns just to enqueue ids.
|
||||
candidate_stmt = select(Account.id, Account.quota_disabled)
|
||||
if not is_admin(user.role):
|
||||
if not has_global_scope(user.role):
|
||||
candidate_stmt = candidate_stmt.where(Account.owner_id == user.id)
|
||||
if not body.all_accounts:
|
||||
candidate_stmt = candidate_stmt.where(Account.id.in_(requested_ids))
|
||||
@@ -2331,9 +2337,11 @@ async def get_account_start_batch(
|
||||
async def stop_account_rpa(
|
||||
account_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user: User = Depends(require_accounts_write),
|
||||
user: User = Depends(require_accounts_stop),
|
||||
):
|
||||
account = await get_owned_account(db, user, account_id, write=True)
|
||||
account = await get_owned_account(
|
||||
db, user, account_id, write=True, write_permission="accounts.stop"
|
||||
)
|
||||
|
||||
# Cancelling drains an in-flight queued start, which can take as long as
|
||||
# the batch per-account deadline. Do not hold a pooled connection for it.
|
||||
@@ -2415,7 +2423,7 @@ async def create_rule(
|
||||
rule_in.keyword = ""
|
||||
if rule_in.account_id is None:
|
||||
raise HTTPException(status_code=400, detail="请选择适用账号,每条规则必须绑定一个托管账号")
|
||||
await get_owned_account(db, user, rule_in.account_id, write=True)
|
||||
await get_owned_account(db, user, rule_in.account_id, write=True, write_permission="rules.write")
|
||||
|
||||
sort_stmt = select(func.max(AutoReplyRule.sort_order)).where(
|
||||
AutoReplyRule.account_id == rule_in.account_id
|
||||
@@ -2451,7 +2459,7 @@ async def update_rule(
|
||||
rule_in.keyword = ""
|
||||
if rule_in.account_id is None:
|
||||
raise HTTPException(status_code=400, detail="请选择适用账号,每条规则必须绑定一个托管账号")
|
||||
await get_owned_account(db, user, rule_in.account_id, write=True)
|
||||
await get_owned_account(db, user, rule_in.account_id, write=True, write_permission="rules.write")
|
||||
|
||||
rule.account_id = rule_in.account_id
|
||||
rule.keyword = rule_in.keyword
|
||||
@@ -2637,7 +2645,7 @@ async def get_system_logs(
|
||||
category=category,
|
||||
limit=max(1, min(int(limit or 200), 1000)),
|
||||
)
|
||||
if not is_admin(user.role):
|
||||
if not has_global_scope(user.role):
|
||||
allowed = await owned_account_ids(db, user)
|
||||
entries = [e for e in entries if e.get("account_id") in allowed]
|
||||
return [SystemLogResponse(**e) for e in entries]
|
||||
@@ -2646,7 +2654,7 @@ async def get_system_logs(
|
||||
@app.delete("/api/system-logs")
|
||||
async def clear_system_logs(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: User = Depends(require_admin),
|
||||
_: User = Depends(require_system_logs_clear),
|
||||
):
|
||||
"""清空系统诊断日志(内存缓冲区 + 数据库历史)。"""
|
||||
system_logger.clear()
|
||||
@@ -2712,7 +2720,7 @@ async def upload_message_image(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user: User = Depends(require_messages_write),
|
||||
):
|
||||
account = await get_owned_account(db, user, account_id, write=True)
|
||||
account = await get_owned_account(db, user, account_id, write=True, write_permission="messages.write")
|
||||
if not file.content_type or not file.content_type.startswith("image/"):
|
||||
raise HTTPException(status_code=400, detail="仅支持上传图片文件")
|
||||
|
||||
@@ -2803,7 +2811,7 @@ async def send_account_message(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user: User = Depends(require_messages_write),
|
||||
):
|
||||
account = await get_owned_account(db, user, account_id, write=True)
|
||||
account = await get_owned_account(db, user, account_id, write=True, write_permission="messages.write")
|
||||
|
||||
content = normalize_outgoing_content(
|
||||
content=body.content or "",
|
||||
|
||||
Reference in New Issue
Block a user