diff --git a/docker-compose.yml b/docker-compose.yml
index 893a81c6..fe3cff02 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -28,9 +28,7 @@ services:
volumes:
- ${MONDEY_SSL_CERT:-./cert.pem}:/mondey_ssl_cert.pem
- ${MONDEY_SSL_KEY:-./key.pem}:/mondey_ssl_key.pem
-# email:
-# image: "boky/postfix"
-# environment:
-# - ALLOW_EMPTY_SENDER_DOMAINS="true"
-# networks:
-# - mondey-network
+ email:
+ image: "boky/postfix"
+ environment:
+ - ALLOW_EMPTY_SENDER_DOMAINS="true"
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
index d7191075..07727750 100644
--- a/frontend/Dockerfile
+++ b/frontend/Dockerfile
@@ -7,7 +7,9 @@ ARG MONDEY_API_URL
WORKDIR /app
-COPY package*.json ./
+COPY package.json ./
+
+COPY pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install
diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json
index 13c06751..8c6b1501 100644
--- a/frontend/src/locales/de.json
+++ b/frontend/src/locales/de.json
@@ -101,6 +101,8 @@
"submitButtonLabel": "Absenden",
"selectPlaceholder": "Bitte auswählen",
"successMessage": "Bitte überprüfen sie ihr E-Mail Postfach",
+ "emailValidationMessage": "Ihre E-Mail-Adresse wurde bestätigt und Sie können sich jetzt anmelden.",
+ "emailValidationError": "Ungültiger oder abgelaufener E-Mail-Validierungslink",
"goHome": "Zur Hauptseite"
},
"login": {
diff --git a/frontend/src/routes/verify/[[code]]/+page.svelte b/frontend/src/routes/verify/[[code]]/+page.svelte
new file mode 100644
index 00000000..67f7553c
--- /dev/null
+++ b/frontend/src/routes/verify/[[code]]/+page.svelte
@@ -0,0 +1,44 @@
+
+
+
+ {#if success}
+
+
+
+ {$_('registration.emailValidationMessage')}
+
+
+
+ {:else}
+
+
+
+ {$_('registration.emailValidationError')}
+
+
+ {/if}
+
diff --git a/mondey_backend/src/mondey_backend/settings.py b/mondey_backend/src/mondey_backend/settings.py
index af2a7acd..df99559d 100644
--- a/mondey_backend/src/mondey_backend/settings.py
+++ b/mondey_backend/src/mondey_backend/settings.py
@@ -14,6 +14,7 @@ class AppSettings(BaseSettings):
PRIVATE_FILES_PATH: str = "private"
ENABLE_CORS: bool = True
HOST: str = "localhost"
+ SMTP_HOST: str = "email:587"
PORT: int = 8000
RELOAD: bool = True
LOG_LEVEL: str = "debug"
diff --git a/mondey_backend/src/mondey_backend/users.py b/mondey_backend/src/mondey_backend/users.py
index da5a0823..f213105d 100644
--- a/mondey_backend/src/mondey_backend/users.py
+++ b/mondey_backend/src/mondey_backend/users.py
@@ -1,8 +1,8 @@
-# TODO: 17th Oct. 2024: remove the artificial verification setting again as soon as
-# the email verification server has been implemented. See 'README' block @ line 33f
-
from __future__ import annotations
+import logging
+import smtplib
+from email.message import EmailMessage
from typing import Annotated
from fastapi import Depends
@@ -18,29 +18,30 @@
from .databases.users import AccessToken
from .databases.users import User
-from .databases.users import async_session_maker
from .databases.users import get_access_token_db
from .databases.users import get_user_db
from .settings import app_settings
+def send_email_validation_link(email: str, token: str) -> None:
+ msg = EmailMessage()
+ msg["From"] = "no-reply@mondey.lkeegan.dev"
+ msg["To"] = email
+ msg["Subject"] = "MONDEY-Konto aktivieren"
+ msg.set_content(
+ f"Bitte klicken Sie hier, um Ihr MONDEY-Konto zu aktivieren:\n\nhttps://mondey.lkeegan.dev/verify/{token}\n\n-----\n\nPlease click here to activate your MONDEY account:\n\nhttps://mondey.lkeegan.dev/verify/{token}"
+ )
+ with smtplib.SMTP(app_settings.SMTP_HOST) as s:
+ s.send_message(msg)
+
+
class UserManager(IntegerIDMixin, BaseUserManager[User, int]):
reset_password_token_secret = app_settings.SECRET
verification_token_secret = app_settings.SECRET
async def on_after_register(self, user: User, request: Request | None = None):
- # README: Sets the verified flag artificially to allow users to work without an
- # actual verification process for now. this can go again as soon as we have an email server for verification.
- async with async_session_maker() as session:
- user_db = await session.get(User, user.id)
- if user_db:
- user_db.is_verified = True
- await session.commit()
- await session.refresh(user_db)
-
- print(f"User {user_db.id} has registered.")
- print(f"User is verified? {user_db.is_verified}")
- # end README
+ logging.info(f"User {user.email} registered.")
+ await self.request_verify(user, request)
async def on_after_forgot_password(
self, user: User, token: str, request: Request | None = None
@@ -50,7 +51,10 @@ async def on_after_forgot_password(
async def on_after_request_verify(
self, user: User, token: str, request: Request | None = None
):
- print(f"Verification requested for user {user.id}. Verification token: {token}")
+ logging.info(
+ f"Verification requested for user {user.id}. Verification token: {token}"
+ )
+ send_email_validation_link(user.email, token)
async def get_user_manager(
diff --git a/mondey_backend/tests/routers/test_auth.py b/mondey_backend/tests/routers/test_auth.py
new file mode 100644
index 00000000..938b6abf
--- /dev/null
+++ b/mondey_backend/tests/routers/test_auth.py
@@ -0,0 +1,46 @@
+import smtplib
+from email.message import EmailMessage
+
+import pytest
+from fastapi.testclient import TestClient
+
+
+class SMTPMock:
+ last_message: EmailMessage | None = None
+
+ def __init__(self, *args, **kwargs):
+ pass
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args):
+ pass
+
+ def send_message(self, msg: EmailMessage, **kwargs):
+ SMTPMock.last_message = msg
+
+
+@pytest.fixture
+def smtp_mock(monkeypatch: pytest.MonkeyPatch):
+ monkeypatch.setattr(smtplib, "SMTP", SMTPMock)
+ return SMTPMock
+
+
+def test_register_new_user(public_client: TestClient, smtp_mock: SMTPMock):
+ assert smtp_mock.last_message is None
+ email = "u1@asdgdasf.com"
+ response = public_client.post(
+ "/auth/register", json={"email": email, "password": "p1"}
+ )
+ assert response.status_code == 201
+ msg = smtp_mock.last_message
+ assert msg is not None
+ assert "aktivieren" in msg.get("Subject").lower()
+ assert msg.get("To") == email
+ assert "/verify/" in msg.get_content()
+ response = public_client.post("/auth/verify", json={"token": "invalid-token"})
+ assert response.status_code == 400
+ token = msg.get_content().split("\n\n")[1].rsplit("/")[-1]
+ response = public_client.post("/auth/verify", json={"token": token})
+ assert response.status_code == 200