Fix: Correct email configuration and password reset token verification

This commit addresses several issues related to email sending and password reset functionality:

- **docker-compose.yml**: Removed inline default values for MAIL_PORT, MAIL_STARTTLS, MAIL_SSL_TLS, and MAIL_SUPPRESS_SEND environment variables. This ensures that the application correctly uses the values provided in the .env file or environment. Previously, inline defaults could override intended settings, leading to unexpected behavior.

- **main.py**:
    - **MAIL_SUPPRESS_SEND Interpretation**: Corrected the boolean interpretation of the MAIL_SUPPRESS_SEND environment variable. Previously, bool("False") would evaluate to True, inadvertently suppressing email sending even when explicitly set to False. The fix ensures accurate evaluation.
    - **Email Sending Logging**: Added an INFO level log message before attempting to send a password reset email to provide clearer debugging information.
    - **Password Reset Token Verification**: Refactored the reset_password function's token verification logic. Instead of directly querying the database with a raw token (which would fail as tokens are stored hashed), the function now iterates through all stored reset tokens and uses pwd_context.verify to match the provided token against the hashed versions. This ensures secure and correct token validation. Additionally, the token expiration and deletion logic was refined.
This commit is contained in:
2025-12-23 23:16:14 +01:00
parent e0d0e8c3df
commit 5b1a89d30a
2 changed files with 20 additions and 10 deletions

View File

@@ -23,9 +23,9 @@ services:
- MAIL_USERNAME=${MAIL_USERNAME}
- MAIL_PASSWORD=${MAIL_PASSWORD}
- MAIL_FROM=${MAIL_FROM}
- MAIL_PORT=${MAIL_PORT:-587}
- MAIL_PORT=${MAIL_PORT}
- MAIL_SERVER=${MAIL_SERVER}
- MAIL_STARTTLS=${MAIL_STARTTLS:-True}
- MAIL_SSL_TLS=${MAIL_SSL_TLS:-False}
- MAIL_SUPPRESS_SEND=${MAIL_SUPPRESS_SEND:-False}
- MAIL_STARTTLS=${MAIL_STARTTLS}
- MAIL_SSL_TLS=${MAIL_SSL_TLS}
- MAIL_SUPPRESS_SEND=${MAIL_SUPPRESS_SEND}

22
main.py
View File

@@ -48,7 +48,7 @@ conf = ConnectionConfig(
TEMPLATE_FOLDER='./templates/email',
)
conf.SUPPRESS_SEND = bool(os.getenv("MAIL_SUPPRESS_SEND", False))
conf.SUPPRESS_SEND = os.getenv("MAIL_SUPPRESS_SEND", "False").lower() in ('true', '1', 't')
fm = FastMail(conf)
@@ -808,6 +808,7 @@ async def forgot_password(request: Request, forgot_req: ForgotPasswordRequest):
)
try:
logging.info(f"Attempting to send password reset email to {email}")
await fm.send_message(message)
logging.info(f"Password reset email sent to {email}")
except Exception as e:
@@ -820,8 +821,17 @@ async def reset_password(request: ResetPasswordRequest):
conn = sqlite3.connect(DB_FILE)
cursor = conn.cursor()
cursor.execute("SELECT user_id, expires_at, token FROM password_reset_tokens WHERE token = ?", (request.token,))
reset_data = cursor.fetchone()
cursor.execute("SELECT user_id, expires_at, token FROM password_reset_tokens")
all_tokens = cursor.fetchall()
reset_data = None
for user_id, expires_at_str, hashed_stored_token in all_tokens:
try:
if pwd_context.verify(request.token, hashed_stored_token):
reset_data = (user_id, expires_at_str, hashed_stored_token)
break
except Exception:
continue
if not reset_data:
conn.close()
@@ -830,11 +840,11 @@ async def reset_password(request: ResetPasswordRequest):
user_id, expires_at_str, hashed_stored_token = reset_data
expires_at = datetime.fromisoformat(expires_at_str)
if datetime.utcnow() > expires_at or not pwd_context.verify(request.token, hashed_stored_token):
conn.close()
# Invalidate token to prevent reuse after expiration or failed hash verification
if datetime.utcnow() > expires_at:
# It's important to also delete the expired token from the database
cursor.execute("DELETE FROM password_reset_tokens WHERE token = ?", (hashed_stored_token,))
conn.commit()
conn.close()
raise HTTPException(status_code=400, detail="Invalid or expired reset token.")
# Update user password