0

I have a system that has overlapping shift workers on it 24/7. Currently it is not uncommon for one to forget to log out and the next worker pick up their session and run with it. This causes some accountability issues.

I do realise there are options for session length ie settings.SESSION_COOKIE_AGE but these are a bit blunt for our purposes. We have different workers with different shift lengths, managers who have 2FA on-action and it's basically just not the path we want to pursue. Simply put...

I want to programmatically set the session death time on login.

We already have a custom Login view but this bubbles up through the built-in django.contrib.auth.forms.AuthenticationForm. And even there I can't see how to set an expiry on a particular session.

Any suggestions?

Edit: request.session's .get_expiry_age() and set_expiry(value) seem relevant but they do appear to update because they cycle around based on when the session was last modified, not when the session started. I need something that sets a maximum age on the session.

Edit 2: I guess I could write into the session on login and run something externally (a cronned management whatsit) that checked the expiries (if existed) and nuked each session that lapsed.

Oli
  • 235,628
  • 64
  • 220
  • 299
  • I don't think you can achieve this by setting an expiry when the user logs on, because the session middleware will [update the expiry time](https://github.com/django/django/blob/7d49ad76562e8c0597a0eb66046ab423b12888d8/django/contrib/sessions/middleware.py#L54) whenever a request causes the session to be saved. You could write a custom middleware (or subclass session middleware) to log out users when their shift ends. – Alasdair Apr 16 '19 at 12:38
  • 1
    Have you taken a look at https://stackoverflow.com/a/14831237/4186008? – Aman Garg Apr 16 '19 at 12:54
  • @AmanGarg I had not. That does look like a similar solution to my last edit and Alasdair's comment. Looks like middleware is the right route out. Allows me to send the user a little message at the same time. – Oli Apr 16 '19 at 13:17

1 Answers1

2

Came up with an answer thanks to the comments. On login I insert the timestamp into session:

request.session['login_timestamp'] = timezone.now().timestamp()

If you're wondering why timestamp, and not datetime.datetime.now() or timezone.now(), Django's default session encoder uses JSON and Django's JSON encoder does not handle datetimes. This is circumventable by writing an encoder that can handle datetimes... But just using an integer seconds-since-epoch value is enough for me.

And then have a little middleware to check that session against the current time.

from django.contrib.auth import logout

class MyMiddleware(object):

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # other checks to make sure this middleware should run.
        # eg, this only happens on authenticated URLs

        login_timestamp_ago = timezone.now().timestamp() - request.session.get('login_timestamp', timezone.now().timestamp())

        if settings.RECEPTION_LOGIN_DURATION and <insert user checks here> and login_timestamp_ago >= settings.RECEPTION_LOGIN_DURATION:
            logout(request)  # nukes session
            messages.warning(request, "Your session has expired. We need you to log in again to confirm your identity.")
            return redirect(request.get_full_path())

The order of events here is quite important. logout(request) destroys the whole session. If you write a message (stored in session) beforehand, it'll be missing after the logout(request).

Oli
  • 235,628
  • 64
  • 220
  • 299