implement reset password email
This commit is contained in:
111
cutt/forgotten_password.html
Normal file
111
cutt/forgotten_password.html
Normal file
@@ -0,0 +1,111 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>cutt - set password</title>
|
||||
</head>
|
||||
<body
|
||||
style="
|
||||
margin: auto;
|
||||
max-width: 800px;
|
||||
padding: 2rem 1.5rem;
|
||||
background-color: white;
|
||||
"
|
||||
>
|
||||
<p
|
||||
style="
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="200" viewBox="0 0 80 50">
|
||||
<ellipse
|
||||
cx="40"
|
||||
cy="25"
|
||||
rx="20.263"
|
||||
ry="20.633"
|
||||
style="
|
||||
fill: #c7d6f1;
|
||||
fill-opacity: 1;
|
||||
stroke: #36c;
|
||||
stroke-width: 8.73336;
|
||||
"
|
||||
/>
|
||||
<path
|
||||
d="M0 17.67h80v14.66H0Z"
|
||||
style="
|
||||
fill: #000;
|
||||
stroke-width: 3.56018;
|
||||
paint-order: stroke fill markers;
|
||||
"
|
||||
/>
|
||||
<text
|
||||
x="39.788"
|
||||
y="29.819"
|
||||
style="
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: normal;
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
font-family: Sans;
|
||||
-inkscape-font-specification: "Sans Bold";
|
||||
text-align: center;
|
||||
letter-spacing: 2.83px;
|
||||
writing-mode: lr-tb;
|
||||
direction: ltr;
|
||||
text-anchor: middle;
|
||||
fill: #fff;
|
||||
stroke: #fff;
|
||||
stroke-width: 0.655;
|
||||
stroke-dasharray: none;
|
||||
stroke-opacity: 1;
|
||||
paint-order: stroke fill markers;
|
||||
"
|
||||
>
|
||||
<tspan
|
||||
x="39.788"
|
||||
y="29.819"
|
||||
style="
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: normal;
|
||||
font-size: 14px;
|
||||
font-family: Sans;
|
||||
-inkscape-font-specification: "Sans Bold";
|
||||
letter-spacing: 2.83px;
|
||||
fill: #fff;
|
||||
stroke: #fff;
|
||||
stroke-width: 0.655;
|
||||
stroke-dasharray: none;
|
||||
stroke-opacity: 1;
|
||||
"
|
||||
>
|
||||
CUTT
|
||||
</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
</p>
|
||||
<p>Hello USER,</p>
|
||||
<p>click on the following link to set yourself a new password.</p>
|
||||
<p style="text-align: center; padding: 2rem">
|
||||
<a
|
||||
href="LINK"
|
||||
style="
|
||||
color: ghostwhite;
|
||||
font-weight: bold;
|
||||
background-color: #36c;
|
||||
border-radius: 0.25rem;
|
||||
padding: 1rem;
|
||||
text-decoration: none;
|
||||
"
|
||||
>reset password</a
|
||||
>
|
||||
</p>
|
||||
<p>Cheers,<br />Julius</p>
|
||||
</body>
|
||||
</html>
|
||||
58
cutt/mail.py
Normal file
58
cutt/mail.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from email.message import EmailMessage
|
||||
from email.utils import formataddr
|
||||
import smtplib
|
||||
import os
|
||||
import ssl
|
||||
from dotenv import load_dotenv
|
||||
from fastapi import Response, status
|
||||
from sqlmodel import Session, select
|
||||
from cutt.db import Player, TokenDB, engine
|
||||
from cutt.security import set_password_token
|
||||
|
||||
P = Player
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
def generate_password_link(user: Player):
|
||||
with Session(engine) as session:
|
||||
token = set_password_token(user)
|
||||
if token:
|
||||
session.add(TokenDB(token=token))
|
||||
session.commit()
|
||||
return f"https://cutt.0124816.xyz/setpassword?token={token}"
|
||||
|
||||
|
||||
def send_forgotten_password_link(email: str):
|
||||
with Session(engine) as session:
|
||||
user = session.exec(
|
||||
select(P).where(P.email == email, P.disabled != True)
|
||||
).one_or_none()
|
||||
if user and user.email:
|
||||
link = generate_password_link(user)
|
||||
msg = EmailMessage()
|
||||
msg["Subject"] = "CUTT - reset password"
|
||||
msg["From"] = "CUTT - cool ultimate team tool <cutt@0124816.xyz>"
|
||||
msg["To"] = formataddr((user.display_name, user.email))
|
||||
msg.set_content(
|
||||
f"Hello {user.display_name},\nclick on the following link to set yourself a new password.\n\n{link}\n\nCheers,\nJulius"
|
||||
)
|
||||
with open("cutt/forgotten_password.html") as f:
|
||||
html_body = (
|
||||
f.read().replace("USER", user.display_name).replace("LINK", link)
|
||||
)
|
||||
msg.add_alternative(html_body, subtype="html")
|
||||
context = ssl.create_default_context()
|
||||
with smtplib.SMTP(
|
||||
host=os.environ["SMTP_HOST"],
|
||||
port=int(os.environ["SMTP_PORT"]),
|
||||
timeout=20,
|
||||
) as server:
|
||||
server.starttls(context=context)
|
||||
server.login(os.environ["SMTP_USER"], os.environ["SMTP_PASS"])
|
||||
server.send_message(msg)
|
||||
|
||||
return Response(
|
||||
"a link will be sent to this email, if it belongs to an existing user.",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
@@ -10,6 +10,7 @@ from sqlmodel import (
|
||||
)
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from cutt.analysis import analysis_router
|
||||
from cutt.mail import send_forgotten_password_link
|
||||
from cutt.security import (
|
||||
get_current_active_user,
|
||||
login_for_access_token,
|
||||
@@ -229,6 +230,9 @@ api_router.include_router(
|
||||
api_router.include_router(team_router, dependencies=[Depends(get_current_active_user)])
|
||||
api_router.include_router(analysis_router)
|
||||
api_router.add_api_route("/token", endpoint=login_for_access_token, methods=["POST"])
|
||||
api_router.add_api_route(
|
||||
"/forgotten_password", endpoint=send_forgotten_password_link, methods=["POST"]
|
||||
)
|
||||
api_router.add_api_route("/set_password", endpoint=set_first_password, methods=["POST"])
|
||||
api_router.add_api_route("/register", endpoint=register, methods=["POST"])
|
||||
api_router.add_api_route("/logout", endpoint=logout, methods=["POST"])
|
||||
|
||||
@@ -212,19 +212,17 @@ async def logout(response: Response):
|
||||
return {"message": "Successfully logged out"}
|
||||
|
||||
|
||||
def set_password_token(username: str):
|
||||
user = get_user(username)
|
||||
if user:
|
||||
expire = timedelta(days=30)
|
||||
token = create_access_token(
|
||||
data={
|
||||
"sub": "set password",
|
||||
"username": username,
|
||||
"name": user.display_name,
|
||||
},
|
||||
expires_delta=expire,
|
||||
)
|
||||
return token
|
||||
def set_password_token(user: Player):
|
||||
expire = timedelta(days=30)
|
||||
token = create_access_token(
|
||||
data={
|
||||
"sub": "set password",
|
||||
"username": user.username,
|
||||
"name": user.display_name,
|
||||
},
|
||||
expires_delta=expire,
|
||||
)
|
||||
return token
|
||||
|
||||
|
||||
def register_token(team_id: int):
|
||||
|
||||
@@ -7,7 +7,7 @@ from cutt.db import Player, TokenDB, engine
|
||||
if len(sys.argv) > 1:
|
||||
with Session(engine) as session:
|
||||
for p in session.exec(
|
||||
select(Player.username).where(Player.username == sys.argv[1].strip())
|
||||
select(Player).where(Player.username == sys.argv[1].strip())
|
||||
):
|
||||
print(p)
|
||||
token = set_password_token(p)
|
||||
|
||||
Reference in New Issue
Block a user