refactor: clean up scaffolding backend

This commit is contained in:
ramvignesh-b
2026-04-16 03:30:42 +05:30
parent e8dac65468
commit cc8e3e4e4e
16 changed files with 174 additions and 109 deletions
-1
View File
@@ -1 +0,0 @@
# Register your models here.
@@ -0,0 +1,16 @@
# Generated by Django 6.0.4 on 2026-04-15 18:13
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("users", "0004_alter_user_public_id"),
]
operations = [
migrations.RemoveField(
model_name="user",
name="kdf_salt",
),
]
+4 -13
View File
@@ -7,13 +7,12 @@ from django.utils.translation import gettext_lazy as _
class CustomUserManager(BaseUserManager):
"""
General User Model
Creates and saves a User with email and password.
"""
def create_user(self, email, password=None, **extra_fields):
if not email:
raise ValueError(_("The Email must be set"))
# set default activation state as False to enforce email verification
extra_fields.setdefault("is_active", False)
email = self.normalize_email(email)
@@ -24,7 +23,7 @@ class CustomUserManager(BaseUserManager):
def create_superuser(self, email, password, **extra_fields):
"""
Admin Model
Creates a Superuser with email and password.
"""
extra_fields.update({"is_staff": True, "is_superuser": True, "is_active": True})
@@ -33,30 +32,22 @@ class CustomUserManager(BaseUserManager):
class User(AbstractUser):
"""
Database table structure.
Note: We use the default integer ID internally for database performance (joins/indexes)
but expose a random 'public_id' (UUID) in URLs and APIs for real privacy.
Creates a User with email as primary identifier.
Requires manual email verification for activation.
"""
public_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, db_index=True)
# Reset default fields
username = None
first_name = None
last_name = None
full_name = models.CharField(max_length=100)
email = models.EmailField(_("email address"), unique=True)
# salt for client-side key derivation
kdf_salt = models.CharField(max_length=128, blank=True, null=True)
# Default is False to enforce email verification
is_active = models.BooleanField(default=False)
objects = CustomUserManager()
# Login uses email instead of username
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
+8
View File
@@ -8,10 +8,18 @@ class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
"""
Specifies the public_id as readonly for the system to auto generate
"""
model = User
fields = ("public_id", "email", "full_name", "password")
read_only_fields = ("public_id",)
def create(self, validated_data):
"""
Validates and creates a new user with the given data.
"""
user = User.objects.create_user(
email=validated_data["email"],
password=validated_data["password"],
+13 -9
View File
@@ -1,4 +1,3 @@
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.tokens import default_token_generator
from django.urls import reverse
@@ -21,30 +20,35 @@ class AuthTests(APITestCase):
self.logout_url = reverse("logout")
def test_login_sets_secure_cookie(self):
"""
Tests if the Login API can generate access token and set secure cookie for refresh token.
"""
data = {"email": self.user.email, "password": self.password}
cookie_name = "refresh_token"
response = self.client.post(self.login_url, data)
cookie_name = settings.SIMPLE_JWT["AUTH_COOKIE"]
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn("access", response.data)
self.assertNotIn("refresh", response.data)
self.assertIn(cookie_name, response.cookies)
# verify the cookie has a value
self.assertTrue(response.cookies[cookie_name].value)
self.assertTrue(response.cookies[cookie_name].httponly)
self.assertEqual(response.cookies[cookie_name]["samesite"], "Lax")
class ActivationTests(APITestCase):
def test_user_activation(self):
# initial user state
user = User.objects.create_user(email="inactive@test.com", password="password1234", is_active=False)
# generate activation link
"""
Tests if the Activation API can activate an inactive user.
"""
user = User.objects.create_user(email="inactiveuser@test.com", password="password1234", is_active=False)
uidb64 = urlsafe_base64_encode(force_bytes(user.public_id))
token = default_token_generator.make_token(user)
# call activation url
activation_url = reverse("activate", kwargs={"uidb64": uidb64, "token": token})
response = self.client.get(activation_url)
user.refresh_from_db()
self.assertEqual(response.status_code, status.HTTP_200_OK)
# check user is activated
user.refresh_from_db()
self.assertTrue(user.is_active)
+5 -5
View File
@@ -27,12 +27,12 @@ def set_response_cookies(response, refresh_token):
if "refresh" in _response.data:
del _response.data["refresh"] # remove refresh token from response body
_response.set_cookie(
key=settings.SIMPLE_JWT["AUTH_COOKIE"],
key=settings.AUTH_COOKIE["NAME"],
value=refresh_token,
max_age=settings.SIMPLE_JWT["REFRESH_TOKEN_LIFETIME"].total_seconds(),
httponly=settings.SIMPLE_JWT["AUTH_COOKIE_HTTPONLY"],
secure=settings.SIMPLE_JWT["AUTH_COOKIE_SECURE"],
samesite=settings.SIMPLE_JWT["AUTH_COOKIE_SAMESITE"],
domain=settings.SIMPLE_JWT["AUTH_COOKIE_DOMAIN"],
httponly=settings.AUTH_COOKIE["HTTPONLY"],
secure=settings.AUTH_COOKIE["SECURE"],
samesite=settings.AUTH_COOKIE["SAMESITE"],
domain=settings.AUTH_COOKIE["DOMAIN"],
)
return _response
+4 -4
View File
@@ -74,7 +74,7 @@ class RefreshTokenView(TokenRefreshView):
permission_classes = (permissions.AllowAny,)
def post(self, request, *args, **kwargs):
refresh_token = request.COOKIES.get(settings.SIMPLE_JWT["AUTH_COOKIE"])
refresh_token = request.COOKIES.get(settings.AUTH_COOKIE["NAME"])
if not refresh_token:
return Response({"detail": "Refresh token not found"}, status=status.HTTP_401_UNAUTHORIZED)
request.data["refresh"] = refresh_token
@@ -92,9 +92,9 @@ class LogoutView(generics.GenericAPIView):
response = Response({"detail": "Successfully logged out"}, status=status.HTTP_200_OK)
# Clear the secure cookie
response.delete_cookie(
key=settings.SIMPLE_JWT["AUTH_COOKIE"],
domain=settings.SIMPLE_JWT.get("AUTH_COOKIE_DOMAIN"),
samesite=settings.SIMPLE_JWT.get("AUTH_COOKIE_SAMESITE"),
key=settings.AUTH_COOKIE["NAME"],
domain=settings.AUTH_COOKIE.get("DOMAIN"),
samesite=settings.AUTH_COOKIE.get("SAMESITE"),
path="/",
)
return response