Skip to content

Add login with two-factor authentication - #9

Merged
t2d merged 8 commits into
mainfrom
feat/twofactor-login
Oct 26, 2026
Merged

t2d merged 8 commits into
mainfrom
feat/twofactor-login

Conversation

@t2d

@t2d t2d commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Requires systemli/userli PR 1394.

Goal

Two-factor authentication for webmail. Userli can require app passwords in mail programs for users with 2FA, so Roundcube needs its own 2FA step.

Changes

  • authenticate sends the password to POST /api/roundcube/login. For users with 2FA, the login form turns into a code step (TOTP or backup code). The password is kept encrypted in the session for up to 5 minutes and is never shown in the form.
  • Roundcube uses the returned webmail credential for IMAP and SMTP, so the password is no longer stored in the Roundcube session.
  • The session ends when the credential expires (12 hours). The credential is revoked on logout (session_destroy, after Roundcube's own logout actions) and when the IMAP login fails after Userli issued it.
  • There are messages for a wrong or expired code, too many attempts, and a required password change.
  • The code step has a "Use another account" link. It is answered with HTTP 200, so proxy brute-force detection (CrowdSec) does not count a correct password as a failure. Wrong codes stay 401.
  • If Userli has no login endpoint yet (404), the plugin writes a note to the userli log and keeps logging in with the password. The plugin can therefore be deployed before Userli.
  • Alias identities work as before. They still use the password, because the aliases API rejects credentials.
  • Now requires Roundcube 1.6 (the first version supporting PHP 8.1).

Screenshots

Two-factor code step

Alias identities

Tested end to end against the Userli dev stack: password login, 2FA with TOTP and backup code, wrong and expired code, logout, alias sync, the session ending when the credential expires, the rate limit, and a request without CSRF token. The setting was tested both on and off.

🤖 Generated with Claude Code

The plugin exchanges the password, and for users with 2FA a TOTP or
backup code entered in a second step, for a webmail credential from
the Userli API. Roundcube uses the credential for IMAP and SMTP, the
session ends when it expires, and logging out revokes it.

With a Userli version without the login endpoint, the plugin keeps
logging in with the password.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
t2d and others added 2 commits October 21, 2026 18:05
- Skip requests that Roundcube rejects anyway (no CSRF token or session)
  instead of sending the password to Userli
- Drop an expired pending login before showing the code form, and do not
  call Userli for a code without a valid pending login
- Remove the pending login with rcube_session::remove(), so it does not
  stay in the stored session
- Revoke the credential in session_destroy, after Roundcube's own logout
  actions, and when the IMAP login fails after Userli issued one
- Show messages for too many attempts and a required password change
- Log the fallback to password login when Userli has no login endpoint
- Validate the credential and expiry types in the Userli response
- Drop the inputmode attribute, which Roundcube filters out
- Require Roundcube 1.5 (get_input_string, get_http_client)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d login

The code step gets a "Use another account" link that drops the pending
login. It is answered with HTTP 200 instead of 401, because the password
was right: brute-force detection on the proxy access log (CrowdSec) must
not count it. Wrong codes stay 401.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
t2d and others added 4 commits October 25, 2026 20:58
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@t2d
t2d marked this pull request as ready for review October 25, 2026 20:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@t2d
t2d merged commit 4614bee into main Oct 26, 2026
4 checks passed
@t2d
t2d deleted the feat/twofactor-login branch October 26, 2026 06:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant