438 lines
15 KiB
Python
Executable File
438 lines
15 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Cloudflare Credentials Setup Web Interface
|
|
Provides a web UI to configure Cloudflare API credentials
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import subprocess
|
|
import re
|
|
from pathlib import Path
|
|
from flask import Flask, render_template_string, request, jsonify, redirect, url_for
|
|
|
|
app = Flask(__name__)
|
|
SCRIPT_DIR = Path(__file__).parent.parent
|
|
ENV_FILE = SCRIPT_DIR / ".env"
|
|
|
|
# HTML Template
|
|
HTML_TEMPLATE = """
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Cloudflare Credentials Setup</title>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 20px;
|
|
}
|
|
.container {
|
|
background: white;
|
|
border-radius: 12px;
|
|
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
max-width: 600px;
|
|
width: 100%;
|
|
padding: 40px;
|
|
}
|
|
h1 {
|
|
color: #333;
|
|
margin-bottom: 10px;
|
|
font-size: 28px;
|
|
}
|
|
.subtitle {
|
|
color: #666;
|
|
margin-bottom: 30px;
|
|
font-size: 14px;
|
|
}
|
|
.form-group {
|
|
margin-bottom: 20px;
|
|
}
|
|
label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
color: #333;
|
|
font-weight: 500;
|
|
font-size: 14px;
|
|
}
|
|
input[type="text"], input[type="email"], input[type="password"] {
|
|
width: 100%;
|
|
padding: 12px;
|
|
border: 2px solid #e0e0e0;
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
transition: border-color 0.3s;
|
|
}
|
|
input:focus {
|
|
outline: none;
|
|
border-color: #667eea;
|
|
}
|
|
.help-text {
|
|
font-size: 12px;
|
|
color: #888;
|
|
margin-top: 4px;
|
|
}
|
|
.btn {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
border: none;
|
|
padding: 14px 28px;
|
|
border-radius: 6px;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
width: 100%;
|
|
transition: transform 0.2s, box-shadow 0.2s;
|
|
}
|
|
.btn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
|
|
}
|
|
.btn:active {
|
|
transform: translateY(0);
|
|
}
|
|
.btn-secondary {
|
|
background: #6c757d;
|
|
margin-top: 10px;
|
|
}
|
|
.alert {
|
|
padding: 12px;
|
|
border-radius: 6px;
|
|
margin-bottom: 20px;
|
|
font-size: 14px;
|
|
}
|
|
.alert-success {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
border: 1px solid #c3e6cb;
|
|
}
|
|
.alert-error {
|
|
background: #f8d7da;
|
|
color: #721c24;
|
|
border: 1px solid #f5c6cb;
|
|
}
|
|
.alert-info {
|
|
background: #d1ecf1;
|
|
color: #0c5460;
|
|
border: 1px solid #bee5eb;
|
|
}
|
|
.status-section {
|
|
background: #f8f9fa;
|
|
border-radius: 6px;
|
|
padding: 20px;
|
|
margin-bottom: 20px;
|
|
}
|
|
.status-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 8px 0;
|
|
border-bottom: 1px solid #e0e0e0;
|
|
}
|
|
.status-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
.status-label {
|
|
color: #666;
|
|
font-size: 14px;
|
|
}
|
|
.status-value {
|
|
color: #333;
|
|
font-weight: 500;
|
|
font-size: 14px;
|
|
}
|
|
.status-value.valid {
|
|
color: #28a745;
|
|
}
|
|
.status-value.invalid {
|
|
color: #dc3545;
|
|
}
|
|
.links {
|
|
margin-top: 20px;
|
|
text-align: center;
|
|
}
|
|
.links a {
|
|
color: #667eea;
|
|
text-decoration: none;
|
|
font-size: 13px;
|
|
margin: 0 10px;
|
|
}
|
|
.links a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>🔐 Cloudflare Setup</h1>
|
|
<p class="subtitle">Configure your Cloudflare API credentials</p>
|
|
|
|
{% if message %}
|
|
<div class="alert alert-{{ message_type }}">
|
|
{{ message }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="status-section">
|
|
<h3 style="margin-bottom: 15px; font-size: 16px; color: #333;">Current Status</h3>
|
|
<div class="status-item">
|
|
<span class="status-label">Email:</span>
|
|
<span class="status-value {% if current_email %}valid{% else %}invalid{% endif %}">
|
|
{{ current_email or 'Not set' }}
|
|
</span>
|
|
</div>
|
|
<div class="status-item">
|
|
<span class="status-label">API Key:</span>
|
|
<span class="status-value {% if has_api_key %}valid{% else %}invalid{% endif %}">
|
|
{{ 'Set' if has_api_key else 'Not set' }}
|
|
</span>
|
|
</div>
|
|
<div class="status-item">
|
|
<span class="status-label">API Token:</span>
|
|
<span class="status-value {% if has_api_token %}valid{% else %}invalid{% endif %}">
|
|
{{ 'Set' if has_api_token else 'Not set' }}
|
|
</span>
|
|
</div>
|
|
<div class="status-item">
|
|
<span class="status-label">Account ID:</span>
|
|
<span class="status-value {% if current_account_id %}valid{% else %}invalid{% endif %}">
|
|
{{ current_account_id or 'Not set' }}
|
|
</span>
|
|
</div>
|
|
<div class="status-item">
|
|
<span class="status-label">Zone ID:</span>
|
|
<span class="status-value {% if current_zone_id %}valid{% else %}invalid{% endif %}">
|
|
{{ current_zone_id or 'Not set' }}
|
|
</span>
|
|
</div>
|
|
<div class="status-item">
|
|
<span class="status-label">Domain:</span>
|
|
<span class="status-value {% if current_domain %}valid{% else %}invalid{% endif %}">
|
|
{{ current_domain or 'Not set' }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<form method="POST" action="/save">
|
|
<div class="form-group">
|
|
<label for="email">Cloudflare Email *</label>
|
|
<input type="email" id="email" name="email" value="{{ current_email or '' }}" required>
|
|
<div class="help-text">Your Cloudflare account email address</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="api_key">Global API Key</label>
|
|
<input type="password" id="api_key" name="api_key" placeholder="Leave empty to keep current">
|
|
<div class="help-text">
|
|
<a href="https://dash.cloudflare.com/profile/api-tokens" target="_blank">Get from Cloudflare Dashboard</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="api_token">API Token (Alternative)</label>
|
|
<input type="password" id="api_token" name="api_token" placeholder="Leave empty to keep current">
|
|
<div class="help-text">
|
|
<a href="https://dash.cloudflare.com/profile/api-tokens" target="_blank">Create API Token</a> (Recommended - more secure)
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="account_id">Account ID</label>
|
|
<input type="text" id="account_id" name="account_id" value="{{ current_account_id or '' }}">
|
|
<div class="help-text">Your Cloudflare account ID</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="zone_id">Zone ID</label>
|
|
<input type="text" id="zone_id" name="zone_id" value="{{ current_zone_id or '' }}">
|
|
<div class="help-text">DNS zone ID for your domain</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="domain">Domain</label>
|
|
<input type="text" id="domain" name="domain" value="{{ current_domain or '' }}" placeholder="example.com">
|
|
<div class="help-text">Your domain name (e.g., d-bis.org)</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="tunnel_token">Tunnel Token</label>
|
|
<input type="password" id="tunnel_token" name="tunnel_token" placeholder="Leave empty to keep current">
|
|
<div class="help-text">Cloudflare Tunnel service token</div>
|
|
</div>
|
|
|
|
<button type="submit" class="btn">💾 Save Credentials</button>
|
|
</form>
|
|
|
|
<form method="POST" action="/test" style="margin-top: 10px;">
|
|
<button type="submit" class="btn btn-secondary">🧪 Test API Connection</button>
|
|
</form>
|
|
|
|
<div class="links">
|
|
<a href="https://dash.cloudflare.com/profile/api-tokens" target="_blank">Cloudflare API Tokens</a>
|
|
<a href="https://dash.cloudflare.com/" target="_blank">Cloudflare Dashboard</a>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
def load_env():
|
|
"""Load current .env file values"""
|
|
env_vars = {}
|
|
if ENV_FILE.exists():
|
|
with open(ENV_FILE, 'r') as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if line and not line.startswith('#') and '=' in line:
|
|
key, value = line.split('=', 1)
|
|
key = key.strip()
|
|
value = value.strip().strip('"').strip("'")
|
|
env_vars[key] = value
|
|
return env_vars
|
|
|
|
def save_env(env_vars):
|
|
"""Save environment variables to .env file"""
|
|
# Read existing file to preserve comments and structure
|
|
lines = []
|
|
if ENV_FILE.exists():
|
|
with open(ENV_FILE, 'r') as f:
|
|
lines = f.readlines()
|
|
|
|
# Update or add variables
|
|
updated_keys = set()
|
|
new_lines = []
|
|
|
|
for line in lines:
|
|
stripped = line.strip()
|
|
if stripped and not stripped.startswith('#') and '=' in stripped:
|
|
key = stripped.split('=', 1)[0].strip()
|
|
if key in env_vars:
|
|
new_lines.append(f'{key}="{env_vars[key]}"\n')
|
|
updated_keys.add(key)
|
|
continue
|
|
new_lines.append(line)
|
|
|
|
# Add new variables
|
|
for key, value in env_vars.items():
|
|
if key not in updated_keys:
|
|
new_lines.append(f'{key}="{value}"\n')
|
|
|
|
with open(ENV_FILE, 'w') as f:
|
|
f.writelines(new_lines)
|
|
|
|
def test_api_credentials(email, api_key, api_token):
|
|
"""Test Cloudflare API credentials"""
|
|
import requests
|
|
|
|
headers = {"Content-Type": "application/json"}
|
|
|
|
if api_token:
|
|
headers["Authorization"] = f"Bearer {api_token}"
|
|
url = "https://api.cloudflare.com/client/v4/user"
|
|
elif api_key and email:
|
|
headers["X-Auth-Email"] = email
|
|
headers["X-Auth-Key"] = api_key
|
|
url = "https://api.cloudflare.com/client/v4/user"
|
|
else:
|
|
return False, "No credentials provided"
|
|
|
|
try:
|
|
response = requests.get(url, headers=headers, timeout=10)
|
|
data = response.json()
|
|
|
|
if data.get('success'):
|
|
user_email = data.get('result', {}).get('email', '')
|
|
return True, f"✓ Authentication successful! Logged in as: {user_email}"
|
|
else:
|
|
error = data.get('errors', [{}])[0].get('message', 'Unknown error')
|
|
return False, f"✗ Authentication failed: {error}"
|
|
except Exception as e:
|
|
return False, f"✗ Connection error: {str(e)}"
|
|
|
|
@app.route('/')
|
|
def index():
|
|
"""Display the setup form"""
|
|
env = load_env()
|
|
|
|
return render_template_string(HTML_TEMPLATE,
|
|
current_email=env.get('CLOUDFLARE_EMAIL', ''),
|
|
has_api_key=bool(env.get('CLOUDFLARE_API_KEY', '')),
|
|
has_api_token=bool(env.get('CLOUDFLARE_API_TOKEN', '')),
|
|
current_account_id=env.get('CLOUDFLARE_ACCOUNT_ID', ''),
|
|
current_zone_id=env.get('CLOUDFLARE_ZONE_ID', ''),
|
|
current_domain=env.get('CLOUDFLARE_DOMAIN', ''),
|
|
message=request.args.get('message', ''),
|
|
message_type=request.args.get('type', 'info')
|
|
)
|
|
|
|
@app.route('/save', methods=['POST'])
|
|
def save():
|
|
"""Save credentials to .env file"""
|
|
env = load_env()
|
|
|
|
# Update only provided fields
|
|
if request.form.get('email'):
|
|
env['CLOUDFLARE_EMAIL'] = request.form.get('email')
|
|
|
|
if request.form.get('api_key'):
|
|
env['CLOUDFLARE_API_KEY'] = request.form.get('api_key')
|
|
|
|
if request.form.get('api_token'):
|
|
env['CLOUDFLARE_API_TOKEN'] = request.form.get('api_token')
|
|
# Remove API key if token is provided
|
|
if 'CLOUDFLARE_API_KEY' in env:
|
|
del env['CLOUDFLARE_API_KEY']
|
|
|
|
if request.form.get('account_id'):
|
|
env['CLOUDFLARE_ACCOUNT_ID'] = request.form.get('account_id')
|
|
|
|
if request.form.get('zone_id'):
|
|
env['CLOUDFLARE_ZONE_ID'] = request.form.get('zone_id')
|
|
|
|
if request.form.get('domain'):
|
|
env['CLOUDFLARE_DOMAIN'] = request.form.get('domain')
|
|
|
|
if request.form.get('tunnel_token'):
|
|
env['CLOUDFLARE_TUNNEL_TOKEN'] = request.form.get('tunnel_token')
|
|
|
|
save_env(env)
|
|
|
|
return redirect(url_for('index', message='Credentials saved successfully!', type='success'))
|
|
|
|
@app.route('/test', methods=['POST'])
|
|
def test():
|
|
"""Test API credentials"""
|
|
env = load_env()
|
|
|
|
email = request.form.get('email') or env.get('CLOUDFLARE_EMAIL', '')
|
|
api_key = request.form.get('api_key') or env.get('CLOUDFLARE_API_KEY', '')
|
|
api_token = request.form.get('api_token') or env.get('CLOUDFLARE_API_TOKEN', '')
|
|
|
|
if not email and not api_token:
|
|
return redirect(url_for('index', message='Please provide email and API key, or API token', type='error'))
|
|
|
|
success, message = test_api_credentials(email, api_key, api_token)
|
|
|
|
return redirect(url_for('index', message=message, type='success' if success else 'error'))
|
|
|
|
if __name__ == '__main__':
|
|
print("\n" + "="*60)
|
|
print("🌐 Cloudflare Credentials Setup Web Interface")
|
|
print("="*60)
|
|
print(f"\n📁 .env file location: {ENV_FILE}")
|
|
print(f"\n🔗 Open in your browser:")
|
|
print(f" http://localhost:5000")
|
|
print(f" http://127.0.0.1:5000")
|
|
print(f"\n⚠️ This server is only accessible from localhost")
|
|
print(f" Press Ctrl+C to stop the server\n")
|
|
print("="*60 + "\n")
|
|
|
|
app.run(host='127.0.0.1', port=5000, debug=False)
|
|
|