Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Roundcube Userli Plugin

A Roundcube plugin that automatically synchronizes user identities from a userli aliases API at login.
A Roundcube plugin for [Userli](https://github.com/systemli/userli):

* **Login with two-factor authentication:** the plugin checks the password and, for users with 2FA, asks for the TOTP or backup code in a second step. Userli then issues a webmail credential, which Roundcube uses for IMAP and SMTP instead of the password. It is valid for 12 hours; the Roundcube session ends with it. Logging out revokes it. With a Userli version without the webmail login endpoint, the plugin keeps logging in with the password.

![Two-factor code step](screenshots/twofactor.png)
* **Alias identities:** at login, the plugin creates a Roundcube identity for each alias of the user.

![Alias identities](screenshots/aliases.png)

## Installation

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
},
"extra": {
"roundcube": {
"min-version": "1.4.0"
"min-version": "1.6.0"
}
},
"config": {
Expand Down
4 changes: 2 additions & 2 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions localization/de_DE.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

$labels = [];
$labels['twofactor_code'] = 'Zwei-Faktor-Code';
$labels['use_another_account'] = 'Anderes Konto verwenden';

$messages = [];
$messages['twofactor_required'] = 'Gib den Code aus deiner Authenticator-App oder einen Backup-Code ein.';
$messages['twofactor_invalid'] = 'Der Code ist ungültig. Bitte versuche es erneut.';
$messages['twofactor_expired'] = 'Die Anmeldung hat zu lange gedauert. Bitte gib dein Passwort erneut ein.';
$messages['too_many_attempts'] = 'Zu viele Anmeldeversuche. Bitte versuche es später erneut.';
$messages['password_change_required'] = 'Bitte ändere zuerst dein Passwort in deinen Kontoeinstellungen.';
$messages['login_unavailable'] = 'Die Anmeldung ist gerade nicht möglich. Bitte versuche es später erneut.';
13 changes: 13 additions & 0 deletions localization/en_US.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

$labels = [];
$labels['twofactor_code'] = 'Two-factor code';
$labels['use_another_account'] = 'Use another account';

$messages = [];
$messages['twofactor_required'] = 'Enter the code from your authenticator app or a backup code.';
$messages['twofactor_invalid'] = 'The code is invalid. Please try again.';
$messages['twofactor_expired'] = 'The login took too long. Please enter your password again.';
$messages['too_many_attempts'] = 'Too many login attempts. Please try again later.';
$messages['password_change_required'] = 'Please change your password in your account settings first.';
$messages['login_unavailable'] = 'Login is currently not possible. Please try again later.';
Binary file added screenshots/aliases.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/twofactor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 0 additions & 10 deletions tests/UserliAliasesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,6 @@ public function testInitRegistersHooks(): void
$this->assertArrayHasKey('login_after', $hooks);
}

public function testAuthenticateStoresPassword(): void
{
$args = ['pass' => 'test_password_123'];

$this->plugin->authenticate_store_pass($args);

// We can't directly test the private property, but we can verify no errors occur
$this->assertTrue(true);
}

public function testLoginAfterWithSuccessfulApiResponse(): void
{
// Mock configuration
Expand Down
Loading
Loading