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:
parent
ae918a379c
commit
0f8565f65d
|
|
@ -50,11 +50,14 @@ final class FeishuAuthService {
|
|||
request.httpMethod = "POST"
|
||||
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
|
||||
request.setValue("Bearer \(tenantToken)", forHTTPHeaderField: "Authorization")
|
||||
// Use manual JSON to rule out JSONEncoder issues
|
||||
let jsonBody = "{\"grant_type\":\"authorization_code\",\"code\":\"\(code)\"}"
|
||||
request.httpBody = jsonBody.data(using: .utf8)
|
||||
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")...")
|
||||
return tokenData
|
||||
}
|
||||
|
|
@ -66,12 +69,13 @@ final class FeishuAuthService {
|
|||
request.httpMethod = "POST"
|
||||
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
|
||||
request.setValue("Bearer \(tenantToken)", forHTTPHeaderField: "Authorization")
|
||||
request.httpBody = try JSONEncoder().encode([
|
||||
"grant_type": "refresh_token",
|
||||
"refresh_token": refreshToken,
|
||||
] as [String: String])
|
||||
// Token endpoints return data at top level, not wrapped in {code, msg, data}
|
||||
let tokenData: OAuthTokenData = try await send(request)
|
||||
let jsonBody = "{\"grant_type\":\"refresh_token\",\"refresh_token\":\"\(refreshToken)\"}"
|
||||
request.httpBody = jsonBody.data(using: .utf8)
|
||||
// Uses same {code, message, data: {...}} wrapper as exchangeCode
|
||||
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)
|
||||
}
|
||||
return tokenData
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,22 @@ struct FeishuAPIResponse<T: Decodable>: Decodable {
|
|||
let code: Int
|
||||
let msg: String
|
||||
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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue