Preparations for Android app
This commit is contained in:
parent
02400d5ce0
commit
ae2a6eda79
6 changed files with 76 additions and 0 deletions
|
@ -10,6 +10,16 @@ from django.shortcuts import resolve_url
|
||||||
|
|
||||||
from ..models.session import AuthSession
|
from ..models.session import AuthSession
|
||||||
|
|
||||||
|
# Somewhat shamelessly copied from django.contrib.auth.views
|
||||||
|
#
|
||||||
|
# Original source:
|
||||||
|
# https://github.com/django/django/blob/main/django/contrib/auth/views.py
|
||||||
|
#
|
||||||
|
# License:
|
||||||
|
# BSD 3-Clause "New" or "Revised" License
|
||||||
|
# Copyright (c) Django Software Foundation and individual contributors.
|
||||||
|
# All rights reserved.
|
||||||
|
# https://github.com/django/django/blob/main/LICENSE
|
||||||
|
|
||||||
class AuthSessionRequiredMixin(RedirectURLMixin):
|
class AuthSessionRequiredMixin(RedirectURLMixin):
|
||||||
redirect_field_name = REDIRECT_FIELD_NAME
|
redirect_field_name = REDIRECT_FIELD_NAME
|
||||||
|
|
|
@ -10,6 +10,7 @@ from django.shortcuts import resolve_url
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from ..models.otp import TOTPSecret
|
from ..models.otp import TOTPSecret
|
||||||
|
from ..models.app import AppSession
|
||||||
|
|
||||||
|
|
||||||
class TimeoutMixin:
|
class TimeoutMixin:
|
||||||
|
@ -30,6 +31,9 @@ class TimeoutMixin:
|
||||||
elif request.session["LastActivity"] < (timezone.now() - timezone.timedelta(minutes=settings.REVERIFY_AFTER_INACTIVITY_MINUTES)).timestamp():
|
elif request.session["LastActivity"] < (timezone.now() - timezone.timedelta(minutes=settings.REVERIFY_AFTER_INACTIVITY_MINUTES)).timestamp():
|
||||||
try:
|
try:
|
||||||
assert request.user.totpsecret.active
|
assert request.user.totpsecret.active
|
||||||
|
|
||||||
|
request.session["AppSession"] = AppSession.get_for_user(request.user)
|
||||||
|
|
||||||
return redirect_to_login(path, resolve_url("auth:reverify"), REDIRECT_FIELD_NAME)
|
return redirect_to_login(path, resolve_url("auth:reverify"), REDIRECT_FIELD_NAME)
|
||||||
except (AssertionError, TOTPSecret.DoesNotExist):
|
except (AssertionError, TOTPSecret.DoesNotExist):
|
||||||
messages.error(
|
messages.error(
|
||||||
|
@ -39,6 +43,7 @@ class TimeoutMixin:
|
||||||
messages.error(
|
messages.error(
|
||||||
request, "Something went wrong, please try logging in again."
|
request, "Something went wrong, please try logging in again."
|
||||||
)
|
)
|
||||||
|
logout(request)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
request.session["LastActivity"] = timezone.now().timestamp()
|
request.session["LastActivity"] = timezone.now().timestamp()
|
||||||
|
|
49
authentication/models/app.py
Normal file
49
authentication/models/app.py
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from jwt import decode, InvalidTokenError
|
||||||
|
|
||||||
|
class AppKey(models.Model):
|
||||||
|
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
||||||
|
user = models.ForeignKey(get_user_model(), models.CASCADE)
|
||||||
|
device = models.CharField(max_length=255)
|
||||||
|
key = models.TextField()
|
||||||
|
active = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.user.username} - {self.device}"
|
||||||
|
|
||||||
|
def validateJWT(self, jwt):
|
||||||
|
try:
|
||||||
|
return decode(jwt, self.key, algorithms=['HS256'])
|
||||||
|
except InvalidTokenError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
class AppSession(models.Model):
|
||||||
|
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
||||||
|
user = models.ForeignKey(get_user_model(), models.CASCADE)
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
used = models.DateTimeField(null=True, blank=True)
|
||||||
|
approved = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def valid(self):
|
||||||
|
return self.created > timezone.now() - timezone.timedelta(minutes=5)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_for_user(cls, user, create = True):
|
||||||
|
assert user
|
||||||
|
|
||||||
|
if not user.appkey_set.filter(active=True).exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
user_sessions = cls.objects.filter(user=user)
|
||||||
|
|
||||||
|
for session in user_sessions:
|
||||||
|
if session.valid and not session.used:
|
||||||
|
return session
|
||||||
|
|
||||||
|
if create:
|
||||||
|
return cls.objects.create(user=user)
|
2
authentication/views/app.py
Normal file
2
authentication/views/app.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
from django.views.generic import JSONView
|
||||||
|
|
|
@ -4,6 +4,7 @@ from django.http import HttpResponseRedirect
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
from ..forms.otp import TOTPLoginForm
|
from ..forms.otp import TOTPLoginForm
|
||||||
|
from ..models.app import AppSession
|
||||||
from frontend.mixins.views import TitleMixin
|
from frontend.mixins.views import TitleMixin
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,4 +18,12 @@ class ReverifyView(TitleMixin, LoginView):
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
self.request.session["LastActivity"] = timezone.now().timestamp()
|
self.request.session["LastActivity"] = timezone.now().timestamp()
|
||||||
|
|
||||||
|
try:
|
||||||
|
app_session = AppSession.objects.get(id=self.request.session["AppSession"])
|
||||||
|
app_session.used = True
|
||||||
|
app_session.save()
|
||||||
|
except AppSession.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
return HttpResponseRedirect(self.get_success_url())
|
return HttpResponseRedirect(self.get_success_url())
|
|
@ -15,6 +15,7 @@ django-crispy-forms
|
||||||
pyqrcode
|
pyqrcode
|
||||||
pypng
|
pypng
|
||||||
django-ajax-datatable
|
django-ajax-datatable
|
||||||
|
pyjwt
|
||||||
|
|
||||||
# For MySQL:
|
# For MySQL:
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue