Skip to content

Forgot Password

confirm_new_password(request) async

Confirm a password reset with the provided code and new password.

Validates the reset code and its expiry, ensures password confirmation matches, updates the user's password, and invalidates the code.

Parameters:

Name Type Description Default
request ForgotPasswordConfirmation

Confirmation payload with code and new password.

required

Returns:

Type Description

Dict[str, str]: Confirmation message.

Raises:

Type Description
HTTPException

404 if code is invalid or expired, or user not found.

HTTPException

400 if passwords do not match.

Source code in routers/forgot_password.py
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
@router.post("/confirm", response_model=Dict[str, str])
async def confirm_new_password(request: ForgotPasswordConfirmation):
    """Confirm a password reset with the provided code and new password.

    Validates the reset code and its expiry, ensures password confirmation
    matches, updates the user's password, and invalidates the code.

    Args:
        request (ForgotPasswordConfirmation): Confirmation payload with code and new password.

    Returns:
        Dict[str, str]: Confirmation message.

    Raises:
        HTTPException: 404 if code is invalid or expired, or user not found.
        HTTPException: 400 if passwords do not match.
    """
    forgot_password = await ForgotPassword.find_one({"code": request.code})
    if not forgot_password:
        raise HTTPException(status_code=404, detail="Invalid code")

    current_time = datetime.now(timezone.utc)
    expires_at = forgot_password.expires_at

    if expires_at.tzinfo is None:
        expires_at = expires_at.replace(tzinfo=timezone.utc)

    if not forgot_password or expires_at < current_time:
        raise HTTPException(status_code=404, detail="Invalid code")

    if request.new_password != request.confirm_password:
        raise HTTPException(status_code=400, detail="Passwords do not match")

    user = await User.find_one({"email": forgot_password.email})
    if not user:
        raise HTTPException(status_code=404, detail="User not found")

    user.password_hash = hash_password(request.new_password)
    await user.save()
    await forgot_password.delete()
    return {"message": "Password changed"}

send_forgot_password_code(request) async

Send a password reset code to the user's email.

Generates a one-time code, stores it with an expiry, and emails a reset link.

Parameters:

Name Type Description Default
request ForgotPasswordRequest

Request containing the user's email.

required

Returns:

Type Description

Dict[str, str]: Confirmation message.

Raises:

Type Description
HTTPException

404 if user not found.

Source code in routers/forgot_password.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@router.post("/code", response_model=Dict[str, str])
async def send_forgot_password_code(request: ForgotPasswordRequest):
    """Send a password reset code to the user's email.

    Generates a one-time code, stores it with an expiry, and emails a reset link.

    Args:
        request (ForgotPasswordRequest): Request containing the user's email.

    Returns:
        Dict[str, str]: Confirmation message.

    Raises:
        HTTPException: 404 if user not found.
    """
    user = await User.find_one({"email": request.email})
    if not user:
        raise HTTPException(status_code=404, detail="User not found")

    code = "".join(random.choices(string.ascii_uppercase + string.digits, k=6))

    already_exists = await ForgotPassword.find_one({"email": request.email})
    if already_exists:
        already_exists.code = code
        already_exists.expires_at = datetime.now(timezone.utc) + timedelta(
            minutes=FORGOT_PASSWORD_CODE_EXPIRE_MINUTES
        )
        await already_exists.save()

    reset_url = f"{FRONTEND_URL}/reset-password?code={code}"
    email_service.send_email(
        to_email=request.email,
        subject="Forgot Password",
        template_name="forgot_password.html",
        context={
            "reset_url": reset_url,
            "expiry_minutes": FORGOT_PASSWORD_CODE_EXPIRE_MINUTES,
        },
    )

    await ForgotPassword.create(email=request.email, code=code)

    return {"message": "Code sent"}