Skip to content

Commit 65411d7

Browse files
committed
Tests and schema added
1 parent e20e578 commit 65411d7

3 files changed

Lines changed: 85 additions & 18 deletions

File tree

core/api.py

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
from annotated_types import Ge, Le, MinLen
44
from django.conf import settings
5-
from django.db.models import F, Q
6-
from ninja import Schema
5+
from django.contrib.auth import login
6+
from django.db.models import F
77
from django.http import HttpResponse
8-
from ninja import File, Query
8+
from ninja import File, Query, Status
99
from ninja.security import SessionAuth
1010
from ninja_extra import ControllerBase, api_controller, paginate, route
1111
from ninja_extra.exceptions import PermissionDenied
@@ -19,6 +19,7 @@
1919
from core.schemas import (
2020
FamilyGodfatherSchema,
2121
GroupSchema,
22+
LoginSchema,
2223
MarkdownSchema,
2324
SithFileSchema,
2425
UploadedFileSchema,
@@ -29,8 +30,8 @@
2930
UserSchema,
3031
)
3132
from core.templatetags.renderer import markdown
33+
from core.views.forms import LoginForm
3234
from counter.utils import is_logged_in_counter
33-
from counter.models import Customer
3435

3536

3637
@api_controller("/markdown")
@@ -170,25 +171,28 @@ def get_family_graph(
170171
}
171172

172173

173-
class LoginSchema(Schema):
174-
identifier: str
175-
password: str
176-
177-
178174
@api_controller("/auth")
179175
class AuthController(ControllerBase):
180-
@route.post("/login", auth=None)
176+
@route.post(
177+
"/login",
178+
auth=None,
179+
response={200: dict[Literal["id"], int], 401: dict[str, list[str]]},
180+
)
181181
def login(self, body: LoginSchema):
182182
"""Authenticate a user by username, email or AE account id.
183183
184184
Returns the user's id on success, 401 on failure.
185185
"""
186-
ident = body.identifier.strip()
187-
user = User.objects.filter(Q(username=ident) | Q(email=ident)).first()
188-
if user is None:
189-
cust = Customer.objects.select_related("user").filter(account_id=ident).first()
190-
if cust:
191-
user = cust.user
192-
if user is None or not user.check_password(body.password):
193-
return HttpResponse(status=401)
186+
if self.context.request.user.is_authenticated:
187+
raise PermissionDenied
188+
189+
login_form = LoginForm(
190+
self.context.request,
191+
data={"username": body.identifier, "password": body.password},
192+
)
193+
if not login_form.is_valid():
194+
return Status(401, login_form.errors)
195+
196+
user = login_form.get_user()
197+
login(self.context.request, user)
194198
return {"id": user.id}

core/schemas.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,11 @@ class MarkdownSchema(Schema):
155155
text: str
156156

157157

158+
class LoginSchema(Schema):
159+
identifier: str
160+
password: str
161+
162+
158163
class FamilyGodfatherSchema(Schema):
159164
godfather: int
160165
godchild: int

core/tests/test_auth_api.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import json
2+
3+
import pytest
4+
from django.contrib.auth.hashers import make_password
5+
from django.test import Client
6+
from django.urls import reverse
7+
from model_bakery import baker
8+
9+
from core.models import User
10+
from counter.models import Customer
11+
12+
13+
def post_login(client: Client, identifier: str, password: str):
14+
return client.post(
15+
reverse("api:login"),
16+
data=json.dumps({"identifier": identifier, "password": password}),
17+
content_type="application/json",
18+
)
19+
20+
@pytest.fixture()
21+
def user(db) -> User:
22+
return baker.make(User, password=make_password("plop"))
23+
24+
25+
@pytest.mark.django_db
26+
@pytest.mark.parametrize(
27+
"identifier_getter",
28+
[
29+
lambda user: user.username,
30+
lambda user: user.email,
31+
lambda user: Customer.get_or_create(user)[0].account_id,
32+
],
33+
)
34+
def test_api_login_success(client: Client, user: User, identifier_getter):
35+
response = post_login(client, identifier_getter(user), "plop")
36+
37+
assert response.status_code == 200
38+
assert response.json() == {"id": user.id}
39+
assert int(client.session["_auth_user_id"]) == user.id
40+
41+
42+
@pytest.mark.django_db
43+
def test_api_login_fail_invalid_credentials(client: Client, user: User):
44+
response = post_login(client, user.username, "wrong-password")
45+
46+
assert response.status_code == 401
47+
assert "_auth_user_id" not in client.session
48+
49+
50+
@pytest.mark.django_db
51+
def test_api_login_fail_if_already_authenticated(client: Client, user: User):
52+
already_logged_user = baker.make(User)
53+
client.force_login(already_logged_user)
54+
55+
response = post_login(client, user.username, "plop")
56+
57+
assert response.status_code == 403
58+
assert int(client.session["_auth_user_id"]) == already_logged_user.id

0 commit comments

Comments
 (0)