From 27c908e1ed0f3450be799c9aaebd621c3b53f26f Mon Sep 17 00:00:00 2001 From: edraft Date: Fri, 2 May 2025 03:02:49 +0200 Subject: [PATCH] User invitations #15 --- .../mutations/user_space_mutation.py | 23 +++++++++- api/src/service/mail_service.py | 46 +++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 api/src/service/mail_service.py diff --git a/api/src/api_graphql/mutations/user_space_mutation.py b/api/src/api_graphql/mutations/user_space_mutation.py index 0bd6956..0f95436 100644 --- a/api/src/api_graphql/mutations/user_space_mutation.py +++ b/api/src/api_graphql/mutations/user_space_mutation.py @@ -1,5 +1,6 @@ from uuid import uuid4 +from api.middleware.request import get_request from api.route import Route from api_graphql.abc.mutation_abc import MutationABC from api_graphql.field.mutation_field_builder import MutationFieldBuilder @@ -15,6 +16,7 @@ from data.schemas.public.user_space import UserSpace from data.schemas.public.user_space_dao import userSpaceDao from data.schemas.public.user_space_user import UserSpaceUser from data.schemas.public.user_space_user_dao import userSpaceUserDao +from service.mail_service import mailService from service.permission.permissions_enum import Permissions logger = APILogger(__name__) @@ -156,6 +158,7 @@ class UserSpaceMutation(MutationABC): email_invitations = {} + base_url = str(get_request().base_url) for email in emails: if email in user_space_user_emails: continue @@ -180,14 +183,30 @@ class UserSpaceMutation(MutationABC): user_space.id, ) ) + email_invitations[email] = ( + f"{base_url}api/invitation/accept/{invitation}" + ) continue - email_invitations[email] = invitation + email_invitations[email] = ( + f"{base_url}api/invitation/user-space/accept/{invitation}" + ) await userSpaceUserDao.create( UserSpaceUser(0, user_space.id, user.id, str(invitation)) ) - # todo: send actual emails + for email, invitation in email_invitations.items(): + logger.debug(f"Invitation for {email}: {invitation}") + mailService.send( + email, + "You're invited to join Open-Redirect", + f"Hi,\n\nYou’ve been invited to join Open-Redirect, a secure collaboration platform for your team. " + f"To complete your registration, please use the following invitation code:\n\n" + f"{invitation}\n\n" + "If you weren’t expecting this invitation, you can safely ignore this message.\n\n" + "Thanks,\n" + "The Open-Redirect Team", + ) return True diff --git a/api/src/service/mail_service.py b/api/src/service/mail_service.py new file mode 100644 index 0000000..884ce14 --- /dev/null +++ b/api/src/service/mail_service.py @@ -0,0 +1,46 @@ +import smtplib +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText + +from core.environment import Environment +from core.logger import Logger + +logger = Logger("MailService") + + +class MailService: + + def __init__(self): + self._server = Environment.get("MAIL_SERVER", str) + self._port = Environment.get("MAIL_PORT", int) + self._user = Environment.get("MAIL_USER", str) + self._password = Environment.get("MAIL_PASSWORD", str) + self._from = Environment.get("MAIL_FROM", str) + + if not all([self._server, self._port, self._user, self._password, self._from]): + raise ValueError("Mail server configuration is incomplete.") + + def send(self, to: str, subject: str, body: str): + logger.debug(f"Preparing to send email to: {to}, subject: {subject}") + + msg = MIMEMultipart() + msg["From"] = self._from + msg["To"] = to + msg["Subject"] = subject + + msg.attach(MIMEText(body, "plain")) + + try: + logger.info(f"Connecting to mail server: {self._server}:{self._port}") + with smtplib.SMTP(self._server, self._port) as server: + server.starttls() + logger.debug("Starting TLS encryption") + server.login(self._user, self._password) + logger.info(f"Logged in as: {self._user}") + server.sendmail(self._from, to, msg.as_string()) + logger.info(f"Email successfully sent to: {to}") + except Exception as e: + logger.error(f"Failed to send email to {to}", e) + + +mailService = MailService()