""" Cloudflare Accounts Management Routes """ from flask import Blueprint, request, jsonify from app.models import db, CloudflareAccount, AuditLog from app.routes.auth import token_required from app.config import Config from functools import wraps cf_accounts_bp = Blueprint('cf_accounts', __name__) def internal_api_required(f): """Decorator for internal API endpoints (requires internal API key)""" @wraps(f) def decorated_function(*args, **kwargs): api_key = request.headers.get('X-Internal-API-Key') if not api_key or api_key != Config.CUSTOMER_API_INTERNAL_KEY: return jsonify({'error': 'Unauthorized - Invalid internal API key'}), 401 return f(*args, **kwargs) return decorated_function @cf_accounts_bp.route('', methods=['GET']) @token_required def get_cf_accounts(current_admin): """Get all CF accounts""" try: accounts = CloudflareAccount.query.order_by(CloudflareAccount.created_at.desc()).all() return jsonify({ 'status': 'success', 'accounts': [acc.to_dict() for acc in accounts] }), 200 except Exception as e: return jsonify({'error': str(e)}), 500 @cf_accounts_bp.route('/', methods=['GET']) @token_required def get_cf_account(current_admin, account_id): """Get single CF account""" try: account = CloudflareAccount.query.get(account_id) if not account: return jsonify({'error': 'Account not found'}), 404 return jsonify({ 'status': 'success', 'account': account.to_dict(include_token=True) }), 200 except Exception as e: return jsonify({'error': str(e)}), 500 @cf_accounts_bp.route('', methods=['POST']) @token_required def create_cf_account(current_admin): """Create new CF account""" try: data = request.get_json() required = ['name', 'email', 'api_token'] for field in required: if not data.get(field): return jsonify({'error': f'{field} is required'}), 400 account = CloudflareAccount( name=data['name'], email=data['email'], api_token=data['api_token'], # TODO: Encrypt this max_domains=data.get('max_domains', 100), notes=data.get('notes'), is_active=data.get('is_active', True), use_for_verification=data.get('use_for_verification', True) ) db.session.add(account) db.session.commit() # Log action log = AuditLog( admin_id=current_admin.id, action='create_cf_account', resource_type='cf_account', resource_id=account.id, details={'account_name': account.name}, ip_address=request.remote_addr ) db.session.add(log) db.session.commit() return jsonify({ 'status': 'success', 'message': 'CF account created successfully', 'account': account.to_dict() }), 201 except Exception as e: db.session.rollback() return jsonify({'error': str(e)}), 500 @cf_accounts_bp.route('/', methods=['PUT']) @token_required def update_cf_account(current_admin, account_id): """Update CF account""" try: account = CloudflareAccount.query.get(account_id) if not account: return jsonify({'error': 'Account not found'}), 404 data = request.get_json() if 'name' in data: account.name = data['name'] if 'email' in data: account.email = data['email'] if 'api_token' in data and data['api_token']: account.api_token = data['api_token'] # TODO: Encrypt if 'max_domains' in data: account.max_domains = data['max_domains'] if 'notes' in data: account.notes = data['notes'] if 'is_active' in data: account.is_active = data['is_active'] if 'use_for_verification' in data: account.use_for_verification = data['use_for_verification'] db.session.commit() # Log action log = AuditLog( admin_id=current_admin.id, action='update_cf_account', resource_type='cf_account', resource_id=account.id, details={'account_name': account.name}, ip_address=request.remote_addr ) db.session.add(log) db.session.commit() return jsonify({ 'status': 'success', 'message': 'CF account updated successfully', 'account': account.to_dict() }), 200 except Exception as e: db.session.rollback() return jsonify({'error': str(e)}), 500 @cf_accounts_bp.route('/', methods=['DELETE']) @token_required def delete_cf_account(current_admin, account_id): """Delete CF account""" try: account = CloudflareAccount.query.get(account_id) if not account: return jsonify({'error': 'Account not found'}), 404 if account.current_domains > 0: return jsonify({'error': 'Cannot delete account with active domains'}), 400 account_name = account.name db.session.delete(account) db.session.commit() # Log action log = AuditLog( admin_id=current_admin.id, action='delete_cf_account', resource_type='cf_account', resource_id=account_id, details={'account_name': account_name}, ip_address=request.remote_addr ) db.session.add(log) db.session.commit() return jsonify({ 'status': 'success', 'message': 'CF account deleted successfully' }), 200 except Exception as e: db.session.rollback() return jsonify({'error': str(e)}), 500 # Internal API Endpoints (for Customer Panel) @cf_accounts_bp.route('/internal/available', methods=['GET']) @internal_api_required def get_available_cf_accounts_internal(): """ Internal API endpoint for Customer Panel to fetch available CF accounts Requires X-Internal-API-Key header """ try: # Get active CF accounts that are enabled for verification accounts = CloudflareAccount.query.filter_by( is_active=True, use_for_verification=True ).order_by(CloudflareAccount.created_at.desc()).all() result = [] for account in accounts: account_dict = account.to_dict(include_token=False) # Calculate available capacity account_dict['available_capacity'] = account.max_domains - account.current_domains account_dict['is_full'] = account.current_domains >= account.max_domains result.append(account_dict) return jsonify({ 'status': 'success', 'accounts': result, 'total': len(result) }), 200 except Exception as e: return jsonify({ 'status': 'error', 'error': str(e) }), 500 @cf_accounts_bp.route('/internal/', methods=['GET']) @internal_api_required def get_cf_account_internal(account_id): """ Internal API endpoint to get specific CF account with API token Requires X-Internal-API-Key header """ try: account = CloudflareAccount.query.get(account_id) if not account: return jsonify({ 'status': 'error', 'error': 'Account not found' }), 404 if not account.is_active or not account.use_for_verification: return jsonify({ 'status': 'error', 'error': 'Account not available for verification' }), 403 # Return account with API token (for internal use) account_dict = account.to_dict(include_token=True) return jsonify({ 'status': 'success', 'account': account_dict }), 200 except Exception as e: return jsonify({ 'status': 'error', 'error': str(e) }), 500 @cf_accounts_bp.route('/internal//increment', methods=['POST']) @internal_api_required def increment_domain_count_internal(account_id): """ Internal API endpoint to increment domain count Called when Customer Panel creates a domain using this CF account Requires X-Internal-API-Key header """ try: account = CloudflareAccount.query.get(account_id) if not account: return jsonify({ 'status': 'error', 'error': 'Account not found' }), 404 if not account.is_active: return jsonify({ 'status': 'error', 'error': 'Account is not active' }), 400 # Check capacity if account.current_domains >= account.max_domains: return jsonify({ 'status': 'error', 'error': f'Account is full ({account.current_domains}/{account.max_domains})' }), 400 # Increment domain count old_count = account.current_domains account.current_domains += 1 db.session.commit() return jsonify({ 'status': 'success', 'message': 'Domain count incremented', 'account_id': account.id, 'old_count': old_count, 'new_count': account.current_domains, 'available_capacity': account.max_domains - account.current_domains }), 200 except Exception as e: db.session.rollback() return jsonify({ 'status': 'error', 'error': str(e) }), 500 @cf_accounts_bp.route('/internal//decrement', methods=['POST']) @internal_api_required def decrement_domain_count_internal(account_id): """ Internal API endpoint to decrement domain count Called when Customer Panel deletes a domain using this CF account Requires X-Internal-API-Key header """ try: account = CloudflareAccount.query.get(account_id) if not account: return jsonify({ 'status': 'error', 'error': 'Account not found' }), 404 # Decrement domain count (prevent negative) old_count = account.current_domains account.current_domains = max(0, account.current_domains - 1) db.session.commit() return jsonify({ 'status': 'success', 'message': 'Domain count decremented', 'account_id': account.id, 'old_count': old_count, 'new_count': account.current_domains, 'available_capacity': account.max_domains - account.current_domains }), 200 except Exception as e: db.session.rollback() return jsonify({ 'status': 'error', 'error': str(e) }), 500