Роли ADMIN и USER, User panel и Admin panel
continuous-integration/drone/pr Build is passing

- Роль ADMIN = users.is_admin; встроенный администратор (mailserver.admins, по
  умолчанию admin@) всегда ADMIN и получает роль в базе при старте.
- PUT /api/admin/mailboxes/{id}/role {ADMIN|USER}: только ADMIN; не свою, не
  встроенного, общий ящик не может быть ADMIN.
- Admin panel (/admin, только ADMIN): USERS (логины и роли, MAKE ADMIN / MAKE USER),
  MAILBOXES, LISTS. User panel (/settings): профиль, пароль, хранилище, вид, мои ящики.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-07 20:18:18 +01:00
co-authored by Claude Opus 5.5
parent 402d2c403b
commit cf87b60b38
13 changed files with 215 additions and 18 deletions
@@ -186,6 +186,32 @@ class MailboxAdminControllerTest {
verify(mail, never()).deleteMailbox(admin);
}
@Test
void administratorGivesAndTakesRoleAdminNotOwnNotBuiltInNotShared() {
when(users.setAdmin(any(), any(Boolean.class))).thenAnswer(i -> {
User u = i.getArgument(0);
u.setAdmin(i.getArgument(1));
return Mono.just(u);
});
var made = controller.role(as("admin@jambocheck.com"), 2L, Mono.just(new MailboxAdminController.RoleRequest("ADMIN"))).block();
assertThat(made.role()).isEqualTo("ADMIN");
assertThat(admins.isAdmin("alice@jambocheck.com")).isTrue();
// alice is ADMIN now — she manages roles too, but not the built-in one and not her own
assertThat(status(() -> controller.role(as("alice@jambocheck.com"), 1L, Mono.just(new MailboxAdminController.RoleRequest("USER"))).block()))
.isEqualTo(HttpStatus.BAD_REQUEST);
assertThat(status(() -> controller.role(as("alice@jambocheck.com"), 2L, Mono.just(new MailboxAdminController.RoleRequest("USER"))).block()))
.isEqualTo(HttpStatus.BAD_REQUEST);
var back = controller.role(as("admin@jambocheck.com"), 2L, Mono.just(new MailboxAdminController.RoleRequest("USER"))).block();
assertThat(back.role()).isEqualTo("USER");
assertThat(admins.isAdmin("alice@jambocheck.com")).isFalse();
assertThat(status(() -> controller.role(as("alice@jambocheck.com"), 1L, Mono.just(new MailboxAdminController.RoleRequest("ADMIN"))).block()))
.isEqualTo(HttpStatus.FORBIDDEN);
alice.setShared(true);
assertThat(status(() -> controller.role(as("admin@jambocheck.com"), 2L, Mono.just(new MailboxAdminController.RoleRequest("ADMIN"))).block()))
.isEqualTo(HttpStatus.BAD_REQUEST);
}
@Test
void administratorOpensAnyMailboxOthersCanNot() {
when(jwt.generateToken("alice@jambocheck.com", "admin@jambocheck.com")).thenReturn("alice-token");