diff --git a/docs/flow/examples/enrollment-2-stage.json b/docs/flow/examples/enrollment-2-stage.json index e8e4372c8..0a4082aab 100644 --- a/docs/flow/examples/enrollment-2-stage.json +++ b/docs/flow/examples/enrollment-2-stage.json @@ -117,7 +117,7 @@ }, "model": "passbook_stages_user_login.userloginstage", "attrs": { - "session_duration": 0 + "session_duration": "seconds=-1" } }, { diff --git a/docs/flow/examples/enrollment-email-verification.json b/docs/flow/examples/enrollment-email-verification.json index 299ad2d49..003183458 100644 --- a/docs/flow/examples/enrollment-email-verification.json +++ b/docs/flow/examples/enrollment-email-verification.json @@ -136,7 +136,7 @@ }, "model": "passbook_stages_user_login.userloginstage", "attrs": { - "session_duration": 0 + "session_duration": "seconds=-1" } }, { diff --git a/docs/flow/examples/login-2fa.json b/docs/flow/examples/login-2fa.json index a1b9637a5..6006ebed0 100644 --- a/docs/flow/examples/login-2fa.json +++ b/docs/flow/examples/login-2fa.json @@ -20,7 +20,7 @@ }, "model": "passbook_stages_user_login.userloginstage", "attrs": { - "session_duration": 0 + "session_duration": "seconds=-1" } }, { diff --git a/docs/flow/examples/login-conditional-captcha.json b/docs/flow/examples/login-conditional-captcha.json index f498b2cfb..8ba162d7c 100644 --- a/docs/flow/examples/login-conditional-captcha.json +++ b/docs/flow/examples/login-conditional-captcha.json @@ -20,7 +20,7 @@ }, "model": "passbook_stages_user_login.userloginstage", "attrs": { - "session_duration": 0 + "session_duration": "seconds=-1" } }, { diff --git a/docs/flow/examples/recovery-email-verification.json b/docs/flow/examples/recovery-email-verification.json index e3c849263..e63566fbe 100644 --- a/docs/flow/examples/recovery-email-verification.json +++ b/docs/flow/examples/recovery-email-verification.json @@ -118,7 +118,7 @@ }, "model": "passbook_stages_user_login.userloginstage", "attrs": { - "session_duration": 0 + "session_duration": "seconds=-1" } }, { diff --git a/passbook/stages/user_login/forms.py b/passbook/stages/user_login/forms.py index ce7fb8996..376ec1ebf 100644 --- a/passbook/stages/user_login/forms.py +++ b/passbook/stages/user_login/forms.py @@ -13,4 +13,5 @@ class UserLoginStageForm(forms.ModelForm): fields = ["name", "session_duration"] widgets = { "name": forms.TextInput(), + "session_duration": forms.TextInput(), } diff --git a/passbook/stages/user_login/migrations/0003_session_duration_delta.py b/passbook/stages/user_login/migrations/0003_session_duration_delta.py new file mode 100644 index 000000000..5d0c0d8d9 --- /dev/null +++ b/passbook/stages/user_login/migrations/0003_session_duration_delta.py @@ -0,0 +1,41 @@ +# Generated by Django 3.1.2 on 2020-10-26 20:21 + +from django.apps.registry import Apps +from django.db import migrations, models +from django.db.backends.base.schema import BaseDatabaseSchemaEditor + +import passbook.lib.utils.time + + +def update_duration(apps: Apps, schema_editor: BaseDatabaseSchemaEditor): + UserLoginStage = apps.get_model("passbook_stages_user_login", "userloginstage") + + db_alias = schema_editor.connection.alias + + for stage in UserLoginStage.objects.using(db_alias).all(): + if stage.session_duration.isdigit(): + if stage.session_duration == 0: + stage.session_duration = "seconds=1" + else: + stage.session_duration = f"seconds={stage.session_duration}" + stage.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("passbook_stages_user_login", "0002_userloginstage_session_duration"), + ] + + operations = [ + migrations.AlterField( + model_name="userloginstage", + name="session_duration", + field=models.TextField( + default="seconds=-1", + help_text="Determines how long a session lasts. Default of -1 means that the sessions lasts until the browser is closed. (Format: hours=-1;minutes=-2;seconds=-3)", + validators=[passbook.lib.utils.time.timedelta_string_validator], + ), + ), + migrations.RunPython(update_duration), + ] diff --git a/passbook/stages/user_login/models.py b/passbook/stages/user_login/models.py index 5f500900c..690de176e 100644 --- a/passbook/stages/user_login/models.py +++ b/passbook/stages/user_login/models.py @@ -8,16 +8,19 @@ from django.views import View from rest_framework.serializers import BaseSerializer from passbook.flows.models import Stage +from passbook.lib.utils.time import timedelta_string_validator class UserLoginStage(Stage): """Attaches the currently pending user to the current session.""" - session_duration = models.PositiveIntegerField( - default=0, + session_duration = models.TextField( + default="seconds=-1", + validators=[timedelta_string_validator], help_text=_( - "Determines how long a session lasts, in seconds. Default of 0 means" - " that the sessions lasts until the browser is closed." + "Determines how long a session lasts. Default of -1 means " + "that the sessions lasts until the browser is closed. " + "(Format: hours=-1;minutes=-2;seconds=-3)" ), ) diff --git a/passbook/stages/user_login/stage.py b/passbook/stages/user_login/stage.py index 907d6ce32..dc3619007 100644 --- a/passbook/stages/user_login/stage.py +++ b/passbook/stages/user_login/stage.py @@ -2,11 +2,13 @@ from django.contrib import messages from django.contrib.auth import login from django.http import HttpRequest, HttpResponse +from django.utils.timezone import now from django.utils.translation import gettext as _ from structlog import get_logger from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER from passbook.flows.stage import StageView +from passbook.lib.utils.time import timedelta_from_string from passbook.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND LOGGER = get_logger() @@ -32,7 +34,12 @@ class UserLoginStageView(StageView): self.executor.plan.context[PLAN_CONTEXT_PENDING_USER], backend=backend, ) - self.request.session.set_expiry(self.executor.current_stage.session_duration) + delta = timedelta_from_string(self.executor.current_stage.session_duration) + if delta.seconds == -1: + self.request.session.set_expiry(0) + else: + expiry = now() + delta + self.request.session.set_expiry(expiry) LOGGER.debug( "Logged in", user=self.executor.plan.context[PLAN_CONTEXT_PENDING_USER], diff --git a/passbook/stages/user_login/tests.py b/passbook/stages/user_login/tests.py index 199bca512..fe1b8a978 100644 --- a/passbook/stages/user_login/tests.py +++ b/passbook/stages/user_login/tests.py @@ -105,5 +105,7 @@ class TestUserLoginStage(TestCase): def test_form(self): """Test Form""" - data = {"name": "test", "session_duration": 0} + data = {"name": "test", "session_duration": "seconds=0"} self.assertEqual(UserLoginStageForm(data).is_valid(), True) + data = {"name": "test", "session_duration": "123"} + self.assertEqual(UserLoginStageForm(data).is_valid(), False)