Skip to main content
Glama
credential-manager.ts14.5 kB
/** * Credential Manager * * Secure credential handling for Git MCP server with support for * environment variables, validation, and provider-specific credentials. */ import { logger } from './logger.js'; export interface GitHubCredentials { token: string; username: string; } export interface GiteaCredentials { url: string; token: string; username: string; } export interface CredentialValidationResult { valid: boolean; error?: string; suggestions?: string[]; } export interface ProviderCredentials { github?: GitHubCredentials; gitea?: GiteaCredentials; } export class CredentialManager { private credentials: ProviderCredentials = {}; private validationCache: Map<string, { result: CredentialValidationResult; timestamp: number }> = new Map(); private cacheTimeout = 5 * 60 * 1000; // 5 minutes constructor() { this.loadCredentials(); } /** * Load credentials from environment variables */ loadCredentials(): void { logger.debug('Loading credentials from environment variables', 'CREDENTIALS'); // Load GitHub credentials const githubToken = process.env.GITHUB_TOKEN; const githubUsername = process.env.GITHUB_USERNAME; if (githubToken && githubUsername) { this.credentials.github = { token: githubToken, username: githubUsername }; logger.info('GitHub credentials loaded', 'CREDENTIALS', { username: githubUsername, tokenLength: githubToken.length }); } else { logger.debug('GitHub credentials not found in environment', 'CREDENTIALS', { hasToken: !!githubToken, hasUsername: !!githubUsername }); } // Load Gitea credentials const giteaUrl = process.env.GITEA_URL; const giteaToken = process.env.GITEA_TOKEN; const giteaUsername = process.env.GITEA_USERNAME; if (giteaUrl && giteaToken && giteaUsername) { this.credentials.gitea = { url: giteaUrl, token: giteaToken, username: giteaUsername }; logger.info('Gitea credentials loaded', 'CREDENTIALS', { url: giteaUrl, username: giteaUsername, tokenLength: giteaToken.length }); } else { logger.debug('Gitea credentials not found in environment', 'CREDENTIALS', { hasUrl: !!giteaUrl, hasToken: !!giteaToken, hasUsername: !!giteaUsername }); } // Log overall credential status const availableProviders = Object.keys(this.credentials); logger.configuration('loaded', { availableProviders, providerCount: availableProviders.length }); } /** * Get GitHub credentials */ getGitHubCredentials(): GitHubCredentials | undefined { return this.credentials.github; } /** * Get Gitea credentials */ getGiteaCredentials(): GiteaCredentials | undefined { return this.credentials.gitea; } /** * Get credentials for specific provider */ getProviderCredentials(provider: 'github' | 'gitea'): GitHubCredentials | GiteaCredentials | undefined { return this.credentials[provider]; } /** * Check if provider is configured */ isProviderConfigured(provider: 'github' | 'gitea'): boolean { return !!this.credentials[provider]; } /** * Get list of configured providers */ getConfiguredProviders(): ('github' | 'gitea')[] { return Object.keys(this.credentials) as ('github' | 'gitea')[]; } /** * Validate provider credentials */ validateProviderCredentials(provider: 'github' | 'gitea'): CredentialValidationResult { const cacheKey = `validate_${provider}`; // Check cache first const cached = this.validationCache.get(cacheKey); if (cached && Date.now() - cached.timestamp < this.cacheTimeout) { return cached.result; } let result: CredentialValidationResult; if (provider === 'github') { result = this.validateGitHubCredentials(); } else if (provider === 'gitea') { result = this.validateGiteaCredentials(); } else { result = { valid: false, error: `Unknown provider: ${provider}`, suggestions: ['Use "github" or "gitea" as provider'] }; } // Cache result this.validationCache.set(cacheKey, { result, timestamp: Date.now() }); logger.debug(`Credential validation for ${provider}`, 'CREDENTIALS', { provider, valid: result.valid, error: result.error }); return result; } /** * Validate GitHub credentials */ private validateGitHubCredentials(): CredentialValidationResult { const creds = this.credentials.github; if (!creds) { return { valid: false, error: 'GitHub credentials not configured', suggestions: [ 'Set GITHUB_TOKEN environment variable', 'Set GITHUB_USERNAME environment variable', 'Ensure both variables are set correctly' ] }; } // Validate token format if (!creds.token.startsWith('ghp_') && !creds.token.startsWith('github_pat_')) { return { valid: false, error: 'Invalid GitHub token format', suggestions: [ 'GitHub personal access tokens should start with "ghp_" or "github_pat_"', 'Generate a new token at https://github.com/settings/tokens', 'Ensure the token has required permissions' ] }; } // Validate token length if (creds.token.length < 20) { return { valid: false, error: 'GitHub token appears to be too short', suggestions: [ 'Verify the complete token was copied', 'Generate a new token if the current one is incomplete' ] }; } // Validate username format if (!creds.username || creds.username.trim().length === 0) { return { valid: false, error: 'GitHub username is empty', suggestions: [ 'Set GITHUB_USERNAME environment variable', 'Use your GitHub username (not email)' ] }; } // Basic username validation if (!/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/.test(creds.username)) { return { valid: false, error: 'Invalid GitHub username format', suggestions: [ 'GitHub usernames can only contain alphanumeric characters and hyphens', 'Username cannot start or end with a hyphen', 'Verify your GitHub username is correct' ] }; } return { valid: true }; } /** * Validate Gitea credentials */ private validateGiteaCredentials(): CredentialValidationResult { const creds = this.credentials.gitea; if (!creds) { return { valid: false, error: 'Gitea credentials not configured', suggestions: [ 'Set GITEA_URL environment variable', 'Set GITEA_TOKEN environment variable', 'Set GITEA_USERNAME environment variable', 'Ensure all three variables are set correctly' ] }; } // Validate URL format try { const url = new URL(creds.url); if (!['http:', 'https:'].includes(url.protocol)) { return { valid: false, error: 'Gitea URL must use HTTP or HTTPS protocol', suggestions: [ 'Use https:// or http:// in GITEA_URL', 'Example: https://gitea.example.com' ] }; } } catch { return { valid: false, error: 'Invalid Gitea URL format', suggestions: [ 'Provide a valid URL in GITEA_URL', 'Example: https://gitea.example.com', 'Include protocol (https:// or http://)' ] }; } // Validate token if (!creds.token || creds.token.trim().length === 0) { return { valid: false, error: 'Gitea token is empty', suggestions: [ 'Set GITEA_TOKEN environment variable', 'Generate a token in your Gitea instance settings' ] }; } // Validate token length (Gitea tokens are typically 40 characters) if (creds.token.length < 20) { return { valid: false, error: 'Gitea token appears to be too short', suggestions: [ 'Verify the complete token was copied', 'Generate a new token if the current one is incomplete' ] }; } // Validate username if (!creds.username || creds.username.trim().length === 0) { return { valid: false, error: 'Gitea username is empty', suggestions: [ 'Set GITEA_USERNAME environment variable', 'Use your Gitea username (not email)' ] }; } return { valid: true }; } /** * Validate all configured credentials */ validateAllCredentials(): Record<string, CredentialValidationResult> { const results: Record<string, CredentialValidationResult> = {}; for (const provider of this.getConfiguredProviders()) { results[provider] = this.validateProviderCredentials(provider); } return results; } /** * Get credential configuration status */ getCredentialStatus(): { configured: string[]; missing: string[]; valid: string[]; invalid: string[]; errors: Record<string, string>; } { const allProviders = ['github', 'gitea'] as const; const configured: string[] = []; const missing: string[] = []; const valid: string[] = []; const invalid: string[] = []; const errors: Record<string, string> = {}; for (const provider of allProviders) { if (this.isProviderConfigured(provider)) { configured.push(provider); const validation = this.validateProviderCredentials(provider); if (validation.valid) { valid.push(provider); } else { invalid.push(provider); errors[provider] = validation.error || 'Unknown validation error'; } } else { missing.push(provider); } } return { configured, missing, valid, invalid, errors }; } /** * Get configuration guide for missing providers */ getConfigurationGuide(providers?: ('github' | 'gitea')[]): Record<string, { envVars: string[]; example: Record<string, string>; instructions: string[]; }> { const targetProviders = providers || ['github', 'gitea']; const guide: Record<string, any> = {}; for (const provider of targetProviders) { if (provider === 'github') { guide.github = { envVars: ['GITHUB_TOKEN', 'GITHUB_USERNAME'], example: { GITHUB_TOKEN: 'ghp_xxxxxxxxxxxxxxxxxxxx', GITHUB_USERNAME: 'your-github-username' }, instructions: [ '1. Go to https://github.com/settings/tokens', '2. Click "Generate new token (classic)"', '3. Select required scopes (repo, user)', '4. Copy the generated token', '5. Set GITHUB_TOKEN environment variable', '6. Set GITHUB_USERNAME to your GitHub username' ] }; } if (provider === 'gitea') { guide.gitea = { envVars: ['GITEA_URL', 'GITEA_TOKEN', 'GITEA_USERNAME'], example: { GITEA_URL: 'https://gitea.example.com', GITEA_TOKEN: 'your-gitea-access-token', GITEA_USERNAME: 'your-gitea-username' }, instructions: [ '1. Log in to your Gitea instance', '2. Go to Settings > Applications', '3. Generate new access token', '4. Copy the generated token', '5. Set GITEA_URL to your Gitea instance URL', '6. Set GITEA_TOKEN to the generated token', '7. Set GITEA_USERNAME to your Gitea username' ] }; } } return guide; } /** * Mask sensitive credential information for logging */ maskCredentials(credentials: any): any { if (!credentials || typeof credentials !== 'object') { return credentials; } const masked = { ...credentials }; // Mask tokens if (masked.token && typeof masked.token === 'string') { masked.token = this.maskToken(masked.token); } // Recursively mask nested objects for (const key in masked) { if (typeof masked[key] === 'object' && masked[key] !== null) { masked[key] = this.maskCredentials(masked[key]); } } return masked; } /** * Mask token for safe logging */ private maskToken(token: string): string { if (token.length <= 8) { return '*'.repeat(token.length); } const start = token.substring(0, 4); const end = token.substring(token.length - 4); const middle = '*'.repeat(token.length - 8); return `${start}${middle}${end}`; } /** * Clear validation cache */ clearValidationCache(): void { this.validationCache.clear(); logger.debug('Credential validation cache cleared', 'CREDENTIALS'); } /** * Refresh credentials from environment */ refreshCredentials(): void { this.clearValidationCache(); this.loadCredentials(); logger.info('Credentials refreshed from environment', 'CREDENTIALS'); } /** * Get credential summary for status reporting */ getCredentialSummary(): { totalProviders: number; configuredProviders: number; validProviders: number; providers: Record<string, { configured: boolean; valid: boolean; error?: string; }>; } { const status = this.getCredentialStatus(); const providers: Record<string, any> = {}; // Add configured providers for (const provider of status.configured) { const isValid = status.valid.includes(provider); providers[provider] = { configured: true, valid: isValid, error: isValid ? undefined : status.errors[provider] }; } // Add missing providers for (const provider of status.missing) { providers[provider] = { configured: false, valid: false, error: 'Not configured' }; } return { totalProviders: 2, // github + gitea configuredProviders: status.configured.length, validProviders: status.valid.length, providers }; } } // Export singleton instance export const credentialManager = new CredentialManager();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Andre-Buzeli/git-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server