UNPKG

ssh-bridge-ai

Version:

One Command Magic SSH with Invisible Analytics - Connect to any server instantly with 'sshbridge user@server'. Zero setup, zero friction, pure magic. Industry-standard security with behind-the-scenes business intelligence.

217 lines (186 loc) 6.32 kB
const axios = require('axios'); const { Config } = require('./config'); const { ErrorHandler, NetworkError, ValidationError } = require('./utils/errors'); const { ValidationUtils } = require('./utils/validation'); const logger = require('./utils/logger'); const { API, AUTH, NETWORK } = require('./utils/constants'); class APIClient { constructor() { // Use environment variable for API URL this.baseURL = process.env.SSHBRIDGE_API_URL; if (!this.baseURL) { throw new Error('SSHBRIDGE_API_URL environment variable must be set for security. No hardcoded endpoints allowed.'); } // SECURITY: Validate URL format try { const url = new URL(this.baseURL); if (!['http:', 'https:'].includes(url.protocol)) { throw new Error('API URL must use HTTP or HTTPS protocol'); } } catch (error) { throw new Error(`Invalid API URL format: ${this.baseURL}`); } this.config = new Config(); } // SECURITY: Enhanced request method with validation async request(method, endpoint, data = null) { // SECURITY: Validate endpoint to prevent SSRF if (!this.isValidEndpoint(endpoint)) { throw new Error('Invalid endpoint detected'); } const headers = { 'Content-Type': 'application/json', }; const apiKey = this.config.getApiKey(); if (apiKey) { headers['Authorization'] = `Bearer ${apiKey}`; } try { const response = await axios({ method, url: `${this.baseURL}${endpoint}`, headers, data, timeout: NETWORK.HTTP_TIMEOUT, // SECURITY: Prevent redirects to external domains maxRedirects: 0, // SECURITY: Validate response status validateStatus: (status) => status >= 200 && status < 300 }); return response.data; } catch (error) { if (error.response) { let errorMessage = 'API request failed'; if (error.response.data) { if (typeof error.response.data === 'string') { errorMessage = error.response.data; } else if (error.response.data.detail) { errorMessage = error.response.data.detail; } else if (error.response.data.message) { errorMessage = error.response.data.message; } else if (error.response.status === 422) { errorMessage = 'Validation error - please check your input'; } else if (error.response.status === 400) { errorMessage = 'Bad request - please check your input'; } } throw new Error(errorMessage); } else if (error.request) { throw new Error('Network error - please check your connection'); } else { throw new Error(`Request failed: ${error.message}`); } } } // SECURITY: Endpoint validation to prevent SSRF isValidEndpoint(endpoint) { if (!endpoint || typeof endpoint !== 'string') { return false; } // Block dangerous endpoints const dangerousEndpoints = [ /^\/\//, // Absolute URLs /^https?:\/\//, // Full URLs /\.\./, // Directory traversal /\/etc\//, // System directories /\/proc\//, // Process info /\/sys\//, // System info /\/dev\//, // Device files /\/root\//, // Root directory /\/var\//, // Variable data /\/usr\// // User programs ]; return !dangerousEndpoints.some(pattern => pattern.test(endpoint)); } async register(email) { return this.request('POST', '/auth/register', { email }); } async verifyEmail(email, code) { return this.request('POST', '/auth/verify', { email, code }); } async validateUsage() { return this.request('GET', '/usage/validate'); } async logUsage(server, command) { return this.request('POST', '/usage/log', { server, command, timestamp: new Date().toISOString() }); } async getUsage() { return this.request('GET', '/usage'); } async getUserStatus(email) { try { // First try to get usage stats (requires authentication) const usage = await this.request('GET', '/usage'); return { tier: usage.plan === 'pro' ? 'pro' : 'free', serversUsed: usage.servers || 0, commandsUsed: usage.used || 0, serversLimit: usage.maxServers || 5, commandsLimit: usage.limit || 50 }; } catch (error) { // If user is not authenticated (no API key), return default free tier status if (error.message.includes('401') || error.message.includes('Unauthorized') || error.message.includes('Network error') || !this.config.getApiKey()) { return { tier: 'free', serversUsed: 0, commandsUsed: 0, serversLimit: 5, commandsLimit: 50 }; } // For other errors, re-throw throw error; } } // New authentication methods async checkUserExists(email) { return this.request('POST', '/auth/check-user', { email }); } async sendVerificationCode(email) { return this.request('POST', '/auth/send-code', { email }); } async createAccount(userData) { return this.request('POST', '/auth/create-account', userData); } async verifyEmail(email, code) { return this.request('POST', '/auth/verify', { email, code }); } async getServers() { return this.request('GET', '/servers'); } async upgradeAccount(plan) { return this.request('POST', '/billing/upgrade', { plan }); } async executeSSHCommand(host, username, command, options = {}) { const payload = { host, username, command, port: options.port || 22, private_key: options.private_key || null, password: options.password || null }; return this.request('POST', '/ssh/execute', payload); } async getExecutionToken(mode = 'hybrid') { return this.request('POST', '/ssh/get-token', { mode }); } async reportUsage(token, server, command, executionTime, success) { return this.request('POST', '/ssh/report-usage', { token, server, command, execution_time: executionTime, success }); } } module.exports = { APIClient };