feat: Implement temporary API key based on system UUID for UI access
This commit introduces a new authentication mechanism for the web UI. Instead of requiring a pre-configured API key, a temporary API key is generated based on the system's UUID. This key is passed to the UI as a URL parameter and used for API requests. Changes: - Added a new utility to get the system UUID and generate a temporary API key. - Modified the `ccr ui` command to generate and pass the temporary API key. - Updated the authentication middleware to validate the temporary API key. - Adjusted the frontend to use the temporary API key from the URL. - Added a dedicated endpoint to test API access without modifying data. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -128,6 +128,20 @@ export function ConfigProvider({ children }: ConfigProviderProps) {
|
||||
fetchConfig();
|
||||
}, [hasFetched, apiKey]);
|
||||
|
||||
// Check if user has full access
|
||||
useEffect(() => {
|
||||
const checkAccess = async () => {
|
||||
if (config) {
|
||||
const hasFullAccess = await api.checkFullAccess();
|
||||
// Store access level in a global state or context if needed
|
||||
// For now, we'll just log it
|
||||
console.log('User has full access:', hasFullAccess);
|
||||
}
|
||||
};
|
||||
|
||||
checkAccess();
|
||||
}, [config]);
|
||||
|
||||
return (
|
||||
<ConfigContext.Provider value={{ config, setConfig, error }}>
|
||||
{children}
|
||||
|
||||
@@ -61,18 +61,23 @@ export function Login() {
|
||||
url: window.location.href
|
||||
}));
|
||||
|
||||
// Test the API key by fetching config (skip if apiKey is empty)
|
||||
if (apiKey) {
|
||||
await api.getConfig();
|
||||
}
|
||||
// Test the API key by fetching config
|
||||
await api.getConfig();
|
||||
|
||||
// Navigate to dashboard
|
||||
// The ConfigProvider will handle fetching the config
|
||||
navigate('/dashboard');
|
||||
} catch {
|
||||
} catch (error: any) {
|
||||
// Clear the API key on failure
|
||||
api.setApiKey('');
|
||||
setError(t('login.invalidApiKey'));
|
||||
|
||||
// Check if it's an unauthorized error
|
||||
if (error.message && error.message.includes('401')) {
|
||||
setError(t('login.invalidApiKey'));
|
||||
} else {
|
||||
// For other errors, still allow access (restricted mode)
|
||||
navigate('/dashboard');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -4,11 +4,14 @@ import type { Config, Provider, Transformer } from '@/types';
|
||||
class ApiClient {
|
||||
private baseUrl: string;
|
||||
private apiKey: string;
|
||||
private tempApiKey: string | null;
|
||||
|
||||
constructor(baseUrl: string = '/api', apiKey: string = '') {
|
||||
this.baseUrl = baseUrl;
|
||||
// Load API key from localStorage if available
|
||||
this.apiKey = apiKey || localStorage.getItem('apiKey') || '';
|
||||
// Load temp API key from URL if available
|
||||
this.tempApiKey = new URLSearchParams(window.location.search).get('tempApiKey');
|
||||
}
|
||||
|
||||
// Update base URL
|
||||
@@ -26,14 +29,25 @@ class ApiClient {
|
||||
localStorage.removeItem('apiKey');
|
||||
}
|
||||
}
|
||||
|
||||
// Update temp API key
|
||||
setTempApiKey(tempApiKey: string | null) {
|
||||
this.tempApiKey = tempApiKey;
|
||||
}
|
||||
|
||||
// Create headers with API key authentication
|
||||
private createHeaders(contentType: string = 'application/json'): HeadersInit {
|
||||
const headers: Record<string, string> = {
|
||||
'X-API-Key': this.apiKey,
|
||||
'Accept': 'application/json',
|
||||
};
|
||||
|
||||
// Use temp API key if available, otherwise use regular API key
|
||||
if (this.tempApiKey) {
|
||||
headers['X-Temp-API-Key'] = this.tempApiKey;
|
||||
} else if (this.apiKey) {
|
||||
headers['X-API-Key'] = this.apiKey;
|
||||
}
|
||||
|
||||
if (contentType) {
|
||||
headers['Content-Type'] = contentType;
|
||||
}
|
||||
@@ -126,6 +140,22 @@ class ApiClient {
|
||||
return this.post<Config>('/config', config);
|
||||
}
|
||||
|
||||
// Check if user has full access
|
||||
async checkFullAccess(): Promise<boolean> {
|
||||
try {
|
||||
// Try to access test endpoint (won't actually modify anything)
|
||||
// This will return 403 if user doesn't have full access
|
||||
await this.post<Config>('/config/test', { test: true });
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
if (error.message && error.message.includes('403')) {
|
||||
return false;
|
||||
}
|
||||
// For other errors, assume user has access (to avoid blocking legitimate users)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Get providers
|
||||
async getProviders(): Promise<Provider[]> {
|
||||
return this.get<Provider[]>('/api/providers');
|
||||
|
||||
@@ -40,3 +40,5 @@ export interface Config {
|
||||
API_TIMEOUT_MS: string;
|
||||
PROXY_URL: string;
|
||||
}
|
||||
|
||||
export type AccessLevel = 'restricted' | 'full';
|
||||
|
||||
Reference in New Issue
Block a user