12👍
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:
- detect/extend the login event (use the “user_logged_in” signal)
- for each login, remove the other existing sessions from the same user (see “Clearing the session store”)
Other (more complete, but more complex) approaches would be using Two-factor authentication, blocking per IP, throttling the login event, requiring email confirmation, etc…
3👍
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
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 > 1: # whatever your limit is
request.SESSION['EXTRA_LOGIN'] = True
# Do your stuff here
Other 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['LOGIN_COUNT'] = user.user_sessions.count()
# your login view
def login(request):
auth_login(request)
if request.user.is_authenticated() and request.session['LOGIN_COUNT'] > 1:
# 'LOGIN_COUNT' populated by signal
request.session['EXTRA_LOGIN'] = True
# Do your stuff
If EXTRA_LOGIN
is True
, you can list the previous sessions, and ask the user to choose which sessions to logout. (Don’t stop him from logging in, else he might be locked out – if he doesn’t have access to his previous sessions now)
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
- Django logging – django.request logger and extra context
- Django multi-table inheritance, how to know which is the child class of a model?