major
#29092
Multi-factor authentication and self-service account management
Overview
Add platform-level support for multi-factor authentication (MFA), self-service account management, and invitation-based self-registration to the TopLogic framework.
Proposed New Modules
| Module | Artifact | Description |
| com.top_logic.security.auth.totp | tl-security-auth-totp | TOTP second-factor device (RFC 6238) |
| com.top_logic.security.otp | tl-security-otp | One-time password email verification service |
| com.top_logic.security.selfservice | tl-security-selfservice | Password and TOTP self-service reset workflows |
| com.top_logic.security.invite | tl-security-invite | Invitation tokens and self-registration framework |
Changes to Existing Code (tl-core)
- New SecondFactorDevice interface alongside the existing AuthenticationDevice
- New secondFactorDeviceID attribute on Person
- Extended LoginPageServlet / Login for two-step MFA login flow
- New SecondFactorDevice accessor in TLSecurityDeviceManager
Specification
See specs/multi-factor-auth-spec.md in the repository for the full specification document.
Motivation
Applications increasingly require:
- Two-factor authentication (password + TOTP) for users without external identity providers
- Self-service password and TOTP reset without administrator intervention
- Invitation-based self-registration for external users
Migration
The new modules (tl-security-auth-totp, tl-security-otp, tl-security-selfservice, tl-security-invite) are optional dependencies and require no action. However, the core’s login mechanism was rewritten to support the two-step flow, so applications that have customized the login page, extended the account management classes, or configured authentication devices will need to be updated.
Login page replaced by an in-app dialog
LoginPageServlet (mapping /servlet/login), login.jsp, login.banner.inc, doOnload.inc, jsp/main/loginError.jsp, jsp/util/administration/changePwd.jsp, and script/tl/loginError.js have been removed. A request without a session now receives an anonymous session, and the layout provides the LoginViewDialog through the new <login-hooks> in MainLayout$GlobalConfig (framework default: OpenLoginDialogHook).
- Delete application overlays for the removed JSPs and includes, as well as web.xml entries for LoginPageServlet. Bookmarks and monitoring probes pointing to /login.jsp or /servlet/login must use /servlet/LayoutServlet.
- Remove `login`, `loginRetryPage`, `loginErrorPage`, and `changePassword` from an `ApplicationPages$Config` override; these properties no longer exist (an unknown property results in a startup error). `loginPage`, `logoutPage`, and `loginRetrySSO` default to `/servlet/LayoutServlet`.
- Login-messages add-on: LoginMessagesMainLayout and its GlobalConfig are replaced by the login hook LoginMessagesHook. Remove the MAIN_LAYOUT_CLASS theme setting that references the old class and the LoginMessagesMainLayout$GlobalConfig block; the add-on’s loginMessagesConf.config.xml registers the hook:
{{{#!xml <config config:interface="com.top_logic.mig.html.layout.MainLayout$GlobalConfig">
<login-hooks>
<login-hook class="com.top_logic.addons.loginmessages.layout.LoginMessagesHook" showLoginMessages="true"/>
</login-hooks>
</config> }}}
- Resource keys: tl.logout is now class.com.top_logic.layout.component.configuration.I18NConstants.LOGOUT; layouts.admin.persons.changePassword.* moved to class.com.top_logic.knowledge.gui.layout.person.I18NConstants.CHANGE_PASSWORD_FORM.*; MAX_USERS_EXCEEDED has been removed.
- A session may now belong to the anonymous account (PersonManager.getAnonymous(), TLContext.isAnonymous()), and Person.all() includes that account. Commands that must not be made available to anonymous users use the executability rule AnonymousAccountDisabled.
Java API
- Login.login(userName, request, response) is replaced by Login.checkUserPassword(userName, char[] password, request, response), and Login.login(request, response, credentials) by Login.checkLoginCredentials(credentials, request, response). Both methods only verify the credentials; the caller creates the session using SessionService.getInstance().loginUser(request, response, person). MaxUsersExceededException is replaced by LoginHookFailedException. LoginCredentials is no longer AutoCloseable: call clearPassword() in a finally block.
- ExternalAuthenticationServlet (base class of SSO servlets) extends NoContextServlet instead of LoginPageServlet: forwardPage(...) is now forwardToPage(...), forwardToStartPage(...) is redirectToStartPage(...), checkRequest(...) and forwardToTarget(...) can no longer be overridden, and forwardToSSOLoginFailed(...) declares IOException and ServletException. Subclasses that only implement ` retrieveLoginCredentials(...) ` require no changes. ` LoginPageServlet.appendCustomParameters(...) ` and ` PARAM_START_PAGE ` have been moved to ` AbstractTopLogicServlet`.
- TLPersonManager has been removed: configure and extend com.top_logic.knowledge.wrap.person.PersonManager (as ContactPersonManager does; its Config is no longer generic). PersonManager extends KBBasedManagedClass, so subclasses must call super(context, config). Replacement for getAllAliveFullPersons(): Person.all().stream().filter(Person.FULL_USER_FILTER).
- Person.create(kb, name, String deviceId) is now Person.create(kb, name, AuthenticationDevice device).
- AuthenticationDevice.getMFARequirement() is a new abstract method: custom devices implement it (e.g., by returning MfaRequirement.DISABLED).
- FormMember.setLabel(ResKey), setTooltip(ResKey), and setTooltipCaption(ResKey) were added alongside the String variants; a call with a literal null is ambiguous and must be cast (setLabel((String) null)).
- ListStorage.listConfig(...), SetStorage.setConfig(...), and SingletonLinkStorage.singletonLinkConfig(...) take a trailing boolean without versioning (pass `false ` for the previous behavior).
- The deprecated MetaElementUtil.getAllInstancesOf(TLClass) and getAllDirectInstancesOf(TLClass) have been removed; use the overloads with the expected class (getAllInstancesOf(type, Wrapper.class)).
- Base32 has been renamed to EncodeTypable (static method names remain the same).
Configuration
- mfa-requirement is mandatory on DBAuthenticationAccessDevice.Config and LDAPAuthenticationAccessDevice.Config. The core sets ` m fa - requ irement` to `optional` for `dbSecurity ` and `%LDAP_MFA_REQUIREMENT% ` in ` ldapConf.config.xml`; an application that declares its own `<security-device>` of one of these classes (or a copied ` ldapConf`) must add `mfa-requirement="optional|required|disabled"`.
- Applications that maintain their own <modules> list must enable LoginFailuresModule$Module.
- The migration Ticket_29092_multi_factor_authentication (tl-element) automatically adds Person#mfaSecret and Person#mfaRequirement. The tl-layout-formeditor migration of the same name replaces the form definition annotation for tl.accounts:Person: any in-app customization of the account form is lost and must be reapplied after the update.