UPDATE: Almost fully implemented forget email feature

This commit is contained in:
JustIceO7
2025-02-06 19:30:37 +00:00
parent 00d627a1e2
commit bb67f13417
7 changed files with 63 additions and 52 deletions

View File

@@ -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() {
<Route path="/:streamerName" element={<StreamerRoute />} />
<Route path="/user/:username" element={<UserPage />} />
<Route path="/reset_password" element={<ForgotPasswordPage />}></Route>
<Route path="/reset_password/:token" element={<ResetPasswordPage />}></Route>
<Route path="/category/:category_name" element={<CategoryPage />}></Route>
<Route path="*" element={<NotFoundPage />} />

View File

@@ -28,7 +28,7 @@ const PasswordResetForm: React.FC<SubmitProps> = ({ 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<SubmitProps> = ({ onSubmit, token }) => {
if (validateResetForm()) {
try {
const response = await fetch("/user/reset_password/<string:${token}", {
const response = await fetch(`/api/user/reset_password/${token}/${resetData.newPassword}`, {
method: "POST",
headers: {
"Content-Type": "application/json",

View File

@@ -1,37 +0,0 @@
import React from "react";
import PasswordResetForm from "../components/Auth/PasswordResetForm";
import { useParams, useNavigate } from "react-router-dom";
const ForgotPasswordPage: React.FC = () => {
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 (
<div className="flex flex-col items-center justify-center h-screen">
<h1 className="text-2xl font-bold mb-4">Invalid Token</h1>
<p className="text-red-500">The reset token is missing or invalid.</p>
<button
onClick={() => navigate("/login")}
className="text-blue-500 underline mt-4"
>
Go back to Login
</button>
</div>
);
}
const handlePasswordReset = () => {
};
return (
<div className="flex flex-col items-center justify-center h-screen">
<h1 className="text-2xl font-bold mb-4">Forgot Password</h1>
<PasswordResetForm onSubmit={handlePasswordReset} token={token} />
</div>
);
};
export default ForgotPasswordPage;

View File

@@ -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 (
<div className="flex flex-col items-center justify-center h-screen">
<h1 className="text-2xl font-bold mb-4">Forgot Password</h1>
<PasswordResetForm onSubmit={handlePasswordReset} token={token} />
</div>
);
};
export default ResetPasswordPage;

View File

@@ -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"""

View File

@@ -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/<string:email>', 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/<string:token>/<string:new_password>')
@user_bp.route('/user/reset_password/<string:token>/<string:new_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)
return abort(404)

View File

@@ -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
"""
try:
email = serializer.loads(token, salt=salt_value, max_age=3600)
return email if email else False
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:
"""