"""Маршруты auth: HTML (`/login`, `/logout`) и JSON (`/api/auth/*`).""" from __future__ import annotations import logging from flask import ( Blueprint, flash, jsonify, redirect, render_template, request, session, url_for, ) from ..config import is_assignment_feature_enabled, is_dev_ui from ..messages import RU from .decorators import login_required, current_user from .services import AuthError, authenticate_credentials log = logging.getLogger(__name__) auth_bp = Blueprint('auth', __name__) def _safe_next(default: str = '/') -> str: """Защита от open-redirect: разрешаем только относительные пути.""" nxt = request.values.get('next') or default if not nxt.startswith('/') or nxt.startswith('//'): return default return nxt def _do_login(login: str, password: str): user = authenticate_credentials(login, password) session.clear() session['user_id'] = user.id session.permanent = True return user # ─── HTML ──────────────────────────────────────────────────────────── @auth_bp.route('/login', methods=['GET']) def login_page(): if current_user() is not None: return redirect(_safe_next('/')) return render_template('auth/login.html', next=_safe_next('/')) @auth_bp.route('/login', methods=['POST']) def login_submit(): login = (request.form.get('login') or '').strip() password = request.form.get('password') or '' try: _do_login(login, password) except AuthError as e: flash(e.message, 'error') return render_template('auth/login.html', next=_safe_next('/'), login=login), e.status except Exception: log.exception('login_submit failed') flash(RU['loginFailed'], 'error') return render_template('auth/login.html', next=_safe_next('/'), login=login), 500 return redirect(_safe_next('/')) @auth_bp.route('/logout', methods=['POST', 'GET']) def logout(): session.clear() if request.method == 'GET': return redirect(url_for('auth.login_page')) return redirect(url_for('auth.login_page')) # ─── JSON API ──────────────────────────────────────────────────────── @auth_bp.route('/api/auth/login', methods=['POST']) def api_login(): data = request.get_json(silent=True) or {} login = (data.get('login') or '').strip() password = data.get('password') or '' try: user = _do_login(login, password) except AuthError as e: return jsonify(error=e.message), e.status except Exception: log.exception('api_login failed') return jsonify(error=RU['loginFailed']), 500 return jsonify(user=user.to_public_dict()) @auth_bp.route('/api/auth/logout', methods=['POST']) def api_logout(): session.clear() return jsonify(message=RU['loggedOut']) @auth_bp.route('/api/auth/me', methods=['GET']) @login_required def api_me(): user = current_user() return jsonify( user=user.to_public_dict() if user else None, devUi=is_dev_ui(), assignmentUi=is_assignment_feature_enabled(), )