emresar's picture
Upload folder using huggingface_hub
3a94b90 verified
import { COOKIE_NAME, ONE_YEAR_MS } from "@shared/const";
import type { Express, Request, Response } from "express";
import * as db from "../db";
import { getSessionCookieOptions } from "./cookies";
import { sdk } from "./sdk";
import { ENV } from "./env";
function getQueryParam(req: Request, key: string): string | undefined {
const value = req.query[key];
return typeof value === "string" ? value : undefined;
}
// Dev user for development mode
const DEV_USER = {
openId: "dev-user-001",
name: "Dev User",
email: "dev@localhost",
loginMethod: "dev",
};
// Check if HuggingFace OAuth is configured
function isHfOAuthConfigured(): boolean {
return !!(ENV.hfOAuthClientId && ENV.hfOAuthClientSecret);
}
// Get the base URL for redirects
function getBaseUrl(req: Request): string {
if (ENV.hfSpaceHost) {
return `https://${ENV.hfSpaceHost}`;
}
const protocol = req.headers["x-forwarded-proto"] || req.protocol;
const host = req.headers["x-forwarded-host"] || req.headers.host;
return `${protocol}://${host}`;
}
export function registerOAuthRoutes(app: Express) {
// HuggingFace OAuth login route
app.get("/api/hf/login", (req: Request, res: Response) => {
if (!isHfOAuthConfigured()) {
res.status(400).json({ error: "HuggingFace OAuth not configured" });
return;
}
const baseUrl = getBaseUrl(req);
const redirectUri = `${baseUrl}/api/hf/callback`;
const scope = "openid profile";
const state = Buffer.from(JSON.stringify({ redirect: "/" })).toString("base64");
const authUrl = new URL("https://huggingface.co/oauth/authorize");
authUrl.searchParams.set("client_id", ENV.hfOAuthClientId);
authUrl.searchParams.set("redirect_uri", redirectUri);
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("scope", scope);
authUrl.searchParams.set("state", state);
console.log("[HF OAuth] Redirecting to:", authUrl.toString());
res.redirect(302, authUrl.toString());
});
// HuggingFace OAuth callback
app.get("/api/hf/callback", async (req: Request, res: Response) => {
const code = getQueryParam(req, "code");
const error = getQueryParam(req, "error");
if (error) {
console.error("[HF OAuth] Error:", error);
res.status(400).json({ error: `OAuth error: ${error}` });
return;
}
if (!code) {
res.status(400).json({ error: "Missing authorization code" });
return;
}
try {
const baseUrl = getBaseUrl(req);
const redirectUri = `${baseUrl}/api/hf/callback`;
// Exchange code for token
const tokenResponse = await fetch("https://huggingface.co/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
client_id: ENV.hfOAuthClientId,
client_secret: ENV.hfOAuthClientSecret,
code,
redirect_uri: redirectUri,
}),
});
if (!tokenResponse.ok) {
const errorText = await tokenResponse.text();
console.error("[HF OAuth] Token exchange failed:", errorText);
res.status(400).json({ error: "Token exchange failed" });
return;
}
const tokens = await tokenResponse.json() as { access_token: string };
// Get user info
const userInfoResponse = await fetch("https://huggingface.co/oauth/userinfo", {
headers: { Authorization: `Bearer ${tokens.access_token}` },
});
if (!userInfoResponse.ok) {
res.status(400).json({ error: "Failed to get user info" });
return;
}
const userInfo = await userInfoResponse.json() as {
sub: string;
name?: string;
preferred_username?: string;
email?: string;
};
const openId = `hf-${userInfo.sub}`;
const name = userInfo.name || userInfo.preferred_username || "HF User";
const email = userInfo.email || null;
// Save user to database
try {
await db.upsertUser({
openId,
name,
email,
loginMethod: "huggingface",
lastSignedIn: new Date(),
});
} catch (dbError) {
console.log("[HF OAuth] Database not available, proceeding without persistence");
}
// Create session
const sessionToken = await sdk.createSessionToken(openId, {
name,
expiresInMs: ONE_YEAR_MS,
});
const cookieOptions = getSessionCookieOptions(req);
res.cookie(COOKIE_NAME, sessionToken, { ...cookieOptions, maxAge: ONE_YEAR_MS });
console.log("[HF OAuth] User logged in:", name);
// Use client-side redirect instead of 302 - Safari has issues with cookies on redirects
res.setHeader("Content-Type", "text/html");
res.send(`<!DOCTYPE html>
<html><head>
<meta http-equiv="refresh" content="0;url=/">
<script>window.location.href="/";</script>
</head><body>Redirecting...</body></html>`);
} catch (error) {
console.error("[HF OAuth] Callback error:", error);
res.status(500).json({ error: "OAuth callback failed" });
}
});
// Development/Demo login route - enabled when HF OAuth is not configured
app.get("/api/dev/login", async (req: Request, res: Response) => {
// Allow demo login if HF OAuth is not configured (demo mode)
const isDev = process.env.NODE_ENV !== "production";
const isDemo = !isHfOAuthConfigured(); // Allow demo login when no HF OAuth
if (!isDev && !isDemo) {
res.status(403).json({ error: "Login not available - please configure HuggingFace OAuth" });
return;
}
try {
// Try to create or update dev user in database (optional)
try {
await db.upsertUser({
openId: DEV_USER.openId,
name: DEV_USER.name,
email: DEV_USER.email,
loginMethod: DEV_USER.loginMethod,
lastSignedIn: new Date(),
});
} catch (dbError) {
// Database not available - that's OK for dev mode
console.log("[DevAuth] Database not available, proceeding without user persistence");
}
// Create session token
const sessionToken = await sdk.createSessionToken(DEV_USER.openId, {
name: DEV_USER.name,
expiresInMs: ONE_YEAR_MS,
});
// Set session cookie
const cookieOptions = getSessionCookieOptions(req);
res.cookie(COOKIE_NAME, sessionToken, { ...cookieOptions, maxAge: ONE_YEAR_MS });
console.log("[DevAuth] Dev user logged in:", DEV_USER.email);
// Use client-side redirect instead of 302 - Safari has issues with cookies on redirects
res.setHeader("Content-Type", "text/html");
res.send(`<!DOCTYPE html>
<html><head>
<meta http-equiv="refresh" content="0;url=/">
<script>window.location.href="/";</script>
</head><body>Redirecting...</body></html>`);
} catch (error) {
console.error("[DevAuth] Dev login failed:", error);
res.status(500).json({ error: "Dev login failed" });
}
});
// Endpoint to check auth config (used by frontend)
app.get("/api/auth/config", (req: Request, res: Response) => {
const hfOAuthEnabled = isHfOAuthConfigured();
res.json({
hfOAuthEnabled,
// Dev/demo login is enabled in dev mode OR when HF OAuth is not configured
devLoginEnabled: process.env.NODE_ENV !== "production" || !hfOAuthEnabled,
});
});
app.get("/api/oauth/callback", async (req: Request, res: Response) => {
const code = getQueryParam(req, "code");
const state = getQueryParam(req, "state");
if (!code || !state) {
res.status(400).json({ error: "code and state are required" });
return;
}
try {
const tokenResponse = await sdk.exchangeCodeForToken(code, state);
const userInfo = await sdk.getUserInfo(tokenResponse.accessToken);
if (!userInfo.openId) {
res.status(400).json({ error: "openId missing from user info" });
return;
}
await db.upsertUser({
openId: userInfo.openId,
name: userInfo.name || null,
email: userInfo.email ?? null,
loginMethod: userInfo.loginMethod ?? userInfo.platform ?? null,
lastSignedIn: new Date(),
});
const sessionToken = await sdk.createSessionToken(userInfo.openId, {
name: userInfo.name || "",
expiresInMs: ONE_YEAR_MS,
});
const cookieOptions = getSessionCookieOptions(req);
res.cookie(COOKIE_NAME, sessionToken, { ...cookieOptions, maxAge: ONE_YEAR_MS });
res.redirect(302, "/");
} catch (error) {
console.error("[OAuth] Callback failed", error);
res.status(500).json({ error: "OAuth callback failed" });
}
});
}