lotr-sut/sut/backend/routes/auth.py
Fellowship Scholar f6a5823439 init commit
2026-03-29 20:07:56 +00:00

122 lines
4.2 KiB
Python

"""Authentication routes."""
from flask import Blueprint, request, session
from flask_restx import Api, Resource, fields
from models.user import User
from services.auth_service import authenticate_user, register_user
from typing import Dict, Any
auth_bp = Blueprint('auth', __name__, url_prefix='/api')
auth_api = Api(auth_bp, doc=False, prefix='/auth')
# Request/Response models for Swagger
login_model = auth_api.model('Login', {
'username': fields.String(required=True, description='Username'),
'password': fields.String(required=True, description='Password')
})
user_response_model = auth_api.model('UserResponse', {
'id': fields.Integer(description='User ID'),
'username': fields.String(description='Username'),
'email': fields.String(description='Email'),
'role': fields.String(description='Fellowship member role'),
'gold': fields.Integer(description='Current gold balance'),
})
login_response_model = auth_api.model('LoginResponse', {
'message': fields.String(description='Success message'),
'user': fields.Nested(user_response_model, description='User information')
})
signup_model = auth_api.model('Signup', {
'username': fields.String(required=True, description='Desired username (minimum 3 characters)'),
'password': fields.String(required=True, description='Desired password (minimum 8 characters and at least one number)'),
'email': fields.String(required=False, description='Optional email address')
})
@auth_api.route('/signup')
class Signup(Resource):
"""Public signup endpoint for open SUT registration."""
@auth_api.expect(signup_model, validate=False)
@auth_api.response(201, 'Signup successful', login_response_model)
@auth_api.response(400, 'Validation error')
@auth_api.doc(description='Register user and create session')
def post(self) -> tuple[Dict[str, Any], int]:
"""Register a user and immediately log them in."""
data = request.get_json() or {}
username = data.get('username')
password = data.get('password')
email = data.get('email')
try:
user = register_user(username=username, password=password, email=email)
except ValueError as error:
return {'error': str(error)}, 400
session['user_id'] = user.id
session['username'] = user.username
return {
'message': 'Signup successful',
'user': user.to_dict(),
}, 201
@auth_api.route('/login')
class Login(Resource):
"""User login endpoint."""
@auth_api.expect(login_model)
@auth_api.marshal_with(login_response_model)
@auth_api.doc(description='Authenticate user and create session')
def post(self) -> tuple[Dict[str, Any], int]:
"""Login user."""
data = request.get_json()
username = data.get('username')
password = data.get('password')
if not username or not password:
return {'error': 'Username and password are required'}, 400
user = authenticate_user(username, password)
if not user:
return {'error': 'Invalid credentials'}, 401
# Create session
session['user_id'] = user.id
session['username'] = user.username
return {
'message': 'Login successful',
'user': user.to_dict()
}, 200
@auth_api.route('/logout')
class Logout(Resource):
"""User logout endpoint."""
@auth_api.doc(description='Logout user and destroy session')
def post(self) -> tuple[Dict[str, Any], int]:
"""Logout user."""
session.clear()
return {'message': 'Logout successful'}, 200
@auth_api.route('/me')
class CurrentUser(Resource):
"""Get current authenticated user."""
@auth_api.marshal_with(user_response_model)
@auth_api.doc(description='Get current authenticated user information')
def get(self) -> tuple[Dict[str, Any], int]:
"""Get current user."""
user_id = session.get('user_id')
if not user_id:
return {'error': 'Not authenticated'}, 401
user = User.query.get(user_id)
if not user:
return {'error': 'User not found'}, 404
return user.to_dict(), 200