rafmacalaba's picture
fix: remove OAuth state verification (cookies don't survive HF iframe redirect)
c796a00
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 });
}
}