Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent multiple login in Django

Tags:

django

session

I'm writing a User system that cannot login at the same time. If the account in login state in somewhere, and someone login the same account in other position. The latter one will be logged in. And the previous will be logged out. I'm using a model with oneToOneField associated to the User model, And save session ids of this user. The code is like below.

from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from .myModels import JSONField

class Profile(models.Model):
    user = models.OneToOneField(User, models.CASCADE)
    sessionids = JSONField(null=True)


@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)

The JSONField is a field that using textField to store JSON string. When a user login, I go to get all session ids of this user and delete all the session ids. Then I add current session id to the Profile. By doing this, I can logout in the previous position. the code is like below.

def login(request):
    if request.method == "POST":
        if request.user.is_authenticated:
            return HttpResponse("the user session is authenticated.")

        username = request.POST.get('username', '')
        password = request.POST.get('password', '')

        user = auth.authenticate(username=username, password=password)

        if user is not None and user.is_active:
            auth.login(request, user)

            #remove cur user all sessions
            sessionidsToDelete = request.user.profile.sessionids
            if sessionidsToDelete != None:
                sessions = Session.objects.filter(session_key__in=sessionidsToDelete)
                for session in sessions:
                    session.delete()

            #add cur user sessions
            sessionidsToStore = user.profile.sessionids
            print("sessionidsToStore = ")
            print(sessionidsToStore)
            print("sessionidsToDelete = ")
            print(sessionidsToDelete)

            if sessionidsToStore== None:
                sessionidsToStore = []
            else:
                sessionidsToStore = list(set(sessionidsToStore) - set(sessionidsToDelete))
            print("sessionidsToStore = ")
            print(sessionidsToStore)
            sessionidsToStore.append(request.session.session_key)
            user.profile.sessionids = json.dumps(sessionidsToStore)
            user.profile.save()

            rotate_token(request)
            return HttpResponse("login sucessful")
        elif user.is_active == False:
            userNotActivedHttpresponse = HttpResponse()
            userNotActivedHttpresponse.status_code = 605
            userNotActivedHttpresponse.reason_phrase = "This user not active"
            return userNotActivedHttpresponse
        else:
            return HttpResponse("Please Input the correct username and password")
    else:
        return HttpResponseBadRequest("Please use POST to login")

But I think something will happen. When there two people want to login the same account at the same time. For example, there are two people know the same account. They login at the same time. It may be happen that B append B's session id to Profile after A remove all other session ids. In this situation, A and B will still in login state, and won't be logout. How could I prevent this problem?

like image 265
fnsne Avatar asked Jun 13 '18 09:06

fnsne


1 Answers

I think you make things very complicated, by storing data in UserProfiles, etc. and then have signals, you introduce a lot of levels, and at each level, things can go wrong.

We basically need two things here: a table that maps Users to their corresponding settings. We can implement this with a UserSession model:

# models.py

from django.conf import settings
from django.db import models
from django.contrib.sessions.models import Session

class UserSession(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    session = models.OneToOneField(Session, on_delete=models.CASCADE)

So the UserSession object makes a link between User and Sessions. Now we can implement a login hook: a signal that is triggered in case a user logs in. In that case we perform two things:

  1. we delete all Sessions (and corresponding UserSessions) of the User that are active; and
  2. we create a new Session and corresponding UserSession that we can remove later. Like:
from django.contrib.auth import user_logged_in
from django.dispatch.dispatcher import receiver

@receiver(user_logged_in)
def remove_other_sessions(sender, user, request, **kwargs):
    # remove other sessions
    Session.objects.filter(usersession__user=user).delete()
    
    # save current session
    request.session.save()

    # create a link from the user to the current session (for later removal)
    UserSession.objects.get_or_create(
        user=user,
        session_id=request.session.session_key
    )

Update: I wrapped this into a small reusable app [GitHub] that can be installed through pip.

like image 155
Willem Van Onsem Avatar answered Nov 15 '22 07:11

Willem Van Onsem