diff --git a/backend/config.py b/backend/config.py index 04039e5adbf896b51b5a5958672c48ea229776ce..7f9c276e90e8bcd89e235bd6c26e7baf04ef1cb3 100644 --- a/backend/config.py +++ b/backend/config.py @@ -24,3 +24,4 @@ LOAD_INCLUSTER_CONFIG = os.environ.get("LOAD_INCLUSTER_CONFIG").lower() == "true RUN_BY_GUNICORN = "gunicorn" in os.environ.get("SERVER_SOFTWARE", "") DEMO_INSTANCE = os.environ.get("DASHBOARD_DEMO_INSTANCE", "False").lower() in ('true', '1') +ENFORCE_2FA = os.environ.get("DASHBOARD_ENFORCE_2FA", "False").lower() in ('true', '1') diff --git a/backend/web/login/login.py b/backend/web/login/login.py index 6947038ec10f7c4445e4eda90875750049326f0e..71117746cc5f5e7f27243c78dca1a3cf18d11bf0 100644 --- a/backend/web/login/login.py +++ b/backend/web/login/login.py @@ -17,6 +17,7 @@ from ory_hydra_client.models import AcceptConsentRequest, AcceptLoginRequest, Co import ory_hydra_client.exceptions as hydra_exceptions import ory_kratos_client from ory_kratos_client.api import frontend_api, identity_api +from ory_kratos_client.model.authenticator_assurance_level import AuthenticatorAssuranceLevel from flask import abort, current_app, jsonify, redirect, render_template, request from database import db @@ -335,6 +336,24 @@ def consent(): current_app.logger.error(f"Conflict. Consent request {challenge} already used") abort(503, description="Consent request already used. Please try again") + if ENFORCE_2FA: + # Check for session status, in particular 2FA. + cookie = get_kratos_cookie() + if not cookie: + current_app.logger.info("consent: no kratos cookie set, redirecting to set up 2fa") + response = redirect(KRATOS_PUBLIC_URL + "self-service/settings/browser") + response.set_cookie('stackspin_context', '2fa-required') + return response + session = kratos_public_frontend_api.to_session(cookie=cookie) + # Check session aal. + aal = session['authenticator_assurance_level'] + current_app.logger.info(f"aal: {aal}") + if aal == AuthenticatorAssuranceLevel('aal1'): + current_app.logger.info("aal is only aal1, so not accepting consent request") + response = redirect(KRATOS_PUBLIC_URL + "self-service/settings/browser") + response.set_cookie('stackspin_context', '2fa-required') + return response + # Get information about this consent request: # False positive: pylint: disable=no-member try: diff --git a/backend/web/static/base.js b/backend/web/static/base.js index bf79733c8c7eaf990a84556bf5753d67430be9e9..d362b57907208b006c1ee3ef70b5ef590c76f168 100644 --- a/backend/web/static/base.js +++ b/backend/web/static/base.js @@ -218,6 +218,16 @@ function flow_settings() { $('#pills-password-tab').tab('show'); Cookies.set('stackspin_context', ''); } + + // If the user is required to set up 2FA, switch to + // that tab and show a message. + if (context == "2fa-required") { + $('#pills-totp-tab').tab('show'); + $("#contentMessages").html('Setting up a second factor is required to continue.'); + $("#contentMessages").addClass("alert"); + $("#contentMessages").addClass("alert-warning"); + Cookies.set('stackspin_context', ''); + } }, complete: function (obj) { // If we get a 410, the flow is expired, need to refresh the flow