new file: .claude/settings.local.json
new file: .gitignore new file: backend/.env.example new file: backend/.gitignore new file: backend/ecosystem.config.js new file: backend/nodemon.json new file: backend/package-lock.json new file: backend/package.json new file: backend/src/config/database.ts new file: backend/src/config/dynamicSwagger.ts new file: backend/src/config/environment.ts new file: backend/src/config/swagger.ts new file: backend/src/controllers/apiKeyController.ts new file: backend/src/controllers/authController.ts new file: backend/src/controllers/databaseController.ts new file: backend/src/controllers/databaseManagementController.ts new file: backend/src/controllers/dynamicApiController.ts new file: backend/src/controllers/endpointController.ts new file: backend/src/controllers/folderController.ts new file: backend/src/controllers/logsController.ts new file: backend/src/controllers/userController.ts new file: backend/src/middleware/apiKey.ts new file: backend/src/middleware/auth.ts new file: backend/src/middleware/logging.ts new file: backend/src/migrations/001_initial_schema.sql new file: backend/src/migrations/002_add_logging.sql new file: backend/src/migrations/003_add_scripting.sql new file: backend/src/migrations/004_add_superadmin.sql new file: backend/src/migrations/run.ts new file: backend/src/migrations/seed.ts new file: backend/src/routes/apiKeys.ts new file: backend/src/routes/auth.ts new file: backend/src/routes/databaseManagement.ts new file: backend/src/routes/databases.ts new file: backend/src/routes/dynamic.ts new file: backend/src/routes/endpoints.ts new file: backend/src/routes/folders.ts new file: backend/src/routes/logs.ts new file: backend/src/routes/users.ts new file: backend/src/server.ts new file: backend/src/services/DatabasePoolManager.ts new file: backend/src/services/ScriptExecutor.ts new file: backend/src/services/SqlExecutor.ts new file: backend/src/types/index.ts new file: backend/tsconfig.json new file: frontend/.gitignore new file: frontend/index.html new file: frontend/nginx.conf new file: frontend/package-lock.json new file: frontend/package.json new file: frontend/postcss.config.js new file: frontend/src/App.tsx new file: frontend/src/components/CodeEditor.tsx
This commit is contained in:
83
backend/src/middleware/apiKey.ts
Normal file
83
backend/src/middleware/apiKey.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { mainPool } from '../config/database';
|
||||
|
||||
export interface ApiKeyRequest extends Request {
|
||||
apiKey?: {
|
||||
id: string;
|
||||
name: string;
|
||||
permissions: string[];
|
||||
user_id: string;
|
||||
enable_logging: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
// Optional API key middleware - validates if key is provided but doesn't require it
|
||||
export const apiKeyMiddleware = async (
|
||||
req: ApiKeyRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const apiKey = req.headers['x-api-key'] as string;
|
||||
|
||||
// If no API key provided, continue without setting req.apiKey
|
||||
// The endpoint controller will decide if key is required
|
||||
if (!apiKey) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Fetch API key from database
|
||||
const result = await mainPool.query(
|
||||
`SELECT id, name, user_id, permissions, is_active, expires_at, enable_logging
|
||||
FROM api_keys
|
||||
WHERE key = $1`,
|
||||
[apiKey]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(401).json({ error: 'Invalid API key' });
|
||||
}
|
||||
|
||||
const keyData = result.rows[0];
|
||||
|
||||
// Check if key is active
|
||||
if (!keyData.is_active) {
|
||||
return res.status(401).json({ error: 'API key is inactive' });
|
||||
}
|
||||
|
||||
// Check if key is expired
|
||||
if (keyData.expires_at && new Date(keyData.expires_at) < new Date()) {
|
||||
return res.status(401).json({ error: 'API key has expired' });
|
||||
}
|
||||
|
||||
req.apiKey = {
|
||||
id: keyData.id,
|
||||
name: keyData.name,
|
||||
permissions: keyData.permissions || [],
|
||||
user_id: keyData.user_id,
|
||||
enable_logging: keyData.enable_logging || false,
|
||||
};
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('API key middleware error:', error);
|
||||
return res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
};
|
||||
|
||||
export const checkEndpointPermission = (endpointId: string) => {
|
||||
return (req: ApiKeyRequest, res: Response, next: NextFunction) => {
|
||||
if (!req.apiKey) {
|
||||
return res.status(401).json({ error: 'API key required' });
|
||||
}
|
||||
|
||||
const hasPermission = req.apiKey.permissions.includes(endpointId) ||
|
||||
req.apiKey.permissions.includes('*');
|
||||
|
||||
if (!hasPermission) {
|
||||
return res.status(403).json({ error: 'Access denied to this endpoint' });
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
65
backend/src/middleware/auth.ts
Normal file
65
backend/src/middleware/auth.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { config } from '../config/environment';
|
||||
import { mainPool } from '../config/database';
|
||||
|
||||
export interface AuthRequest extends Request {
|
||||
user?: {
|
||||
id: string;
|
||||
username: string;
|
||||
role: string;
|
||||
is_superadmin: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export const authMiddleware = async (
|
||||
req: AuthRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
return res.status(401).json({ error: 'No token provided' });
|
||||
}
|
||||
|
||||
const token = authHeader.substring(7);
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, config.jwt.secret) as any;
|
||||
|
||||
// Fetch user from database
|
||||
const result = await mainPool.query(
|
||||
'SELECT id, username, role, is_superadmin FROM users WHERE id = $1',
|
||||
[decoded.userId]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(401).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
req.user = result.rows[0];
|
||||
next();
|
||||
} catch (error) {
|
||||
return res.status(401).json({ error: 'Invalid token' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Auth middleware error:', error);
|
||||
return res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
};
|
||||
|
||||
export const adminOnly = (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
if (req.user?.role !== 'admin') {
|
||||
return res.status(403).json({ error: 'Admin access required' });
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
export const superAdminOnly = (req: AuthRequest, res: Response, next: NextFunction) => {
|
||||
if (!req.user?.is_superadmin) {
|
||||
return res.status(403).json({ error: 'Superadmin access required' });
|
||||
}
|
||||
next();
|
||||
};
|
||||
107
backend/src/middleware/logging.ts
Normal file
107
backend/src/middleware/logging.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { Response, NextFunction } from 'express';
|
||||
import { ApiKeyRequest } from './apiKey';
|
||||
import { mainPool } from '../config/database';
|
||||
|
||||
interface LogData {
|
||||
endpoint_id: string | null;
|
||||
api_key_id: string | null;
|
||||
method: string;
|
||||
path: string;
|
||||
request_params: any;
|
||||
request_body: any;
|
||||
response_status: number;
|
||||
response_data: any;
|
||||
execution_time: number;
|
||||
error_message: string | null;
|
||||
ip_address: string;
|
||||
user_agent: string;
|
||||
}
|
||||
|
||||
export const createLoggingMiddleware = (endpointId: string, shouldLog: boolean) => {
|
||||
return async (req: ApiKeyRequest, res: Response, next: NextFunction) => {
|
||||
if (!shouldLog) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
// Capture original methods
|
||||
const originalJson = res.json.bind(res);
|
||||
const originalSend = res.send.bind(res);
|
||||
|
||||
let responseData: any = null;
|
||||
let isLogged = false;
|
||||
|
||||
const logRequest = async (data: any, status: number, errorMsg: string | null = null) => {
|
||||
if (isLogged) return; // Prevent duplicate logging
|
||||
isLogged = true;
|
||||
|
||||
const executionTime = Date.now() - startTime;
|
||||
|
||||
const logData: LogData = {
|
||||
endpoint_id: endpointId,
|
||||
api_key_id: req.apiKey?.id || null,
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
request_params: req.query || {},
|
||||
request_body: req.body || {},
|
||||
response_status: status,
|
||||
response_data: data,
|
||||
execution_time: executionTime,
|
||||
error_message: errorMsg,
|
||||
ip_address: req.ip || req.socket.remoteAddress || 'unknown',
|
||||
user_agent: req.headers['user-agent'] || 'unknown',
|
||||
};
|
||||
|
||||
try {
|
||||
await mainPool.query(
|
||||
`INSERT INTO request_logs (
|
||||
endpoint_id, api_key_id, method, path,
|
||||
request_params, request_body, response_status,
|
||||
response_data, execution_time, error_message,
|
||||
ip_address, user_agent
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`,
|
||||
[
|
||||
logData.endpoint_id,
|
||||
logData.api_key_id,
|
||||
logData.method,
|
||||
logData.path,
|
||||
JSON.stringify(logData.request_params),
|
||||
JSON.stringify(logData.request_body),
|
||||
logData.response_status,
|
||||
JSON.stringify(logData.response_data),
|
||||
logData.execution_time,
|
||||
logData.error_message,
|
||||
logData.ip_address,
|
||||
logData.user_agent,
|
||||
]
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to log request:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Override res.json
|
||||
res.json = function (data: any) {
|
||||
responseData = data;
|
||||
logRequest(data, res.statusCode);
|
||||
return originalJson(data);
|
||||
};
|
||||
|
||||
// Override res.send
|
||||
res.send = function (data: any) {
|
||||
responseData = data;
|
||||
logRequest(data, res.statusCode);
|
||||
return originalSend(data);
|
||||
};
|
||||
|
||||
// Handle errors
|
||||
res.on('finish', () => {
|
||||
if (!isLogged && responseData === null) {
|
||||
logRequest(null, res.statusCode);
|
||||
}
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user