diff --git a/backend/app/api/routes/login.py b/backend/app/api/routes/login.py index 8fb65c42e0..b2675a2914 100644 --- a/backend/app/api/routes/login.py +++ b/backend/app/api/routes/login.py @@ -2,10 +2,11 @@ from typing import Annotated, Any from fastapi import APIRouter, Depends, HTTPException +from fastapi.responses import HTMLResponse from fastapi.security import OAuth2PasswordRequestForm from app import crud -from app.api.deps import CurrentUser, SessionDep +from app.api.deps import CurrentUser, SessionDep, get_current_active_superuser from app.core import security from app.core.config import settings from app.core.security import get_password_hash @@ -79,10 +80,10 @@ def reset_password(session: SessionDep, body: NewPassword) -> Message: """ Reset password """ - user_id = verify_password_reset_token(token=body.token) - if not user_id: + email = verify_password_reset_token(token=body.token) + if not email: raise HTTPException(status_code=400, detail="Invalid token") - user = session.get(User, int(user_id)) + user = crud.get_user_by_email(session=session, email=email) if not user: raise HTTPException( status_code=404, @@ -95,3 +96,29 @@ def reset_password(session: SessionDep, body: NewPassword) -> Message: session.add(user) session.commit() return Message(message="Password updated successfully") + + +@router.post( + "/password-recovery-html-content/{email}", + dependencies=[Depends(get_current_active_superuser)], + response_class=HTMLResponse, +) +def recover_password_html_content(email: str, session: SessionDep) -> Any: + """ + HTML Content for Password Recovery + """ + user = crud.get_user_by_email(session=session, email=email) + + if not user: + raise HTTPException( + status_code=404, + detail="The user with this username does not exist in the system.", + ) + password_reset_token = generate_password_reset_token(email=email) + email_data = generate_reset_password_email( + email_to=user.email, email=email, token=password_reset_token + ) + + return HTMLResponse( + content=email_data.html_content, headers={"subject:": email_data.subject} + ) diff --git a/backend/app/email-templates/build/reset_password.html b/backend/app/email-templates/build/reset_password.html index e1d029bb82..4148a5b773 100644 --- a/backend/app/email-templates/build/reset_password.html +++ b/backend/app/email-templates/build/reset_password.html @@ -21,5 +21,5 @@
{{ project_name }} - Password Recovery
Hello {{ username }}
We've received a request to reset your password. You can do it by clicking the button below:
Reset password
Or copy and paste the following link into your browser:
This password will expire in {{ valid_hours }} hours.

{{ project_name }} - Password Recovery
Hello {{ username }}
We've received a request to reset your password. You can do it by clicking the button below:
Reset password
Or copy and paste the following link into your browser:
This password will expire in {{ valid_hours }} hours.

If you didn't request a password recovery you can disregard this email.
\ No newline at end of file diff --git a/backend/app/email-templates/src/reset_password.mjml b/backend/app/email-templates/src/reset_password.mjml index 6cd04c176a..743f5d77f4 100644 --- a/backend/app/email-templates/src/reset_password.mjml +++ b/backend/app/email-templates/src/reset_password.mjml @@ -3,9 +3,9 @@ {{ project_name }} - Password Recovery - Hello {{ username }} + Hello {{ username }} We've received a request to reset your password. You can do it by clicking the button below: - Reset password + Reset password Or copy and paste the following link into your browser: {{ link }} This password will expire in {{ valid_hours }} hours. diff --git a/backend/app/tests/api/api_v1/test_login.py b/backend/app/tests/api/api_v1/test_login.py index ec099eed44..6102c68d61 100644 --- a/backend/app/tests/api/api_v1/test_login.py +++ b/backend/app/tests/api/api_v1/test_login.py @@ -1,3 +1,4 @@ +from app.utils import generate_password_reset_token from fastapi.testclient import TestClient from app.core.config import settings @@ -64,13 +65,7 @@ def test_recovery_password_user_not_exits( def test_reset_password( client: TestClient, superuser_token_headers: dict[str, str] ) -> None: - login_data = { - "username": settings.FIRST_SUPERUSER, - "password": settings.FIRST_SUPERUSER_PASSWORD, - } - r = client.post(f"{settings.API_V1_STR}/login/access-token", data=login_data) - token = r.json().get("access_token") - + token = generate_password_reset_token(email=settings.FIRST_SUPERUSER) data = {"new_password": "changethis", "token": token} r = client.post( f"{settings.API_V1_STR}/reset-password/", diff --git a/frontend/src/routes/reset-password.tsx b/frontend/src/routes/reset-password.tsx index a39db3a08c..5f20b14f0b 100644 --- a/frontend/src/routes/reset-password.tsx +++ b/frontend/src/routes/reset-password.tsx @@ -8,7 +8,7 @@ import { Input, Text, } from '@chakra-ui/react' -import { createFileRoute, redirect } from '@tanstack/react-router' +import { createFileRoute, redirect, useNavigate } from '@tanstack/react-router' import { SubmitHandler, useForm } from 'react-hook-form' import { useMutation } from 'react-query' @@ -36,6 +36,7 @@ function ResetPassword() { register, handleSubmit, getValues, + reset, formState: { errors }, } = useForm({ mode: 'onBlur', @@ -45,6 +46,7 @@ function ResetPassword() { }, }) const showToast = useCustomToast() + const navigate = useNavigate() const resetPassword = async (data: NewPassword) => { const token = new URLSearchParams(window.location.search).get('token') @@ -56,6 +58,8 @@ function ResetPassword() { const mutation = useMutation(resetPassword, { onSuccess: () => { showToast('Success!', 'Password updated.', 'success') + reset() + navigate({ to: '/login' }) }, onError: (err: ApiError) => { const errDetail = err.body.detail