Skip to content

Commit

Permalink
Improve handling of session timeout
Browse files Browse the repository at this point in the history
GitHub: related to #1032
GitHub: related to #1036
  • Loading branch information
fabcor-maxiv committed Sep 14, 2023
1 parent 4923a96 commit 65bb9c7
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 7 deletions.
21 changes: 18 additions & 3 deletions docs/source/Login.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
Login API
========================
Login
=====

MXCuBE web sessions are meant to expire when there is no activity,
as opposed to a typical web session that expires when the browser is closed.

So typically a MXCuBE web session closes 1 minute after the browser tab closes.
After that it is necessary to sign in again.

For this purpose:

* Flask config setting ``PERMANENT_SESSION_LIFETIME`` is set to 60 seconds.

* There is a ``/mxcube/api/v0.1/login/refresh_session`` endpoint
that the front end must call regularly (as long as the browser tab is open).


Login API
---------

.. autoflask:: mxcube3.routes.Login:mxcube
:endpoints: login, signout, loginInfo, get_initial_state, proposal_samples

3 changes: 1 addition & 2 deletions mxcube3/core/components/user/usermanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,7 @@ def update_active_users(self):
and _u.last_request_timestamp
and (
datetime.datetime.now() - _u.last_request_timestamp
).total_seconds()
> 60
) > flask.current_app.permanent_session_lifetime
):
logging.getLogger("HWR.MX3").info(
f"Logged out inactive user {_u.username}"
Expand Down
4 changes: 3 additions & 1 deletion mxcube3/routes/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,10 @@ def send_feedback():
@bp.route("/refresh_session", methods=["GET"])
@server.restrict
def refresh_session():
# Since default value of `SESSION_REFRESH_EACH_REQUEST` config setting is `True`
# there is no need to do anything to refresh the session.
logging.getLogger("MX3.HWR").debug("Session refresh")
server.flask.permanent_session_lifetime = timedelta(minutes=1)
app.usermanager.update_active_users()
return make_response("", 200)

return bp
46 changes: 45 additions & 1 deletion test/test_authn.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"""Authentication tests."""


import datetime
import os
import time

import pytest

Expand All @@ -15,12 +17,15 @@
URL_SIGNIN = f"{URL_BASE}/" # Trailing slash is necessary
URL_SIGNOUT = f"{URL_BASE}/signout"
URL_INFO = f"{URL_BASE}/login_info"
URL_REFRESH = f"{URL_BASE}/refresh_session"

CREDENTIALS_0 = {"proposal": "idtest0", "password": "sUpErSaFe"}
# Password has to be `wrong` to simulate wrong password in `ISPyBClientMockup`
CREDENTIALS_0_WRONG = {"proposal": "idtest0", "password": "wrong"}
CREDENTIALS_1 = {"proposal": "idtest1", "password": "sUpErSaFe"}

SESSION_LIFETIME = 2.0 # seconds

USER_DB_PATH = "/tmp/mxcube-test-user.db"


Expand All @@ -36,6 +41,9 @@ def server():
argv = []
server_, _ = mxcube3.build_server_and_config(test=True, argv=argv)
server_.flask.config["TESTING"] = True
# For the tests we override the configured value of the session lifetime
# with a much smaller value, so that tests do not need to wait as long.
server_.flask.permanent_session_lifetime = SESSION_LIFETIME

yield server_

Expand Down Expand Up @@ -87,7 +95,6 @@ def test_authn_info(client):
The login info should have `loggedIn` false before authentication
and true after successful authentication.
"""

resp = client.get(URL_INFO)
assert resp.status_code == 200
assert resp.json["loggedIn"] == False
Expand Down Expand Up @@ -140,4 +147,41 @@ def test_authn_different_proposals(make_client):
assert resp.json["msg"] == "Could not authenticate"


def test_authn_session_timeout(client):
"""Test the session timeout
The session can be refreshed, and can expire.
It should be possible to sign in again after a valid session expired.
"""

# Sign in and --as a side effect-- create a session
client.post(URL_SIGNIN, json=CREDENTIALS_0)
resp = client.get(URL_INFO)

# Let the session nearly expire
time.sleep(SESSION_LIFETIME * 0.9)

# Refresh the session
resp = client.get(URL_REFRESH)

# Let the session nearly expire again
time.sleep(SESSION_LIFETIME * 0.9)

# Check that the session still has not expired
resp = client.get(URL_INFO)
assert resp.json["loggedIn"] == True, "Session did not refresh"

# Let the session expire completely
time.sleep(SESSION_LIFETIME * 1.5)

# Check that the session has expired
resp = client.get(URL_INFO)
assert resp.json["loggedIn"] == False, "Session did not expire"

# Check that it is possible to sign in again
client.post(URL_SIGNIN, json=CREDENTIALS_0)
resp = client.get(URL_INFO)
assert resp.json["loggedIn"] == True, "We can not login again"


# EOF

0 comments on commit 65bb9c7

Please sign in to comment.