62 lines
2.7 KiB
Python
62 lines
2.7 KiB
Python
"""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
|