Major update 2 / login
This commit is contained in:
@@ -8,6 +8,38 @@ from flask import make_response
|
||||
api = Blueprint('api', __name__)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Helper — look up clan by the X-Clan-Key header.
|
||||
# Returns the clan row dict, or None if key is missing / invalid.
|
||||
# ------------------------------------------------------------------
|
||||
def _get_clan_from_request():
|
||||
key = request.headers.get('X-Clan-Key', '').strip()
|
||||
if not key:
|
||||
return None
|
||||
conn = get_db()
|
||||
clan = conn.execute('SELECT * FROM clans WHERE clan_key = ?', (key,)).fetchone()
|
||||
conn.close()
|
||||
return clan
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Helper — auto-register a player_id under a clan on first push.
|
||||
# ------------------------------------------------------------------
|
||||
def _auto_register_member(clan_id, player_id, player_name):
|
||||
conn = get_db()
|
||||
conn.execute('''
|
||||
INSERT OR IGNORE INTO clan_members (clan_id, player_id, player_name)
|
||||
VALUES (?, ?, ?)
|
||||
''', (clan_id, str(player_id), player_name or ''))
|
||||
# Update name in case it changed
|
||||
conn.execute('''
|
||||
UPDATE clan_members SET player_name = ?
|
||||
WHERE clan_id = ? AND player_id = ?
|
||||
''', (player_name or '', clan_id, str(player_id)))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# POST /api/state
|
||||
# Tampermonkey pushes a full town snapshot every poll cycle.
|
||||
@@ -18,11 +50,16 @@ def receive_state():
|
||||
if not data:
|
||||
return jsonify({'error': 'no data'}), 400
|
||||
|
||||
towns = data.get('towns', [])
|
||||
player = data.get('player', '')
|
||||
player_id = data.get('player_id', '')
|
||||
alliance_id = str(data.get('alliance_id', '') or '')
|
||||
world_id = data.get('world_id', '')
|
||||
towns = data.get('towns', [])
|
||||
player = data.get('player', '')
|
||||
player_id = data.get('player_id', '')
|
||||
alliance_id = str(data.get('alliance_id', '') or '')
|
||||
world_id = data.get('world_id', '')
|
||||
|
||||
# Auto-register this player to the clan that matches the key (if any)
|
||||
clan = _get_clan_from_request()
|
||||
if clan:
|
||||
_auto_register_member(clan['id'], player_id, player)
|
||||
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
@@ -260,9 +297,15 @@ def farm_status():
|
||||
# ------------------------------------------------------------------
|
||||
@api.route('/api/bot', methods=['GET'])
|
||||
def serve_bot():
|
||||
# Require a valid clan key — reject unknown clients
|
||||
clan = _get_clan_from_request()
|
||||
if not clan:
|
||||
return make_response('Unauthorized: invalid or missing clan key', 403)
|
||||
|
||||
bot_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'bot_modules')
|
||||
if not os.path.exists(bot_dir):
|
||||
return make_response("Bot modules directory not found", 404)
|
||||
|
||||
|
||||
modules = sorted([f for f in os.listdir(bot_dir) if f.endswith('.js')])
|
||||
|
||||
|
||||
194
routes/auth.py
Normal file
194
routes/auth.py
Normal file
@@ -0,0 +1,194 @@
|
||||
from flask import Blueprint, render_template, request, jsonify, redirect, url_for, flash
|
||||
from flask_login import login_user, logout_user, login_required, current_user
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from db import get_db, generate_clan_key
|
||||
from datetime import datetime
|
||||
|
||||
auth = Blueprint('auth', __name__)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Helper — resolve the User class from app (avoid circular import)
|
||||
# ------------------------------------------------------------------
|
||||
def _make_user(row):
|
||||
from app import User
|
||||
return User(row['id'], row['username'])
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET/POST /auth/login
|
||||
# ------------------------------------------------------------------
|
||||
@auth.route('/auth/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('dashboard.index'))
|
||||
|
||||
error = None
|
||||
if request.method == 'POST':
|
||||
username = request.form.get('username', '').strip()
|
||||
password = request.form.get('password', '')
|
||||
|
||||
conn = get_db()
|
||||
row = conn.execute(
|
||||
'SELECT id, username, password_hash FROM users WHERE username = ?', (username,)
|
||||
).fetchone()
|
||||
conn.close()
|
||||
|
||||
if row and check_password_hash(row['password_hash'], password):
|
||||
user = _make_user(row)
|
||||
login_user(user, remember=True)
|
||||
return redirect(url_for('dashboard.index'))
|
||||
else:
|
||||
error = 'Λάθος όνομα χρήστη ή κωδικός.'
|
||||
|
||||
return render_template('login.html', error=error)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET/POST /auth/register
|
||||
# ------------------------------------------------------------------
|
||||
@auth.route('/auth/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('dashboard.index'))
|
||||
|
||||
error = None
|
||||
if request.method == 'POST':
|
||||
username = request.form.get('username', '').strip()
|
||||
password = request.form.get('password', '')
|
||||
confirm = request.form.get('confirm', '')
|
||||
|
||||
if not username or not password:
|
||||
error = 'Συμπλήρωσε όνομα χρήστη και κωδικό.'
|
||||
elif password != confirm:
|
||||
error = 'Οι κωδικοί δεν ταιριάζουν.'
|
||||
elif len(password) < 6:
|
||||
error = 'Ο κωδικός πρέπει να έχει τουλάχιστον 6 χαρακτήρες.'
|
||||
else:
|
||||
conn = get_db()
|
||||
existing = conn.execute(
|
||||
'SELECT id FROM users WHERE username = ?', (username,)
|
||||
).fetchone()
|
||||
if existing:
|
||||
error = 'Το όνομα χρήστη χρησιμοποιείται ήδη.'
|
||||
conn.close()
|
||||
else:
|
||||
pw_hash = generate_password_hash(password)
|
||||
conn.execute(
|
||||
'INSERT INTO users (username, password_hash) VALUES (?, ?)',
|
||||
(username, pw_hash)
|
||||
)
|
||||
conn.commit()
|
||||
row = conn.execute(
|
||||
'SELECT id, username, password_hash FROM users WHERE username = ?', (username,)
|
||||
).fetchone()
|
||||
conn.close()
|
||||
user = _make_user(row)
|
||||
login_user(user, remember=True)
|
||||
return redirect(url_for('auth.options'))
|
||||
|
||||
return render_template('register.html', error=error)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /auth/logout
|
||||
# ------------------------------------------------------------------
|
||||
@auth.route('/auth/logout')
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET/POST /auth/options — Clan management page
|
||||
# ------------------------------------------------------------------
|
||||
@auth.route('/auth/options', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def options():
|
||||
conn = get_db()
|
||||
|
||||
# Load this user's clan (one clan per user for now)
|
||||
clan = conn.execute(
|
||||
'SELECT * FROM clans WHERE owner_id = ?', (current_user.id,)
|
||||
).fetchone()
|
||||
|
||||
members = []
|
||||
if clan:
|
||||
members = conn.execute(
|
||||
'''SELECT cm.id, cm.player_id, cm.player_name, cm.joined_at,
|
||||
ts.updated_at
|
||||
FROM clan_members cm
|
||||
LEFT JOIN town_state ts ON ts.player_id = cm.player_id
|
||||
WHERE cm.clan_id = ?
|
||||
GROUP BY cm.player_id
|
||||
ORDER BY cm.joined_at DESC''',
|
||||
(clan['id'],)
|
||||
).fetchall()
|
||||
|
||||
conn.close()
|
||||
return render_template('options.html', clan=clan, members=members)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# POST /auth/clan/create
|
||||
# ------------------------------------------------------------------
|
||||
@auth.route('/auth/clan/create', methods=['POST'])
|
||||
@login_required
|
||||
def create_clan():
|
||||
clan_name = request.form.get('clan_name', '').strip()
|
||||
if not clan_name:
|
||||
return redirect(url_for('auth.options'))
|
||||
|
||||
conn = get_db()
|
||||
existing = conn.execute(
|
||||
'SELECT id FROM clans WHERE owner_id = ?', (current_user.id,)
|
||||
).fetchone()
|
||||
|
||||
if not existing:
|
||||
key = generate_clan_key()
|
||||
conn.execute(
|
||||
'INSERT INTO clans (owner_id, name, clan_key) VALUES (?, ?, ?)',
|
||||
(current_user.id, clan_name, key)
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
conn.close()
|
||||
return redirect(url_for('auth.options'))
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# POST /auth/clan/regenerate-key
|
||||
# ------------------------------------------------------------------
|
||||
@auth.route('/auth/clan/regenerate-key', methods=['POST'])
|
||||
@login_required
|
||||
def regenerate_key():
|
||||
new_key = generate_clan_key()
|
||||
conn = get_db()
|
||||
conn.execute(
|
||||
'UPDATE clans SET clan_key = ? WHERE owner_id = ?',
|
||||
(new_key, current_user.id)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return redirect(url_for('auth.options'))
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# POST /auth/clan/remove-member/<player_id>
|
||||
# ------------------------------------------------------------------
|
||||
@auth.route('/auth/clan/remove-member/<player_id>', methods=['POST'])
|
||||
@login_required
|
||||
def remove_member(player_id):
|
||||
conn = get_db()
|
||||
clan = conn.execute(
|
||||
'SELECT id FROM clans WHERE owner_id = ?', (current_user.id,)
|
||||
).fetchone()
|
||||
if clan:
|
||||
conn.execute(
|
||||
'DELETE FROM clan_members WHERE clan_id = ? AND player_id = ?',
|
||||
(clan['id'], player_id)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return redirect(url_for('auth.options'))
|
||||
@@ -1,4 +1,5 @@
|
||||
from flask import Blueprint, render_template, request, jsonify
|
||||
from flask_login import login_required, current_user
|
||||
from db import get_db
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
@@ -11,21 +12,36 @@ dashboard = Blueprint('dashboard', __name__)
|
||||
# Serve the dashboard HTML
|
||||
# ------------------------------------------------------------------
|
||||
@dashboard.route('/')
|
||||
@login_required
|
||||
def index():
|
||||
conn = get_db()
|
||||
|
||||
# Get the logged-in user's clan
|
||||
clan = conn.execute(
|
||||
'SELECT id FROM clans WHERE owner_id = ?', (current_user.id,)
|
||||
).fetchone()
|
||||
|
||||
if not clan:
|
||||
# User has no clan yet — send them to options to create one
|
||||
conn.close()
|
||||
return render_template('index.html', players=[], no_clan=True)
|
||||
|
||||
clan_id = clan['id']
|
||||
|
||||
# Only fetch players that are members of this clan
|
||||
rows = conn.execute('''
|
||||
SELECT player, player_id, MAX(updated_at) as last_seen, MAX(world_id) as world_id
|
||||
FROM town_state
|
||||
WHERE player IS NOT NULL
|
||||
GROUP BY player, player_id
|
||||
ORDER BY player ASC
|
||||
''').fetchall()
|
||||
|
||||
# Pre-fetch all active captchas
|
||||
SELECT ts.player, ts.player_id, MAX(ts.updated_at) as last_seen, MAX(ts.world_id) as world_id
|
||||
FROM town_state ts
|
||||
INNER JOIN clan_members cm ON cm.player_id = ts.player_id AND cm.clan_id = ?
|
||||
WHERE ts.player IS NOT NULL
|
||||
GROUP BY ts.player, ts.player_id
|
||||
ORDER BY ts.player ASC
|
||||
''', (clan_id,)).fetchall()
|
||||
|
||||
captcha_rows = conn.execute("SELECT key, value FROM kv_store WHERE key LIKE 'captcha_active_%'").fetchall()
|
||||
active_captchas = {r['key'].replace('captcha_active_', ''): True for r in captcha_rows if r['value'] == '1'}
|
||||
conn.close()
|
||||
|
||||
|
||||
players = []
|
||||
now = datetime.utcnow()
|
||||
for r in rows:
|
||||
@@ -37,26 +53,30 @@ def index():
|
||||
is_online = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
players.append({
|
||||
'player': r['player'],
|
||||
'player_id': r['player_id'],
|
||||
'world_id': r['world_id'] or 'Unknown',
|
||||
'is_online': is_online,
|
||||
'player': r['player'],
|
||||
'player_id': r['player_id'],
|
||||
'world_id': r['world_id'] or 'Unknown',
|
||||
'is_online': is_online,
|
||||
'captcha_active': active_captchas.get(r['player_id'], False)
|
||||
})
|
||||
|
||||
return render_template('index.html', players=players)
|
||||
return render_template('index.html', players=players, no_clan=False)
|
||||
|
||||
|
||||
@dashboard.route('/player/<player_id>')
|
||||
@login_required
|
||||
def player_hub(player_id):
|
||||
return render_template('hub.html', player_id=player_id)
|
||||
|
||||
@dashboard.route('/player/<player_id>/admin')
|
||||
@login_required
|
||||
def player_dashboard(player_id):
|
||||
return render_template('dashboard.html', player_id=player_id)
|
||||
|
||||
@dashboard.route('/player/<player_id>/farm')
|
||||
@login_required
|
||||
def player_farm(player_id):
|
||||
return render_template('farm.html', player_id=player_id)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user