diff --git a/app/main.py b/app/main.py index 0d88ef2..e8b578b 100644 --- a/app/main.py +++ b/app/main.py @@ -1,7 +1,7 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from .routers import users, token, mail, events, tags +from .routers import users, token, mail, events, tags, password from .dependencies import user_add import os @@ -30,7 +30,7 @@ app.include_router(token.router) app.include_router(mail.router) app.include_router(events.router) app.include_router(tags.router) - +app.include_router(password.router) @app.on_event("startup") async def startup_event(): diff --git a/app/models/users.py b/app/models/users.py index c99e820..6abf94f 100644 --- a/app/models/users.py +++ b/app/models/users.py @@ -46,6 +46,9 @@ class UserCreate(BaseModel): firstName: str name: str +class UserForgotPassword(BaseModel): + email: EmailStr + class UserInDB(User): password: str diff --git a/app/routers/password.py b/app/routers/password.py new file mode 100644 index 0000000..4ef7cb9 --- /dev/null +++ b/app/routers/password.py @@ -0,0 +1,111 @@ +from fastapi import APIRouter, HTTPException, status, Request, Form +from fastapi.templating import Jinja2Templates +from ..dependencies import users_token, database, mail +from ..models import users, email +from fastapi.responses import JSONResponse, HTMLResponse +from fastapi_mail import MessageSchema, MessageType, FastMail +import random, os + +router = APIRouter() + +# Assurer que le chemin vers "templates" est correct +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +templates = Jinja2Templates(directory=os.path.join(BASE_DIR, "templates")) + +@router.post("/password/forgot", tags=["password"]) +async def forgot_password(userSingle: users.UserForgotPassword): + if not userSingle.email: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Email is required" + ) + + # Recherche de l'utilisateur dans la base de données + user_repository = users.UserRepository(database=database.database) + user = user_repository.find_one_by({"email": {"$eq": userSingle.email}}) + + if user is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + + # Génération d'un token temporaire pour réinitialisation + reset_token = str(random.randint(100000, 999999)) + key_hashed = users_token.get_password_hash(reset_token) + + # Créer le lien de réinitialisation + reset_link = f"https://votresite.com/password/reset?key={reset_token}&email={user.email}" + + # Préparer les données à envoyer au template + email_body = { + "username": user.username, + "reset_link": reset_link + } + + # Créer le message à envoyer + message = MessageSchema( + subject="Password Reset Request", + recipients=[user.email], + template_body=email_body, + subtype=MessageType.html, + ) + + # Utilisation de FastMail pour envoyer l'email + fm = FastMail(mail.conf) + await fm.send_message(message, template_name="forgot_password_email.html") + + # Stockage du token temporaire dans Redis avec une expiration d'1 heure + database.connect_redis.setex(user.email, 3600, key_hashed) + + return JSONResponse(status_code=status.HTTP_200_OK, content={"message": "Password reset email has been sent"}) + +@router.get("/password/reset", tags=["password"]) +async def reset_password(request: Request, key: str | None = None, email: str | None = None): + if not key or not email: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Parameters 'key' and 'email' are required" + ) + + # Vérifier que la clé correspond à celle stockée dans Redis + key_hashed = database.connect_redis.get(email) + + if key_hashed is None or key_hashed.decode() != key: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Key is invalid or expired" + ) + + # Afficher la page HTML de réinitialisation du mot de passe + return templates.TemplateResponse("reset_password.html", {"request": request, "email": email, "key": key}) + +@router.post("/password/update", tags=["password"]) +async def update_password(request: Request, email: str = Form(...), key: str = Form(...), new_password: str = Form(...)): # Vérification du token dans Redis + key_hashed = database.connect_redis.get(email) + + if key_hashed is None or key_hashed.decode() != key: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Key is invalid or expired" + ) + + # Recherche de l'utilisateur dans la base de données + user_repository = users.UserRepository(database=database.database) + user = user_repository.find_one_by({"email": {"$eq": email}}) + + if user is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + + # Mise à jour du mot de passe de l'utilisateur + user.password = users_token.get_password_hash(new_password) + user_repository.save(user) + + # Suppression du token temporaire dans Redis + database.connect_redis.delete(email) + + # Afficher un message de succès dans une réponse HTML + return templates.TemplateResponse("password_update_success.html", {"request": request, "email": email}) diff --git a/app/templates/forgot_password_email.html b/app/templates/forgot_password_email.html new file mode 100644 index 0000000..9291baf --- /dev/null +++ b/app/templates/forgot_password_email.html @@ -0,0 +1,69 @@ + + + + + + + Réinitialisation de votre mot de passe + + + +
+

Demande de réinitialisation du mot de passe

+

Bonjour {{ username }},

+

Nous avons reçu une demande pour réinitialiser votre mot de passe sur notre site. Si vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer cet email.

+

Pour réinitialiser votre mot de passe, cliquez sur le lien ci-dessous :

+

Réinitialiser mon mot de passe

+

Le lien est valable pendant une heure. Si vous ne pouvez pas cliquer dessus, copiez et collez-le dans votre navigateur.

+
+ + + + diff --git a/app/templates/password_update_success.html b/app/templates/password_update_success.html new file mode 100644 index 0000000..2a6bb6a --- /dev/null +++ b/app/templates/password_update_success.html @@ -0,0 +1,65 @@ + + + + + + + Mot de Passe Mis à Jour + + + +
+

Votre mot de passe a été mis à jour avec succès

+

Votre mot de passe a été réinitialisé avec succès. Vous pouvez maintenant utiliser votre nouveau mot de passe pour vous connecter.

+
+ + + + diff --git a/app/templates/reset_password.html b/app/templates/reset_password.html new file mode 100644 index 0000000..6c1f1c1 --- /dev/null +++ b/app/templates/reset_password.html @@ -0,0 +1,87 @@ + + + + + + Réinitialisation du mot de passe + + + +
+

Réinitialisation du mot de passe

+ +
+ + + +
+ + +
+ + +
+ +
+

Vous avez des questions ? Contactez-nous

+
+
+ +