From 402213697052b853e42112db4623d890ba6ff637 Mon Sep 17 00:00:00 2001 From: julius Date: Tue, 23 Dec 2025 08:53:46 +0100 Subject: [PATCH] forgotten password routine --- cutt/mail.py | 20 +++++++--- frontend/src/CUTT.tsx | 2 + frontend/src/Login.tsx | 87 ++++++++++++++++++++++++++++++++++++++++-- pyproject.toml | 1 + 4 files changed, 101 insertions(+), 9 deletions(-) diff --git a/cutt/mail.py b/cutt/mail.py index 912b084..10a4bda 100644 --- a/cutt/mail.py +++ b/cutt/mail.py @@ -1,10 +1,13 @@ from email.message import EmailMessage from email.utils import formataddr +from random import random import smtplib import os import ssl +from time import sleep from dotenv import load_dotenv -from fastapi import Response, status +from fastapi.responses import PlainTextResponse +from pydantic import BaseModel from sqlmodel import Session, select from cutt.db import Player, TokenDB, engine from cutt.security import set_password_token @@ -23,10 +26,14 @@ def generate_password_link(user: Player): return f"https://cutt.0124816.xyz/setpassword?token={token}" -def send_forgotten_password_link(email: str): +class EmailRequest(BaseModel): + email: str + + +def send_forgotten_password_link(email: EmailRequest): with Session(engine) as session: user = session.exec( - select(P).where(P.email == email, P.disabled != True) + select(P).where(P.email == email.email, P.disabled != True) ).one_or_none() if user and user.email: link = generate_password_link(user) @@ -51,8 +58,9 @@ def send_forgotten_password_link(email: str): server.starttls(context=context) server.login(os.environ["SMTP_USER"], os.environ["SMTP_PASS"]) server.send_message(msg) + else: + sleep(random()) - return Response( - "a link will be sent to this email, if it belongs to an existing user.", - status_code=status.HTTP_200_OK, + return PlainTextResponse( + "a link will be sent to this email, if it belongs to an existing user." ) diff --git a/frontend/src/CUTT.tsx b/frontend/src/CUTT.tsx index 0d962c4..e5ab79f 100644 --- a/frontend/src/CUTT.tsx +++ b/frontend/src/CUTT.tsx @@ -9,6 +9,7 @@ import { GraphComponent } from "./Network"; import MVPChart from "./MVPChart"; import { SetPassword } from "./SetPassword"; import { Register } from "./Register"; +import { ForgotPassword } from "./Login"; const Maintenance = () => { return ( @@ -28,6 +29,7 @@ function App() { } /> } /> + } /> { onLogin(user); } - function handleSubmit(e: React.FormEvent) { + function handleSubmit(e: FormEvent) { e.preventDefault(); doLogin(); } @@ -86,6 +86,9 @@ export const Login = ({ onLogin }: LoginProps) => { }} />

+

+ forgot password? +

{error &&

{error}

}
@@ -102,3 +105,81 @@ export const Login = ({ onLogin }: LoginProps) => { ); }; + +export const ForgotPassword = () => { + const [email, setEmail] = useState(""); + const [error, setError] = useState(""); + + async function handleSubmit(e: FormEvent) { + e.preventDefault(); + const req = new Request(`${baseUrl}api/forgotten_password`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ email: decodeURI(email) }), + }); + let resp: Response; + try { + resp = await fetch(req); + } catch (e) { + throw new Error(`request failed: ${e}`); + } + if (resp.ok) { + const content = await resp.text(); + if (content) setError(content); + } + + if (!resp.ok) { + const content = await resp.text(); + if (content) setError(content); + else setError("unauthorized"); + throw new Error("Unauthorized"); + } + } + + return ( +
+
+
+

+ cool ultimate team tool +

+
+

Forgot password

+
+
+
+ +
+ { + setEmail(e.target.value); + setError(""); + }} + /> +
+
+
+
+

+ {error} +

+
+
+ +
+
+
+
+ ); +}; diff --git a/pyproject.toml b/pyproject.toml index cdacec5..7f44352 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,4 +21,5 @@ dependencies = [ [dependency-groups] dev = [ "pyqt6>=6.10.1", + "python-dotenv>=1.2.1", ]