import { NextResponse } from 'next/server'; /** * GET /api/auth/callback * Handles the OAuth callback from HuggingFace. * Exchanges code for access token, fetches userinfo, sets session cookie. */ export async function GET(request) { const { searchParams } = new URL(request.url); const code = searchParams.get('code'); const state = searchParams.get('state'); if (!code) { return NextResponse.json({ error: 'Missing code parameter' }, { status: 400 }); } const clientId = process.env.OAUTH_CLIENT_ID; const clientSecret = process.env.OAUTH_CLIENT_SECRET; if (!clientId || !clientSecret) { return NextResponse.json({ error: 'OAuth not configured' }, { status: 500 }); } const host = process.env.SPACE_HOST ? `https://${process.env.SPACE_HOST}` : 'http://localhost:3000'; const redirectUri = `${host}/api/auth/callback`; try { // Exchange code for access token const tokenRes = await fetch('https://huggingface.co/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`, }, body: new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: redirectUri, }), }); if (!tokenRes.ok) { const errText = await tokenRes.text(); console.error('Token exchange failed:', errText); return NextResponse.json({ error: 'Failed to exchange code for token' }, { status: 500 }); } const tokenData = await tokenRes.json(); // Fetch user info const userRes = await fetch('https://huggingface.co/oauth/userinfo', { headers: { 'Authorization': `Bearer ${tokenData.access_token}` }, }); if (!userRes.ok) { return NextResponse.json({ error: 'Failed to fetch user info' }, { status: 500 }); } const userInfo = await userRes.json(); const username = userInfo.preferred_username || userInfo.name || 'user'; // Check allowlist (if ALLOWED_USERS is set) const allowedUsers = process.env.ALLOWED_USERS; if (allowedUsers) { const allowlist = allowedUsers.split(',').map(u => u.trim().toLowerCase()); if (!allowlist.includes(username.toLowerCase())) { return NextResponse.json( { error: `Access denied. User "${username}" is not in the allowed list.` }, { status: 403 } ); } } // Set session cookie with username const response = NextResponse.redirect(host); response.cookies.set('hf_user', JSON.stringify({ username, name: userInfo.name, picture: userInfo.picture, }), { httpOnly: false, // readable by client JS secure: process.env.NODE_ENV === 'production', sameSite: 'lax', maxAge: 60 * 60 * 8, // 8 hours path: '/', }); return response; } catch (error) { console.error('OAuth callback error:', error); return NextResponse.json({ error: 'OAuth callback failed: ' + error.message }, { status: 500 }); } }