Files
openclaw/src/plugin-sdk/fetch-auth.ts

72 lines
2.0 KiB
TypeScript

export type ScopeTokenProvider = {
getAccessToken: (scope: string) => Promise<string>;
};
function isAuthFailureStatus(status: number): boolean {
return status === 401 || status === 403;
}
export async function fetchWithBearerAuthScopeFallback(params: {
url: string;
scopes: readonly string[];
tokenProvider?: ScopeTokenProvider;
fetchFn?: typeof fetch;
requestInit?: RequestInit;
requireHttps?: boolean;
shouldAttachAuth?: (url: string) => boolean;
shouldRetry?: (response: Response) => boolean;
}): Promise<Response> {
const fetchFn = params.fetchFn ?? fetch;
let parsedUrl: URL;
try {
parsedUrl = new URL(params.url);
} catch {
throw new Error(`Invalid URL: ${params.url}`);
}
if (params.requireHttps === true && parsedUrl.protocol !== "https:") {
throw new Error(`URL must use HTTPS: ${params.url}`);
}
const fetchOnce = (headers?: Headers): Promise<Response> =>
fetchFn(params.url, {
...params.requestInit,
...(headers ? { headers } : {}),
});
const firstAttempt = await fetchOnce();
if (firstAttempt.ok) {
return firstAttempt;
}
if (!params.tokenProvider) {
return firstAttempt;
}
const shouldRetry =
params.shouldRetry ?? ((response: Response) => isAuthFailureStatus(response.status));
if (!shouldRetry(firstAttempt)) {
return firstAttempt;
}
if (params.shouldAttachAuth && !params.shouldAttachAuth(params.url)) {
return firstAttempt;
}
for (const scope of params.scopes) {
try {
const token = await params.tokenProvider.getAccessToken(scope);
const authHeaders = new Headers(params.requestInit?.headers);
authHeaders.set("Authorization", `Bearer ${token}`);
const authAttempt = await fetchOnce(authHeaders);
if (authAttempt.ok) {
return authAttempt;
}
if (!shouldRetry(authAttempt)) {
continue;
}
} catch {
// Ignore token/fetch errors and continue trying remaining scopes.
}
}
return firstAttempt;
}