Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .coverage
Binary file not shown.
1 change: 1 addition & 0 deletions app/middlewares.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ async def dispatch(
self, request: Request, call_next: RequestResponseEndpoint
) -> Response:
client_ip = request.client.host # type: ignore

if "/docs" in request.url.path:
if client_ip not in self.allowed_ips:
return JSONResponse(
Expand Down
20 changes: 11 additions & 9 deletions app/routers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,18 @@
async def signup(
db: DBDep,
bg_task: BackgroundTasks, # needed to send verification/welcome email
request_data: auth_schemas.UserSignUpData,
payload: auth_schemas.UserSignUpData,
):
return await auth_services.signup_user(request_data, db, bg_task)
return await auth_services.signup_user(payload, db, bg_task)


@router.post("/activation")
async def activate_user(
db: DBDep,
bg_task: BackgroundTasks, # needed to send verification/welcome email
payload: auth_schemas.UserVerificationModel,
):
return await auth_services.activate_user(payload, db, bg_task)


@router.post("/initiate_password_reset")
Expand All @@ -41,13 +50,6 @@ async def initiate_password_reset(
return await auth_services.initiate_password_reset(email, db, background_task)


@router.post("/verify_password_reset")
async def verify_reset_password_otp(
db: DBDep, code: Annotated[str, Body(embed=True)], email: EmailBody
):
return await auth_services.verify_reset_password_otp(code, email, db)


@router.post("/reset_password")
async def reset_password(
db: DBDep,
Expand Down
24 changes: 11 additions & 13 deletions app/routers/tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,33 +53,31 @@ async def test_signup_fails(
assert error_msg == error_message


async def test_initiate_password_reset(client: AsyncClient, signup_data: dict):
data = {"email": signup_data["email"]}
async def test_activate_user(client: AsyncClient, user: UserDB):
redis_manager.cache_json_item(f"verification-code-{user.email}", {"code": "000000"})
response: Response = await client.post(
"/v1/auth/initiate_password_reset", json=data
"/v1/auth/activation", json={"code": "000000", "email": user.email}
)
assert response.status_code == 200
assert response.json().get("detail") == "Password reset code sent"
assert response.json().get("detail") == "Email Activation Successful"


async def test_verify_reset_password_otp(client: AsyncClient, user: UserDB):
# Seed the value to be validated against
redis_manager.cache_json_item(f"reset-code-{user.email}", {"code": "0000"})
verification_data = {"email": user.email, "code": "0000"}
async def test_initiate_password_reset(client: AsyncClient, signup_data: dict):
data = {"email": signup_data["email"]}
response: Response = await client.post(
"/v1/auth/verify_password_reset", json=verification_data
"/v1/auth/initiate_password_reset", json=data
)
assert response.status_code == 200
assert response.json().get("detail") == "Verification is Successful"
assert response.json().get("detail") == "Password Reset Code Sent"


async def test_reset_password(client: AsyncClient, user: UserDB):
# Seed the value to be validated against
redis_manager.cache_json_item(f"reset-code-{user.email}", {"code": "0000"})
data = {"new_password": "password", "email": user.email, "code": "0000"}
redis_manager.cache_json_item(f"reset-code-{user.email}", {"code": "000000"})
data = {"new_password": "password", "email": user.email, "code": "000000"}
response: Response = await client.post("/v1/auth/reset_password", json=data)
assert response.status_code == 200
assert response.json().get("detail") == "Password reset successfully"
assert response.json().get("detail") == "Password Reset Successfully"


async def test_reset_password_fails(client: AsyncClient, user: UserDB):
Expand Down
42 changes: 25 additions & 17 deletions app/services/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ async def initiate_password_reset(
):
user = await get_user(email, session)
if not user:
return {"detail": "Password reset code sent"}
return {"detail": "Password Reset Code Sent"}

code = str(randint(1000, 9999))
redis_manager.cache_json_item(
Expand All @@ -147,19 +147,7 @@ async def initiate_password_reset(
template="auth/initiate_password_reset.html",
)

return {"detail": "Password reset code sent"}


async def verify_reset_password_otp(code: str, email: str, session: AsyncSession):
data = redis_manager.get_json_item(f"reset-code-{email}")
if not data or data.get("code") != code:
raise HTTPException(status_code=400, detail="Invalid Reset Code")

user = await get_user(email, session)
if not user:
raise HTTPException(status_code=400, detail="Invalid Reset Code")

return {"detail": "Verification is Successful"}
return {"detail": "Password Reset Code Sent"}


async def reset_password(
Expand All @@ -184,7 +172,7 @@ async def reset_password(
await session.execute(stmt)
await session.commit()

return {"detail": "Password reset successfully"}
return {"detail": "Password Reset Successfully"}


async def update_user(
Expand Down Expand Up @@ -262,14 +250,34 @@ async def signup_user(
):
user = await create_user(data, session)

background_task.add_task(
return user


async def activate_user(
verification_data: auth_schema.UserVerificationModel,
session: AsyncSession,
bg_task: BackgroundTasks,
):
data = redis_manager.get_json_item(f"verification-code-{verification_data.email}")

if not data or data.get("code") != verification_data.code:
raise HTTPException(status_code=400, detail="Invalid Reset Code")

user = await get_user(verification_data.email, session)
if not user:
raise HTTPException(status_code=400, detail="Invalid Reset Code")

# TODO: Perform the actual user verification here

bg_task.add_task(
send_mail,
subject="Welcome to {{ project_name }}",
receipients=[user.email],
payload={"username": user.email.split("@")[0].title()},
template="auth/welcome.html",
)
return user

return {"detail": "Email Activation Successful"}


async def signin_user(data: auth_schema.UserSignInData, session: AsyncSession):
Expand Down
33 changes: 3 additions & 30 deletions app/services/tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,41 +108,14 @@ async def test_initiate_password_reset_for_user(user: UserDB, session: AsyncSess
result = await auth_services.initiate_password_reset(
user.email, session, BackgroundTasks(tasks=[])
)
assert result == {"detail": "Password reset code sent"}
assert result == {"detail": "Password Reset Code Sent"}


async def test_initiate_password_reset_for_none_user(session: AsyncSession):
result = await auth_services.initiate_password_reset(
faker.email(), session, BackgroundTasks(tasks=[])
)
assert result == {"detail": "Password reset code sent"}


async def test_verify_reset_password_otp_for_user(user: UserDB, session: AsyncSession):
code = "000000"
redis_manager.cache_json_item(f"reset-code-{user.email}", {"code": code})
result = await auth_services.verify_reset_password_otp(code, user.email, session)
assert result == {"detail": "Verification is Successful"}


@pytest.mark.parametrize(
"code",
[
"000001",
"000000",
],
)
async def test_verify_reset_password_otp_fails(session: AsyncSession, code: str):
none_existence_email = faker.email()
redis_manager.cache_json_item(
f"reset-code-{none_existence_email}", {"code": "000000"}
)
with pytest.raises(HTTPException) as err:
await auth_services.verify_reset_password_otp(
code, none_existence_email, session
)

assert err.value.detail == "Invalid Reset Code"
assert result == {"detail": "Password Reset Code Sent"}


async def test_reset_password_for_user(user: UserDB, session: AsyncSession):
Expand All @@ -153,7 +126,7 @@ async def test_reset_password_for_user(user: UserDB, session: AsyncSession):
),
session,
)
assert result == {"detail": "Password reset successfully"}
assert result == {"detail": "Password Reset Successfully"}


@pytest.mark.parametrize(
Expand Down
Empty file.
20 changes: 20 additions & 0 deletions app/tests/test_middlewares.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import pytest
from httpx import ASGITransport, AsyncClient, Response

from app.main import app


# Lazy override of the global client fixture to patch the ip address
@pytest.fixture
async def client():
transport = ASGITransport(
app=app,
client=("10.0.0.5", 12345),
)
async with AsyncClient(transport=transport, base_url="http://testserver") as ac:
yield ac


async def test_docs_route_is_whitelisted(client: AsyncClient):
response: Response = await client.get("/docs")
assert response.status_code == 500
Loading