user login start
This commit is contained in:
parent
ac879e2993
commit
63ccbd4bb4
@ -1,11 +1,11 @@
|
||||
from flask import Flask, request, jsonify, render_template
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from .handlers.webhook_handler import handle_payment_webhook
|
||||
from .handlers.payment_handler import BTCPayHandler
|
||||
from .utils.db.operations import DatabaseManager
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
@ -13,8 +13,14 @@ logging.basicConfig(
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'dev-secret-key')
|
||||
|
||||
btcpay_handler = BTCPayHandler()
|
||||
|
||||
# Register blueprints
|
||||
from .routes.user import user_bp
|
||||
app.register_blueprint(user_bp, url_prefix='/user')
|
||||
|
||||
# Existing webhook route
|
||||
@app.route('/webhook/vpn', methods=['POST'])
|
||||
def handle_payment():
|
||||
|
0
app/routes/__init__.py
Normal file
0
app/routes/__init__.py
Normal file
86
app/routes/user.py
Normal file
86
app/routes/user.py
Normal file
@ -0,0 +1,86 @@
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash, session, jsonify
|
||||
from ..utils.db.operations import DatabaseManager
|
||||
from ..utils.db.models import SubscriptionStatus
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
user_bp = Blueprint('user', __name__)
|
||||
|
||||
@user_bp.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
user_id = request.form.get('user_id')
|
||||
if not user_id:
|
||||
return jsonify({'error': 'User ID is required'}), 400
|
||||
|
||||
user = DatabaseManager.get_user_by_uuid(user_id)
|
||||
if user:
|
||||
session['user_id'] = user_id
|
||||
return jsonify({'redirect': url_for('user.dashboard')})
|
||||
return jsonify({'error': 'Invalid User ID'}), 401
|
||||
|
||||
return render_template('user/login.html')
|
||||
|
||||
@user_bp.route('/dashboard')
|
||||
def dashboard():
|
||||
if 'user_id' not in session:
|
||||
return redirect(url_for('user.login'))
|
||||
|
||||
user = DatabaseManager.get_user_by_uuid(session['user_id'])
|
||||
if not user:
|
||||
session.pop('user_id', None)
|
||||
return redirect(url_for('user.login'))
|
||||
|
||||
subscriptions = DatabaseManager.get_user_subscriptions(user.id)
|
||||
active_subscription = next(
|
||||
(sub for sub in subscriptions
|
||||
if sub.status == SubscriptionStatus.ACTIVE),
|
||||
None
|
||||
)
|
||||
|
||||
subscription_data = []
|
||||
for sub in subscriptions:
|
||||
remaining_time = None
|
||||
if sub.expiry_time > datetime.utcnow():
|
||||
remaining_time = sub.expiry_time - datetime.utcnow()
|
||||
|
||||
subscription_data.append({
|
||||
'id': sub.id,
|
||||
'status': sub.status.value,
|
||||
'start_time': sub.start_time,
|
||||
'expiry_time': sub.expiry_time,
|
||||
'remaining_time': str(remaining_time) if remaining_time else None,
|
||||
'assigned_ip': sub.assigned_ip
|
||||
})
|
||||
|
||||
return render_template(
|
||||
'user/dashboard.html',
|
||||
user=user,
|
||||
subscriptions=subscription_data,
|
||||
active_subscription=active_subscription
|
||||
)
|
||||
|
||||
@user_bp.route('/logout')
|
||||
def logout():
|
||||
session.pop('user_id', None)
|
||||
return redirect(url_for('user.login'))
|
||||
|
||||
@user_bp.route('/api/subscription/config/<subscription_id>')
|
||||
def get_subscription_config(subscription_id):
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'error': 'Unauthorized'}), 401
|
||||
|
||||
user = DatabaseManager.get_user_by_uuid(session['user_id'])
|
||||
if not user:
|
||||
return jsonify({'error': 'User not found'}), 404
|
||||
|
||||
subscription = DatabaseManager.get_subscription_by_id(subscription_id)
|
||||
if not subscription or subscription.user_id != user.id:
|
||||
return jsonify({'error': 'Subscription not found'}), 404
|
||||
|
||||
return jsonify({
|
||||
'config': subscription.config_data,
|
||||
'assigned_ip': subscription.assigned_ip,
|
||||
'expiry_time': subscription.expiry_time.isoformat()
|
||||
})
|
@ -118,6 +118,13 @@
|
||||
Pay with Bitcoin
|
||||
</button>
|
||||
</form>
|
||||
<div class="mt-4 text-center">
|
||||
<p class="text-gray-400">Already have a subscription?</p>
|
||||
<a href="{{ url_for('user.login') }}"
|
||||
class="text-blue-400 hover:text-blue-300">
|
||||
Login to Dashboard
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
114
app/templates/user/dashboard.html
Normal file
114
app/templates/user/dashboard.html
Normal file
@ -0,0 +1,114 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div class="min-h-screen bg-dark py-8 px-4">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="flex justify-between items-center mb-8">
|
||||
<h1 class="text-2xl font-bold">Dashboard</h1>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="text-gray-400">User ID: {{ user.user_id }}</span>
|
||||
<a href="{{ url_for('user.logout') }}"
|
||||
class="px-4 py-2 bg-red-600 hover:bg-red-700 rounded text-white">
|
||||
Logout
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if active_subscription %}
|
||||
<div class="bg-dark-lighter rounded-lg p-6 mb-8">
|
||||
<h2 class="text-xl font-semibold mb-4">Active Subscription</h2>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p class="text-gray-400">Status</p>
|
||||
<p class="font-medium">{{ active_subscription.status }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-gray-400">IP Address</p>
|
||||
<p class="font-mono">{{ active_subscription.assigned_ip }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-gray-400">Expires</p>
|
||||
<p class="font-medium">{{ active_subscription.expiry_time.strftime('%Y-%m-%d %H:%M UTC') }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-gray-400">Time Remaining</p>
|
||||
<p class="font-medium">{{ active_subscription.remaining_time }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex space-x-4">
|
||||
<button onclick="downloadConfig('{{ active_subscription.id }}')"
|
||||
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded text-white">
|
||||
Download Config
|
||||
</button>
|
||||
<button onclick="showQRCode('{{ active_subscription.id }}')"
|
||||
class="px-4 py-2 bg-green-600 hover:bg-green-700 rounded text-white">
|
||||
Show QR Code
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="bg-dark-lighter rounded-lg p-6 mb-8 text-center">
|
||||
<p class="text-gray-400 mb-4">No active subscription</p>
|
||||
<a href="{{ url_for('index') }}"
|
||||
class="inline-block px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded text-white">
|
||||
Subscribe Now
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="bg-dark-lighter rounded-lg p-6">
|
||||
<h2 class="text-xl font-semibold mb-4">Subscription History</h2>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="text-left text-gray-400">
|
||||
<th class="p-2">Status</th>
|
||||
<th class="p-2">Start Date</th>
|
||||
<th class="p-2">End Date</th>
|
||||
<th class="p-2">IP Address</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for sub in subscriptions %}
|
||||
<tr class="border-t border-gray-700">
|
||||
<td class="p-2">{{ sub.status }}</td>
|
||||
<td class="p-2">{{ sub.start_time.strftime('%Y-%m-%d %H:%M UTC') }}</td>
|
||||
<td class="p-2">{{ sub.expiry_time.strftime('%Y-%m-%d %H:%M UTC') }}</td>
|
||||
<td class="p-2 font-mono">{{ sub.assigned_ip }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function downloadConfig(subscriptionId) {
|
||||
try {
|
||||
const response = await fetch(`/api/subscription/config/${subscriptionId}`);
|
||||
if (!response.ok) throw new Error('Failed to fetch config');
|
||||
|
||||
const data = await response.json();
|
||||
const blob = new Blob([data.config], { type: 'text/plain' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'wireguard.conf';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error('Error downloading config:', error);
|
||||
alert('Failed to download configuration');
|
||||
}
|
||||
}
|
||||
|
||||
function showQRCode(subscriptionId) {
|
||||
// TODO: Implement QR code display
|
||||
alert('QR Code feature coming soon');
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
56
app/templates/user/login.html
Normal file
56
app/templates/user/login.html
Normal file
@ -0,0 +1,56 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div class="min-h-screen bg-dark flex items-center justify-center py-12 px-4">
|
||||
<div class="max-w-md w-full bg-dark-lighter rounded-lg shadow-lg p-8">
|
||||
<h2 class="text-2xl font-bold text-center mb-8">Login to Dashboard</h2>
|
||||
|
||||
<form id="login-form" class="space-y-6">
|
||||
<div>
|
||||
<label for="user-id" class="block text-sm font-medium mb-2">User ID</label>
|
||||
<input type="text" id="user-id" name="user_id" required
|
||||
class="w-full px-3 py-2 bg-dark border border-gray-600 rounded-md text-white focus:outline-none focus:border-blue-500">
|
||||
<p class="mt-1 text-sm text-gray-400">Enter the User ID you received during subscription</p>
|
||||
</div>
|
||||
|
||||
<button type="submit"
|
||||
class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition-colors">
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="mt-8 text-center">
|
||||
<p class="text-gray-400">Don't have a subscription?</p>
|
||||
<a href="{{ url_for('index') }}" class="text-blue-400 hover:text-blue-300">
|
||||
Subscribe Now
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('login-form').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
const response = await fetch('{{ url_for("user.login") }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: new URLSearchParams(new FormData(this))
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.redirect) {
|
||||
window.location.href = data.redirect;
|
||||
} else {
|
||||
alert(data.error || 'Login failed');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
alert('Login failed. Please try again.');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@ -3,7 +3,7 @@ from sqlalchemy.orm import sessionmaker
|
||||
from pathlib import Path
|
||||
|
||||
def get_db_path():
|
||||
base_dir = Path(__file__).resolve().parent.parent.parent
|
||||
base_dir = Path(__file__).resolve().parent.parent.parent.parent
|
||||
data_dir = base_dir / 'data'
|
||||
data_dir.mkdir(exist_ok=True)
|
||||
return data_dir / 'vpn.db'
|
||||
|
@ -13,13 +13,11 @@ class DatabaseManager:
|
||||
def get_next_available_ip():
|
||||
"""Get the next available IP from the WireGuard subnet"""
|
||||
with get_session() as session:
|
||||
# Get all assigned IPs
|
||||
assigned_ips = session.query(Subscription.assigned_ip)\
|
||||
.filter(Subscription.assigned_ip.isnot(None))\
|
||||
.all()
|
||||
assigned_ips = [ip[0] for ip in assigned_ips]
|
||||
|
||||
# Start from 10.8.0.2 (10.8.0.1 is server)
|
||||
network = ipaddress.IPv4Network('10.8.0.0/24')
|
||||
for ip in network.hosts():
|
||||
str_ip = str(ip)
|
||||
@ -53,12 +51,37 @@ class DatabaseManager:
|
||||
.filter(Subscription.invoice_id == invoice_id)\
|
||||
.first()
|
||||
|
||||
@staticmethod
|
||||
def get_subscription_by_id(subscription_id):
|
||||
"""Get subscription by ID"""
|
||||
with get_session() as session:
|
||||
return session.query(Subscription)\
|
||||
.filter(Subscription.id == subscription_id)\
|
||||
.first()
|
||||
|
||||
@staticmethod
|
||||
def get_user_subscriptions(user_id):
|
||||
"""Get all subscriptions for a user"""
|
||||
with get_session() as session:
|
||||
return session.query(Subscription)\
|
||||
.filter(Subscription.user_id == user_id)\
|
||||
.order_by(Subscription.start_time.desc())\
|
||||
.all()
|
||||
|
||||
@staticmethod
|
||||
def get_active_subscription_for_user(user_id):
|
||||
"""Get active subscription for a user"""
|
||||
with get_session() as session:
|
||||
return session.query(Subscription)\
|
||||
.filter(Subscription.user_id == user_id)\
|
||||
.filter(Subscription.status == SubscriptionStatus.ACTIVE)\
|
||||
.first()
|
||||
|
||||
@staticmethod
|
||||
def create_subscription(user_id, invoice_id, public_key, duration_hours):
|
||||
"""Create a new subscription"""
|
||||
with get_session() as session:
|
||||
try:
|
||||
# Get user or create if doesn't exist
|
||||
user = session.query(User).filter(User.user_id == user_id).first()
|
||||
if not user:
|
||||
user = User(user_id=user_id)
|
||||
@ -67,8 +90,6 @@ class DatabaseManager:
|
||||
|
||||
start_time = datetime.utcnow()
|
||||
expiry_time = start_time + timedelta(hours=duration_hours)
|
||||
|
||||
# Get next available IP
|
||||
assigned_ip = DatabaseManager.get_next_available_ip()
|
||||
|
||||
subscription = Subscription(
|
||||
@ -84,7 +105,6 @@ class DatabaseManager:
|
||||
session.add(subscription)
|
||||
session.commit()
|
||||
|
||||
# Return a dictionary of values instead of the SQLAlchemy object
|
||||
return {
|
||||
'id': subscription.id,
|
||||
'user_id': user.id,
|
||||
|
BIN
data/vpn.db
BIN
data/vpn.db
Binary file not shown.
Loading…
Reference in New Issue
Block a user