Skip to content

Commit 49c4583

Browse files
committed
feature: user forgot-password route & email sending functionality added
1 parent 1767f1b commit 49c4583

File tree

3 files changed

+179
-1
lines changed

3 files changed

+179
-1
lines changed

src/auth/routes.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010
from pkg.errors import UserAlreadyExists
1111
from pkg.mail import send_email
1212

13-
from .schemas import UserCreateResponseSchema, UserCreateSchema
13+
from .schemas import (
14+
UserCreateResponseSchema,
15+
UserCreateSchema,
16+
UserForgotPasswordSchema,
17+
)
1418
from .service import UserService
1519
from .utils import decode_url_safe_token, generate_url_safe_token
1620

@@ -103,3 +107,39 @@ async def activate_user(
103107
).model_dump(),
104108
},
105109
)
110+
111+
112+
@auth_router.post("/forgot-password", status_code=status.HTTP_200_OK)
113+
async def forgot_password(
114+
user_data: UserForgotPasswordSchema,
115+
session: AsyncSession = Depends(get_session),
116+
):
117+
user = await user_service.get_user_by_email(user_data.email, session)
118+
119+
if not user:
120+
return JSONResponse(
121+
status_code=status.HTTP_404_NOT_FOUND,
122+
content={"message": "User not found"},
123+
)
124+
125+
password_reset_token = generate_url_safe_token(
126+
{
127+
"user_uid": str(user.uid),
128+
"expires_at": (datetime.now() + timedelta(minutes=15)).timestamp(),
129+
}
130+
)
131+
password_reset_link = (
132+
f"http://{Config.DOMAIN}/api/v1/auth/reset-password/{password_reset_token}"
133+
)
134+
135+
await send_email(
136+
[user.email],
137+
"Reset your password",
138+
"auth/forgot_password_email.html",
139+
{"first_name": user.first_name, "password_reset_link": password_reset_link},
140+
)
141+
142+
return JSONResponse(
143+
status_code=status.HTTP_200_OK,
144+
content={"message": "Password reset link sent to your email"},
145+
)

src/auth/schemas.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,9 @@ class UserCreateResponseSchema(BaseModel):
4949
}
5050
}
5151
}
52+
53+
54+
class UserForgotPasswordSchema(BaseModel):
55+
email: str = Field(max_length=50)
56+
57+
model_config = {"json_schema_extra": {"example": {"email": "johndoe@example.com"}}}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Reset Your Bookly Password</title>
7+
<style>
8+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
9+
10+
body {
11+
background-color: #fffbeb;
12+
font-family: 'Inter', sans-serif;
13+
margin: 0;
14+
padding: 0;
15+
-webkit-font-smoothing: antialiased;
16+
-moz-osx-font-smoothing: grayscale;
17+
}
18+
.container {
19+
width: 100%;
20+
max-width: 600px;
21+
margin: 40px auto;
22+
background-color: #ffffff;
23+
border-radius: 16px;
24+
box-shadow: 0 4px 24px rgba(234, 179, 8, 0.15);
25+
overflow: hidden;
26+
}
27+
.header {
28+
background-color: #fbbf24;
29+
padding-top: 32px;
30+
padding-bottom: 22px;
31+
text-align: center;
32+
}
33+
.logo {
34+
height: 56px;
35+
width: auto;
36+
}
37+
.content {
38+
padding: 48px 40px;
39+
padding-bottom: 20px;
40+
text-align: left;
41+
}
42+
h1 {
43+
font-size: 28px;
44+
font-weight: 700;
45+
color: #854d0e;
46+
margin-bottom: 24px;
47+
}
48+
p {
49+
font-size: 16px;
50+
line-height: 1.6;
51+
color: #713f12;
52+
}
53+
a {
54+
color: #ca8a04;
55+
text-decoration: none;
56+
font-weight: 600;
57+
}
58+
.button {
59+
display: inline-block;
60+
background-color: #fbbf24;
61+
color: #854d0e;
62+
padding: 14px 32px;
63+
border-radius: 8px;
64+
text-decoration: none;
65+
font-weight: 600;
66+
font-size: 16px;
67+
margin: 12px 0;
68+
transition: background-color 0.3s ease, transform 0.2s ease;
69+
}
70+
.button:hover {
71+
background-color: #f59e0b;
72+
transform: translateY(-2px);
73+
}
74+
.note {
75+
background-color: #fef3c7;
76+
border-left: 4px solid #fbbf24;
77+
padding: 16px;
78+
margin-top: 26px;
79+
border-radius: 0 8px 8px 0;
80+
}
81+
.footer {
82+
background-color: #fffbeb;
83+
padding: 24px 40px;
84+
text-align: center;
85+
font-size: 14px;
86+
color: #92400e;
87+
}
88+
.footer a {
89+
color: #854d0e;
90+
}
91+
.divider {
92+
height: 1px;
93+
background-color: #fde68a;
94+
margin: 32px 0;
95+
}
96+
@media (max-width: 600px) {
97+
.container {
98+
margin: 0;
99+
border-radius: 0;
100+
}
101+
.content {
102+
padding: 32px 24px;
103+
}
104+
.footer {
105+
padding: 24px;
106+
}
107+
}
108+
</style>
109+
</head>
110+
<body>
111+
<div class="container">
112+
<div class="header">
113+
<img src="/api/placeholder/200/80" alt="Bookly Logo" class="logo"/>
114+
</div>
115+
<div class="content">
116+
<h1>Reset Your Password, {{first_name}}</h1>
117+
<p>We received a request to reset your Bookly account password. If you didn't make this request, please ignore this email.</p>
118+
<p>To reset your password, click the button below:</p>
119+
<a href="{{password_reset_link}}" class="button">Reset Your Password</a>
120+
<div class="note">
121+
<p><strong>Important:</strong> This password reset link is valid for 15 minutes. If you don't reset your password within this time, you'll need to request a new link.</p>
122+
</div>
123+
<div class="divider"></div>
124+
<p>If you didn't request for a password reset with Bookly, please disregard this email. Your privacy and security are our top priorities.</p>
125+
</div>
126+
<div class="footer">
127+
<p>&copy; 2024 Bookly - Rohit Vilas Ingole (DataRohit). All rights reserved.</p>
128+
<a href="https://github.com/datarohit">Visit our GitHub</a>
129+
</div>
130+
</div>
131+
</body>
132+
</html>

0 commit comments

Comments
 (0)