diff --git a/.vscode/settings.json b/.vscode/settings.json index 5f5d25f..1c192dc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,6 @@ "noscript", "stylesheet", "tauth", - "TCOMMON" + "tcommon" ] } diff --git a/tauth/migrations/0002_tsession.py b/tauth/migrations/0002_tsession.py new file mode 100644 index 0000000..0207558 --- /dev/null +++ b/tauth/migrations/0002_tsession.py @@ -0,0 +1,25 @@ +# Generated by Django 5.0.7 on 2024-12-24 02:08 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tauth', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='tSession', + fields=[ + ('session_id', models.CharField(max_length=128, primary_key=True, serialize=False, unique=True)), + ('last_use', models.IntegerField()), + ('created', models.IntegerField()), + ('u_for', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sessions', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/tauth/models.py b/tauth/models.py index fc1cb2a..998fd61 100644 --- a/tauth/models.py +++ b/tauth/models.py @@ -1,6 +1,36 @@ +import math +import time +from typing import TYPE_CHECKING + +from django.contrib import admin as django_admin +from django.contrib.admin.exceptions import AlreadyRegistered # type: ignore from django.contrib.auth.models import User from django.db import models class tUser(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True) + + if TYPE_CHECKING: + sessions = models.Manager["tSession"] + + def __str__(self) -> str: + return self.user.username + +class tSession(models.Model): + u_for = models.ForeignKey(User, on_delete=models.CASCADE, related_name="sessions") + session_id = models.CharField(max_length=128, unique=True, primary_key=True) + last_use = models.IntegerField() + created = models.IntegerField() + + def register_usage(self): + self.last_use = math.floor(time.time()) + self.save(update_fields=["last_use"]) + + def __str__(self) -> str: + return f"{self.u_for.username} - created: {self.created} - last usage: {self.last_use}" + +try: + django_admin.site.register((tUser, tSession)) +except AlreadyRegistered: + ... diff --git a/tauth/sessions.py b/tauth/sessions.py new file mode 100644 index 0000000..1398b58 --- /dev/null +++ b/tauth/sessions.py @@ -0,0 +1,99 @@ +import hashlib +import math +import random +import threading +import time +from datetime import datetime + +from django.contrib.auth.models import AbstractUser, User +from django.core.handlers.wsgi import WSGIRequest +from django.http import HttpResponse + +from tauth.models import tSession +from tauth.settings import config + +_last_trim = 0 + +def _get_session_id(request: WSGIRequest, /, username: str) -> str: + return hashlib.sha3_512(str.encode(f"{request.META['HTTP_USER_AGENT']}-{time.time()}-{random.random()}-{username}")).hexdigest() + +def _trim_sessions(): + if time.time() < _last_trim + 60 * 60: + return + + thread = threading.Thread(target=__th_trim_sessions) + thread.start() + +def __th_trim_sessions(): + global _last_trim + _last_trim = time.time() + + tSession.objects.filter( + last_use__lt=math.floor(time.time()) - config["session_timeout"] * 60 * 60 + ).delete() + + if config["session_length"] != -1: + tSession.objects.filter( + created__lt=math.floor(time.time()) - config["session_length"] * 60 * 60 + ).delete() + +def create_session(request: WSGIRequest, response: HttpResponse | None, /, user: User | AbstractUser) -> tSession: + _trim_sessions() + session = tSession.objects.create( + u_for=user, + session_id=_get_session_id(request, user.username), + created=math.floor(time.time()), + last_use=math.floor(time.time()) + ) + + if response is not None: + response.set_cookie("session_id", session.session_id, max_age=365 * 24 * 60 * 60 if config["session_length"] == -1 else (config["session_length"] * 60 * 60)) + + return session + +def clear_session(request: WSGIRequest, response: HttpResponse | None=None, /, delete_session: bool=True): + _trim_sessions() + session_id = request.COOKIES.get("session_id") + + if session_id is None or len(session_id) != 128: + return + + if response is not None: + response.set_cookie("session_id", "", max_age=0, expires=datetime(0, 0, 0)) + + try: + session = tSession.objects.get(session_id=session_id) + except tSession.DoesNotExist: + ... + + if delete_session: + session.delete() + +def get_user(request: WSGIRequest, /) -> User | None: + _trim_sessions() + session_id = request.COOKIES.get("session_id") + + if session_id is None or len(session_id) != 128: + return None + + try: + session = tSession.objects.get(session_id=session_id) + except tSession.DoesNotExist: + return None + + session.register_usage() + return session.u_for + +def is_authenticated(request: WSGIRequest, /) -> bool: + _trim_sessions() + session_id = request.COOKIES.get("session_id") + + if session_id is None or len(session_id) != 128: + return False + + try: + tSession.objects.get(session_id=session_id).register_usage() + except tSession.DoesNotExist: + return False + + return True diff --git a/tauth/templates/noauth/index.html b/tauth/templates/noauth/index.html index 0a23e19..3aff433 100644 --- a/tauth/templates/noauth/index.html +++ b/tauth/templates/noauth/index.html @@ -6,9 +6,9 @@
Log in - {% if new_users %} - Sign up{% endif %} + {% if config.new_users %} - Sign up{% endif %}
- {% if not new_users %} + {% if not config.new_users %}