test / index.html
Memex
Update index.html
68f53da verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hugging Face OAuth</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #f3f4f6; /* Light gray background */
}
.container {
max-width: 800px;
margin: 4rem auto;
padding: 2rem;
background-color: #ffffff;
border-radius: 1rem; /* More rounded corners */
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
align-items: center;
}
.btn-primary {
background-color: #3b82f6; /* Blue */
color: white;
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
font-weight: 600;
transition: background-color 0.3s ease;
}
.btn-primary:hover {
background-color: #2563eb; /* Darker blue on hover */
}
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid #3b82f6;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
display: none; /* Hidden by default */
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.message-box {
background-color: #e0f2fe; /* Light blue */
color: #0c4a6e; /* Dark blue text */
padding: 1rem;
border-radius: 0.5rem;
margin-top: 1rem;
width: 100%;
text-align: left;
word-wrap: break-word; /* Ensure long text wraps */
}
.error-box {
background-color: #fee2e2; /* Light red */
color: #991b1b; /* Dark red text */
padding: 1rem;
border-radius: 0.5rem;
margin-top: 1rem;
width: 100%;
text-align: left;
word-wrap: break-word;
}
</style>
</head>
<body>
<div class="container">
<h1 class="text-3xl font-bold text-gray-800 mb-6">Hugging Face OAuth Example</h1>
<button id="loginButton" class="btn-primary">Login with Hugging Face</button>
<div id="loader" class="loader mt-4"></div>
<div id="messageBox" class="message-box hidden"></div>
<div id="profileDisplay" class="mt-8 p-6 bg-gray-50 rounded-lg shadow-inner w-full hidden">
<h2 class="text-2xl font-semibold text-gray-700 mb-4">User Profile</h2>
<pre id="profileJson" class="bg-gray-100 p-4 rounded-md text-sm overflow-x-auto text-gray-800"></pre>
</div>
</div>
<script>
// IMPORTANT: Replace with your actual Client ID and Client Secret from Hugging Face.
// You need to register an OAuth application on Hugging Face to get these.
// For production, the CLIENT_SECRET should NEVER be exposed in client-side code.
// It should be handled securely on a backend server.
const CLIENT_ID = '3e1bae2d-bc04-4fbe-95ec-0be5741a31eb'; // e.g., 'hf_YOURCLIENTID12345'
const CLIENT_SECRET = '6b0e7728-22d5-435f-a9e3-79239f955bda'; // e.g., 'hf_YOURCLIENTSECRET12345'
// The redirect_uri MUST match the one registered in your Hugging Face OAuth app settings.
// For this example, it's set to the current page's URL.
const REDIRECT_URI = 'https://memex-in-test.static.hf.space';
// Hugging Face OAuth Endpoints
const HF_AUTHORIZE_URL = 'https://huggingface.co/oauth/authorize';
const HF_TOKEN_URL = 'https://huggingface.co/oauth/token';
const HF_USER_INFO_URL = 'https://huggingface.co/api/whoami-v2';
// Get DOM elements
const loginButton = document.getElementById('loginButton');
const loader = document.getElementById('loader');
const messageBox = document.getElementById('messageBox');
const profileDisplay = document.getElementById('profileDisplay');
const profileJson = document.getElementById('profileJson');
/**
* Displays a message in the message box.
* @param {string} message - The message to display.
* @param {boolean} isError - True if it's an error message, false otherwise.
*/
function showMessage(message, isError = false) {
messageBox.textContent = message;
messageBox.classList.remove('hidden', 'message-box', 'error-box');
if (isError) {
messageBox.classList.add('error-box');
} else {
messageBox.classList.add('message-box');
}
}
/**
* Hides the message box.
*/
function hideMessage() {
messageBox.classList.add('hidden');
}
/**
* Shows or hides the loader.
* @param {boolean} show - True to show, false to hide.
*/
function showLoader(show) {
if (show) {
loader.style.display = 'block';
} else {
loader.style.display = 'none';
}
}
/**
* Initiates the Hugging Face OAuth login process.
*/
loginButton.addEventListener('click', () => {
hideMessage();
profileDisplay.classList.add('hidden');
const authUrl = new URL(HF_AUTHORIZE_URL);
authUrl.searchParams.append('client_id', CLIENT_ID);
authUrl.searchParams.append('redirect_uri', REDIRECT_URI);
authUrl.searchParams.append('response_type', 'code');
authUrl.searchParams.append('scope', 'profile'); // Requesting profile scope
window.location.href = authUrl.toString();
});
/**
* Exchanges the authorization code for an access token.
* @param {string} code - The authorization code received from Hugging Face.
* @returns {Promise<string>} - A promise that resolves with the access token.
*/
async function getAccessToken(code) {
showLoader(true);
hideMessage();
try {
const response = await fetch(HF_TOKEN_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
redirect_uri: REDIRECT_URI,
code: code,
}).toString(),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(`Failed to get access token: ${errorData.error_description || response.statusText}`);
}
const data = await response.json();
return data.access_token;
} catch (error) {
showMessage(`Error getting access token: ${error.message}`, true);
console.error('Error getting access token:', error);
return null;
} finally {
showLoader(false);
}
}
/**
* Fetches the user's profile information using the access token.
* @param {string} accessToken - The access token.
* @returns {Promise<object>} - A promise that resolves with the user's profile data.
*/
async function fetchUserProfile(accessToken) {
showLoader(true);
hideMessage();
try {
const response = await fetch(HF_USER_INFO_URL, {
headers: {
'Authorization': `Bearer ${accessToken}`,
},
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(`Failed to fetch user profile: ${errorData.error_description || response.statusText}`);
}
const profile = await response.json();
return profile;
} catch (error) {
showMessage(`Error fetching user profile: ${error.message}`, true);
console.error('Error fetching user profile:', error);
return null;
} finally {
showLoader(false);
}
}
/**
* Handles the OAuth callback when the page loads.
*/
async function handleOAuthCallback() {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const error = urlParams.get('error');
const errorDescription = urlParams.get('error_description');
if (error) {
showMessage(`OAuth Error: ${error} - ${errorDescription || 'Unknown error'}`, true);
// Clear the URL parameters to prevent re-processing on refresh
window.history.replaceState({}, document.title, REDIRECT_URI);
return;
}
if (code) {
showMessage('Authorization code received. Exchanging for access token...', false);
// Clear the URL parameters to prevent re-processing on refresh
window.history.replaceState({}, document.title, REDIRECT_URI);
const accessToken = await getAccessToken(code);
if (accessToken) {
showMessage('Access token obtained. Fetching user profile...', false);
const userProfile = await fetchUserProfile(accessToken);
if (userProfile) {
showMessage('Successfully logged in and fetched profile!', false);
profileJson.textContent = JSON.stringify(userProfile, null, 2);
profileDisplay.classList.remove('hidden');
}
}
} else {
// If no code and no error, it's a fresh load or not an OAuth callback.
// You might want to show a default message or hide profile display.
profileDisplay.classList.add('hidden');
hideMessage();
}
}
// Call the handler when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', handleOAuthCallback);
</script>
</body>
</html>