# ═══════════════════════════════════════════════════════════════════════════════ # NEW FEATURES: NOTIFICATIONS, 2FA, SUPPORT # ═══════════════════════════════════════════════════════════════════════════════ # These endpoints should be added to server/api.py # ── NOTIFICATIONS ────────────────────────────────────────────────────────────── from server import notifications as notif_service class NotificationPrefsRequest(BaseModel): email_enabled: bool = True sms_enabled: bool = False web_enabled: bool = True crawl_complete: bool = True analysis_complete: bool = True alert_triggered: bool = True weekly_report: bool = True @app.get('/api/notifications') async def api_get_notifications(request: Request, unread_only: bool = False): """Get user notifications""" try: auth = request.headers.get('authorization', '') token = auth.split(' ', 1)[1].strip() uid = user_mgmt.verify_token(token) if not uid: return JSONResponse({'ok': False, 'error': 'unauthorized'}, status_code=401) notifs = notif_service.get_notifications(uid, unread_only) return {'ok': True, 'notifications': notifs, 'count': len(notifs)} except Exception as e: return JSONResponse({'ok': False, 'error': str(e)}, status_code=500) @app.post('/api/notifications/{notification_id}/read') async def api_mark_notification_read(notification_id: int, request: Request): """Mark notification as read""" try: notif_service.mark_as_read(notification_id) return {'ok': True} except Exception as e: return JSONResponse({'ok': False, 'error': str(e)}, status_code=500) @app.get('/api/notifications/preferences') async def api_get_notification_prefs(request: Request): """Get notification preferences""" try: auth = request.headers.get('authorization', '') token = auth.split(' ', 1)[1].strip() uid = user_mgmt.verify_token(token) if not uid: return JSONResponse({'ok': False, 'error': 'unauthorized'}, status_code=401) prefs = notif_service.get_preferences(uid) return {'ok': True, 'preferences': prefs} except Exception as e: return JSONResponse({'ok': False, 'error': str(e)}, status_code=500) @app.post('/api/notifications/preferences') async def api_update_notification_prefs(req: NotificationPrefsRequest, request: Request): """Update notification preferences""" try: auth = request.headers.get('authorization', '') token = auth.split(' ', 1)[1].strip() uid = user_mgmt.verify_token(token) if not uid: return JSONResponse({'ok': False, 'error': 'unauthorized'}, status_code=401) prefs_dict = req.dict() notif_service.update_preferences(uid, prefs_dict) return {'ok': True} except Exception as e: return JSONResponse({'ok': False, 'error': str(e)}, status_code=500) # ── 2FA (TWO-FACTOR AUTHENTICATION) ──────────────────────────────────────────── from server import two_factor_auth as tfa_service @app.post('/api/2fa/setup') async def api_setup_2fa(request: Request): """Generate 2FA secret and QR code""" try: auth = request.headers.get('authorization', '') token = auth.split(' ', 1)[1].strip() uid = user_mgmt.verify_token(token) if not uid: return JSONResponse({'ok': False, 'error': 'unauthorized'}, status_code=401) user = user_mgmt.get_user(uid) secret, qr_code = tfa_service.generate_secret(uid, user['email']) return { 'ok': True, 'secret': secret, 'qr_code': qr_code, 'backup_codes': tfa_service.get_backup_codes(uid) or [] } except Exception as e: return JSONResponse({'ok': False, 'error': str(e)}, status_code=500) @app.post('/api/2fa/enable') async def api_enable_2fa(request: Request): """Enable 2FA with verification""" try: data = await request.json() secret = data.get('secret') token_code = data.get('token') auth = request.headers.get('authorization', '') token = auth.split(' ', 1)[1].strip() uid = user_mgmt.verify_token(token) if not uid: return JSONResponse({'ok': False, 'error': 'unauthorized'}, status_code=401) # Verify token before enabling import pyotp totp = pyotp.TOTP(secret) if not totp.verify(token_code): return JSONResponse({'ok': False, 'error': 'Invalid token'}, status_code=400) tfa_service.enable_2fa(uid, secret) backup_codes = tfa_service.get_backup_codes(uid) return { 'ok': True, 'message': '2FA enabled successfully', 'backup_codes': backup_codes } except Exception as e: return JSONResponse({'ok': False, 'error': str(e)}, status_code=500) @app.post('/api/2fa/disable') async def api_disable_2fa(request: Request): """Disable 2FA""" try: auth = request.headers.get('authorization', '') token = auth.split(' ', 1)[1].strip() uid = user_mgmt.verify_token(token) if not uid: return JSONResponse({'ok': False, 'error': 'unauthorized'}, status_code=401) tfa_service.disable_2fa(uid) return {'ok': True, 'message': '2FA disabled'} except Exception as e: return JSONResponse({'ok': False, 'error': str(e)}, status_code=500) @app.get('/api/2fa/status') async def api_2fa_status(request: Request): """Check 2FA status""" try: auth = request.headers.get('authorization', '') token = auth.split(' ', 1)[1].strip() uid = user_mgmt.verify_token(token) if not uid: return JSONResponse({'ok': False, 'error': 'unauthorized'}, status_code=401) enabled = tfa_service.is_2fa_enabled(uid) return {'ok': True, 'enabled': enabled} except Exception as e: return JSONResponse({'ok': False, 'error': str(e)}, status_code=500) @app.get('/api/2fa/login-history') async def api_login_history(request: Request, days: int = 30): """Get login history""" try: auth = request.headers.get('authorization', '') token = auth.split(' ', 1)[1].strip() uid = user_mgmt.verify_token(token) if not uid: return JSONResponse({'ok': False, 'error': 'unauthorized'}, status_code=401) history = tfa_service.get_login_history(uid, days) return {'ok': True, 'history': history} except Exception as e: return JSONResponse({'ok': False, 'error': str(e)}, status_code=500) @app.get('/api/2fa/suspicious-activity') async def api_check_suspicious_activity(request: Request): """Check for suspicious login activity""" try: auth = request.headers.get('authorization', '') token = auth.split(' ', 1)[1].strip() uid = user_mgmt.verify_token(token) if not uid: return JSONResponse({'ok': False, 'error': 'unauthorized'}, status_code=401) activity = tfa_service.detect_suspicious_activity(uid) return {'ok': True, **activity} except Exception as e: return JSONResponse({'ok': False, 'error': str(e)}, status_code=500) # ── SUPPORT SYSTEM ───────────────────────────────────────────────────────────── from server import support as support_service class SupportTicketRequest(BaseModel): subject: str description: str category: str priority: str = 'medium' class TicketMessageRequest(BaseModel): message: str attachment_url: Optional[str] = None @app.post('/api/support/tickets') async def api_create_ticket(req: SupportTicketRequest, request: Request): """Create support ticket""" try: auth = request.headers.get('authorization', '') token = auth.split(' ', 1)[1].strip() uid = user_mgmt.verify_token(token) if not uid: return JSONResponse({'ok': False, 'error': 'unauthorized'}, status_code=401) ticket_id = support_service.create_ticket( uid, req.subject, req.description, req.category, req.priority ) return {'ok': True, 'ticket_id': ticket_id} except Exception as e: return JSONResponse({'ok': False, 'error': str(e)}, status_code=500) @app.get('/api/support/tickets') async def api_list_tickets(request: Request, status: Optional[str] = None): """List user tickets""" try: auth = request.headers.get('authorization', '') token = auth.split(' ', 1)[1].strip() uid = user_mgmt.verify_token(token) if not uid: return JSONResponse({'ok': False, 'error': 'unauthorized'}, status_code=401) tickets = support_service.get_user_tickets(uid, status) return {'ok': True, 'tickets': tickets, 'count': len(tickets)} except Exception as e: return JSONResponse({'ok': False, 'error': str(e)}, status_code=500) @app.get('/api/support/tickets/{ticket_id}') async def api_get_ticket(ticket_id: int, request: Request): """Get ticket details""" try: ticket = support_service.get_ticket(ticket_id) if not ticket: return JSONResponse({'ok': False, 'error': 'Ticket not found'}, status_code=404) messages = support_service.get_ticket_messages(ticket_id) return {'ok': True, 'ticket': ticket, 'messages': messages} except Exception as e: return JSONResponse({'ok': False, 'error': str(e)}, status_code=500) @app.post('/api/support/tickets/{ticket_id}/messages') async def api_add_ticket_message(ticket_id: int, req: TicketMessageRequest, request: Request): """Add message to ticket""" try: auth = request.headers.get('authorization', '') token = auth.split(' ', 1)[1].strip() uid = user_mgmt.verify_token(token) if not uid: return JSONResponse({'ok': False, 'error': 'unauthorized'}, status_code=401) msg_id = support_service.add_ticket_message(ticket_id, uid, req.message, req.attachment_url) return {'ok': True, 'message_id': msg_id} except Exception as e: return JSONResponse({'ok': False, 'error': str(e)}, status_code=500) @app.post('/api/support/tickets/{ticket_id}/status') async def api_update_ticket_status(ticket_id: int, request: Request): """Update ticket status""" try: data = await request.json() status = data.get('status') support_service.update_ticket_status(ticket_id, status) return {'ok': True} except Exception as e: return JSONResponse({'ok': False, 'error': str(e)}, status_code=500) @app.get('/api/support/kb') async def api_get_kb_articles(category: Optional[str] = None): """Get knowledge base articles""" try: articles = support_service.get_kb_articles(category) return {'ok': True, 'articles': articles, 'count': len(articles)} except Exception as e: return JSONResponse({'ok': False, 'error': str(e)}, status_code=500) @app.get('/api/support/kb/search') async def api_search_kb(q: str): """Search knowledge base""" try: results = support_service.search_kb(q) return {'ok': True, 'results': results, 'count': len(results)} except Exception as e: return JSONResponse({'ok': False, 'error': str(e)}, status_code=500) @app.get('/api/support/faqs') async def api_get_faqs(category: Optional[str] = None): """Get FAQs""" try: faqs = support_service.get_faqs(category) return {'ok': True, 'faqs': faqs, 'count': len(faqs)} except Exception as e: return JSONResponse({'ok': False, 'error': str(e)}, status_code=500) @app.get('/api/support/stats') async def api_support_stats(): """Get support statistics""" try: stats = support_service.get_ticket_stats() return {'ok': True, 'stats': stats} except Exception as e: return JSONResponse({'ok': False, 'error': str(e)}, status_code=500)