UNPKG

@neuroequalityorg/knightcode

Version:

Knightcode CLI - Your local AI coding assistant using Ollama, LM Studio, and more

202 lines (178 loc) 5.81 kB
/** * Authentication Manager * * Handles authentication state and token management. */ import { EventEmitter } from 'events'; import { AuthToken, AuthMethod, AuthState, AuthResult, TokenStorage, AuthConfig } from './types.js'; import { createTokenStorage } from './tokens.js'; import { performOAuthFlow, refreshOAuthToken } from './oauth.js'; import { logger } from '../utils/logger.js'; /** * Authentication events */ export const AUTH_EVENTS = { STATE_CHANGED: 'auth:state_changed', TOKEN_REFRESHED: 'auth:token_refreshed', ERROR: 'auth:error' }; /** * Authentication Manager class */ export class AuthManager extends EventEmitter { private config: AuthConfig; private tokenStorage: TokenStorage; private state: AuthState; private currentToken: AuthToken | null; private tokenKey: string; private refreshTimeout: NodeJS.Timeout | null; public constructor(config: Partial<AuthConfig> = {}) { super(); this.config = config; this.tokenStorage = createTokenStorage(); this.state = AuthState.INITIAL; this.currentToken = null; this.tokenKey = 'auth_token'; this.refreshTimeout = null; } /** * Initialize the authentication manager */ public async initialize(): Promise<void> { try { // Load stored token const token = await this.tokenStorage.getToken(this.tokenKey); if (token) { this.currentToken = token; this.setState(AuthState.AUTHENTICATED); } } catch (error) { logger.error('Failed to initialize auth manager', error); this.setState(AuthState.FAILED); } } /** * Get the current authentication state */ public getState(): AuthState { return this.state; } /** * Check if user is authenticated */ public isAuthenticated(): boolean { return this.state === AuthState.AUTHENTICATED && !!this.currentToken; } /** * Get the current token */ public getToken(): AuthToken | null { return this.currentToken; } /** * Get authorization header */ public getAuthorizationHeader(): string | null { if (!this.currentToken) { return null; } return `Bearer ${this.currentToken.accessToken}`; } /** * Authenticate the user */ public async authenticate(method?: AuthMethod): Promise<AuthResult> { try { this.setState(AuthState.AUTHENTICATING); if (method === AuthMethod.API_KEY) { return await this.authenticateWithApiKey(); } else if (method === AuthMethod.OAUTH) { return await this.authenticateWithOAuth(); } else { throw new Error('Invalid authentication method'); } } catch (error) { this.setState(AuthState.FAILED); return { success: false, error: error instanceof Error ? error.message : String(error), state: AuthState.FAILED }; } } /** * Authenticate with API key */ private async authenticateWithApiKey(): Promise<AuthResult> { const apiKey = process.env.ANTHROPIC_API_KEY; if (!apiKey) { throw new Error('API key not found in environment'); } const token: AuthToken = { accessToken: apiKey, expiresAt: Date.now() + 365 * 24 * 60 * 60 * 1000, // 1 year tokenType: 'Bearer', scope: 'api' }; this.currentToken = token; await this.tokenStorage.saveToken(this.tokenKey, token); this.setState(AuthState.AUTHENTICATED); return { success: true, token, state: AuthState.AUTHENTICATED }; } /** * Authenticate with OAuth */ private async authenticateWithOAuth(): Promise<AuthResult> { if (!this.config.oauth) { throw new Error('OAuth configuration is required'); } const result = await performOAuthFlow(this.config.oauth); if (result.success && result.token) { this.currentToken = result.token; await this.tokenStorage.saveToken(this.tokenKey, result.token); this.setState(AuthState.AUTHENTICATED); } return result; } /** * Refresh the current token */ private async refreshToken(): Promise<boolean> { if (!this.currentToken?.refreshToken || !this.config.oauth) { return false; } try { this.setState(AuthState.REFRESHING); const newToken = await refreshOAuthToken(this.currentToken.refreshToken, this.config.oauth); this.currentToken = newToken; await this.tokenStorage.saveToken(this.tokenKey, newToken); this.setState(AuthState.AUTHENTICATED); return true; } catch (error) { logger.error('Failed to refresh token', error); this.setState(AuthState.FAILED); return false; } } /** * Log out the current user */ public async logout(): Promise<void> { await this.tokenStorage.deleteToken(this.tokenKey); this.currentToken = null; this.setState(AuthState.INITIAL); } /** * Set the authentication state */ private setState(newState: AuthState): void { const oldState = this.state; this.state = newState; logger.debug(`Authentication state changed: ${oldState} → ${newState}`); this.emit(AUTH_EVENTS.STATE_CHANGED, newState); } }