Несколько ящиков у одного логина, общие ящики, кто ответил, вход ящик*логин
continuous-integration/drone/pr Build is passing
continuous-integration/drone/pr Build is passing
- V7: users.is_shared, mailbox_access (MEMBER от админа/Jambotron, ATTACHED по паролю ящика), messages.answered_by
- /api/mailboxes: мои ящики с непрочитанными, сессия другого ящика (JWT с claim by), подключить по паролю
(LoginGuard), убрать из меню
- общий ящик: без своего входа (веб/SMTP/IMAP), участники открывают; «прочитано» общее; ответ из веба → answered_by
- SMTP/IMAP: ящик*логин + пароль логина (как master user в Dovecot)
- открытый чужой ящик (общий, подключённый, OPEN админа): профиль и пароль не меняются (403)
- админ: создать shared, участники ящика (PUT /{id}/members); служебное API: PUT /shared/{address};
выключение аккаунта убирает и из общих ящиков
- UI: MAILBOXES в левом меню (переключение, ⊕ подключить, × убрать), shared/участники в настройках,
«Answered by» у письма
- 88 тестов; живой прогон на Postgres 17 (апгрейд V6→V7 на данных), скриншоты UI
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import com.mailserver.repository.UserRepository;
|
||||
import com.mailserver.service.DkimService;
|
||||
import com.mailserver.service.LocalDelivery;
|
||||
import com.mailserver.service.MailLists;
|
||||
import com.mailserver.service.MailboxAccess;
|
||||
import com.mailserver.service.UserService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -38,9 +39,12 @@ public class MailRouter implements SmtpBackend {
|
||||
private final DkimService dkim;
|
||||
private final SmtpDeliveryClient client;
|
||||
private final MailLists lists;
|
||||
private final MailboxAccess access;
|
||||
|
||||
public MailRouter(MailDomains domains, UserRepository users, UserService userService, LocalDelivery localDelivery,
|
||||
OutboundQueue queue, DkimService dkim, SmtpDeliveryClient client, MailLists lists) {
|
||||
OutboundQueue queue, DkimService dkim, SmtpDeliveryClient client, MailLists lists,
|
||||
MailboxAccess access) {
|
||||
this.access = access;
|
||||
this.domains = domains;
|
||||
this.users = users;
|
||||
this.userService = userService;
|
||||
@@ -52,9 +56,9 @@ public class MailRouter implements SmtpBackend {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean authenticate(String username, String password) {
|
||||
User user = users.findByEmail(username.toLowerCase(Locale.ROOT)).block();
|
||||
return user != null && user.isActive() && userService.checkPassword(user, password);
|
||||
public String authenticate(String username, String password) {
|
||||
User mailbox = access.login(username, password);
|
||||
return mailbox == null ? null : mailbox.getEmail();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -32,8 +32,11 @@ public interface SmtpBackend {
|
||||
record Envelope(String remoteIp, String helo, String mailFrom, String authenticatedUser, List<Recipient> recipients) {
|
||||
}
|
||||
|
||||
/** true — the address and password belong to an active mailbox. */
|
||||
boolean authenticate(String username, String password);
|
||||
/**
|
||||
* {@code login} or {@code mailbox*login} with the login's password → the mailbox address the session works as,
|
||||
* or null.
|
||||
*/
|
||||
String authenticate(String username, String password);
|
||||
|
||||
Route route(String recipient, String authenticatedUser);
|
||||
|
||||
|
||||
@@ -373,12 +373,13 @@ public class SmtpServer {
|
||||
closing = true;
|
||||
return reply("421 4.7.0 Too many failed logins, try again in " + LoginGuard.human(wait));
|
||||
}
|
||||
return blocking(() -> backend.authenticate(username, password))
|
||||
.flatMap(ok -> {
|
||||
if (ok) {
|
||||
return blocking(() -> java.util.Optional.ofNullable(backend.authenticate(username, password)))
|
||||
.flatMap(found -> {
|
||||
if (found.isPresent()) {
|
||||
guard.success(remoteIp, username);
|
||||
authenticatedUser = username;
|
||||
log.info("SMTP {}: authenticated as {}", remoteIp, username);
|
||||
authenticatedUser = found.get();
|
||||
log.info("SMTP {}: authenticated as {}{}", remoteIp, found.get(),
|
||||
found.get().equalsIgnoreCase(username) ? "" : " (login " + username + ")");
|
||||
return Mono.just("235 2.7.0 Authentication successful\r\n");
|
||||
}
|
||||
guard.failure(remoteIp, username);
|
||||
|
||||
Reference in New Issue
Block a user