Pierre Andrews
lower scope
18ca2f5
raw
history blame
3.54 kB
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;
}
};
// Configuration loading
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();
};
// OAuth functions
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();
};