fix: OAuth token endpoint wraps in {code,message,data} — not flat

The /authen/v1/oidc/access_token response uses the standard Feishu envelope
with 'message' (not 'msg') field. Updated FeishuAPIResponse to handle both
'msg' and 'message' via custom init(from:). exchangeCode and
refreshAccessToken now decode via FeishuAPIResponse<OAuthTokenData>.

Tenant token endpoint (/auth/v3/tenant_access_token/internal) returns flat
response — TenantAccessTokenData decoded directly.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
tigerenwork 2026-06-29 00:33:53 +08:00
parent ae918a379c
commit 0f8565f65d
2 changed files with 28 additions and 8 deletions

View File

@ -50,11 +50,14 @@ final class FeishuAuthService {
request.httpMethod = "POST" request.httpMethod = "POST"
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(tenantToken)", forHTTPHeaderField: "Authorization") request.setValue("Bearer \(tenantToken)", forHTTPHeaderField: "Authorization")
// Use manual JSON to rule out JSONEncoder issues
let jsonBody = "{\"grant_type\":\"authorization_code\",\"code\":\"\(code)\"}" let jsonBody = "{\"grant_type\":\"authorization_code\",\"code\":\"\(code)\"}"
request.httpBody = jsonBody.data(using: .utf8) request.httpBody = jsonBody.data(using: .utf8)
print("[BUGGER] exchangeCode: sending to \(url.absoluteString)...") print("[BUGGER] exchangeCode: sending to \(url.absoluteString)...")
let tokenData: OAuthTokenData = try await send(request) // OAuth token response is wrapped in {code, message, data: {...}}
let response: FeishuAPIResponse<OAuthTokenData> = try await send(request)
guard response.code == 0, let tokenData = response.data else {
throw FeishuError.apiError(code: response.code, message: response.msg)
}
print("[BUGGER] exchangeCode: SUCCESS access=\(tokenData.accessToken.prefix(8))... refresh=\(tokenData.refreshToken?.prefix(8) ?? "nil")...") print("[BUGGER] exchangeCode: SUCCESS access=\(tokenData.accessToken.prefix(8))... refresh=\(tokenData.refreshToken?.prefix(8) ?? "nil")...")
return tokenData return tokenData
} }
@ -66,12 +69,13 @@ final class FeishuAuthService {
request.httpMethod = "POST" request.httpMethod = "POST"
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(tenantToken)", forHTTPHeaderField: "Authorization") request.setValue("Bearer \(tenantToken)", forHTTPHeaderField: "Authorization")
request.httpBody = try JSONEncoder().encode([ let jsonBody = "{\"grant_type\":\"refresh_token\",\"refresh_token\":\"\(refreshToken)\"}"
"grant_type": "refresh_token", request.httpBody = jsonBody.data(using: .utf8)
"refresh_token": refreshToken, // Uses same {code, message, data: {...}} wrapper as exchangeCode
] as [String: String]) let response: FeishuAPIResponse<OAuthTokenData> = try await send(request)
// Token endpoints return data at top level, not wrapped in {code, msg, data} guard response.code == 0, let tokenData = response.data else {
let tokenData: OAuthTokenData = try await send(request) throw FeishuError.apiError(code: response.code, message: response.msg)
}
return tokenData return tokenData
} }

View File

@ -4,6 +4,22 @@ struct FeishuAPIResponse<T: Decodable>: Decodable {
let code: Int let code: Int
let msg: String let msg: String
let data: T? let data: T?
enum CodingKeys: String, CodingKey {
case code, msg, message, data
}
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
code = try c.decode(Int.self, forKey: .code)
// Some endpoints use "msg", others use "message"
if let m = try c.decodeIfPresent(String.self, forKey: .msg) {
msg = m
} else {
msg = try c.decode(String.self, forKey: .message)
}
data = try c.decodeIfPresent(T.self, forKey: .data)
}
} }
struct RecordListData: Decodable { struct RecordListData: Decodable {