Aller au contenu principal
Jahia Store
FR

UPA - MFA factors - shared extensions

community
Télécharger 0.3.0

Informations

Identifiant du module
mfa-factors-extensions
Identifiant de groupe
org.jahia.community
Statut
community
Catégorie
Authentication
Auteur
Florent BOURASSE
Site web du développeur
http://www.jahia.com
Nécessite Jahia
8.2.3.0
Mis à jour
2026-09-21
Source
scm:git:git@github.com:Jahia/jahia-private.git/jahia-modules/mfa-factors-extensions

The factor-agnostic foundation of the MFA Community family. This bundle has no UI of its own; it provides the plumbing the TOTP and WebAuthn factors plug into: the /cms/login gate with IP allow-listing, the login/logout URL provider, the backup-code generator, per-site and global enforcement policy, an audit log, and the MfaSiteProvider SPI that lets each factor contribute its own enforcement state and per-site routing.

Install this first. Both factor modules declare it as a hard dependency (jahia-depends), so it starts ahead of them. Server administrators configure it under Administration → Server → Configuration → MFA Community.

  • Factor-agnostic /cms/login gate with IPv4/IPv6 and CIDR allow-listing
  • Global and per-site login/logout URL routing (file-backed OSGi configuration, one file per site)
  • One-shot backup-code generation shared across factors
  • Session-level GraphQL API, including the locked-out "request reset" flow (no account enumeration)
  • Consolidated audit and enrollment reporting surface for all installed factors

Captures d'écran

Versions

Single-purpose security release. It closes the remaining half of an MFA bypass by changing one default, and it is a breaking change for any deployment whose automation authenticates with a username and password.

Breaking change — loginGate.gateBasicAuth now defaults to true

Jahia's HttpBasicAuthValve authenticates a username/password taken from an Authorization: Basic header and never consults MFA factors. While MFA enforcement is armed, curl -u user:password https://…/modules/graphql was therefore a complete second-factor bypass on every endpoint, for anyone holding a stolen password.

0.2.0 shipped the gate that closes it, but opt-in (loginGate.gateBasicAuth=false), so a default 0.2.0 installation still reproduced the bypass. From 0.3.0 the gate is on by default. Absent, blank and unparseable values all read as ON; only a literal false disarms it.

Are you impacted?

You are impacted if both are true:

  1. You arm MFA enforcement (enforcedFactors is non-empty on PID org.jahia.modules.mfa.extensions), and
  2. something authenticates to Jahia with an Authorization: Basic header — scripts, CI jobs, monitoring probes, WebDAV clients, custom integrations.

While enforcement is armed, those callers now receive 403 on every endpoint, including /modules/api/provisioning, /modules/graphql and /modules/tools/*.

If you do not arm enforcement, nothing changes: the gate is inert while enforcedFactors is empty.

What to do

  1. Move non-interactive callers to a personal API token. Jahia's TokenAuthValve is not gated and authenticates ahead of this gate. This is the supported path.
  2. Or whitelist them — loginGate.ipWhitelist. Verify a whitelisted client actually gets through before you arm enforcement: behind a reverse proxy, including Tomcat's RemoteIpValve (enabled by default on the Jahia EE image), the whitelist fails closed unless loginGate.trustForwardedFor=true as well (GHSA-4v3g-mcmj-83fp).
  3. Or opt out — set loginGate.gateBasicAuth=false. This re-opens the bypass for that deployment. It is logged at WARN on every reconfiguration while it is off, and it is meant to be temporary.

⚠️ Upgrading from 0.2.0 does NOT arm it on its own

The shipped .cfg carries the # default configuration - won't be overridden marker, so Jahia's module extender does not rewrite your deployed <karaf.etc>/org.jahia.modules.mfa.extensions.cfg. An installation that already ran 0.2.0 keeps the loginGate.gateBasicAuth=false line 0.2.0 wrote, and stays exposed.

Upgrading the bundle is not enough. Edit the deployed file and either set the key to true or delete the line, then confirm the server log reports the gate on.

The new default reaches fresh installations only.

What is not affected

The unconditional /cms/login form-parameter block is unchanged, and still has no switch. Token credentials (TokenAuthValve, personal API tokens) are not gated. The gate remains inert while enforcedFactors is empty.

Verification

  • Unit: 222/222 on mfa-factors-extensions. Three cases in MfaLoginGateDecisionTest are red without the change.
  • End to end: the full Cypress suite green — phase 1 93/93 across 25 specs, phase 2 1/1.
  • A/B on a Jahia 8.2.4.0-SNAPSHOT lab with UPA 0.2.0, the same spec file in both arms and the bundle as the only variable: the published 0.2.0 answered 200 to a Basic credential with enforcement armed and the key untouched (8 passing, 1 failing); this build answered 403 (9 passing).

Requires the User Password Authentication (UPA) module. 

Full changelog: 0_2_0...0_3_0

Nécessite Jahia 8.2.3.0Mis à jour 2026-09-21

Security

Three vulnerabilities affecting 0.1.0 / 0.1.1, found by an adversarial review and each confirmed by reproduction against a live Jahia 8.2 before and after the fix (#100).

All three shared one root cause: a client-supplied site value was trusted to narrow or suppress an access-control decision.

  • The site request parameter defeated the /cms/login gate. MfaLoginGateDecision.isGated() resolved the site from request.getParameter("site"), validated only against [A-Za-z0-9_-]+, and used it to replace the "is any site enforcing?" answer. Naming a site where the factor was not enabled turned gating off — a password-only POST authenticated as root with no second factor while enforcement was active. The site context now only ever widens the decision.
  • The site argument to mfaInitiate suppressed a globally enforced factor. Per-site applicability was evaluated before enforcement, so a caller holding only a password could name a site with the factor disabled, receive a "skipped" preparation, and have verify() accept an empty code. Global enforcement is now evaluated first, and a per-site switch can never skip a user who owns the factor.
  • Any single-site administrator could strip MFA from any account, including root. resetUserMfa / resetUserWebauthn authorized on siteKey alone, then acted on an unconstrained userId. The subject is now resolved once and authorized in its own right; a reset targeting a global user requires server-administrator rights.

Also: enrollmentReport no longer hands any site administrator the MFA status of every account platform-wide, and unknown usernames are reported instead of returning a green confirmation for a user that was never touched.

New — loginGate.gateBasicAuth

The /cms/login gate can now also refuse a password presented in an Authorization: Basic header, closing the same bypass on the machine-facing surface. Default false, and deliberately opt-in: that credential shape is not confined to one endpoint, so arming it refuses the provisioning API, GraphQL, the tools and WebDAV platform-wide the moment one site enforces a factor.

Read the README section before enabling it — in particular, whitelist your automation hosts, not just an admin VPN range, and verify a whitelisted client actually gets through first, because behind a reverse proxy the IP whitelist fails closed (GHSA-4v3g-mcmj-83fp).

Fixed

  • The /cms/login gate wrote the response twice (valve + filter), surfacing as HTTP 500 instead of a redirect whenever a distinct loginUrl was configured.
  • The gate matched on the mere presence of username + password request parameters while running at pipeline position 0, so it refused unrelated endpoints that happened to use those field names. It now mirrors LoginEngineAuthValveImpl#isLoginRequested.
  • The admin screens could wipe your configuration. useQuery was read without error, so a failed load left constructor defaults in the form and Save wrote them back — silently clearing enforcedFactors, the IP whitelist and the login URL.
  • The admin UI stated that trusting X-Forwarded-For was ON by default and defaulted the checkbox to true. It is OFF, because trusting the header makes the IP whitelist spoofable (SEC-135).
  • The self-service disable/regenerate dialog offered backup codes the server does not accept there — each rejected attempt burned a rate-limit strike, walking a user who had lost their authenticator into locking themselves out of their own recovery.
  • The destructive "reset a user's MFA" action never showed its in-flight state: a React 18 batching bug unmounted the confirmation box in the same render that was meant to display it. Affected both the TOTP and WebAuthn copies.

Known limitation (documented, unchanged behaviour)

The /cms/login gate is scoped to global enforcement: with enforcedFactors empty it is inert, and loginGate.enabled does not override that. A per-site opt-in deployment still challenges enrolled users through the MFA login UI, but /cms/login remains a password-only door. See The /cms/login gate → Known limitation in the README for the two supported mitigations.

Quality

The module previously had no CI, no lint gate, no coverage measurement, and no tests for its React admin apps. This release adds all four: a build workflow, yarn lint and yarn test wired into the Maven build, JaCoCo coverage, and 115 component tests — plus 22 dependency updates, ESLint 9, and Node 22.23.2.

352 Java tests + 115 React tests + 26 Cypress specs, all green.

Compatibility

Jahia 8.2.3+, user-password-authentication 0.2.0, graphql-dxm-provider 3.4+, Java 11+.

Operators who use per-site group scoping should note that a globally enforced factor now overrides it (an out-of-scope user is challenged rather than skipped, and the override is logged) — see the README's Global enforcement section.

Full Changelog: 0_1_0...0_2_0

Nécessite Jahia 8.2.3.0Mis à jour 2026-08-13

Fixes

  • Admin UI: restored the vertical scrollbar on the MFA extensions global settings page (/jahia/administration/mfa-extensions-settings). The form was clipped with no scrollbar when it grew past the viewport.

Contents

0.1.0 plus the single fix above. No API or dependency changes.

Requirements

  • Jahia 8.2.3.0
  • user-password-authentication 0.2.0 (modules import org.jahia.modules.upa.mfa at [0.2,1))

Install

Deploy the three .jar bundles via the module manager / hot-deploy folder; install mfa-factors-login-ui-0.1.1.tgz via the provisioning API.

Nécessite Jahia 8.2.3.0Mis à jour 2026-06-29
Nécessite Jahia 8.2.3.0Mis à jour 2026-06-28