Search code examples
pythondjangosessiondjango-middleware

Django allow only one user session in total


I currently try to implement a policy to my application that only one user session at a time is allowed, if a user trys to log-in from another device the old sessions gets killed.

But for some resone i get the following error and i can't find the mistake myself :( :

RelatedObjectDoesNotExist at / User has no logged_in_user.

My project contains two apps, the actually app and a "accounts" app that contains all informations shown here.

signals.py

# Signals that fires when a user logs in and logs out

from django.contrib.auth import user_logged_in, user_logged_out
from django.dispatch import receiver
from .models import LoggedInUser

@receiver(user_logged_in)
def on_user_logged_in(sender, request, **kwargs):
    LoggedInUser.objects.get_or_create(user=kwargs.get('user'))


@receiver(user_logged_out)
def on_user_logged_out(sender, **kwargs):
    LoggedInUser.objects.filter(user=kwargs.get('user')).delete()

models.py

# Model to store the list of logged in users
class LoggedInUser(models.Model):
    user = models.OneToOneField(User, related_name='logged_in_user', on_delete=models.CASCADE)
    session_key = models.CharField(max_length=32, null=True, blank=True)

    def __str__(self):
        return self.user

my User Model is at the same at the same location as this sippet

middleware.py

#Session model stores the session data
from django.contrib.sessions.models import Session
from .models import LoggedInUser

class OneSessionPerUserMiddleware:
    # Called only once when the web server starts
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.
        if request.user.is_authenticated:
            stored_session_key = request.user.logged_in_user.session_key

            # if there is a stored_session_key in the database and it is
            # different from the current session, delete the stored_session_key
            # session_key with from the Session table
            if stored_session_key and stored_session_key != request.session.session_key:
                Session.objects.get(session_key=stored_session_key).delete()

            request.user.logged_in_user.session_key = request.session.session_key
            request.user.logged_in_user.save()

        response = self.get_response(request)

        # This is where you add any extra code to be executed for each request/response after
        # the view is called.

        return response

UPDATE:

my middleware.py file now looks like:

# Session model stores the session data
from django.contrib.sessions.models import Session
from .models import LoggedInUser


class OneSessionPerUserMiddleware:
    # Called only once when the web server starts
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.
        if request.user.is_authenticated:
            session_key = request.session.session_key

            # if there is a stored_session_key  in our database and it is
            # different from the current session, delete the stored_session_key
            # session_key with from the Session table
            try:
                logged_in_user = request.user.logged_in_user
                stored_session_key = logged_in_user.session_key
                # stored_session_key exists so delete it if it's different
                if stored_session_key != session_key:
                    Session.objects.filter(session_key=stored_session_key).delete()
                logged_in_user.session_key = session_key
                logged_in_user.save()
            except LoggedInUser.DoesNotExist:
                LoggedInUser.objects.create(user=request.user, session_key=session_key)

        response = self.get_response(request)

        return response

which seems to work fine but if i log in with the same user from two different browsers, I'm still able to do this and i still get two different sessions keys and the session stays open.

If i check at the database while logged in from two diffrent computers to access the application as the same user, the saved session key at the LoggedInUser table simple changes but the application does not act like the key has been revoked from at least the oldest session!?


Solution

  • Actually the error occurs here,

    stored_session_key = request.user.logged_in_user.session_key
    

    ie. the related logged_in_user (LoggedInUser object) doesn't exists for the logged in user. So you have to create a a new one.

    session_key = request.session.session_key
    
    try:
        logged_in_user = request.user.logged_in_user
        stored_session_key = logged_in_user.session_key
        # stored_session_key exists so delete it if it's different
        if stored_session_key != session_key:
            Session.objects.filter(session_key=stored_session_key).delete()
        logged_in_user.session_key = session_key
        logged_in_user.save()
    except LoggedInUser.DoesNotExist:
        LoggedInUser.objects.create(user=request.user, session_key=session_key)