From d68c4d38ffcc05b63d588a441fc78e4cbebae0e8 Mon Sep 17 00:00:00 2001 From: oguz ozturk Date: Mon, 12 Jan 2026 16:58:06 +0300 Subject: [PATCH] feat: Add internal API endpoints for Customer Panel integration - Add internal_api_required decorator for internal API authentication - Add /internal/available endpoint to fetch available CF accounts - Add /internal/ endpoint to get CF account with API token - Filter accounts by is_active and use_for_verification flags - Require X-Internal-API-Key header for internal endpoints --- backend/app/routes/cf_accounts.py | 84 +++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/backend/app/routes/cf_accounts.py b/backend/app/routes/cf_accounts.py index 2e92fe9..c274811 100644 --- a/backend/app/routes/cf_accounts.py +++ b/backend/app/routes/cf_accounts.py @@ -4,9 +4,21 @@ 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): @@ -170,3 +182,75 @@ def delete_cf_account(current_admin, account_id): 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 +