import { apiClient } from './api-client.js';
import { logger } from './logger.js';
import { UnauthorizedError } from './api-client.js';
export class AuthManager {
constructor() {
this.authContext = null;
}
static getInstance() {
if (!AuthManager.instance) {
AuthManager.instance = new AuthManager();
}
return AuthManager.instance;
}
/**
* Authenticate with various methods
*/
async authenticate(method, credentials) {
try {
switch (method) {
case 'token':
return await this.authenticateWithToken(credentials);
case 'api_key':
return await this.authenticateWithApiKey(credentials);
default:
throw new UnauthorizedError('Invalid authentication method');
}
}
catch (error) {
logger.error('Authentication failed:', error);
throw error;
}
}
/**
* Authenticate with user session token (legacy - not used with API)
*/
async authenticateWithToken(accessToken) {
throw new UnauthorizedError('Token authentication not supported in API mode. Use API key authentication.');
}
/**
* Authenticate with API key via Helios-9 API
*/
async authenticateWithApiKey(apiKey) {
// Set the API key in the client
process.env.HELIOS_API_KEY = apiKey;
// For MCP API keys (hel9_*), we skip the auth/validate endpoint
// since MCP endpoints handle their own authentication
if (apiKey.startsWith('hel9_')) {
logger.info('MCP API key detected, using MCP authentication mode');
// Create a context that will be populated from API responses
this.authContext = {
userId: 'mcp-pending', // Will be updated from first API response
tenantId: null,
profile: {
id: 'mcp-pending',
email: 'mcp@helios9.app',
full_name: 'MCP Service Account',
avatar_url: null,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
username: 'mcp-service',
tenant_id: null
},
authenticated: true
};
logger.info('MCP authentication context created');
return this.authContext;
}
// For other API keys, validate normally
const profile = await apiClient.authenticate();
this.authContext = {
userId: profile.id,
tenantId: profile.tenant_id || null,
profile,
authenticated: true
};
logger.info(`Service authenticated via API key for user: ${profile.email}`);
return this.authContext;
}
/**
* Get current auth context
*/
getAuthContext() {
if (!this.authContext || !this.authContext.authenticated) {
throw new UnauthorizedError('No authenticated session');
}
return this.authContext;
}
/**
* Check if authenticated
*/
isAuthenticated() {
return this.authContext?.authenticated ?? false;
}
/**
* Ensure authenticated before API calls
*/
async ensureAuthenticated() {
if (this.isAuthenticated()) {
return this.authContext;
}
// Try to authenticate with API key if available
const apiKey = process.env.HELIOS_API_KEY;
if (!apiKey) {
throw new UnauthorizedError('HELIOS_API_KEY environment variable is required');
}
return await this.authenticate('api_key', apiKey);
}
/**
* Clear authentication
*/
clearAuth() {
this.authContext = null;
logger.info('Authentication cleared');
}
/**
* Middleware for MCP operations that require authentication
*/
requireAuth(operation) {
return async (...args) => {
if (!this.isAuthenticated()) {
throw new UnauthorizedError('Authentication required for this operation');
}
return operation(...args);
};
}
/**
* Get rate limiting info for current user
*/
getRateLimitInfo() {
// Basic rate limiting - in production, you'd implement proper rate limiting
const isServiceAccount = this.authContext?.profile?.email?.includes('mcp-service');
return {
limit: isServiceAccount ? 1000 : 100, // requests per window
window: 3600, // 1 hour in seconds
current: 0 // would track actual usage
};
}
/**
* Validate permissions for specific operations
*/
async validatePermission(resource, action, resourceId) {
const context = this.getAuthContext();
// Admin role can do everything
if (context.profile.role === 'admin') {
return true;
}
// Basic permission checks
switch (resource) {
case 'project':
return action === 'read' || action === 'create' || context.userId === resourceId;
case 'task':
case 'document':
// For tasks and documents, check project ownership through the API service
return true; // Simplified - actual checks happen in API service
default:
return false;
}
}
}
// Export singleton instance
export const authManager = AuthManager.getInstance();
// Helper function for authentication
export async function authenticate(method, credentials) {
return authManager.authenticate(method, credentials);
}
// Helper function to require authentication
export function requireAuth(operation) {
return authManager.requireAuth(operation);
}