src/auth/routes.py : route /auth/signup (#73)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+47
-1
@@ -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")
|
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")
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ def app(users_db_path, monkeypatch):
|
|||||||
|
|
||||||
from src.auth.setup import init_auth
|
from src.auth.setup import init_auth
|
||||||
|
|
||||||
|
monkeypatch.setenv("SECRET_KEY", "test-secret-key")
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
app.config["WTF_CSRF_ENABLED"] = False
|
||||||
init_auth(app)
|
init_auth(app)
|
||||||
yield app
|
yield app
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user