Files
reactive-mail-server/backend/src/test/java/com/mailserver/controller/AuthenticationControllerTest.java
T
Liosha84andClaude Opus 5.5 99e12d33f3
continuous-integration/drone/pr Build is passing
Вход через Jambotron (OAuth 2.0 code + PKCE), пароли приложений, системные ящики
- /api/oauth/start → JAMBOTRON_URL/oauth/authorize (client mail, PKCE S256);
  /api/oauth/callback: код меняется на сервере у Jambotron /api/oauth/token
  (Bearer SERVICE_TOKEN) → рабочий ящик сотрудника → сессия, ADMIN Jambotron — ADMIN.
- Вход по паролю в веб — только встроенный администратор (когда вход через Jambotron включён).
- Пароли приложений (V9 app_passwords): User panel → APP PASSWORDS, только SMTP/IMAP.
- Системные ящики (V9 users.is_system): без входа в веб, открывают встроенные
  администраторы, не в USERS; Admin panel → MAILBOXES → кнопка SYSTEM.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 20:57:30 +01:00

103 lines
5.0 KiB
Java

package com.mailserver.controller;
import com.mailserver.config.MailDomains;
import com.mailserver.domain.User;
import com.mailserver.dto.LoginRequest;
import com.mailserver.security.LoginGuard;
import com.mailserver.service.MailService;
import com.mailserver.service.UserService;
import com.mailserver.util.JwtUtils;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import reactor.core.publisher.Mono;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.Map;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class AuthenticationControllerTest {
private final UserService users = mock(UserService.class);
private final JwtUtils jwt = mock(JwtUtils.class);
private final LoginGuard guard = new LoginGuard(new LoginGuard.Settings(true, 3, 100, Duration.ofMinutes(15),
Duration.ofMinutes(15), Duration.ofHours(24), Duration.ofDays(30), Set.of(), Duration.ZERO));
private final com.mailserver.config.MailAdmins admins = new com.mailserver.config.MailAdmins("admin@x.test", "", new MailDomains("x.test", ""));
private final AuthenticationController controller = new AuthenticationController(users, mock(MailService.class), jwt,
mock(MailDomains.class), false, guard, admins, new com.mailserver.config.JambotronSso("", "", "", "mail.x.test"));
private final User alice = new User();
AuthenticationControllerTest() {
alice.setEmail("alice@x.test");
alice.setPassword("hash");
when(users.findByEmail("alice@x.test")).thenReturn(Mono.just(alice));
when(users.findByEmail("ghost@x.test")).thenReturn(Mono.empty());
when(users.checkPassword(eq(alice), anyString())).thenAnswer(i -> "secret".equals(i.getArgument(1)));
when(jwt.generateToken(any())).thenReturn("token");
}
private ResponseEntity<?> login(String ip, String email, String password) {
LoginRequest request = new LoginRequest();
request.setEmail(email);
request.setPassword(password);
MockServerHttpRequest http = MockServerHttpRequest.post("/api/login")
.remoteAddress(new InetSocketAddress(ip, 40000)).build();
return controller.login(Mono.just(request), http).block();
}
// «Sign in with Jambotron» on: the web password only for the built-in administrator
@Test
void withJambotronSignInOnlyTheBuiltInAdministratorLogsInWithAPassword() {
AuthenticationController sso = new AuthenticationController(users, mock(MailService.class), jwt,
mock(MailDomains.class), false, guard, admins,
new com.mailserver.config.JambotronSso("https://jambotron.test", "tok", "", "mail.x.test"));
User admin = new User();
admin.setEmail("admin@x.test");
when(users.findByEmail("admin@x.test")).thenReturn(Mono.just(admin));
when(users.checkPassword(eq(admin), anyString())).thenReturn(true);
LoginRequest request = new LoginRequest();
request.setEmail("alice@x.test");
request.setPassword("secret");
MockServerHttpRequest http = MockServerHttpRequest.post("/api/login").remoteAddress(new InetSocketAddress("9.9.9.9", 1)).build();
assertThat(sso.login(Mono.just(request), http).block().getBody())
.isEqualTo(Map.of("error", "Sign in with Jambotron — the password is only for mail programs"));
request.setEmail("admin@x.test");
assertThat(sso.login(Mono.just(request), http).block().getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
void unknownMailboxAndWrongPasswordLookTheSameAndBothCount() {
assertThat(login("1.2.3.4", "ghost@x.test", "x").getBody()).isEqualTo(Map.of("error", "Invalid credentials"));
assertThat(login("1.2.3.4", "alice@x.test", "x").getBody()).isEqualTo(Map.of("error", "Invalid credentials"));
login("1.2.3.4", "alice@x.test", "y");
ResponseEntity<?> blocked = login("1.2.3.4", "alice@x.test", "secret");
assertThat(blocked.getStatusCode()).isEqualTo(HttpStatus.TOO_MANY_REQUESTS);
assertThat(blocked.getHeaders().getFirst("Retry-After")).isEqualTo("900");
assertThat(blocked.getBody()).isEqualTo(Map.of("error", "Too many failed logins, try again in 15 min"));
assertThat(login("5.6.7.8", "alice@x.test", "secret").getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
void blockedAttemptDoesNotCheckThePassword() {
for (int i = 0; i < 3; i++) {
login("1.2.3.4", "ghost@x.test", "x");
}
login("1.2.3.4", "alice@x.test", "secret");
verify(users, never()).checkPassword(any(), eq("secret"));
}
}