@profullstack/auth-system
Version:
Flexible authentication system with user registration, login/logout, password reset, and session management
372 lines (313 loc) • 11.2 kB
JavaScript
/**
* API Keys Page Example
*
* This example shows how to use the AuthClient to implement an API keys page
* that checks authentication status and displays API keys.
*/
import { AuthClient } from './auth-client.js';
/**
* Initialize API keys page
*/
export async function initApiKeysPage() {
try {
// Create AuthClient instance
const authClient = await createAuthClient();
// Check authentication status
const authStatus = await authClient.checkAuthStatus();
if (!authStatus.authenticated) {
console.log('Not authenticated, redirecting to login page:', authStatus.message);
window.router.navigate('/login');
return;
}
console.log('Authentication verified with server');
// Load API keys
await loadApiKeys(authClient);
// Initialize create API key form
initCreateApiKeyForm(authClient);
// Initialize API key actions (revoke, copy)
initApiKeyActions(authClient);
} catch (error) {
console.error('Error initializing API keys page:', error);
window.router.navigate('/login');
}
}
/**
* Load API keys
* @param {AuthClient} authClient - AuthClient instance
*/
async function loadApiKeys(authClient) {
try {
// Get API keys from server
const apiKeys = await fetchApiKeys(authClient);
// Get API keys container
const apiKeysContainer = document.getElementById('api-keys-container');
if (!apiKeysContainer) return;
// Clear container
apiKeysContainer.innerHTML = '';
if (apiKeys.length === 0) {
// No API keys
apiKeysContainer.innerHTML = `
<div class="empty-state">
<p>You don't have any API keys yet. Create one to get started.</p>
</div>
`;
return;
}
// Create API key elements
apiKeys.forEach(apiKey => {
const apiKeyElement = document.createElement('div');
apiKeyElement.className = 'api-key-item';
apiKeyElement.dataset.id = apiKey.id;
// Format created date
const createdDate = new Date(apiKey.created_at).toLocaleDateString();
// Format last used date
const lastUsedDate = apiKey.last_used_at
? new Date(apiKey.last_used_at).toLocaleDateString()
: 'Never';
// Create API key HTML
apiKeyElement.innerHTML = `
<div class="api-key-details">
<div class="api-key-name">${apiKey.name}</div>
<div class="api-key-value">${maskApiKey(apiKey.key)}</div>
<div class="api-key-meta">
<span>Created: ${createdDate}</span>
<span>Last used: ${lastUsedDate}</span>
</div>
</div>
<div class="api-key-actions">
<button class="copy-api-key" data-key="${apiKey.key}">Copy</button>
<button class="revoke-api-key" data-id="${apiKey.id}">Revoke</button>
</div>
`;
// Add API key element to container
apiKeysContainer.appendChild(apiKeyElement);
});
} catch (error) {
console.error('Error loading API keys:', error);
// Show error message
const { default: PfDialog } = await import('./components/pf-dialog.js');
PfDialog.alert('Error loading API keys: ' + (error.message || 'Unknown error'));
}
}
/**
* Initialize create API key form
* @param {AuthClient} authClient - AuthClient instance
*/
function initCreateApiKeyForm(authClient) {
const createApiKeyForm = document.getElementById('create-api-key-form');
if (!createApiKeyForm) return;
createApiKeyForm.addEventListener('submit', async (e) => {
e.preventDefault();
// Show loading state
const submitButton = createApiKeyForm.querySelector('button[type="submit"]');
const originalButtonText = submitButton.textContent;
submitButton.textContent = 'Creating...';
submitButton.disabled = true;
try {
// Get API key name from form
const apiKeyName = document.getElementById('api-key-name').value;
// Create API key
const apiKey = await createApiKey(authClient, apiKeyName);
console.log('API key created successfully:', apiKey);
// Reset form
createApiKeyForm.reset();
// Reset button state
submitButton.textContent = originalButtonText;
submitButton.disabled = false;
// Show success message with the API key
const { default: PfDialog } = await import('./components/pf-dialog.js');
await PfDialog.alert(`
<div class="api-key-created">
<p>Your API key has been created successfully.</p>
<p>Make sure to copy your API key now. You won't be able to see it again.</p>
<div class="api-key-display">
<code>${apiKey.key}</code>
<button onclick="navigator.clipboard.writeText('${apiKey.key}').then(() => this.textContent = 'Copied!'); setTimeout(() => this.textContent = 'Copy', 2000)"
style="margin-left: 8px; padding: 4px 8px; background-color: #f3f4f6; border: 1px solid #d1d5db; border-radius: 4px; cursor: pointer;">Copy</button>
</div>
</div>
`, 'API Key Created');
// Reload API keys
await loadApiKeys(authClient);
} catch (error) {
console.error('Error creating API key:', error);
// Reset button state
submitButton.textContent = originalButtonText;
submitButton.disabled = false;
// Show error message
const { default: PfDialog } = await import('./components/pf-dialog.js');
PfDialog.alert('Error creating API key: ' + (error.message || 'Unknown error'));
}
});
}
/**
* Initialize API key actions (revoke, copy)
* @param {AuthClient} authClient - AuthClient instance
*/
function initApiKeyActions(authClient) {
const apiKeysContainer = document.getElementById('api-keys-container');
if (!apiKeysContainer) return;
// Use event delegation for API key actions
apiKeysContainer.addEventListener('click', async (e) => {
// Copy API key
if (e.target.classList.contains('copy-api-key')) {
const apiKey = e.target.dataset.key;
try {
await navigator.clipboard.writeText(apiKey);
// Change button text temporarily
const originalText = e.target.textContent;
e.target.textContent = 'Copied!';
setTimeout(() => {
e.target.textContent = originalText;
}, 2000);
} catch (error) {
console.error('Error copying API key:', error);
// Show error message
const { default: PfDialog } = await import('./components/pf-dialog.js');
PfDialog.alert('Error copying API key: ' + (error.message || 'Unknown error'));
}
}
// Revoke API key
if (e.target.classList.contains('revoke-api-key')) {
const apiKeyId = e.target.dataset.id;
try {
const { default: PfDialog } = await import('./components/pf-dialog.js');
const confirmed = await PfDialog.confirm(
'Are you sure you want to revoke this API key? This action cannot be undone.',
'Revoke API Key',
null,
null,
'Revoke',
'Cancel'
);
if (confirmed) {
// Show loading state
const originalText = e.target.textContent;
e.target.textContent = 'Revoking...';
e.target.disabled = true;
// Revoke API key
await revokeApiKey(authClient, apiKeyId);
console.log('API key revoked successfully');
// Reload API keys
await loadApiKeys(authClient);
}
} catch (error) {
console.error('Error revoking API key:', error);
// Reset button state
e.target.textContent = 'Revoke';
e.target.disabled = false;
// Show error message
const { default: PfDialog } = await import('./components/pf-dialog.js');
PfDialog.alert('Error revoking API key: ' + (error.message || 'Unknown error'));
}
}
});
}
/**
* Fetch API keys from server
* @param {AuthClient} authClient - AuthClient instance
* @returns {Promise<Array>} - API keys
*/
async function fetchApiKeys(authClient) {
try {
const response = await fetch('/api/1/api-keys', {
headers: {
'Authorization': `Bearer ${authClient.accessToken}`
}
});
if (!response.ok) {
throw new Error('Failed to fetch API keys');
}
const data = await response.json();
return data.api_keys || [];
} catch (error) {
console.error('Error fetching API keys:', error);
throw error;
}
}
/**
* Create API key
* @param {AuthClient} authClient - AuthClient instance
* @param {string} name - API key name
* @returns {Promise<Object>} - Created API key
*/
async function createApiKey(authClient, name) {
try {
const response = await fetch('/api/1/api-keys', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${authClient.accessToken}`
},
body: JSON.stringify({ name })
});
if (!response.ok) {
throw new Error('Failed to create API key');
}
return await response.json();
} catch (error) {
console.error('Error creating API key:', error);
throw error;
}
}
/**
* Revoke API key
* @param {AuthClient} authClient - AuthClient instance
* @param {string} apiKeyId - API key ID
* @returns {Promise<void>}
*/
async function revokeApiKey(authClient, apiKeyId) {
try {
const response = await fetch(`/api/1/api-keys/${apiKeyId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${authClient.accessToken}`
}
});
if (!response.ok) {
throw new Error('Failed to revoke API key');
}
} catch (error) {
console.error('Error revoking API key:', error);
throw error;
}
}
/**
* Mask API key
* @param {string} apiKey - API key
* @returns {string} - Masked API key
*/
function maskApiKey(apiKey) {
if (!apiKey) return '';
// Show first 4 and last 4 characters
const firstFour = apiKey.substring(0, 4);
const lastFour = apiKey.substring(apiKey.length - 4);
return `${firstFour}...${lastFour}`;
}
/**
* Create AuthClient instance
* @returns {Promise<AuthClient>} - AuthClient instance
*/
async function createAuthClient() {
try {
// Fetch Supabase configuration from the server
const configResponse = await fetch('/api/1/config/supabase');
if (!configResponse.ok) {
throw new Error('Failed to fetch Supabase configuration');
}
const { supabaseUrl, supabaseAnonKey, jwtSecret } = await configResponse.json();
// Create AuthClient
return new AuthClient({
supabaseUrl,
supabaseKey: supabaseAnonKey,
jwtSecret,
onAuthChanged: (authenticated, user) => {
console.log('Auth state changed:', authenticated, user);
// You can update UI elements here based on auth state
}
});
} catch (error) {
console.error('Error creating AuthClient:', error);
throw error;
}
}