

























@@ -6,6 +6,7 @@
66 */
7788import type { Server } from "node:http";
9+import { parseStrictPositiveInteger } from "../../../infra/parse-finite-number.js";
910import {
1011buildOAuthRequestSignal,
1112createOAuthLoginCancelledError,
@@ -122,6 +123,63 @@ function formatTokenResponseParseContext(responseBody: string): string {
122123return `bodyBytes=${Buffer.byteLength(responseBody, "utf8")}`;
123124}
124125126+function resolveTokenExpiresAt(value: unknown): number | undefined {
127+const expiresInSeconds = parseStrictPositiveInteger(value);
128+if (expiresInSeconds === undefined) {
129+return undefined;
130+}
131+132+const lifetimeMs = expiresInSeconds * 1000;
133+const expiresAt = Date.now() + lifetimeMs - 5 * 60 * 1000;
134+return Number.isSafeInteger(lifetimeMs) && Number.isSafeInteger(expiresAt)
135+ ? expiresAt
136+ : undefined;
137+}
138+139+function parseTokenCredentials(
140+responseBody: string,
141+options: {
142+invalidJsonMessage: string;
143+invalidFieldsMessage: string;
144+},
145+): OAuthCredentials {
146+let data: unknown;
147+try {
148+data = JSON.parse(responseBody);
149+} catch (error) {
150+throw new Error(
151+`${options.invalidJsonMessage} url=${TOKEN_URL}; ${formatTokenResponseParseContext(responseBody)}; details=${formatErrorDetails(error)}`,
152+{ cause: error },
153+);
154+}
155+156+if (!data || typeof data !== "object") {
157+throw new Error(
158+`${options.invalidFieldsMessage} url=${TOKEN_URL}; ${formatTokenResponseParseContext(responseBody)}`,
159+);
160+}
161+162+const record = data as Record<string, unknown>;
163+const expires = resolveTokenExpiresAt(record.expires_in);
164+if (
165+typeof record.access_token !== "string" ||
166+!record.access_token ||
167+typeof record.refresh_token !== "string" ||
168+!record.refresh_token ||
169+expires === undefined
170+) {
171+throw new Error(
172+`${options.invalidFieldsMessage} url=${TOKEN_URL}; ${formatTokenResponseParseContext(responseBody)}`,
173+);
174+}
175+176+return {
177+refresh: record.refresh_token,
178+access: record.access_token,
179+ expires,
180+};
181+}
182+125183async function startCallbackServer(expectedState: string): Promise<CallbackServerInfo> {
126184const { createServer } = await getNodeApis();
127185@@ -256,25 +314,10 @@ async function exchangeAuthorizationCode(
256314);
257315}
258316259-let tokenData: { access_token: string; refresh_token: string; expires_in: number };
260-try {
261-tokenData = JSON.parse(responseBody) as {
262-access_token: string;
263-refresh_token: string;
264-expires_in: number;
265-};
266-} catch (error) {
267-throw new Error(
268-`Token exchange returned invalid JSON. url=${TOKEN_URL}; ${formatTokenResponseParseContext(responseBody)}; details=${formatErrorDetails(error)}`,
269-{ cause: error },
270-);
271-}
272-273-return {
274-refresh: tokenData.refresh_token,
275-access: tokenData.access_token,
276-expires: Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000,
277-};
317+return parseTokenCredentials(responseBody, {
318+invalidJsonMessage: "Token exchange returned invalid JSON.",
319+invalidFieldsMessage: "Token exchange returned invalid token fields.",
320+});
278321}
279322280323/**
@@ -430,26 +473,10 @@ export async function refreshAnthropicToken(refreshToken: string): Promise<OAuth
430473);
431474}
432475433-let data: { access_token: string; refresh_token: string; expires_in: number; scope?: string };
434-try {
435-data = JSON.parse(responseBody) as {
436-access_token: string;
437-refresh_token: string;
438-expires_in: number;
439-scope?: string;
440-};
441-} catch (error) {
442-throw new Error(
443-`Anthropic token refresh returned invalid JSON. url=${TOKEN_URL}; ${formatTokenResponseParseContext(responseBody)}; details=${formatErrorDetails(error)}`,
444-{ cause: error },
445-);
446-}
447-448-return {
449-refresh: data.refresh_token,
450-access: data.access_token,
451-expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
452-};
476+return parseTokenCredentials(responseBody, {
477+invalidJsonMessage: "Anthropic token refresh returned invalid JSON.",
478+invalidFieldsMessage: "Anthropic token refresh returned invalid token fields.",
479+});
453480}
454481455482export const anthropicOAuthProvider: OAuthProviderInterface = {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。