Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app-graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ async function startServer() {
});

// Setup global error handling
setupGlobalErrorHandling();
setupGlobalErrorHandling(app);

} catch (error) {
logger.error('Failed to start GraphQL server', { error: error.message, stack: error.stack });
Expand Down
91 changes: 7 additions & 84 deletions app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import express from 'express';
import swaggerUi from 'swagger-ui-express';
import { apiLimiter, ddosDetector, checkBlockedIP, ipRestriction, progressiveLimiter, authLimiter } from './middleware/rateLimiter';
import { configureSecurity } from './middleware/security';
import { apiKeyAuth } from './middleware/auth';
import { apiKeyAuth } from './src/config/auth';
import { authenticate, authorize, optionalAuth } from './middleware/authentication';
import { loggingMiddleware, setupGlobalErrorHandling, errorTracker, logger } from './middleware/logger';
import { errorTracker as abuseDetector } from './middleware/abuseDetection';

Expand All @@ -11,38 +12,11 @@ import { uploadDocument } from './controllers/DocumentController';
import { getDashboardData, generateReport, exportData } from './controllers/AnalyticsController';
import { applyPaymentSecurity, processPayment, getPaymentHistory, validatePayment } from './controllers/PaymentController';
import { setupRateLimitRoutes } from './routes/rateLimitRoutes';
import auditRoutes from './routes/auditRoutes';
import fraudRoutes from './routes/fraudRoutes';

import { auditCleanupService } from './services/AuditCleanupService';
import { registerAuditHandlers } from './databases/event-patterns/handlers/auditHandlers';
import { EventBus } from './databases/event-patterns/EventBus';
import { AuthenticationController } from './controllers/AuthenticationController';
import { UserController } from './controllers/UserController';
import { authenticate, authorize } from './middleware/authentication';

// Define UserRole locally since it's not exported from @prisma/client
enum UserRole {
USER = 'USER',
ADMIN = 'ADMIN',
SUPER_ADMIN = 'SUPER_ADMIN'
}

// Initialize controllers
const authController = new AuthenticationController();
const userController = new UserController();

// Mock services for now - replace with actual implementations
const performanceMonitor = {
getHealthStatus: () => ({ status: 'healthy' }),
getMemoryUsage: () => ({ heapUsed: 0, heapTotal: 0, external: 0 }),
getRequestMetrics: (limit: number) => [],
getCustomMetrics: (limit: number) => []
};

const analyticsService = {
getAnalyticsData: () => ({ userEvents: [], activeUsers: 0 })
};
import { performanceMonitor } from './services/performanceMonitoring';
import analyticsService from './services/analytics';
import { UserRole } from '@prisma/client';

const app = express();

Expand Down Expand Up @@ -272,58 +246,7 @@ app.get('/api/analytics/dashboard', apiKeyAuth, getDashboardData);
*/
app.post('/api/analytics/reports', apiKeyAuth, generateReport);
app.get('/api/analytics/export', apiKeyAuth, exportData);

// Additional authentication routes for wallet login and 2FA
app.post('/api/auth/wallet',
authLimiter,
auditAuth(AuditAction.USER_LOGIN),
authController.loginWithWallet.bind(authController)
);
app.post('/api/auth/refresh',
authLimiter,
authController.refreshToken.bind(authController)
);

// Two-factor authentication endpoints
app.post('/api/user/2fa/enable',
authenticate,
auditAuth(AuditAction.USER_ENABLE_2FA),
authController.enableTwoFactor.bind(authController)
);
app.post('/api/user/2fa/disable',
authenticate,
auditAuth(AuditAction.USER_DISABLE_2FA),
authController.disableTwoFactor.bind(authController)
);

// User sessions
app.get('/api/user/sessions', authenticate, userController.getUserSessions.bind(userController));
app.delete('/api/user/sessions/:sessionId',
authenticate,
auditAuth(AuditAction.USER_REVOKE_SESSION),
userController.revokeSession.bind(userController)
);

// Initialize audit system
const initializeAuditSystem = async () => {
try {
// Register audit event handlers
const eventBus = EventBus.getInstance();
registerAuditHandlers(eventBus);

// Start audit cleanup service
auditCleanupService.start();

logger.info('Audit system initialized successfully');
} catch (error) {
logger.error('Failed to initialize audit system:', error);
}
};

// Initialize audit system on startup
initializeAuditSystem();

// Cache Management Routes (Admin only)
app.use('/api/cache', cacheRoutes);
// Setup global error handling
setupGlobalErrorHandling(app);

export default app;
57 changes: 57 additions & 0 deletions ml-model-api/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from flask import Flask, request, jsonify
from auth import token_required, roles_required, generate_token
import os

app = Flask(__name__)

# Dummy model inference function
def run_model_inference(data):
# This represents a placeholder for actual ML model inference
return {"status": "success", "result": "Classification result for given data"}

@app.route('/health', methods=['GET'])
def health():
return jsonify({"status": "UP", "service": "ml-model-api"})

@app.route('/predict', methods=['POST'])
@token_required
def predict():
data = request.get_json()
if not data:
return jsonify({"message": "No input data provided"}), 400

result = run_model_inference(data)
return jsonify(result)

@app.route('/admin/stats', methods=['GET'])
@token_required
@roles_required('admin')
def get_stats():
# Only admins can access this endpoint
return jsonify({
"total_inferences": 150,
"active_models": 2,
"up_time": "24h"
})

# Helper route to generate tokens (for testing purposes)
@app.route('/login', methods=['POST'])
def login():
credentials = request.get_json()
if not credentials:
return jsonify({"message": "Missing credentials"}), 400

user_id = credentials.get('user_id')
password = credentials.get('password')
role = credentials.get('role', 'user')

# Simple identification check for placeholder purposes
if user_id and password == "password": # Dummy check
token = generate_token(user_id, role)
return jsonify({"token": f"Bearer {token}"})
else:
return jsonify({"message": "Could not verify"}), 401

if __name__ == '__main__':
port = int(os.environ.get('PORT', 5000))
app.run(host='0.0.0.0', port=port)
57 changes: 57 additions & 0 deletions ml-model-api/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import jwt
import datetime
import os
from functools import wraps
from flask import request, jsonify

SECRET_KEY = os.getenv('JWT_SECRET_KEY', 'your-secret-key')
ALGORITHM = 'HS256'

def generate_token(user_id, role):
payload = {
'exp': datetime.datetime.utcnow() + datetime.timedelta(days=1),
'iat': datetime.datetime.utcnow(),
'sub': user_id,
'role': role
}
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)

def token_required(f):
@wraps(f)
def decorated(*args, **kwargs):
token = None
if 'Authorization' in request.headers:
auth_header = request.headers['Authorization']
if auth_header.startswith('Bearer '):
token = auth_header.split(" ")[1]

if not token:
return jsonify({'message': 'Token is missing!'}), 401

try:
data = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
request.user = data
except jwt.ExpiredSignatureError:
return jsonify({'message': 'Token has expired!'}), 401
except jwt.InvalidTokenError:
return jsonify({'message': 'Invalid token!'}), 401
except Exception as e:
return jsonify({'message': f'Token error: {str(e)}'}), 401

return f(*args, **kwargs)
return decorated

def roles_required(*roles):
def wrapper(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not hasattr(request, 'user'):
return jsonify({'message': 'Authentication required!'}), 401

user_role = request.user.get('role')
if user_role not in roles:
return jsonify({'message': 'Insufficient permissions!'}), 403

return f(*args, **kwargs)
return decorated_function
return wrapper
4 changes: 4 additions & 0 deletions ml-model-api/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
flask
PyJWT
cryptography
python-dotenv
49 changes: 49 additions & 0 deletions nepa-frontend/src/pages/offline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, { useEffect, useState } from 'react';
import { getOfflineHistory } from '../utils/pwa-utils';

const OfflinePage: React.FC = () => {
const [offlineHistory, setOfflineHistory] = useState<any[]>([]);

useEffect(() => {
const loadHistory = async () => {
const history = await getOfflineHistory();
setOfflineHistory(history);
};
loadHistory();
}, []);

return (
<div className="offline-container p-6 text-center">
<h1 className="text-3xl font-bold text-gray-800 mb-4">You are currently offline</h1>
<p className="text-gray-600 mb-8">
The application will function normally once a stable connection is regained.
Your classification history is currently being saved to your local storage.
</p>

<div className="history-section mt-12">
<h2 className="text-xl font-semibold mb-6">Local Classification History Samples</h2>
{offlineHistory.length > 0 ? (
<div className="history-list grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{offlineHistory.map((item, index) => (
<div key={index} className="history-item p-4 bg-white shadow-md rounded-lg">
<p className="font-medium">{item.label}</p>
<p className="text-sm text-gray-400">{new Date(item.timestamp).toLocaleString()}</p>
</div>
))}
</div>
) : (
<p className="text-gray-400 italic">No offline classifications found.</p>
)}
</div>

<button
className="mt-12 px-8 py-3 bg-blue-600 text-white font-bold rounded-lg hover:bg-blue-700 transition"
onClick={() => window.location.reload()}
>
Try Reconnecting
</button>
</div>
);
};

export default OfflinePage;
64 changes: 64 additions & 0 deletions nepa-frontend/src/utils/pwa-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { openDB } from 'idb';

const DB_NAME = 'nepa-offline-db';
const STORE_NAME = 'classification-history';

export const initDB = async () => {
return openDB(DB_NAME, 1, {
upgrade(db) {
if (!db.objectStoreNames.contains(STORE_NAME)) {
db.createObjectStore(STORE_NAME, { keyPath: 'id', autoIncrement: true });
}
},
});
};

export const saveOfflineClassification = async (classification: any) => {
const db = await initDB();
return db.add(STORE_NAME, {
...classification,
timestamp: new Date().toISOString()
});
};

export const getOfflineHistory = async () => {
const db = await initDB();
return db.getAll(STORE_NAME);
};

export const registerServiceWorker = async () => {
if ('serviceWorker' in navigator) {
try {
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('ServiceWorker registration successful with scope: ', registration.scope);
} catch (err) {
console.log('ServiceWorker registration failed: ', err);
}
}
};

export const isOnline = () => navigator.onLine;

export const syncOfflineData = async () => {
if (!isOnline()) return;
const history = await getOfflineHistory();
if (history.length === 0) return;

// Assuming we have an API endpoint to sync data
try {
for (const item of history) {
const response = await fetch('/api/sync-classification', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(item)
});
if (response.ok) {
const db = await initDB();
await db.delete(STORE_NAME, item.id);
}
}
console.log('Offline history synced successfully');
} catch (error) {
console.error('Data synchronization failed', error);
}
};
24 changes: 21 additions & 3 deletions services/errorTracking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ class ErrorTracker {

export const errorTracker = new ErrorTracker();

export const errorHandler = (error: Error, req: Request, res: Response, next: NextFunction) => {
export const errorHandler = (error: any, req: Request, res: Response, next: NextFunction) => {
const eventId = errorTracker.captureException(error, {
request: {
method: req.method,
Expand All @@ -227,8 +227,26 @@ export const errorHandler = (error: Error, req: Request, res: Response, next: Ne
userId: (req as any).user?.id
});

res.status(500).json({
error: 'Internal Server Error',
// Handle Multer errors
if (error.code === 'LIMIT_FILE_SIZE') {
return res.status(413).json({
error: 'File too large',
message: 'The uploaded file exceeds the maximum allowed size (10MB)',
eventId: process.env.NODE_ENV === 'development' ? eventId : undefined
});
}

if (error.name === 'MulterError') {
return res.status(400).json({
error: 'File upload error',
message: error.message,
eventId: process.env.NODE_ENV === 'development' ? eventId : undefined
});
}

res.status(error.status || 500).json({
error: error.name || 'Internal Server Error',
message: error.message || 'An unexpected error occurred',
eventId: process.env.NODE_ENV === 'development' ? eventId : undefined
});
};
Expand Down
Loading
Loading