diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 653c2bb..54f0566 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6,7 +6,7 @@ import HomePage from "./pages/HomePage"; import StreamerRoute from "./components/Stream/StreamerRoute"; import NotFoundPage from "./pages/NotFoundPage"; import UserPage from "./pages/UserPage"; -import ForgotPasswordPage from "./pages/ForgotPasswordPage"; +import ResetPasswordPage from "./pages/ResetPasswordPage"; import CategoryPage from "./pages/CategoryPage"; function App() { @@ -42,7 +42,7 @@ function App() { } /> } /> - }> + }> }> } /> diff --git a/frontend/src/components/Auth/PasswordResetForm.tsx b/frontend/src/components/Auth/PasswordResetForm.tsx index 9931abc..4beee3a 100644 --- a/frontend/src/components/Auth/PasswordResetForm.tsx +++ b/frontend/src/components/Auth/PasswordResetForm.tsx @@ -28,7 +28,7 @@ const PasswordResetForm: React.FC = ({ onSubmit, token }) => { }); const confirmPasswordReset = () => { - alert('Password reset successfully!'); + alert(`${resetData.newPassword} - ${token}`); // You can replace this with navigation or API success handling logic }; @@ -64,7 +64,7 @@ const PasswordResetForm: React.FC = ({ onSubmit, token }) => { if (validateResetForm()) { try { - const response = await fetch("/user/reset_password/ { - const { token } = useParams<{ token: string }>(); - const navigate = useNavigate(); - - // If the token is missing, handle the error (e.g., redirect or show a message) - if (!token) { - return ( - - Invalid Token - The reset token is missing or invalid. - navigate("/login")} - className="text-blue-500 underline mt-4" - > - Go back to Login - - - ); - } - - const handlePasswordReset = () => { - - }; - - return ( - - Forgot Password - - - ); -}; - -export default ForgotPasswordPage; diff --git a/frontend/src/pages/ResetPasswordPage.tsx b/frontend/src/pages/ResetPasswordPage.tsx new file mode 100644 index 0000000..ad266b7 --- /dev/null +++ b/frontend/src/pages/ResetPasswordPage.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import PasswordResetForm from "../components/Auth/PasswordResetForm"; +import { useParams, useNavigate } from "react-router-dom"; + +const ResetPasswordPage: React.FC = () => { + const { token } = useParams<{ token: string }>(); + const navigate = useNavigate(); + + const handlePasswordReset = () => { + + }; + + return ( + + Forgot Password + + + ); +}; + +export default ResetPasswordPage; diff --git a/web_server/blueprints/email.py b/web_server/blueprints/email.py index 67d75bb..a52b38e 100644 --- a/web_server/blueprints/email.py +++ b/web_server/blueprints/email.py @@ -6,7 +6,10 @@ from random import randrange from dotenv import load_dotenv from utils.user_utils import generate_token from secrets import token_hex +import redis +redis_url = "redis://redis:6379/1" +r = redis.from_url(redis_url, decode_responses=True) load_dotenv() @@ -24,7 +27,7 @@ def send_email(email, func) -> None: # Setup up the receiver details login_code = randrange(100000, 1000000) body = func() - print(body, flush=True) + msg = MIMEText(body, "html") msg["Subject"] = "Reset Gander Login" msg["From"] = SMTP_EMAIL @@ -45,8 +48,14 @@ def send_email(email, func) -> None: print("Error: ", e, flush=True) def forgot_password_body(email): - token = generate_token(email, token_hex(32)) + """ + Handles the creation of the email body for resetting password + """ + salt = token_hex(32) + + token = generate_token(email, salt) url = getenv("VITE_API_URL") + r.setex(token, 3600, salt) full_url = url + "/reset_password/" + token content = f""" diff --git a/web_server/blueprints/user.py b/web_server/blueprints/user.py index 6508b57..775ce86 100644 --- a/web_server/blueprints/user.py +++ b/web_server/blueprints/user.py @@ -2,6 +2,10 @@ from flask import Blueprint, jsonify, session, abort, abort from utils.user_utils import * from blueprints.utils import login_required from blueprints.email import send_email, forgot_password_body +import redis + +redis_url = "redis://redis:6379/1" +r = redis.from_url(redis_url, decode_responses=True) user_bp = Blueprint("user", __name__) @@ -93,23 +97,27 @@ def get_login_status(): @user_bp.route('/user/forgot_password/', methods=['GET','POST']) def user_forgot_password(email): """ - Will send link to email to reset password by looking at the user_id within session to see whos password should be reset - Creates a super random number to be used a the link to reset password I guess a random number generator seeded with a secret + Initializes the function to handle password reset """ send_email(email, lambda: forgot_password_body(email)) return email -@user_bp.route('/user/reset_password//') +@user_bp.route('/user/reset_password//', methods=['POST']) def user_reset_password(token, new_password): """ Given token and new password resets the users password """ - email = verify_token(token) + salt_value = r.get(token) + if salt_value: + r.delete(token) + + email = verify_token(token, salt_value) + if email: response = reset_password(new_password, email) if response: return 200 else: abort(500) - return abort(500) \ No newline at end of file + return abort(404) \ No newline at end of file diff --git a/web_server/utils/user_utils.py b/web_server/utils/user_utils.py index fffbefe..00b7297 100644 --- a/web_server/utils/user_utils.py +++ b/web_server/utils/user_utils.py @@ -1,7 +1,7 @@ from database.database import Database from typing import Optional, List from datetime import datetime -from itsdangerous import URLSafeTimedSerializer +from itsdangerous import URLSafeTimedSerializer, BadSignature, SignatureExpired from os import getenv from werkzeug.security import generate_password_hash, check_password_hash from dotenv import load_dotenv @@ -145,10 +145,20 @@ def generate_token(email, salt_value) -> str: def verify_token(token: str, salt_value) -> Optional[str]: """ - Given a token verifies token and decodes the token into an email + Given a token, verifies and decodes it into an email """ - email = serializer.loads(token, salt=salt_value, max_age=3600) - return email if email else False + + try: + email = serializer.loads(token, salt=salt_value, max_age=3600) + return email + except SignatureExpired: + # Token expired + print("Token has expired", flush=True) + return None + except BadSignature: + # Invalid token + print("Token is invalid", flush=True) + return None def reset_password(new_password: str, email: str) -> bool: """
The reset token is missing or invalid.