Repository navigation
✨ Add Roundcube login with 2FA and short-lived webmail credentials - #1394
Merged
Merged
Conversation
t2d
force-pushed
the
feat/webmail-2fa
branch
3 times, most recently
from
October 21, 2026 15:13
d9e204d to
5bcad2d
Compare
Contributor
Author
POST /api/roundcube/login checks the main password and, for users with 2FA, a TOTP or backup code. It returns an app password that expires after 12 hours, which Roundcube uses for IMAP and SMTP instead of the main password. POST /api/roundcube/logout revokes it. Webmail credentials are hidden from the account page, the admin list, the metrics and the user stats. Expired ones are pruned daily. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New setting twofactor_app_passwords_required: Dovecot and Postfix reject the main password of users with 2FA, so their mail programs need app passwords and webmail the Roundcube login. It defaults to on for new installations. A migration turns it off when users already exist, so existing mail setups keep working until the admin enables it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
App passwords were never released, so the table is created with last_login_time and expires_at right away. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Roundcube plugin ends the webmail session when the credential expires. 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>
Failed attempts on /api/roundcube/login get no Dovecot auth penalty, so password and 2FA code guesses are limited to 10 per user in 15 minutes. Spam users and users who must change their password get 403 like in the Dovecot API, and a backup code is used up only once the credential exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gration The migration relied on the settings cache being cleared after the upgrade; a stale cache made existing installs fall back to the default and enforce it. Now the default is off and the wizard turns it on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rebase onto main replaced the allowAppPassword flag with authenticateMailClient(), so the check moves there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Check the spam role and a required password change before authenticating in the Roundcube login, like Dovecot and Postfix, so rejected logins don't count as last login - Schedule the removal of webmail credentials on password change in the same flush as the new password, like removeByUser(), instead of a bulk delete in a listener after the flush 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>
…allations Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Webmail credentials create no MailCrypt keys for users without them - Metrics count only app passwords, not webmail credentials - app:admin:password ends webmail sessions, admin reset without MailCrypt removes app passwords - Roundcube login answers a MailCrypt decryption failure like the Dovecot API Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Webmail credentials of users without a MailCrypt key carry no key copy, so Dovecot fails on them once a key exists. Also run the settings migration insert through addSql and note why the login event comes first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spam role and a required password change were reported for any password, which revealed that the account exists and its state. verifyPassword() dispatches no login event, so rejected logins still don't count as last login. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The limit let anyone lock a user out of webmail and is not needed to avoid a regression: per-IP protection belongs to the webserver, as for the web login. Per-user limits for all password and 2FA code checks follow separately with consistent bounds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
None of them is in a release yet, so app_passwords is created in its final shape together with the setting for existing installations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…abled The first app password or a recovery token creates a key without turning MailCrypt on, so Dovecot never asks for it and the credential keeps working. Only remove webmail credentials when MailCrypt gets enabled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mail login Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
t2d
marked this pull request as ready for review
October 26, 2026 09:17
t2d
requested review from
0x46616c6b,
doobry-systemli and
y3n4
as code owners
October 26, 2026 09:17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Plugin side: roundcube-userli-plugin PR 9.
Goal
Enforce 2FA for mail. Roundcube logs in with the main password plus a TOTP code and then uses a short-lived app password for IMAP and SMTP. Mail programs of 2FA users need app passwords.
Changes
POST /api/roundcube/loginchecks the main password and, for 2FA users, a TOTP or backup code. It returns a webmail credential (an app password that expires after 12 hours) and itsexpiresAt. If the code is missing, it returns401 twofactor required. Spam users and users who must change their password get403, only after a correct password. Per-user attempt limits follow in Per-user limits for password and 2FA code checks #1400. App passwords are rejected.POST /api/roundcube/logoutrevokes the webmail credential. It never touches regular app passwords. A password change and turning on MailCrypt also revoke all webmail credentials. An admin password reset removes all app passwords.app_passwords.expires_at: expired credentials no longer authenticate and are pruned daily. Webmail credentials are hidden from the account page, the admin list, the metrics and the user stats.twofactor_app_passwords_required): Dovecot and Postfix reject the main password of 2FA users. It is on by default. A migration turns it off on existing installations, so their mail programs keep working. When it is on, the 2FA page tells users that their mail programs need app passwords and links to them.Tested end to end with plugin release 1.1.0 against the dev stack (Roundcube, Dovecot, MailCrypt), with the setting both on and off.
🤖 Generated with Claude Code