Spaces:
Running
Running
| <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> | |