user login start

This commit is contained in:
Enki 2025-01-10 08:20:21 +00:00
parent ac879e2993
commit 63ccbd4bb4
9 changed files with 297 additions and 8 deletions

View File

@ -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
View File

86
app/routes/user.py Normal file
View 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()
})

View File

@ -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>

View 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 %}

View 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 %}

View File

@ -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'

View File

@ -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,

Binary file not shown.