user login start
This commit is contained in:
parent
ac879e2993
commit
63ccbd4bb4
@ -1,11 +1,11 @@
|
|||||||
from flask import Flask, request, jsonify, render_template
|
from flask import Flask, request, jsonify, render_template
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from .handlers.webhook_handler import handle_payment_webhook
|
from .handlers.webhook_handler import handle_payment_webhook
|
||||||
from .handlers.payment_handler import BTCPayHandler
|
from .handlers.payment_handler import BTCPayHandler
|
||||||
from .utils.db.operations import DatabaseManager
|
from .utils.db.operations import DatabaseManager
|
||||||
|
|
||||||
# Set up logging
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
@ -13,8 +13,14 @@ logging.basicConfig(
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'dev-secret-key')
|
||||||
|
|
||||||
btcpay_handler = BTCPayHandler()
|
btcpay_handler = BTCPayHandler()
|
||||||
|
|
||||||
|
# Register blueprints
|
||||||
|
from .routes.user import user_bp
|
||||||
|
app.register_blueprint(user_bp, url_prefix='/user')
|
||||||
|
|
||||||
# Existing webhook route
|
# Existing webhook route
|
||||||
@app.route('/webhook/vpn', methods=['POST'])
|
@app.route('/webhook/vpn', methods=['POST'])
|
||||||
def handle_payment():
|
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
|
Pay with Bitcoin
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</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>
|
||||||
</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
|
from pathlib import Path
|
||||||
|
|
||||||
def get_db_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 = base_dir / 'data'
|
||||||
data_dir.mkdir(exist_ok=True)
|
data_dir.mkdir(exist_ok=True)
|
||||||
return data_dir / 'vpn.db'
|
return data_dir / 'vpn.db'
|
||||||
|
@ -13,13 +13,11 @@ class DatabaseManager:
|
|||||||
def get_next_available_ip():
|
def get_next_available_ip():
|
||||||
"""Get the next available IP from the WireGuard subnet"""
|
"""Get the next available IP from the WireGuard subnet"""
|
||||||
with get_session() as session:
|
with get_session() as session:
|
||||||
# Get all assigned IPs
|
|
||||||
assigned_ips = session.query(Subscription.assigned_ip)\
|
assigned_ips = session.query(Subscription.assigned_ip)\
|
||||||
.filter(Subscription.assigned_ip.isnot(None))\
|
.filter(Subscription.assigned_ip.isnot(None))\
|
||||||
.all()
|
.all()
|
||||||
assigned_ips = [ip[0] for ip in assigned_ips]
|
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')
|
network = ipaddress.IPv4Network('10.8.0.0/24')
|
||||||
for ip in network.hosts():
|
for ip in network.hosts():
|
||||||
str_ip = str(ip)
|
str_ip = str(ip)
|
||||||
@ -53,12 +51,37 @@ class DatabaseManager:
|
|||||||
.filter(Subscription.invoice_id == invoice_id)\
|
.filter(Subscription.invoice_id == invoice_id)\
|
||||||
.first()
|
.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
|
@staticmethod
|
||||||
def create_subscription(user_id, invoice_id, public_key, duration_hours):
|
def create_subscription(user_id, invoice_id, public_key, duration_hours):
|
||||||
"""Create a new subscription"""
|
"""Create a new subscription"""
|
||||||
with get_session() as session:
|
with get_session() as session:
|
||||||
try:
|
try:
|
||||||
# Get user or create if doesn't exist
|
|
||||||
user = session.query(User).filter(User.user_id == user_id).first()
|
user = session.query(User).filter(User.user_id == user_id).first()
|
||||||
if not user:
|
if not user:
|
||||||
user = User(user_id=user_id)
|
user = User(user_id=user_id)
|
||||||
@ -67,8 +90,6 @@ class DatabaseManager:
|
|||||||
|
|
||||||
start_time = datetime.utcnow()
|
start_time = datetime.utcnow()
|
||||||
expiry_time = start_time + timedelta(hours=duration_hours)
|
expiry_time = start_time + timedelta(hours=duration_hours)
|
||||||
|
|
||||||
# Get next available IP
|
|
||||||
assigned_ip = DatabaseManager.get_next_available_ip()
|
assigned_ip = DatabaseManager.get_next_available_ip()
|
||||||
|
|
||||||
subscription = Subscription(
|
subscription = Subscription(
|
||||||
@ -84,7 +105,6 @@ class DatabaseManager:
|
|||||||
session.add(subscription)
|
session.add(subscription)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
# Return a dictionary of values instead of the SQLAlchemy object
|
|
||||||
return {
|
return {
|
||||||
'id': subscription.id,
|
'id': subscription.id,
|
||||||
'user_id': user.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