fix: preserve production task and auth integrations
This commit is contained in:
92
app.py
92
app.py
@@ -6,7 +6,9 @@ from flask import Flask, g, jsonify, redirect, request, send_file
|
||||
|
||||
from auth_service import (
|
||||
build_dingtalk_login_url,
|
||||
create_agent_access_token,
|
||||
exchange_dingtalk_user,
|
||||
get_user_by_agent_token,
|
||||
get_user_by_session_token,
|
||||
revoke_session,
|
||||
upsert_user_and_session,
|
||||
@@ -32,10 +34,12 @@ from task_service import (
|
||||
TaskError,
|
||||
cancel_task,
|
||||
create_task,
|
||||
delete_task,
|
||||
get_file,
|
||||
get_task,
|
||||
get_task_extracted,
|
||||
list_tasks,
|
||||
merge_tasks,
|
||||
recollect_task,
|
||||
resume_task,
|
||||
resume_unfinished_tasks,
|
||||
@@ -50,12 +54,8 @@ def create_app() -> Flask:
|
||||
if not Config.LOCAL_AUTH_BYPASS or request.remote_addr not in {"127.0.0.1", "::1"}:
|
||||
return None
|
||||
return {
|
||||
"id": "local-dev",
|
||||
"name": "本地开发用户",
|
||||
"account": "local-dev",
|
||||
"avatar": "",
|
||||
"union_id": "",
|
||||
"open_id": "",
|
||||
"id": "local-dev", "name": "本地开发用户", "account": "local-dev",
|
||||
"avatar": "", "union_id": "", "open_id": "",
|
||||
}
|
||||
|
||||
@app.before_request
|
||||
@@ -67,9 +67,16 @@ def create_app() -> Flask:
|
||||
if request.path == "/health" or any(request.path.startswith(path) for path in open_paths):
|
||||
return None
|
||||
user = local_dev_user() or get_user_by_session_token(request.cookies.get(Config.SESSION_COOKIE_NAME))
|
||||
if user is None:
|
||||
authorization = request.headers.get("Authorization", "")
|
||||
if authorization.lower().startswith("bearer "):
|
||||
user = get_user_by_agent_token(authorization[7:].strip())
|
||||
if user is None and request.path.startswith("/api/"):
|
||||
return jsonify({
|
||||
"error": {"code": "UNAUTHORIZED", "message": "请先通过钉钉授权登录"},
|
||||
"error": {
|
||||
"code": "UNAUTHORIZED",
|
||||
"message": "登录或 AI 访问 Token 已失效,请重新登录后复制 Link AI",
|
||||
},
|
||||
"request_id": g.request_id,
|
||||
}), 401
|
||||
g.current_user = user
|
||||
@@ -104,10 +111,14 @@ def create_app() -> Flask:
|
||||
|
||||
@app.route("/api/v1/auth/dingtalk/login", methods=["GET"])
|
||||
def dingtalk_login():
|
||||
return jsonify({
|
||||
response = jsonify({
|
||||
"data": build_dingtalk_login_url(request.args.get("redirect", "/tasks")),
|
||||
"request_id": g.request_id,
|
||||
})
|
||||
# OAuth URL 带一次性 state,且 scope/corpId 配置可能更新,禁止缓存。
|
||||
response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate"
|
||||
response.headers["Pragma"] = "no-cache"
|
||||
return response
|
||||
|
||||
@app.route("/api/v1/auth/dingtalk/callback", methods=["GET"])
|
||||
def dingtalk_callback():
|
||||
@@ -118,8 +129,13 @@ def create_app() -> Flask:
|
||||
frontend_redirect = state.split(":", 1)[1] or frontend_redirect
|
||||
if not code:
|
||||
return redirect(f"{Config.FRONTEND_BASE_URL}/login?error={quote('钉钉授权缺少 code')}")
|
||||
user_payload = exchange_dingtalk_user(code)
|
||||
user, token, expires_at = upsert_user_and_session(user_payload)
|
||||
try:
|
||||
user_payload = exchange_dingtalk_user(code)
|
||||
user, token, expires_at = upsert_user_and_session(user_payload)
|
||||
except TaskError as exc:
|
||||
return redirect(
|
||||
f"{Config.FRONTEND_BASE_URL}/login?error={quote(exc.message)}"
|
||||
)
|
||||
response = redirect(f"{Config.FRONTEND_BASE_URL}{frontend_redirect}")
|
||||
response.set_cookie(
|
||||
Config.SESSION_COOKIE_NAME,
|
||||
@@ -130,17 +146,6 @@ def create_app() -> Flask:
|
||||
samesite=Config.SESSION_COOKIE_SAMESITE,
|
||||
path="/",
|
||||
)
|
||||
# 给前端“Link AI”复制提示词使用。后端鉴权仍读取 hc_session;
|
||||
# 前端只读取这个辅助 Cookie 的 value,并在提示词中拼成 hc_session=<value>。
|
||||
response.set_cookie(
|
||||
"hc_session_agent",
|
||||
token,
|
||||
expires=expires_at,
|
||||
httponly=False,
|
||||
secure=Config.SESSION_COOKIE_SECURE,
|
||||
samesite=Config.SESSION_COOKIE_SAMESITE,
|
||||
path="/",
|
||||
)
|
||||
app.logger.info("钉钉用户登录成功 user_id=%s name=%s", user["id"], user["name"])
|
||||
return response
|
||||
|
||||
@@ -216,11 +221,34 @@ def create_app() -> Flask:
|
||||
raise TaskError("UNAUTHORIZED", "请先通过钉钉授权登录", 401)
|
||||
return jsonify({"data": {"user": user}, "request_id": g.request_id})
|
||||
|
||||
@app.route("/api/v1/auth/agent-token", methods=["POST"])
|
||||
def auth_agent_token():
|
||||
issued = create_agent_access_token(
|
||||
request.cookies.get(Config.SESSION_COOKIE_NAME)
|
||||
)
|
||||
if not issued:
|
||||
raise TaskError(
|
||||
"UNAUTHORIZED",
|
||||
"登录已失效,请重新完成钉钉登录后再复制 Link AI",
|
||||
401,
|
||||
)
|
||||
token, expires_at = issued
|
||||
response = jsonify({
|
||||
"data": {
|
||||
"token": token,
|
||||
"expires_at": expires_at.isoformat(timespec="seconds"),
|
||||
},
|
||||
"request_id": g.request_id,
|
||||
})
|
||||
response.headers["Cache-Control"] = "no-store"
|
||||
return response
|
||||
|
||||
@app.route("/api/v1/auth/logout", methods=["POST"])
|
||||
def auth_logout():
|
||||
revoke_session(request.cookies.get(Config.SESSION_COOKIE_NAME))
|
||||
response = jsonify({"data": {"ok": True}, "request_id": g.request_id})
|
||||
response.delete_cookie(Config.SESSION_COOKIE_NAME, path="/")
|
||||
# 清理旧版本曾设置的可读辅助 Cookie。
|
||||
response.delete_cookie("hc_session_agent", path="/")
|
||||
return response
|
||||
|
||||
@@ -272,12 +300,27 @@ def create_app() -> Flask:
|
||||
)
|
||||
return jsonify({"data": result, "request_id": g.request_id})
|
||||
|
||||
@app.route("/api/v1/tasks/merge", methods=["POST"])
|
||||
def merge_research_tasks():
|
||||
payload = request.get_json(silent=True) or {}
|
||||
task_ids = payload.get("task_ids")
|
||||
if not isinstance(task_ids, list):
|
||||
raise TaskError("MERGE_TASKS_REQUIRED", "task_ids 必须是任务 ID 数组", 422)
|
||||
return jsonify({
|
||||
"data": merge_tasks(task_ids),
|
||||
"request_id": g.request_id,
|
||||
})
|
||||
|
||||
@app.route("/api/v1/tasks/<task_id>", methods=["GET"])
|
||||
def get_research_task(task_id: str):
|
||||
if request.args.get("view") in {"extracted", "cleaned", "parsed"}:
|
||||
return jsonify({"data": get_task_extracted(task_id), "request_id": g.request_id})
|
||||
return jsonify({"data": get_task(task_id), "request_id": g.request_id})
|
||||
|
||||
@app.route("/api/v1/tasks/<task_id>", methods=["DELETE"])
|
||||
def delete_research_task(task_id: str):
|
||||
return jsonify({"data": delete_task(task_id), "request_id": g.request_id})
|
||||
|
||||
@app.route("/api/v1/tasks/<task_id>/extracted", methods=["GET"])
|
||||
def get_research_task_extracted(task_id: str):
|
||||
return jsonify({"data": get_task_extracted(task_id), "request_id": g.request_id})
|
||||
@@ -297,8 +340,13 @@ def create_app() -> Flask:
|
||||
|
||||
@app.route("/api/v1/tasks/<task_id>/recollect", methods=["POST"])
|
||||
def recollect_research_task(task_id: str):
|
||||
current_user = getattr(g, "current_user", None) or {}
|
||||
return jsonify({
|
||||
"data": recollect_task(task_id),
|
||||
"data": recollect_task(
|
||||
task_id,
|
||||
created_by_id=current_user.get("id"),
|
||||
created_by_name=current_user.get("name"),
|
||||
),
|
||||
"request_id": g.request_id,
|
||||
}), 201
|
||||
|
||||
|
||||
Reference in New Issue
Block a user