diff --git a/src/auth/routes.py b/src/auth/routes.py index 63de288..716505c 100644 --- a/src/auth/routes.py +++ b/src/auth/routes.py @@ -1,3 +1,49 @@ -from flask import Blueprint +from email_validator import EmailNotValidError, validate_email +from flask import Blueprint, redirect, request +from werkzeug.security import generate_password_hash + +from src.auth import db, mailer, tokens +from src.utils import logger auth_bp = Blueprint("auth", __name__, url_prefix="/auth") + +MIN_PASSWORD_LENGTH = 8 + + +def _redirect_with_error(path: str, error: str, email: str | None = None): + url = f"{path}?error={error}" + if email: + url += f"&email={email}" + return redirect(url) + + +@auth_bp.route("/signup", methods=["POST"]) +def signup(): + email = (request.form.get("email") or "").strip() + password = request.form.get("password") or "" + password_confirm = request.form.get("password_confirm") or "" + + try: + valid = validate_email(email, check_deliverability=False) + email = valid.normalized.lower() + except EmailNotValidError: + return _redirect_with_error("/inscription", "invalid_email", email) + + if len(password) < MIN_PASSWORD_LENGTH: + return _redirect_with_error("/inscription", "password_too_short", email) + if password != password_confirm: + return _redirect_with_error("/inscription", "password_mismatch", email) + + if db.get_user_by_email(email) is not None: + return _redirect_with_error("/inscription", "email_taken", email) + + user_id = db.create_user(email, generate_password_hash(password)) + token = tokens.create_verification_token(user_id) + try: + mailer.send_verification_email(email, token) + except Exception: + logger.exception("Échec d'envoi de l'email de vérification") + db.delete_user(user_id) + return _redirect_with_error("/inscription", "email_send_failed", email) + + return redirect("/connexion?pending_verification=1") diff --git a/tests/auth/conftest.py b/tests/auth/conftest.py index ee11048..5de8de8 100644 --- a/tests/auth/conftest.py +++ b/tests/auth/conftest.py @@ -18,7 +18,9 @@ def app(users_db_path, monkeypatch): from src.auth.setup import init_auth + monkeypatch.setenv("SECRET_KEY", "test-secret-key") app = Flask(__name__) + app.config["WTF_CSRF_ENABLED"] = False init_auth(app) yield app diff --git a/tests/auth/test_signup.py b/tests/auth/test_signup.py new file mode 100644 index 0000000..b71971c --- /dev/null +++ b/tests/auth/test_signup.py @@ -0,0 +1,56 @@ +from src.auth import db + + +def _signup(client, email="alice@example.com", password="password12", confirm=None): + return client.post( + "/auth/signup", + data={ + "email": email, + "password": password, + "password_confirm": confirm if confirm is not None else password, + }, + ) + + +def test_signup_creates_unverified_user(client, mail_outbox): + resp = _signup(client) + assert resp.status_code == 302 + assert "pending_verification=1" in resp.headers["Location"] + row = db.get_user_by_email("alice@example.com") + assert row is not None + assert row["email_verified"] == 0 + assert len(mail_outbox) == 1 + assert mail_outbox[0].recipients == ["alice@example.com"] + + +def test_signup_rejects_short_password(client, mail_outbox): + resp = _signup(client, password="short", confirm="short") + assert resp.status_code == 302 + assert "error=password_too_short" in resp.headers["Location"] + assert db.get_user_by_email("alice@example.com") is None + assert len(mail_outbox) == 0 + + +def test_signup_rejects_mismatched_passwords(client, mail_outbox): + resp = _signup(client, password="password12", confirm="different12") + assert "error=password_mismatch" in resp.headers["Location"] + assert db.get_user_by_email("alice@example.com") is None + assert len(mail_outbox) == 0 + + +def test_signup_rejects_invalid_email(client, mail_outbox): + resp = _signup(client, email="pas-un-email") + assert "error=invalid_email" in resp.headers["Location"] + assert len(mail_outbox) == 0 + + +def test_signup_rejects_duplicate_email(client, mail_outbox): + _signup(client) + resp = _signup(client) + assert "error=email_taken" in resp.headers["Location"] + assert len(mail_outbox) == 1 # seul le premier signup a envoyé l'email + + +def test_signup_email_lowercased(client, mail_outbox): + _signup(client, email="Alice@Example.COM") + assert db.get_user_by_email("alice@example.com") is not None