|
from aiohttp import web
|
|
import aiohttp_jinja2
|
|
from aiohttp_session import get_session, new_session
|
|
from aiohttp_pydantic import PydanticView
|
|
from pydantic import BaseModel, EmailStr, Field, ValidationError
|
|
|
|
from ..services.user_service import UserService
|
|
from ..models import RegistrationModel
|
|
from ..helpers.email_sender import send_email # Import send_email
|
|
|
|
|
|
class LoginModel(BaseModel):
|
|
email: EmailStr
|
|
password: str
|
|
|
|
|
|
class ForgotPasswordModel(BaseModel):
|
|
email: EmailStr
|
|
|
|
|
|
class ResetPasswordModel(BaseModel):
|
|
password: str = Field(min_length=8)
|
|
confirm_password: str
|
|
|
|
|
|
class CustomPydanticView(PydanticView):
|
|
template_name: str = ""
|
|
|
|
async def on_validation_error(
|
|
self, exception: ValidationError, context: str
|
|
):
|
|
errors = {
|
|
err["loc"][0]: err["msg"] for err in exception.errors()
|
|
}
|
|
return aiohttp_jinja2.render_template(
|
|
self.template_name, self.request, {"errors": errors, "request": self.request}
|
|
)
|
|
|
|
|
|
class LoginView(CustomPydanticView):
|
|
template_name = "pages/login.html"
|
|
|
|
async def get(self):
|
|
return aiohttp_jinja2.render_template(
|
|
self.template_name, self.request, {"request": self.request, "errors": {}, "data": {}}
|
|
)
|
|
|
|
async def post(self):
|
|
try:
|
|
login_data = LoginModel(**await self.request.post())
|
|
except ValidationError as e:
|
|
errors = {err["loc"][0]: err["msg"] for err in e.errors()}
|
|
return aiohttp_jinja2.render_template(
|
|
self.template_name, self.request, {"errors": errors, "request": self.request, "data": dict(await self.request.post())}
|
|
)
|
|
|
|
user_service: UserService = self.request.app["user_service"]
|
|
if user_service.authenticate_user(login_data.email, login_data.password):
|
|
session = await new_session(self.request)
|
|
session["user_email"] = login_data.email
|
|
raise web.HTTPFound("/dashboard")
|
|
return aiohttp_jinja2.render_template(
|
|
self.template_name,
|
|
self.request,
|
|
{"error": "Invalid email or password", "request": self.request, "errors": {}, "data": dict(await self.request.post())},
|
|
)
|
|
|
|
|
|
class RegistrationView(CustomPydanticView):
|
|
template_name = "pages/register.html"
|
|
|
|
async def get(self):
|
|
return aiohttp_jinja2.render_template(
|
|
self.template_name, self.request, {"request": self.request, "errors": {}, "data": {}}
|
|
)
|
|
|
|
async def post(self):
|
|
data = await self.request.post() # Get raw form data once
|
|
try:
|
|
user_data = RegistrationModel(**data)
|
|
except ValidationError as e:
|
|
errors = {err["loc"][0]: err["msg"] for err in e.errors()}
|
|
return aiohttp_jinja2.render_template(
|
|
self.template_name, self.request, {"errors": errors, "request": self.request, "data": dict(data)}
|
|
)
|
|
|
|
if user_data.password != data.get("confirm_password"):
|
|
return aiohttp_jinja2.render_template(
|
|
self.template_name,
|
|
self.request,
|
|
{"error": "Passwords do not match", "request": self.request, "errors": {}, "data": dict(data)},
|
|
)
|
|
|
|
user_service: UserService = self.request.app["user_service"]
|
|
try:
|
|
user_service.create_user(user_data.full_name, user_data.email, user_data.password) # Changed username to full_name
|
|
|
|
# Render email content
|
|
email_context = {
|
|
"user_name": user_data.full_name,
|
|
"login_url": self.request.url.join(self.request.app.router["login"].url_for()).human_repr(),
|
|
"support_url": self.request.url.join(self.request.app.router["support"].url_for()).human_repr(),
|
|
}
|
|
env = aiohttp_jinja2.get_env(self.request.app)
|
|
template = env.get_template("emails/welcome.html")
|
|
email_body = template.render(email_context)
|
|
|
|
# Schedule email sending
|
|
await self.request.app["scheduler"].spawn(
|
|
send_email(
|
|
self.request.app,
|
|
user_data.email,
|
|
"Welcome to Retoor's Cloud Solutions!",
|
|
email_body
|
|
)
|
|
)
|
|
|
|
raise web.HTTPFound("/login")
|
|
except ValueError:
|
|
return aiohttp_jinja2.render_template(
|
|
self.template_name,
|
|
self.request,
|
|
{
|
|
"error": "User with this email already exists",
|
|
"request": self.request,
|
|
"errors": {},
|
|
"data": dict(data),
|
|
},
|
|
)
|
|
|
|
|
|
class ForgotPasswordView(CustomPydanticView):
|
|
template_name = "pages/forgot_password.html"
|
|
|
|
async def get(self):
|
|
return aiohttp_jinja2.render_template(
|
|
self.template_name, self.request, {"request": self.request, "errors": {}, "data": {}}
|
|
)
|
|
|
|
async def post(self):
|
|
data = await self.request.post()
|
|
try:
|
|
forgot_password_data = ForgotPasswordModel(**data)
|
|
except ValidationError as e:
|
|
errors = {err["loc"][0]: err["msg"] for err in e.errors()}
|
|
return aiohttp_jinja2.render_template(
|
|
self.template_name, self.request, {"errors": errors, "request": self.request, "data": dict(data)}
|
|
)
|
|
|
|
user_service: UserService = self.request.app["user_service"]
|
|
user = user_service.get_user_by_email(forgot_password_data.email)
|
|
|
|
if user:
|
|
token = user_service.generate_reset_token(forgot_password_data.email)
|
|
if token:
|
|
reset_link = self.request.url.join(
|
|
self.request.app.router["reset_password"].url_for(token=token)
|
|
).human_repr()
|
|
|
|
email_context = {
|
|
"user_name": user["full_name"],
|
|
"reset_link": reset_link,
|
|
}
|
|
env = aiohttp_jinja2.get_env(self.request.app)
|
|
template = env.get_template("emails/password_reset_request.html")
|
|
email_body = template.render(email_context)
|
|
|
|
await self.request.app["scheduler"].spawn(
|
|
send_email(
|
|
self.request.app,
|
|
forgot_password_data.email,
|
|
"Retoor's Cloud Solutions - Password Reset Request",
|
|
email_body
|
|
)
|
|
)
|
|
|
|
# Always show a success message to prevent email enumeration attacks
|
|
return aiohttp_jinja2.render_template(
|
|
self.template_name,
|
|
self.request,
|
|
{"message": "If an account with that email exists, a password reset link has been sent.", "request": self.request, "errors": {}, "data": {}},
|
|
)
|
|
|
|
|
|
class ResetPasswordView(CustomPydanticView):
|
|
template_name = "pages/reset_password.html"
|
|
|
|
async def get(self):
|
|
token = self.request.match_info.get("token")
|
|
if not token:
|
|
raise web.HTTPFound("/forgot_password") # Or show an error page
|
|
|
|
# We don't validate the token here, only when the form is submitted
|
|
return aiohttp_jinja2.render_template(
|
|
self.template_name, self.request, {"request": self.request, "errors": {}, "token": token}
|
|
)
|
|
|
|
async def post(self):
|
|
token = self.request.match_info.get("token")
|
|
if not token:
|
|
raise web.HTTPFound("/forgot_password") # Or show an error page
|
|
|
|
data = await self.request.post()
|
|
try:
|
|
reset_password_data = ResetPasswordModel(**data)
|
|
except ValidationError as e:
|
|
errors = {err["loc"][0]: err["msg"] for err in e.errors()}
|
|
return aiohttp_jinja2.render_template(
|
|
self.template_name, self.request, {"errors": errors, "request": self.request, "token": token}
|
|
)
|
|
|
|
if reset_password_data.password != reset_password_data.confirm_password:
|
|
return aiohttp_jinja2.render_template(
|
|
self.template_name,
|
|
self.request,
|
|
{"error": "Passwords do not match", "request": self.request, "errors": {}, "token": token},
|
|
)
|
|
|
|
user_service: UserService = self.request.app["user_service"]
|
|
user = user_service.get_user_by_reset_token(token) # Corrected method call
|
|
if not user:
|
|
return aiohttp_jinja2.render_template(
|
|
self.template_name,
|
|
self.request,
|
|
{"error": "Invalid or expired password reset link.", "request": self.request, "errors": {}, "token": token},
|
|
)
|
|
|
|
if user_service.reset_password(user["email"], token, reset_password_data.password):
|
|
# Send password changed confirmation email
|
|
email_context = {
|
|
"user_name": user["full_name"],
|
|
"login_url": self.request.url.join(self.request.app.router["login"].url_for()).human_repr(),
|
|
}
|
|
env = aiohttp_jinja2.get_env(self.request.app)
|
|
template = env.get_template("emails/password_changed_confirmation.html")
|
|
email_body = template.render(email_context)
|
|
|
|
await self.request.app["scheduler"].spawn(
|
|
send_email(
|
|
self.request.app,
|
|
user["email"],
|
|
"Retoor's Cloud Solutions - Your Password Has Been Changed",
|
|
email_body
|
|
)
|
|
)
|
|
raise web.HTTPFound("/login?message=password_reset_success")
|
|
else:
|
|
return aiohttp_jinja2.render_template(
|
|
self.template_name,
|
|
self.request,
|
|
{"error": "Invalid or expired password reset link.", "request": self.request, "errors": {}, "token": token},
|
|
)
|
|
|
|
|
|
class LogoutView(web.View):
|
|
async def get(self):
|
|
session = await get_session(self.request)
|
|
session.clear()
|
|
raise web.HTTPFound("/") |