Initial clean commit
This commit is contained in:
commit
6784d81c2c
141 changed files with 25219 additions and 0 deletions
203
routers/auth.py
Normal file
203
routers/auth.py
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
"""
|
||||
Authentication router - User registration, login, and logout.
|
||||
"""
|
||||
from flask import Blueprint, request, render_template, redirect, url_for, session, flash
|
||||
from psycopg2 import extras, IntegrityError
|
||||
from db import get_conn
|
||||
from utils.auth import (
|
||||
hash_password, verify_password, is_authenticated,
|
||||
validate_username, validate_password, validate_email
|
||||
)
|
||||
from datetime import datetime
|
||||
|
||||
auth_bp = Blueprint("auth", __name__, url_prefix="/auth")
|
||||
|
||||
|
||||
def migrate_anonymous_favorites(session_id: str, user_id: int):
|
||||
"""Migrate anonymous favorites to user account.
|
||||
|
||||
Args:
|
||||
session_id: Anonymous session ID
|
||||
user_id: User ID to migrate favorites to
|
||||
"""
|
||||
if not session_id:
|
||||
return
|
||||
|
||||
with get_conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
# Migrate favorites, avoiding duplicates
|
||||
cur.execute("""
|
||||
UPDATE favoritos
|
||||
SET user_id = %s, session_id = NULL
|
||||
WHERE session_id = %s
|
||||
AND noticia_id NOT IN (
|
||||
SELECT noticia_id FROM favoritos WHERE user_id = %s
|
||||
)
|
||||
""", (user_id, session_id, user_id))
|
||||
|
||||
# Delete any remaining duplicates
|
||||
cur.execute("""
|
||||
DELETE FROM favoritos
|
||||
WHERE session_id = %s
|
||||
""", (session_id,))
|
||||
conn.commit()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Registration
|
||||
# ============================================================
|
||||
|
||||
@auth_bp.route("/register", methods=["GET", "POST"])
|
||||
def register():
|
||||
"""User registration page and handler."""
|
||||
if is_authenticated():
|
||||
return redirect(url_for('account.index'))
|
||||
|
||||
if request.method == "POST":
|
||||
username = request.form.get("username", "").strip()
|
||||
email = request.form.get("email", "").strip().lower()
|
||||
password = request.form.get("password", "")
|
||||
password_confirm = request.form.get("password_confirm", "")
|
||||
|
||||
# Validation
|
||||
valid_username, username_error = validate_username(username)
|
||||
if not valid_username:
|
||||
flash(username_error, "danger")
|
||||
return render_template("register.html", username=username, email=email)
|
||||
|
||||
valid_email, email_error = validate_email(email)
|
||||
if not valid_email:
|
||||
flash(email_error, "danger")
|
||||
return render_template("register.html", username=username, email=email)
|
||||
|
||||
valid_password, password_error = validate_password(password)
|
||||
if not valid_password:
|
||||
flash(password_error, "danger")
|
||||
return render_template("register.html", username=username, email=email)
|
||||
|
||||
if password != password_confirm:
|
||||
flash("Las contraseñas no coinciden", "danger")
|
||||
return render_template("register.html", username=username, email=email)
|
||||
|
||||
# Create user
|
||||
try:
|
||||
password_hash = hash_password(password)
|
||||
|
||||
with get_conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
INSERT INTO usuarios (username, email, password_hash, last_login)
|
||||
VALUES (%s, %s, %s, NOW())
|
||||
RETURNING id
|
||||
""", (username, email, password_hash))
|
||||
user_id = cur.fetchone()[0]
|
||||
conn.commit()
|
||||
|
||||
# Auto-login after registration
|
||||
old_session_id = session.get('user_session')
|
||||
session['user_id'] = user_id
|
||||
session['username'] = username
|
||||
|
||||
# Migrate anonymous favorites if any
|
||||
if old_session_id:
|
||||
migrate_anonymous_favorites(old_session_id, user_id)
|
||||
session.pop('user_session', None)
|
||||
|
||||
flash(f"¡Bienvenido {username}! Tu cuenta ha sido creada exitosamente.", "success")
|
||||
return redirect(url_for('account.index'))
|
||||
|
||||
except IntegrityError as e:
|
||||
if 'username' in str(e):
|
||||
flash("Este nombre de usuario ya está en uso", "danger")
|
||||
elif 'email' in str(e):
|
||||
flash("Este email ya está registrado", "danger")
|
||||
else:
|
||||
flash("Error al crear la cuenta. Por favor intenta de nuevo.", "danger")
|
||||
return render_template("register.html", username=username, email=email)
|
||||
except Exception as e:
|
||||
flash("Error al crear la cuenta. Por favor intenta de nuevo.", "danger")
|
||||
return render_template("register.html", username=username, email=email)
|
||||
|
||||
return render_template("register.html")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Login
|
||||
# ============================================================
|
||||
|
||||
@auth_bp.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
"""User login page and handler."""
|
||||
if is_authenticated():
|
||||
return redirect(url_for('account.index'))
|
||||
|
||||
if request.method == "POST":
|
||||
username_or_email = request.form.get("username", "").strip()
|
||||
password = request.form.get("password", "")
|
||||
|
||||
if not username_or_email or not password:
|
||||
flash("Por favor ingresa tu usuario/email y contraseña", "danger")
|
||||
return render_template("login.html", username=username_or_email)
|
||||
|
||||
try:
|
||||
with get_conn() as conn:
|
||||
with conn.cursor(cursor_factory=extras.DictCursor) as cur:
|
||||
# Try login with username or email
|
||||
cur.execute("""
|
||||
SELECT id, username, email, password_hash, is_active, avatar_url
|
||||
FROM usuarios
|
||||
WHERE (username = %s OR email = %s) AND is_active = TRUE
|
||||
""", (username_or_email, username_or_email.lower()))
|
||||
user = cur.fetchone()
|
||||
|
||||
if not user:
|
||||
flash("Usuario o contraseña incorrectos", "danger")
|
||||
return render_template("login.html", username=username_or_email)
|
||||
|
||||
if not verify_password(password, user['password_hash']):
|
||||
flash("Usuario o contraseña incorrectos", "danger")
|
||||
return render_template("login.html", username=username_or_email)
|
||||
|
||||
# Update last login
|
||||
cur.execute("""
|
||||
UPDATE usuarios SET last_login = NOW() WHERE id = %s
|
||||
""", (user['id'],))
|
||||
conn.commit()
|
||||
|
||||
# Create session
|
||||
old_session_id = session.get('user_session')
|
||||
session['user_id'] = user['id']
|
||||
session['username'] = user['username']
|
||||
session['avatar_url'] = user.get('avatar_url')
|
||||
|
||||
# Migrate anonymous favorites
|
||||
if old_session_id:
|
||||
migrate_anonymous_favorites(old_session_id, user['id'])
|
||||
session.pop('user_session', None)
|
||||
|
||||
flash(f"¡Bienvenido de vuelta, {user['username']}!", "success")
|
||||
|
||||
# Redirect to 'next' parameter if exists
|
||||
next_page = request.args.get('next')
|
||||
if next_page and next_page.startswith('/'):
|
||||
return redirect(next_page)
|
||||
return redirect(url_for('account.index'))
|
||||
|
||||
except Exception as e:
|
||||
flash("Error al iniciar sesión. Por favor intenta de nuevo.", "danger")
|
||||
return render_template("login.html", username=username_or_email)
|
||||
|
||||
return render_template("login.html")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Logout
|
||||
# ============================================================
|
||||
|
||||
@auth_bp.route("/logout", methods=["POST", "GET"])
|
||||
def logout():
|
||||
"""Log out the current user."""
|
||||
username = session.get('username', 'Usuario')
|
||||
session.clear()
|
||||
flash(f"Hasta luego, {username}. Has cerrado sesión exitosamente.", "info")
|
||||
return redirect(url_for('home.index'))
|
||||
Loading…
Add table
Add a link
Reference in a new issue