Вход через Jambotron (OAuth 2.0 code + PKCE), пароли приложений, системные ящики
continuous-integration/drone/pr Build is passing

- /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>
This commit is contained in:
2026-11-07 20:57:30 +01:00
co-authored by Claude Opus 5.5
parent 910a028612
commit 99e12d33f3
26 changed files with 665 additions and 19 deletions
@@ -57,10 +57,12 @@ public class AuthenticationController {
private final boolean registrationEnabled;
private final LoginGuard guard;
private final MailAdmins admins;
private final com.mailserver.config.JambotronSso sso;
public AuthenticationController(UserService userService, MailService mailService, JwtUtils jwtUtils, MailDomains domains,
@Value("${mailserver.registration.enabled:false}") boolean registrationEnabled,
LoginGuard guard, MailAdmins admins) {
LoginGuard guard, MailAdmins admins, com.mailserver.config.JambotronSso sso) {
this.sso = sso;
this.guard = guard;
this.admins = admins;
this.userService = userService;
@@ -73,7 +75,8 @@ public class AuthenticationController {
/** Public: is registration open, and in which domains mailboxes can be created. */
@GetMapping("/config")
public Map<String, Object> config() {
return Map.of("registrationEnabled", registrationEnabled, "domains", domains.domains().stream().sorted().toList());
return Map.of("registrationEnabled", registrationEnabled, "domains", domains.domains().stream().sorted().toList(),
"ssoEnabled", sso.enabled());
}
@PostMapping("/login")
@@ -106,6 +109,16 @@ public class AuthenticationController {
if (!user.isActive()) {
return Mono.just(ResponseEntity.badRequest().body(Map.of("error", "User is deactivated")));
}
// With «Sign in with Jambotron» the web password is only for the built-in administrator
// (Jambotron down); the others' passwords are for mail programs (SMTP / IMAP)
if (sso.enabled() && !admins.isBuiltIn(user.getEmail())) {
return Mono.just(ResponseEntity.badRequest().body(Map.of("error",
"Sign in with Jambotron — the password is only for mail programs")));
}
if (user.isSystem()) {
return Mono.just(ResponseEntity.badRequest().body(Map.of("error",
"A system mailbox has no web login: its owners open it from their mailbox")));
}
return Mono.just(ResponseEntity.ok(token(user)));
});
});