How to limit number of concurrent users logging in to same account in Django
Asked Answered
D

3

15

My site is a digital marketplace website written in Django.

Digital content(text, images, videos) on the site is 'locked' by default. Only users who bought those content can view it.

There's a story that certain user(who bought the content) give away username/password for free to many people(1,000+ people in Facebook groups, for example). Those 1,000 users can then login using that single username/password and view the 'locked' digital content without paying a cent.

Is it possible to limit number of concurrent login to the same account?

I've found this package:

https://github.com/pcraston/django-preventconcurrentlogins

but what it does is logging previous user out when someone logged in using the same username/password. That would not help because each user only need to type in username/password each time to access 'locked' content.

Developing answered 2/11, 2016 at 11:2 Comment(3)
Try looking into github.com/sobotklp/django-throttle-requestsMizuki
Hi. Thanks. I've never heard of it. Will look into it and see if it help in my case.Developing
Hi. I visited the documentation There doesn't seem to be a rule that limit number of request per user account. Do you have any example to share? Thanks!Developing
C
14

To limit the concurrent users, keep an eye on the existing sessions.

In your current approach, when a user logs in, a new session is created. That new session co-exists with the older sessions, so you have N concurrent sessions at the same time.

You want to allow a single session. The easiest approach would be to invalidate older session when a new login happens:

Other (more complete, but more complex) approaches would be using Two-factor authentication, blocking per IP, throttling the login event, requiring email confirmation, etc...

Counterspy answered 14/11, 2016 at 12:28 Comment(0)
J
4

Store User-Session mapping in another model.

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

class UserSessions(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='user_sessions')
    session = models.OneToOneField(Session, related_name='user_sessions',
                                   on_delete=models.CASCADE)

    def __str__(self):
        return '%s - %s' % (self.user, self.session.session_key)

If you have your own login view, you can update this model yourself:

from django.contrib.auth.views import login as auth_login

MAX_USER_SESSIONS = 5

def login(request):
    auth_login(request)
    if request.user.is_authenticated():
        session = Session.objects.get(session_key=request.session.session_key)
        user_session = UserSession.objects.create(user=request.user, session=session)
    no_of_logins = request.user.user_sessions.count()
    if no_of_logins > MAX_USER_SESSIONS:
        ...

Another option is to use Signal. Django provides signals: user_logged_in, user_login_failed and user_logged_out, if you use the Django login view, that is.

# signals.py
from django.contrib.auth.signals import user_logged_in
from django.dispatch import receiver

@receiver(user_logged_in)
def concurrent_logins(sender, **kwargs):
    user = kwargs.get('user')
    request = kwargs.get('request')
    if user is not None and request is not None:
        session = Session.objects.get(session_key=request.session.session_key)
        UserSessions.objects.create(user=user, session=session)
    if user is not None:
        request.session['NUM_SESSIONS'] = user.user_sessions.count()

# your login view
def login(request):
     auth_login(request)
     if request.user.is_authenticated() and request.session['NUM_SESSIONS'] > MAX_USER_SESSIONS:
         ...

You can list the previous sessions, and ask the user to choose which sessions to logout. (Don't stop the user from logging in, else they might be locked out - if they don't have access to their previous sessions)

Jacal answered 14/11, 2016 at 19:47 Comment(0)
L
3

1 In your users/profiles app add a management command file

To add managment command, follow this guide: https://docs.djangoproject.com/en/1.10/howto/custom-management-commands/

2 The management command code: kills all sessions from users that have more then 10 sessions, you may change that to 1K if needed, or send this value as a param to the management command

from django.core.management.base import BaseCommand, CommandError
from django.contrib.sessions.models import Session
from django.contrib.auth.models import User

class Command(BaseCommand):
    def handle(self, *args, **options):
        session_user_dict = {}


        # users with more than 10 sessions - del all

        for ses in Session.objects.all():
            data = ses.get_decoded()
            user_owner = User.objects.filter(pk = data.get('_auth_user_id', None))

            if int(data.get('_auth_user_id', None)) in session_user_dict:
                session_user_dict[int(data.get('_auth_user_id', None))] += 1
            else:
                session_user_dict[int(data.get('_auth_user_id', None))] = 1

        for k,v in session_user_dict.iteritems():
            if v > 10:
                for ses in Session.objects.all():
                    data = ses.get_decoded()
                    if str(k) == data.get('_auth_user_id', None):
                        ses.delete()

3 Optional password change- after killing the bad-users sessions -change the bad users password to a diff one. To do that change the last loop in the above code

            for k,v in session_user_dict.iteritems():
            if v > 10:
                for ses in Session.objects.all():
                    data = ses.get_decoded()
                    if str(k) == data.get('_auth_user_id', None):
                        ses.delete()
                        theuser =  User.objects.filter(pk=k)
                        #maybe use uuid to pick a password ...
                        theuser.set_password('new_unknown_password')

4 Add a django management command to crontab every minute / hour or whenever use this guide: https://www.cyberciti.biz/faq/how-do-i-add-jobs-to-cron-under-linux-or-unix-oses/

if you are using a virtual env , remember that a management command that runs from cron needs to enter to the virtual env first, you may do it with a .sh script, ask for help if needed

Lepus answered 16/11, 2016 at 12:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.