Skip to content
Open
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
44 changes: 30 additions & 14 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,33 +1,49 @@
# Logs
logs
# LOGS

logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

# Editor directories and files
# EDITORS / OS
.vscode/*
!.vscode/extensions.json
.idea
.idea/
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# Client
client/node_modules
client/dist
client/dist-ssr
client/*.local
# NODE / NEXT.JS
node_modules/
.next/
out/

# PYTHON
venv/
__pycache__/
*.pyc

# ENV FILES
.env
.env.local

# FRONTEND

frontend/node_modules/
frontend/.next/


# Server
server/.env
server/node_modules
server/dist
# BACKEND
backend/venv/
backend/__pycache__/
backend/uploads/

# prompts
# TEMP / NOTES
*.txt
prompts/debug
1 change: 1 addition & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ share/python-wheels/
*.egg
MANIFEST


# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 5.0.2 on 2026-01-29 18:22

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("authentication", "0001_initial"),
("chat", "0004_uploadedfile_uploaded_by"),
]

operations = [
migrations.AddField(
model_name="customuser",
name="role",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="users",
to="chat.role",
),
),
migrations.AlterField(
model_name="customuser",
name="is_active",
field=models.BooleanField(default=True),
),
]
21 changes: 18 additions & 3 deletions backend/authentication/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.contrib.auth.models import (
AbstractBaseUser,
BaseUserManager,
PermissionsMixin,
)
from django.db import models


Expand All @@ -17,8 +21,8 @@ def create_user(self, email, password, **extra_fields):
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.is_active = True
user.save(using=self._db)

return user

def create_superuser(self, email, password, **extra_fields):
Expand All @@ -31,9 +35,20 @@ def create_superuser(self, email, password, **extra_fields):

class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
is_active = models.BooleanField(default=False)

# 🔐 Django auth flags
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)

# 🔑 ROLE-BASED ACCESS CONTROL
role = models.ForeignKey(
"chat.Role",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="users",
)

objects = CustomUserManager()

USERNAME_FIELD = "email"
Expand Down
177 changes: 145 additions & 32 deletions backend/authentication/views.py
Original file line number Diff line number Diff line change
@@ -1,79 +1,192 @@
# from django.conf import settings
# from django.contrib.auth import authenticate, login, logout
# from django.http import JsonResponse
# from django.middleware.csrf import get_token
# from rest_framework import status
# from rest_framework.decorators import api_view

# from authentication.models import CustomUser


# @api_view(["GET"])
# def auth_root_view(request):
# return JsonResponse({"message": "Auth endpoint works!"})


# @api_view(["GET"])
# def csrf_token(request):
# token = get_token(request)
# return JsonResponse({"data": token})


# @api_view(["POST"])
# def login_view(request):
# email = request.data.get("email")
# password = request.data.get("password")

# try:
# user = CustomUser.objects.get(email=email)
# except CustomUser.DoesNotExist:
# return JsonResponse({"error": "Invalid credentials"}, status=status.HTTP_401_UNAUTHORIZED)

# # Check if the user is active
# if not user.is_active:
# return JsonResponse({"error": "User is not active"}, status=status.HTTP_401_UNAUTHORIZED)

# user = authenticate(request, email=email, password=password)
# if user is not None:
# login(request, user)
# response = JsonResponse({"data": "Login successful"})

# # Set session cookie manually
# session_key = request.session.session_key
# session_cookie_name = settings.SESSION_COOKIE_NAME
# max_age = settings.SESSION_COOKIE_AGE
# response.set_cookie(session_cookie_name, session_key, max_age=max_age, httponly=True)

# return response
# else:
# return JsonResponse({"error": "Invalid credentials"}, status=status.HTTP_401_UNAUTHORIZED)


# @api_view(["POST"])
# def logout_view(request):
# logout(request)
# response = JsonResponse({"data": "Logout successful"})
# response.delete_cookie(settings.SESSION_COOKIE_NAME)

# return response


# @api_view(["POST"])
# def register_view(request):
# email = request.data.get("email")
# password = request.data.get("password")
# if not email or not password:
# return JsonResponse({"error": "Email and password are required"}, status=status.HTTP_400_BAD_REQUEST)

# if CustomUser.objects.filter(email=email).exists():
# return JsonResponse({"error": "Email is already taken"}, status=status.HTTP_400_BAD_REQUEST)

# CustomUser.objects.create_user(email, password=password)
# return JsonResponse({"data": "User created successfully"}, status=status.HTTP_201_CREATED)


# @api_view(["GET"])
# def verify_session(request):
# session_cookie = request.COOKIES.get("sessionid")
# is_authenticated = request.user.is_authenticated and session_cookie == request.session.session_key
# return JsonResponse({"data": is_authenticated})
from django.conf import settings
from django.contrib.auth import authenticate, login, logout
from django.http import JsonResponse
from django.middleware.csrf import get_token
from django.views.decorators.csrf import csrf_exempt

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.decorators import (
api_view,
permission_classes,
authentication_classes,
)
from rest_framework.permissions import AllowAny, IsAuthenticated

from authentication.models import CustomUser


@api_view(["GET"])
@permission_classes([AllowAny])
def auth_root_view(request):
return JsonResponse({"message": "Auth endpoint works!"})


@api_view(["GET"])
@permission_classes([AllowAny])
def csrf_token(request):
token = get_token(request)
return JsonResponse({"data": token})
return JsonResponse({"csrfToken": get_token(request)})


# ---------------------------
# LOGIN (CSRF FIXED)
# ---------------------------
@csrf_exempt
@api_view(["POST"])
@authentication_classes([]) # 🔴 disable DRF SessionAuthentication
@permission_classes([AllowAny])
def login_view(request):
email = request.data.get("email")
password = request.data.get("password")

try:
user = CustomUser.objects.get(email=email)
except CustomUser.DoesNotExist:
return JsonResponse({"error": "Invalid credentials"}, status=status.HTTP_401_UNAUTHORIZED)

# Check if the user is active
if not user.is_active:
return JsonResponse({"error": "User is not active"}, status=status.HTTP_401_UNAUTHORIZED)
if not email or not password:
return JsonResponse(
{"error": "Email and password required"},
status=status.HTTP_400_BAD_REQUEST,
)

user = authenticate(request, email=email, password=password)
if user is not None:
login(request, user)
response = JsonResponse({"data": "Login successful"})
if not user:
return JsonResponse(
{"error": "Invalid credentials"},
status=status.HTTP_401_UNAUTHORIZED,
)

# Set session cookie manually
session_key = request.session.session_key
session_cookie_name = settings.SESSION_COOKIE_NAME
max_age = settings.SESSION_COOKIE_AGE
response.set_cookie(session_cookie_name, session_key, max_age=max_age, httponly=True)

return response
else:
return JsonResponse({"error": "Invalid credentials"}, status=status.HTTP_401_UNAUTHORIZED)
if not user.is_active:
return JsonResponse(
{"error": "User inactive"},
status=status.HTTP_403_FORBIDDEN,
)

login(request, user)

response = JsonResponse({"message": "Login successful"})
response.set_cookie(
settings.SESSION_COOKIE_NAME,
request.session.session_key,
httponly=True,
)
return response


# ---------------------------
# LOGOUT (CSRF FIXED)
# ---------------------------
@csrf_exempt
@api_view(["POST"])
@authentication_classes([]) # 🔴 disable DRF SessionAuthentication
@permission_classes([AllowAny])
def logout_view(request):
logout(request)
response = JsonResponse({"data": "Logout successful"})
response = JsonResponse({"message": "Logout successful"})
response.delete_cookie(settings.SESSION_COOKIE_NAME)

return response


@api_view(["POST"])
@permission_classes([AllowAny])
def register_view(request):
email = request.data.get("email")
password = request.data.get("password")

if not email or not password:
return JsonResponse({"error": "Email and password are required"}, status=status.HTTP_400_BAD_REQUEST)
return JsonResponse(
{"error": "Email and password required"},
status=status.HTTP_400_BAD_REQUEST,
)

if CustomUser.objects.filter(email=email).exists():
return JsonResponse({"error": "Email is already taken"}, status=status.HTTP_400_BAD_REQUEST)
return JsonResponse(
{"error": "Email already exists"},
status=status.HTTP_400_BAD_REQUEST,
)

CustomUser.objects.create_user(email, password=password)
return JsonResponse({"data": "User created successfully"}, status=status.HTTP_201_CREATED)
CustomUser.objects.create_user(email=email, password=password)
return JsonResponse(
{"message": "User created"},
status=status.HTTP_201_CREATED,
)


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def verify_session(request):
session_cookie = request.COOKIES.get("sessionid")
is_authenticated = request.user.is_authenticated and session_cookie == request.session.session_key
return JsonResponse({"data": is_authenticated})
return JsonResponse({"authenticated": True})
Loading