Prototype: OTP auth, styleguide overhaul, login/session rework

Replace password reset with email OTP verification flow.
Add auth_otp module, OTP templates, and email delivery.
Expand styleguide CSS with full component library.
Rework login, register, and admin views for cookie sessions.
Remove legacy 2FA templates and password reset flow.
Update SSH wrappers, forms, validators, and middleware.
This commit is contained in:
russell@unturf.com 2026-03-04 16:44:40 -05:00
parent 1dac65fc8a
commit 722c3bd369
38 changed files with 4100 additions and 1835 deletions

View file

@ -129,9 +129,11 @@ class TemplateArgs(StrictAttributeDict):
class BaseAppView(object):
DONT_CHECKOUT_VIEWS = ["channelstream_connect", "ops_ping"]
EXTRA_VIEWS_TO_IGNORE = ["login", "register", "logout"]
SETUP_2FA_VIEW = "setup_2fa"
VERIFY_2FA_VIEW = "check_2fa"
EXTRA_VIEWS_TO_IGNORE = [
"login", "register", "logout",
"login_otp_verify",
"sudo_otp_verify", "resend_otp",
]
def __init__(self, context, request):
self.request = request
@ -147,70 +149,6 @@ class BaseAppView(object):
self._rhodecode_user = request.user # auth user
self._rhodecode_db_user = self._rhodecode_user.get_instance()
self.user_data = self._rhodecode_db_user.user_data if self._rhodecode_db_user else {}
self._maybe_needs_password_change(request.matched_route.name, self._rhodecode_db_user)
self._maybe_needs_2fa_configuration(request.matched_route.name, self._rhodecode_db_user)
self._maybe_needs_2fa_check(request.matched_route.name, self._rhodecode_db_user)
def _maybe_needs_password_change(self, view_name, user_obj):
if view_name in self.DONT_CHECKOUT_VIEWS:
return
log.debug("Checking if user %s needs password change on view %s", user_obj, view_name)
skip_user_views = [
"logout",
"login",
"check_2fa",
"my_account_password",
"my_account_password_update",
]
if not user_obj:
return
if user_obj.username in [User.DEFAULT_USER, User.GHOST_USER, User.AI_USER, User.SECURITY_USER]:
return
now = time.time()
should_change = self.user_data.get("force_password_change")
change_after = safe_int(should_change) or 0
if should_change and now > change_after:
log.debug("User %s requires password change", user_obj)
h.flash(
"You are required to change your password",
"warning",
ignore_duplicate=True,
)
if view_name not in skip_user_views:
raise HTTPFound(self.request.route_path("my_account_password"))
def _maybe_needs_2fa_configuration(self, view_name, user_obj):
if view_name in self.DONT_CHECKOUT_VIEWS + self.EXTRA_VIEWS_TO_IGNORE:
return
if not user_obj:
return
if user_obj.needs_2fa_configure and view_name != self.SETUP_2FA_VIEW:
h.flash(
"You are required to configure 2FA",
"warning",
ignore_duplicate=False,
)
# Special case for users created "on the fly" (ldap case for new user)
user_obj.check_2fa_required = False
raise HTTPFound(self.request.route_path(self.SETUP_2FA_VIEW))
def _maybe_needs_2fa_check(self, view_name, user_obj):
if view_name in self.DONT_CHECKOUT_VIEWS + self.EXTRA_VIEWS_TO_IGNORE:
return
if not user_obj:
return
if user_obj.check_2fa_required and view_name != self.VERIFY_2FA_VIEW:
raise HTTPFound(self.request.route_path(self.VERIFY_2FA_VIEW))
def _log_creation_exception(self, e, repo_name):
_ = self.request.translate