forgotten password routine

This commit is contained in:
2025-12-23 08:53:46 +01:00
parent d0140f4cfb
commit 4022136970
4 changed files with 101 additions and 9 deletions

View File

@@ -1,10 +1,13 @@
from email.message import EmailMessage from email.message import EmailMessage
from email.utils import formataddr from email.utils import formataddr
from random import random
import smtplib import smtplib
import os import os
import ssl import ssl
from time import sleep
from dotenv import load_dotenv 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 sqlmodel import Session, select
from cutt.db import Player, TokenDB, engine from cutt.db import Player, TokenDB, engine
from cutt.security import set_password_token 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}" 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: with Session(engine) as session:
user = session.exec( 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() ).one_or_none()
if user and user.email: if user and user.email:
link = generate_password_link(user) link = generate_password_link(user)
@@ -51,8 +58,9 @@ def send_forgotten_password_link(email: str):
server.starttls(context=context) server.starttls(context=context)
server.login(os.environ["SMTP_USER"], os.environ["SMTP_PASS"]) server.login(os.environ["SMTP_USER"], os.environ["SMTP_PASS"])
server.send_message(msg) server.send_message(msg)
else:
sleep(random())
return Response( return PlainTextResponse(
"a link will be sent to this email, if it belongs to an existing user.", "a link will be sent to this email, if it belongs to an existing user."
status_code=status.HTTP_200_OK,
) )

View File

@@ -9,6 +9,7 @@ import { GraphComponent } from "./Network";
import MVPChart from "./MVPChart"; import MVPChart from "./MVPChart";
import { SetPassword } from "./SetPassword"; import { SetPassword } from "./SetPassword";
import { Register } from "./Register"; import { Register } from "./Register";
import { ForgotPassword } from "./Login";
const Maintenance = () => { const Maintenance = () => {
return ( return (
@@ -28,6 +29,7 @@ function App() {
<Routes> <Routes>
<Route path="/register" element={<Register />} /> <Route path="/register" element={<Register />} />
<Route path="/setpassword" element={<SetPassword />} /> <Route path="/setpassword" element={<SetPassword />} />
<Route path="/forgotten_password" element={<ForgotPassword />} />
<Route <Route
path="/*" path="/*"
element={ element={

View File

@@ -1,5 +1,5 @@
import { useEffect, useState } from "react"; import { FormEvent, useEffect, useState } from "react";
import { currentUser, login, User } from "./api"; import { baseUrl, currentUser, login, User } from "./api";
import Header from "./Header"; import Header from "./Header";
import { useLocation, useNavigate } from "react-router"; import { useLocation, useNavigate } from "react-router";
import { Eye, EyeSlash } from "./Icons"; import { Eye, EyeSlash } from "./Icons";
@@ -35,7 +35,7 @@ export const Login = ({ onLogin }: LoginProps) => {
onLogin(user); onLogin(user);
} }
function handleSubmit(e: React.FormEvent) { function handleSubmit(e: FormEvent) {
e.preventDefault(); e.preventDefault();
doLogin(); doLogin();
} }
@@ -86,6 +86,9 @@ export const Login = ({ onLogin }: LoginProps) => {
}} }}
/> />
</p> </p>
<p className="help is-link has-text-right">
<a href="forgotten_password">forgot password?</a>
</p>
{error && <p className="help is-danger">{error}</p>} {error && <p className="help is-danger">{error}</p>}
</div> </div>
<div className="control"> <div className="control">
@@ -102,3 +105,81 @@ export const Login = ({ onLogin }: LoginProps) => {
</form> </form>
); );
}; };
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 (
<section className="section is-medium">
<div className="container is-max-tablet">
<div className="block">
<p className="level-item has-text-centered">
<img
className="image"
alt="cool ultimate team tool"
src="cutt.svg"
style={{ width: 200 }}
/>
</p>
</div>
<h1 className="title">Forgot password</h1>
<form onSubmit={handleSubmit}>
<div className="field">
<div className="field">
<label className="label">email</label>
<div className="control">
<input
className="input"
required
type="email"
value={email}
onChange={(e) => {
setEmail(e.target.value);
setError("");
}}
/>
</div>
</div>
</div>
<div className="field">
<p className={"help" + (error ? " is-danger" : " is-success")}>
{error}
</p>
</div>
<div className="field is-grouped is-grouped-centered">
<button className="button is-light is-primary">send email</button>
</div>
</form>
</div>
</section>
);
};

View File

@@ -21,4 +21,5 @@ dependencies = [
[dependency-groups] [dependency-groups]
dev = [ dev = [
"pyqt6>=6.10.1", "pyqt6>=6.10.1",
"python-dotenv>=1.2.1",
] ]