"""Authentication routes (register, login).""" import secrets import sqlite3 from flask import Blueprint, jsonify, make_response, request, current_app from werkzeug.security import check_password_hash, generate_password_hash from database import get_db from decorators import rate_limit auth_bp = Blueprint('auth', __name__, url_prefix='/api') @auth_bp.route('/register', methods=['POST']) @rate_limit(max_attempts=5, window=3600) # 5 attempts per hour def register(): """Register a new user with invite code.""" data = request.json username = data.get('username') password = data.get('password') invite_code = data.get('invite_code') if not username or not password or not invite_code: return jsonify({'error': 'Username, password, and invite_code required'}), 400 db = get_db(current_app) code_row = db.execute('SELECT id FROM registration_codes WHERE code = ? AND is_used = 0', (invite_code,)).fetchone() if not code_row: return jsonify({'error': 'Invalid or already used invite code'}), 400 hashed_password = generate_password_hash(password) token = secrets.token_hex(32) try: cur = db.execute('INSERT INTO users (username, password, token, avatar_url, description, pronouns) VALUES (?, ?, ?, ?, ?, ?)', (username, hashed_password, token, '', '', '')) db.execute('UPDATE registration_codes SET is_used = 1 WHERE code = ?', (invite_code,)) db.commit() resp = make_response(jsonify({'token': token, 'user_id': cur.lastrowid, 'username': username}), 201) resp.set_cookie('auth_token', token, httponly=True, samesite='Lax', max_age=86400) return resp except sqlite3.IntegrityError: db.rollback() return jsonify({'error': 'Username already exists'}), 409 @auth_bp.route('/login', methods=['POST']) @rate_limit(max_attempts=10, window=3600) # 10 attempts per hour def login(): """Log in a user.""" data = request.json username = data.get('username') password = data.get('password') if not username or not password: return jsonify({'error': 'Username and password required'}), 400 user = get_db(current_app).execute('SELECT * FROM users WHERE username = ?', (username,)).fetchone() if not user or not check_password_hash(user['password'], password): return jsonify({'error': 'Invalid credentials'}), 401 if user['is_banned'] == 1: return jsonify({'error': 'Account banned'}), 403 resp = make_response(jsonify({'token': user['token'], 'user_id': user['id'], 'username': user['username']}), 200) resp.set_cookie('auth_token', user['token'], httponly=True, samesite='Lax', max_age=86400) return resp