|
|
import { UserInfo } from "../types"; |
|
|
import { sha256, randomString } from "./crypto"; |
|
|
|
|
|
const OAUTH_BASE = "https://huggingface.co"; |
|
|
const CONFIG_URL = "/config.json"; |
|
|
|
|
|
export const getSpaceUrl = (): string => { |
|
|
return window.location.origin; |
|
|
}; |
|
|
|
|
|
export const readFragmentParams = () => { |
|
|
const hash = window.location.hash.replace(/^#/, ""); |
|
|
const params = new URLSearchParams(hash); |
|
|
return Object.fromEntries(params.entries()); |
|
|
}; |
|
|
|
|
|
export const getPageSignFromUrl = (): string | null => { |
|
|
try { |
|
|
const params = new URLSearchParams(window.location.search); |
|
|
const sign = params.get("__sign"); |
|
|
return sign && sign.trim().length > 0 ? sign.trim() : null; |
|
|
} catch (_e) { |
|
|
return null; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
export const loadConfig = async () => { |
|
|
try { |
|
|
const res = await fetch(CONFIG_URL, { cache: "no-store" }); |
|
|
if (res.ok) return await res.json(); |
|
|
} catch {} |
|
|
return {}; |
|
|
}; |
|
|
|
|
|
export const getClientId = async (): Promise<string> => { |
|
|
const params = new URLSearchParams(window.location.search); |
|
|
const override = params.get("client_id"); |
|
|
if (override && override.trim().length > 0) return override.trim(); |
|
|
|
|
|
const cfg = await loadConfig(); |
|
|
if ( |
|
|
cfg && |
|
|
typeof cfg.client_id === "string" && |
|
|
cfg.client_id.trim().length > 0 |
|
|
) { |
|
|
return cfg.client_id.trim(); |
|
|
} |
|
|
return "spaces_oauth_client"; |
|
|
}; |
|
|
|
|
|
export const getRedirectUri = async (): Promise<string> => { |
|
|
const cfg = await loadConfig(); |
|
|
if ( |
|
|
cfg && |
|
|
typeof cfg.redirect_uri === "string" && |
|
|
cfg.redirect_uri.trim().length > 0 |
|
|
) { |
|
|
return cfg.redirect_uri.trim(); |
|
|
} |
|
|
return getSpaceUrl(); |
|
|
}; |
|
|
|
|
|
|
|
|
export const fetchUserInfo = async (token: string): Promise<UserInfo> => { |
|
|
const res = await fetch(`${OAUTH_BASE}/oauth/userinfo`, { |
|
|
headers: { Authorization: `Bearer ${token}` }, |
|
|
}); |
|
|
if (!res.ok) throw new Error("Failed to fetch user profile"); |
|
|
return res.json(); |
|
|
}; |
|
|
|
|
|
export const startLogin = async () => { |
|
|
const clientId = await getClientId(); |
|
|
const redirectUri = await getRedirectUri(); |
|
|
const scope = "openid profile inference-api"; |
|
|
|
|
|
const state = randomString(16); |
|
|
const codeVerifier = randomString(64); |
|
|
const codeChallenge = await sha256(codeVerifier); |
|
|
|
|
|
sessionStorage.setItem("hf_oauth_state", state); |
|
|
sessionStorage.setItem("hf_code_verifier", codeVerifier); |
|
|
|
|
|
const authUrl = new URL(`${OAUTH_BASE}/oauth/authorize`); |
|
|
authUrl.searchParams.set("client_id", clientId); |
|
|
authUrl.searchParams.set("redirect_uri", redirectUri); |
|
|
authUrl.searchParams.set("response_type", "code"); |
|
|
authUrl.searchParams.set("scope", scope); |
|
|
authUrl.searchParams.set("state", state); |
|
|
authUrl.searchParams.set("code_challenge", codeChallenge); |
|
|
authUrl.searchParams.set("code_challenge_method", "S256"); |
|
|
|
|
|
window.location.assign(authUrl.toString()); |
|
|
}; |
|
|
|
|
|
export const exchangeCodeForToken = async (code: string) => { |
|
|
const clientId = await getClientId(); |
|
|
const redirectUri = await getRedirectUri(); |
|
|
const codeVerifier = sessionStorage.getItem("hf_code_verifier"); |
|
|
if (!codeVerifier) throw new Error("Missing code verifier"); |
|
|
|
|
|
const body = new URLSearchParams({ |
|
|
grant_type: "authorization_code", |
|
|
client_id: clientId, |
|
|
code, |
|
|
redirect_uri: redirectUri, |
|
|
code_verifier: codeVerifier, |
|
|
}); |
|
|
|
|
|
const res = await fetch(`${OAUTH_BASE}/oauth/token`, { |
|
|
method: "POST", |
|
|
headers: { "Content-Type": "application/x-www-form-urlencoded" }, |
|
|
body, |
|
|
}); |
|
|
if (!res.ok) throw new Error("Code→token exchange failed"); |
|
|
return res.json(); |
|
|
}; |
|
|
|