fix: add User-Agent/Referer headers, GET index first for session cookie, handle non-JSON login response

This commit is contained in:
yumoqing 2026-05-18 09:40:22 +08:00
parent 26ebfe132c
commit a0a4b5698d

View File

@ -16,9 +16,10 @@ import kotlinx.serialization.json.*
* Sage 客户端 - 处理登录Session 管理和 UI 加载
*
* Sage 使用 Cookie Session 认证
* 1. POST /rbac/user/userpassword_login.dspy 登录获取 session cookie
* 2. 后续请求自动携带 cookie
* 3. GET /xxx.ui 获取 JSON 格式的 UI 描述
* 1. GET 首页获取初始 session cookie
* 2. POST /rbac/user/userpassword_login.dspy 登录
* 3. 后续请求自动携带 cookie
* 4. GET /xxx.ui 获取 JSON 格式的 UI 描述
*/
class SageClient {
@ -54,45 +55,55 @@ class SageClient {
suspend fun login(username: String, password: String): Boolean = mutex.withLock {
_loginError.value = null
try {
// Step 1: GET the index page to obtain initial session cookies
// Sage requires a valid session cookie before accepting login POST
val indexResponse = client.get("$baseUrl/")
println("[Sage] GET / status: ${indexResponse.status}")
// Step 2: POST login
val url = "$baseUrl/rbac/user/userpassword_login.dspy"
val encodedUser = java.net.URLEncoder.encode(username, "UTF-8")
val encodedPass = java.net.URLEncoder.encode(password, "UTF-8")
val formBody = "username=$encodedUser&passwd=$encodedPass"
val response = client.post(url) {
header(HttpHeaders.UserAgent, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36")
header(HttpHeaders.Referrer, "$baseUrl/")
contentType(ContentType.Application.FormUrlEncoded)
setBody(formBody)
}
val body = response.bodyAsText()
println("[Sage] Login response status: ${response.status}")
println("[Sage] Login response body: ${body.take(200)}")
println("[Sage] Login response body: ${body.take(300)}")
// Check if response is JSON
if (!body.trimStart().startsWith("{")) {
// Non-JSON response - likely an error page or auth failure
if (response.status.value == 401 || response.status.value == 403) {
_loginError.value = "登录失败: 服务器拒绝请求 (${response.status.value})"
} else {
_loginError.value = "登录失败: 服务器返回非JSON响应 (${response.status.value})"
}
println("[Sage] Non-JSON login response: ${body.take(200)}")
return@withLock false
}
// Sage 返回 UiMessage 格式: {"widgettype": "Message", "options": {...}}
// 或者返回错误: {"widgettype": "Error", "options": {...}}
val json = try {
Json.parseToJsonElement(body).jsonObject
} catch (e: Exception) {
_loginError.value = "登录响应格式错误: ${e.message}"
_loginError.value = "登录响应解析错误: ${e.message}"
return@withLock false
}
val widgetType = json["widgettype"]?.jsonPrimitive?.content
if (widgetType == "Message" || widgetType == "UiMessage") {
// 检查是否有 session cookie
val cookies = cookieStorage.get(URLBuilder(baseUrl).build())
if (cookies.isNotEmpty()) {
println("[Sage] Login successful, got ${cookies.size} cookies")
_isLoggedIn.value = true
true
} else {
// 即使没有 cookie如果服务器返回成功也算登录成功
// 有些部署可能使用 token 而非 cookie
println("[Sage] Login successful (no cookies)")
_isLoggedIn.value = true
true
}
println("[Sage] Login successful, cookies: ${cookies.size}")
_isLoggedIn.value = true
true
} else {
// 错误消息
val options = json["options"]?.jsonObject ?: JsonObject(emptyMap())
@ -118,7 +129,9 @@ class SageClient {
val url = if (path.startsWith("http")) path else "$baseUrl/${path.trimStart('/')}"
println("[Sage] Fetching UI: $url")
val response = client.get(url)
val response = client.get(url) {
header(HttpHeaders.UserAgent, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36")
}
val body = response.bodyAsText()
if (!response.status.isSuccess()) {
@ -151,6 +164,7 @@ class SageClient {
url {
params.forEach { (k, v) -> parameters.append(k, v) }
}
header(HttpHeaders.UserAgent, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36")
when {
jsonBody != null -> {
contentType(ContentType.Application.Json)
@ -169,6 +183,7 @@ class SageClient {
url {
params.forEach { (k, v) -> parameters.append(k, v) }
}
header(HttpHeaders.UserAgent, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36")
}
}