User email confirmation

Sending a confirmation email on registration

Want more?

This lesson for enrolled students only. Join the course to unlock it!

You can see the code changes implemented in this lecture below.

If you have purchased the course in a different platform, you still have access to the code changes per lecture here on Teclado. The lecture video and lecture notes remain locked.
Join course for $30

Modified files

storeapi/routers/user.py
--- 
+++ 
@@ -1,6 +1,7 @@
 import logging

 from fastapi import APIRouter, HTTPException, Request, status
+from storeapi import tasks
 from storeapi.database import database, user_table
 from storeapi.models.user import UserIn
 from storeapi.security import (
@@ -29,12 +30,13 @@
     logger.debug(query)

     await database.execute(query)
-    return {
-        "detail": "User created. Please confirm your email.",
-        "confirmation_url": request.url_for(
+    await tasks.send_user_registration_email(
+        user.email,
+        confirmation_url=request.url_for(
             "confirm_email", token=create_confirmation_token(user.email)
         ),
-    }
+    )
+    return {"detail": "User created. Please confirm your email."}


 @router.post("/token")
storeapi/tests/test_security.py
--- 
+++ 
@@ -129,5 +129,6 @@
 @pytest.mark.anyio
 async def test_get_current_user_wrong_type_token(registered_user: dict):
     token = security.create_confirmation_token(registered_user["email"])
+
     with pytest.raises(security.HTTPException):
         await security.get_current_user(token)
storeapi/tests/routers/test_user.py
--- 
+++ 
@@ -1,6 +1,6 @@
 import pytest
-from fastapi import Request
 from httpx import AsyncClient
+from storeapi import tasks


 async def register_user(async_client: AsyncClient, email: str, password: str):
@@ -29,13 +29,10 @@

 @pytest.mark.anyio
 async def test_confirm_user(async_client: AsyncClient, mocker):
-    spy = mocker.spy(Request, "url_for")
+    spy = mocker.spy(tasks, "send_user_registration_email")
     await register_user(async_client, "test@example.net", "1234")

-    # We only call Request.url_for once, so this is OK.
-    # This is not a scalable solution.
-    # A better solution will be discussed in the next couple lectures.
-    confirmation_url = str(spy.spy_return)
+    confirmation_url = str(spy.call_args[1]["confirmation_url"])
     response = await async_client.get(confirmation_url)

     assert response.status_code == 200
@@ -51,10 +48,10 @@
 @pytest.mark.anyio
 async def test_confirm_user_expired_token(async_client: AsyncClient, mocker):
     mocker.patch("storeapi.security.confirm_token_expire_minutes", return_value=-1)
-    spy = mocker.spy(Request, "url_for")
+    spy = mocker.spy(tasks, "send_user_registration_email")
     await register_user(async_client, "test@example.net", "1234")

-    confirmation_url = str(spy.spy_return)
+    confirmation_url = str(spy.call_args[1]["confirmation_url"])
     response = await async_client.get(confirmation_url)

     assert response.status_code == 401
@@ -80,7 +77,7 @@
             "password": registered_user["password"],
         },
     )
-    assert response.status_code == 400
+    assert response.status_code == 401


 @pytest.mark.anyio