From 0f8565f65d4262d3d719876bf69f707ddb737134 Mon Sep 17 00:00:00 2001 From: tigerenwork Date: Mon, 29 Jun 2026 00:33:53 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20OAuth=20token=20endpoint=20wraps=20in=20?= =?UTF-8?q?{code,message,data}=20=E2=80=94=20not=20flat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. Tenant token endpoint (/auth/v3/tenant_access_token/internal) returns flat response — TenantAccessTokenData decoded directly. Co-Authored-By: Claude --- .../Services/Feishu/FeishuAuthService.swift | 20 +++++++++++-------- Sources/Services/Feishu/FeishuModels.swift | 16 +++++++++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Sources/Services/Feishu/FeishuAuthService.swift b/Sources/Services/Feishu/FeishuAuthService.swift index f8e6e47..e79194e 100644 --- a/Sources/Services/Feishu/FeishuAuthService.swift +++ b/Sources/Services/Feishu/FeishuAuthService.swift @@ -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 = 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 = try await send(request) + guard response.code == 0, let tokenData = response.data else { + throw FeishuError.apiError(code: response.code, message: response.msg) + } return tokenData } diff --git a/Sources/Services/Feishu/FeishuModels.swift b/Sources/Services/Feishu/FeishuModels.swift index 586f8cf..c6c38bd 100644 --- a/Sources/Services/Feishu/FeishuModels.swift +++ b/Sources/Services/Feishu/FeishuModels.swift @@ -4,6 +4,22 @@ struct FeishuAPIResponse: 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 {