lsh-framework 3.2.4 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +72 -34
  3. package/dist/commands/ipfs.js +7 -12
  4. package/dist/commands/sync.js +51 -39
  5. package/dist/constants/config.js +3 -0
  6. package/dist/lib/floating-point-arithmetic.js +2 -2
  7. package/dist/lib/ipfs-client-manager.js +51 -13
  8. package/dist/lib/ipfs-secrets-storage.js +21 -16
  9. package/dist/lib/ipfs-sync.js +88 -14
  10. package/dist/lib/secrets-manager.js +117 -47
  11. package/dist/lib/sync-key-store.js +87 -0
  12. package/dist/services/secrets/secrets.js +77 -39
  13. package/package.json +16 -16
  14. package/dist/__tests__/fixtures/job-fixtures.js +0 -204
  15. package/dist/__tests__/fixtures/supabase-mocks.js +0 -252
  16. package/dist/daemon/job-registry.js +0 -556
  17. package/dist/daemon/lshd.js +0 -968
  18. package/dist/daemon/saas-api-routes.js +0 -599
  19. package/dist/daemon/saas-api-server.js +0 -231
  20. package/dist/examples/supabase-integration.js +0 -106
  21. package/dist/lib/api-response.js +0 -226
  22. package/dist/lib/base-command-registrar.js +0 -287
  23. package/dist/lib/base-job-manager.js +0 -295
  24. package/dist/lib/cloud-config-manager.js +0 -348
  25. package/dist/lib/cron-job-manager.js +0 -368
  26. package/dist/lib/daemon-client-helper.js +0 -145
  27. package/dist/lib/daemon-client.js +0 -513
  28. package/dist/lib/database-persistence.js +0 -727
  29. package/dist/lib/database-schema.js +0 -259
  30. package/dist/lib/database-types.js +0 -90
  31. package/dist/lib/enhanced-history-system.js +0 -247
  32. package/dist/lib/history-system.js +0 -246
  33. package/dist/lib/job-manager.js +0 -436
  34. package/dist/lib/job-storage-database.js +0 -164
  35. package/dist/lib/job-storage-memory.js +0 -73
  36. package/dist/lib/local-storage-adapter.js +0 -507
  37. package/dist/lib/optimized-job-scheduler.js +0 -356
  38. package/dist/lib/saas-audit.js +0 -215
  39. package/dist/lib/saas-auth.js +0 -465
  40. package/dist/lib/saas-billing.js +0 -503
  41. package/dist/lib/saas-email.js +0 -403
  42. package/dist/lib/saas-encryption.js +0 -221
  43. package/dist/lib/saas-organizations.js +0 -662
  44. package/dist/lib/saas-secrets.js +0 -408
  45. package/dist/lib/saas-types.js +0 -165
  46. package/dist/lib/supabase-client.js +0 -125
  47. package/dist/lib/supabase-utils.js +0 -396
  48. package/dist/services/cron/cron-registrar.js +0 -240
  49. package/dist/services/cron/cron.js +0 -9
  50. package/dist/services/daemon/daemon-registrar.js +0 -585
  51. package/dist/services/daemon/daemon.js +0 -9
  52. package/dist/services/supabase/supabase-registrar.js +0 -375
  53. package/dist/services/supabase/supabase.js +0 -9
@@ -1,231 +0,0 @@
1
- /**
2
- * LSH SaaS API Server
3
- * Express-based RESTful API for the SaaS platform
4
- */
5
- import express from 'express';
6
- import cors from 'cors';
7
- import rateLimit from 'express-rate-limit';
8
- import { setupSaaSApiRoutes } from './saas-api-routes.js';
9
- import { getErrorMessage } from '../lib/saas-types.js';
10
- import { ENV_VARS, DEFAULTS } from '../constants/index.js';
11
- import { ERROR_CODES } from '../constants/errors.js';
12
- /**
13
- * SaaS API Server
14
- */
15
- export class SaaSApiServer {
16
- app;
17
- config;
18
- server;
19
- constructor(config) {
20
- this.config = {
21
- port: config?.port || parseInt(process.env[ENV_VARS.LSH_SAAS_API_PORT] || String(DEFAULTS.SAAS_API_PORT)),
22
- host: config?.host || process.env[ENV_VARS.LSH_SAAS_API_HOST] || DEFAULTS.DEFAULT_HOST,
23
- corsOrigins: config?.corsOrigins || (process.env[ENV_VARS.LSH_CORS_ORIGINS]?.split(',') || [...DEFAULTS.DEFAULT_CORS_ORIGINS]),
24
- rateLimitWindowMs: config?.rateLimitWindowMs || 15 * 60 * 1000, // 15 minutes
25
- rateLimitMax: config?.rateLimitMax || DEFAULTS.MAX_EVENTS_LIMIT, // Max 100 requests per windowMs
26
- };
27
- this.app = express();
28
- this.setupMiddleware();
29
- this.setupRoutes();
30
- this.setupErrorHandlers();
31
- }
32
- /**
33
- * Setup middleware
34
- */
35
- setupMiddleware() {
36
- // CORS
37
- this.app.use(cors({
38
- origin: this.config.corsOrigins,
39
- credentials: true,
40
- methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
41
- allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
42
- }));
43
- // Rate limiting - global limit for all requests
44
- const limiter = rateLimit({
45
- windowMs: this.config.rateLimitWindowMs,
46
- max: this.config.rateLimitMax,
47
- message: {
48
- success: false,
49
- error: {
50
- code: 'RATE_LIMIT_EXCEEDED',
51
- message: 'Too many requests, please try again later',
52
- },
53
- },
54
- standardHeaders: true, // Return rate limit info in `RateLimit-*` headers
55
- legacyHeaders: false, // Disable `X-RateLimit-*` headers
56
- });
57
- this.app.use(limiter);
58
- // Body parsers
59
- this.app.use(express.json({ limit: '10mb' }));
60
- this.app.use(express.urlencoded({ extended: true, limit: '10mb' }));
61
- // Security headers
62
- this.app.use((req, res, next) => {
63
- res.setHeader('X-Content-Type-Options', 'nosniff');
64
- res.setHeader('X-Frame-Options', 'DENY');
65
- res.setHeader('X-XSS-Protection', '1; mode=block');
66
- res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
67
- // CSP
68
- res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'");
69
- next();
70
- });
71
- // Request logging
72
- this.app.use((req, res, next) => {
73
- const start = Date.now();
74
- res.on('finish', () => {
75
- const duration = Date.now() - start;
76
- // Sanitize path to prevent log injection attacks
77
- const sanitizedPath = req.path.replace(/[\r\n]/g, '');
78
- const sanitizedIp = (req.ip || 'unknown').replace(/[\r\n]/g, '');
79
- console.log(`${req.method} ${sanitizedPath} ${res.statusCode} - ${duration}ms - ${sanitizedIp}`);
80
- });
81
- next();
82
- });
83
- }
84
- /**
85
- * Setup routes
86
- */
87
- setupRoutes() {
88
- // Health check
89
- this.app.get('/health', (req, res) => {
90
- res.json({
91
- status: 'ok',
92
- timestamp: new Date().toISOString(),
93
- uptime: process.uptime(),
94
- });
95
- });
96
- // API version
97
- this.app.get('/api/v1', (req, res) => {
98
- res.json({
99
- name: 'LSH SaaS API',
100
- version: '1.0.0',
101
- endpoints: {
102
- auth: '/api/v1/auth/*',
103
- organizations: '/api/v1/organizations/*',
104
- teams: '/api/v1/organizations/:id/teams/*',
105
- secrets: '/api/v1/teams/:id/secrets/*',
106
- billing: '/api/v1/organizations/:id/billing/*',
107
- audit: '/api/v1/organizations/:id/audit-logs',
108
- },
109
- });
110
- });
111
- // Setup SaaS routes
112
- setupSaaSApiRoutes(this.app);
113
- // 404 handler
114
- this.app.use((req, res) => {
115
- res.status(404).json({
116
- success: false,
117
- error: {
118
- code: ERROR_CODES.NOT_FOUND,
119
- message: `Endpoint ${req.method} ${req.path} not found`,
120
- },
121
- });
122
- });
123
- }
124
- /**
125
- * Setup error handlers
126
- */
127
- setupErrorHandlers() {
128
- // Global error handler
129
- const errorHandler = (err, _req, res, _next) => {
130
- console.error('API Error:', err);
131
- // Don't leak error details in production
132
- const isDev = process.env[ENV_VARS.NODE_ENV] !== 'production';
133
- const statusCode = err.status || 500;
134
- const errorCode = err.code || ERROR_CODES.INTERNAL_ERROR;
135
- res.status(statusCode).json({
136
- success: false,
137
- error: {
138
- code: errorCode,
139
- message: isDev ? getErrorMessage(err) : 'Internal server error',
140
- details: isDev ? err.stack : undefined,
141
- },
142
- });
143
- };
144
- this.app.use(errorHandler);
145
- }
146
- /**
147
- * Start the server
148
- */
149
- async start() {
150
- return new Promise((resolve, reject) => {
151
- try {
152
- this.server = this.app.listen(this.config.port, this.config.host, () => {
153
- console.log(`
154
- ╔═══════════════════════════════════════════════════════════════╗
155
- ║ ║
156
- ║ 🔐 LSH SaaS API Server ║
157
- ║ ║
158
- ║ Status: Running ║
159
- ║ Port: ${this.config.port.toString().padEnd(49)} ║
160
- ║ Host: ${this.config.host?.padEnd(49)} ║
161
- ║ ║
162
- ║ Endpoints: ║
163
- ║ - Health: http://${this.config.host}:${this.config.port}/health${' '.repeat(26)} ║
164
- ║ - API: http://${this.config.host}:${this.config.port}/api/v1${' '.repeat(27)} ║
165
- ║ ║
166
- ╚═══════════════════════════════════════════════════════════════╝
167
- `);
168
- resolve();
169
- });
170
- this.server.on('error', (error) => {
171
- if (error.code === 'EADDRINUSE') {
172
- reject(new Error(`Port ${this.config.port} is already in use`));
173
- }
174
- else {
175
- reject(error);
176
- }
177
- });
178
- }
179
- catch (error) {
180
- reject(error);
181
- }
182
- });
183
- }
184
- /**
185
- * Stop the server
186
- */
187
- async stop() {
188
- return new Promise((resolve, reject) => {
189
- if (!this.server) {
190
- resolve();
191
- return;
192
- }
193
- this.server.close((err) => {
194
- if (err) {
195
- reject(err);
196
- }
197
- else {
198
- console.log('LSH SaaS API Server stopped');
199
- resolve();
200
- }
201
- });
202
- });
203
- }
204
- /**
205
- * Get Express app (for testing)
206
- */
207
- getApp() {
208
- return this.app;
209
- }
210
- }
211
- /**
212
- * Start the server if run directly
213
- */
214
- if (import.meta.url === `file://${process.argv[1]}`) {
215
- const server = new SaaSApiServer();
216
- server.start().catch((error) => {
217
- console.error('Failed to start SaaS API server:', error);
218
- process.exit(1);
219
- });
220
- // Graceful shutdown
221
- process.on('SIGINT', async () => {
222
- console.log('\nShutting down gracefully...');
223
- await server.stop();
224
- process.exit(0);
225
- });
226
- process.on('SIGTERM', async () => {
227
- console.log('\nShutting down gracefully...');
228
- await server.stop();
229
- process.exit(0);
230
- });
231
- }
@@ -1,106 +0,0 @@
1
- /**
2
- * Supabase Integration Example
3
- * Demonstrates how to use LSH with Supabase PostgreSQL
4
- */
5
- import { supabaseClient } from '../lib/supabase-client.js';
6
- import DatabasePersistence from '../lib/database-persistence.js';
7
- import CloudConfigManager from '../lib/cloud-config-manager.js';
8
- import EnhancedHistorySystem from '../lib/enhanced-history-system.js';
9
- async function demonstrateSupabaseIntegration() {
10
- console.log('🚀 LSH Supabase Integration Demo\n');
11
- // 1. Test database connection
12
- console.log('1. Testing Supabase connection...');
13
- const isConnected = await supabaseClient.testConnection();
14
- if (!isConnected) {
15
- console.log('❌ Database connection failed');
16
- return;
17
- }
18
- console.log('✅ Database connection successful\n');
19
- // 2. Initialize database persistence
20
- console.log('2. Initializing database persistence...');
21
- const persistence = new DatabasePersistence('demo-user');
22
- console.log('✅ Database persistence initialized\n');
23
- // 3. Demonstrate configuration management
24
- console.log('3. Configuration management demo...');
25
- const configManager = new CloudConfigManager({
26
- userId: 'demo-user',
27
- enableCloudSync: true,
28
- });
29
- // Set some configuration values
30
- configManager.set('theme', 'dark', 'UI theme preference');
31
- configManager.set('max_history', 1000, 'Maximum history entries');
32
- configManager.set('auto_complete', true, 'Enable auto-completion');
33
- console.log('Configuration set:');
34
- configManager.getAll().forEach(config => {
35
- console.log(` ${config.key}: ${JSON.stringify(config.value)}`);
36
- });
37
- console.log('✅ Configuration management working\n');
38
- // 4. Demonstrate history management
39
- console.log('4. History management demo...');
40
- const historySystem = new EnhancedHistorySystem({
41
- userId: 'demo-user',
42
- enableCloudSync: true,
43
- });
44
- // Add some sample commands
45
- historySystem.addCommand('ls -la', 0);
46
- historySystem.addCommand('cd /home/user', 0);
47
- historySystem.addCommand('git status', 0);
48
- historySystem.addCommand('npm install', 0);
49
- // Save to database
50
- const entries = historySystem.getAllEntries();
51
- for (const entry of entries) {
52
- await persistence.saveHistoryEntry({
53
- session_id: persistence.getSessionId(),
54
- command: entry.command,
55
- working_directory: '/home/user',
56
- exit_code: entry.exitCode,
57
- timestamp: new Date(entry.timestamp).toISOString(),
58
- hostname: 'demo-host',
59
- });
60
- }
61
- console.log(`Added ${entries.length} history entries`);
62
- console.log('✅ History management working\n');
63
- // 5. Demonstrate job management
64
- console.log('5. Job management demo...');
65
- await persistence.saveJob({
66
- session_id: persistence.getSessionId(),
67
- job_id: 'job_1',
68
- command: 'long-running-task',
69
- status: 'running',
70
- working_directory: '/home/user',
71
- started_at: new Date().toISOString(),
72
- });
73
- const activeJobs = await persistence.getActiveJobs();
74
- console.log(`Active jobs: ${activeJobs.length}`);
75
- activeJobs.forEach(job => {
76
- console.log(` ${job.job_id}: ${job.command} (${job.status})`);
77
- });
78
- console.log('✅ Job management working\n');
79
- // 6. Demonstrate session management
80
- console.log('6. Session management demo...');
81
- await persistence.startSession('/home/user', {
82
- PATH: '/usr/bin:/bin',
83
- HOME: '/home/user',
84
- USER: 'demo-user',
85
- });
86
- console.log(`Session started: ${persistence.getSessionId()}`);
87
- console.log('✅ Session management working\n');
88
- // 7. Show statistics
89
- console.log('7. Statistics:');
90
- const historyStats = await historySystem.getHistoryStats();
91
- console.log('History stats:', historyStats);
92
- const configStats = configManager.getStats();
93
- console.log('Config stats:', configStats);
94
- // 8. Cleanup
95
- console.log('\n8. Cleanup...');
96
- await persistence.endSession();
97
- historySystem.destroy();
98
- configManager.destroy();
99
- console.log('✅ Cleanup completed');
100
- console.log('\n🎉 Supabase integration demo completed successfully!');
101
- }
102
- // Run the demo if this file is executed directly
103
- if (import.meta.url === `file://${process.argv[1]}`) {
104
- demonstrateSupabaseIntegration().catch(console.error);
105
- }
106
- export default demonstrateSupabaseIntegration;
@@ -1,226 +0,0 @@
1
- /**
2
- * API Response Builder
3
- *
4
- * Standardized response utilities for the SaaS API.
5
- * Centralizes response formatting and error handling.
6
- *
7
- * @module api-response
8
- * @example
9
- * ```typescript
10
- * import { sendSuccess, ApiErrors } from './api-response.js';
11
- *
12
- * // Success response
13
- * app.get('/users/:id', async (req, res) => {
14
- * const user = await getUser(req.params.id);
15
- * return sendSuccess(res, { user });
16
- * });
17
- *
18
- * // Error response
19
- * app.get('/protected', async (req, res) => {
20
- * if (!req.user) {
21
- * return ApiErrors.unauthorized(res);
22
- * }
23
- * });
24
- * ```
25
- */
26
- import { ERROR_CODES } from '../constants/errors.js';
27
- /**
28
- * HTTP status codes mapped to error codes
29
- * @internal
30
- */
31
- const ERROR_STATUS_MAP = {
32
- [ERROR_CODES.UNAUTHORIZED]: 401,
33
- [ERROR_CODES.INVALID_CREDENTIALS]: 401,
34
- [ERROR_CODES.INVALID_TOKEN]: 401,
35
- [ERROR_CODES.EMAIL_NOT_VERIFIED]: 401,
36
- [ERROR_CODES.FORBIDDEN]: 403,
37
- [ERROR_CODES.NOT_FOUND]: 404,
38
- [ERROR_CODES.INVALID_INPUT]: 400,
39
- [ERROR_CODES.ALREADY_EXISTS]: 409,
40
- [ERROR_CODES.EMAIL_ALREADY_EXISTS]: 409,
41
- [ERROR_CODES.PAYMENT_REQUIRED]: 402,
42
- [ERROR_CODES.TIER_LIMIT_EXCEEDED]: 403,
43
- [ERROR_CODES.INTERNAL_ERROR]: 500,
44
- RATE_LIMIT_EXCEEDED: 429,
45
- };
46
- /**
47
- * Send a successful API response
48
- *
49
- * @template T - The type of the data payload
50
- * @param res - Express response object
51
- * @param data - The data to send in the response
52
- * @param statusCode - HTTP status code (default: 200)
53
- * @returns The Express response object
54
- *
55
- * @example
56
- * ```typescript
57
- * return sendSuccess(res, { user: { id: '123', name: 'John' } });
58
- * ```
59
- */
60
- export function sendSuccess(res, data, statusCode = 200) {
61
- return res.status(statusCode).json({
62
- success: true,
63
- data,
64
- });
65
- }
66
- /**
67
- * Send a created (201) response for newly created resources
68
- *
69
- * @template T - The type of the data payload
70
- * @param res - Express response object
71
- * @param data - The created resource data
72
- * @returns The Express response object
73
- *
74
- * @example
75
- * ```typescript
76
- * const newUser = await createUser(userData);
77
- * return sendCreated(res, { user: newUser });
78
- * ```
79
- */
80
- export function sendCreated(res, data) {
81
- return sendSuccess(res, data, 201);
82
- }
83
- /**
84
- * Send an error response with automatic status code mapping
85
- *
86
- * @param res - Express response object
87
- * @param code - Error code (e.g., 'UNAUTHORIZED', 'NOT_FOUND')
88
- * @param message - Human-readable error message
89
- * @param statusCode - Optional explicit HTTP status code (auto-mapped if not provided)
90
- * @returns The Express response object
91
- *
92
- * @example
93
- * ```typescript
94
- * return sendError(res, 'CUSTOM_ERROR', 'Something went wrong', 422);
95
- * ```
96
- */
97
- export function sendError(res, code, message, statusCode) {
98
- const status = statusCode ?? ERROR_STATUS_MAP[code] ?? 500;
99
- return res.status(status).json({
100
- success: false,
101
- error: { code, message },
102
- });
103
- }
104
- /**
105
- * Common error response helpers with pre-configured error codes and messages.
106
- * Each method returns the appropriate HTTP status code automatically.
107
- *
108
- * @example
109
- * ```typescript
110
- * // 401 Unauthorized
111
- * return ApiErrors.unauthorized(res);
112
- *
113
- * // 404 Not Found with custom resource name
114
- * return ApiErrors.notFound(res, 'User');
115
- *
116
- * // 400 Bad Request with validation message
117
- * return ApiErrors.invalidInput(res, 'Email is required');
118
- * ```
119
- */
120
- export const ApiErrors = {
121
- /**
122
- * Send a 401 Unauthorized response
123
- * @param res - Express response object
124
- * @param message - Custom error message (default: 'Authentication required')
125
- */
126
- unauthorized(res, message = 'Authentication required') {
127
- return sendError(res, ERROR_CODES.UNAUTHORIZED, message);
128
- },
129
- /**
130
- * Send a 401 Invalid Token response
131
- * @param res - Express response object
132
- * @param message - Custom error message (default: 'Invalid or expired token')
133
- */
134
- invalidToken(res, message = 'Invalid or expired token') {
135
- return sendError(res, ERROR_CODES.INVALID_TOKEN, message);
136
- },
137
- /**
138
- * Send a 403 Forbidden response
139
- * @param res - Express response object
140
- * @param message - Custom error message (default: 'Access denied')
141
- */
142
- forbidden(res, message = 'Access denied') {
143
- return sendError(res, ERROR_CODES.FORBIDDEN, message);
144
- },
145
- /**
146
- * Send a 404 Not Found response
147
- * @param res - Express response object
148
- * @param resource - Resource name for the error message (default: 'Resource')
149
- */
150
- notFound(res, resource = 'Resource') {
151
- return sendError(res, ERROR_CODES.NOT_FOUND, `${resource} not found`);
152
- },
153
- /**
154
- * Send a 400 Bad Request response for invalid input
155
- * @param res - Express response object
156
- * @param message - Validation error message
157
- */
158
- invalidInput(res, message) {
159
- return sendError(res, ERROR_CODES.INVALID_INPUT, message);
160
- },
161
- /**
162
- * Send a 409 Conflict response for duplicate resources
163
- * @param res - Express response object
164
- * @param resource - Resource name for the error message (default: 'Resource')
165
- */
166
- alreadyExists(res, resource = 'Resource') {
167
- return sendError(res, ERROR_CODES.ALREADY_EXISTS, `${resource} already exists`);
168
- },
169
- /**
170
- * Send a 500 Internal Server Error response
171
- * @param res - Express response object
172
- * @param message - Custom error message (default: 'An internal error occurred')
173
- */
174
- internalError(res, message = 'An internal error occurred') {
175
- return sendError(res, ERROR_CODES.INTERNAL_ERROR, message);
176
- },
177
- /**
178
- * Send a 402 Payment Required response
179
- * @param res - Express response object
180
- * @param message - Custom error message (default: 'Payment required')
181
- */
182
- paymentRequired(res, message = 'Payment required') {
183
- return sendError(res, ERROR_CODES.PAYMENT_REQUIRED, message);
184
- },
185
- /**
186
- * Send a 403 Tier Limit Exceeded response
187
- * @param res - Express response object
188
- * @param message - Custom error message (default: 'Plan limit exceeded')
189
- */
190
- tierLimitExceeded(res, message = 'Plan limit exceeded') {
191
- return sendError(res, ERROR_CODES.TIER_LIMIT_EXCEEDED, message);
192
- },
193
- /**
194
- * Send a 429 Rate Limit Exceeded response
195
- * @param res - Express response object
196
- * @param message - Custom error message (default: 'Too many requests')
197
- */
198
- rateLimitExceeded(res, message = 'Too many requests') {
199
- return sendError(res, 'RATE_LIMIT_EXCEEDED', message, 429);
200
- },
201
- };
202
- /**
203
- * Extract a human-readable error message from an unknown error type.
204
- * Useful in catch blocks where the error type is unknown.
205
- *
206
- * @param error - The caught error (unknown type)
207
- * @returns A string error message
208
- *
209
- * @example
210
- * ```typescript
211
- * try {
212
- * await riskyOperation();
213
- * } catch (error) {
214
- * return ApiErrors.internalError(res, extractApiErrorMessage(error));
215
- * }
216
- * ```
217
- */
218
- export function extractApiErrorMessage(error) {
219
- if (error instanceof Error) {
220
- return error.message;
221
- }
222
- if (typeof error === 'string') {
223
- return error;
224
- }
225
- return 'An unexpected error occurred';
226
- }