Ловушка у ящика (TRAP): что ящик отправляет — в его же INBOX, наружу ничего
continuous-integration/drone/pr Build is passing
continuous-integration/drone/pr Build is passing
Для staging@: staging-стенд шлёт через настоящий mail.jambocheck.com,
но тестовые письма не доходят до живых людей.
- V5: users.is_trap
- MailRouter: отправитель-ловушка → все получатели LOCAL, одна копия в INBOX отправителя
- PUT /api/admin/mailboxes/{id}/trap, MailboxView.trap
- MAILBOXES: кнопка outbox/move_to_inbox, метка TRAP
- тесты: router + admin (77 тестов)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -45,7 +45,7 @@ public class MailboxAdminController {
|
||||
private static final Pattern LOCAL_PART = Pattern.compile("[a-z0-9](?:[a-z0-9._+-]{0,62}[a-z0-9])?");
|
||||
private static final int MIN_PASSWORD = 8;
|
||||
|
||||
public record MailboxView(Long id, String email, String displayName, boolean active, boolean admin,
|
||||
public record MailboxView(Long id, String email, String displayName, boolean active, boolean admin, boolean trap,
|
||||
long usedBytes, long quotaBytes, Instant createdAt) {
|
||||
}
|
||||
|
||||
@@ -60,6 +60,9 @@ public class MailboxAdminController {
|
||||
public record ActiveRequest(boolean active) {
|
||||
}
|
||||
|
||||
public record TrapRequest(boolean trap) {
|
||||
}
|
||||
|
||||
/** @param password the generated password, or null when the administrator chose it */
|
||||
public record Created(MailboxView mailbox, String password) {
|
||||
}
|
||||
@@ -154,6 +157,15 @@ public class MailboxAdminController {
|
||||
}))).flatMap(this::view);
|
||||
}
|
||||
|
||||
/** Trap: what the mailbox sends is stored in its own INBOX, nothing leaves the server (staging@). */
|
||||
@PutMapping("/{id}/trap")
|
||||
public Mono<MailboxView> trap(Mono<Authentication> auth, @PathVariable Long id, @RequestBody Mono<TrapRequest> body) {
|
||||
return admin(auth).flatMap(me -> body.flatMap(request -> mailbox(id).flatMap(user -> {
|
||||
log.info("Mailbox {} trap {} by administrator {}", user.getEmail(), request.trap() ? "ON" : "OFF", me);
|
||||
return users.setTrap(user, request.trap());
|
||||
}))).flatMap(this::view);
|
||||
}
|
||||
|
||||
private Mono<String> admin(Mono<Authentication> auth) {
|
||||
return auth.switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.UNAUTHORIZED)))
|
||||
.map(Authentication::getName)
|
||||
@@ -168,7 +180,7 @@ public class MailboxAdminController {
|
||||
private Mono<MailboxView> view(User user) {
|
||||
return mail.usedBytes(user.getId()).defaultIfEmpty(0L)
|
||||
.map(used -> new MailboxView(user.getId(), user.getEmail(), user.getDisplayName(), user.isActive(),
|
||||
admins.isAdmin(user.getEmail()), used, user.getQuotaBytes() == null ? 0 : user.getQuotaBytes(),
|
||||
admins.isAdmin(user.getEmail()), user.isTrap(), used, user.getQuotaBytes() == null ? 0 : user.getQuotaBytes(),
|
||||
user.getCreatedAt()));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user