feat(webmail,auth): leitor estilo Outlook, responder a todos, importantes e isolamento do admin de dominio
Webmail - Leitor de mensagem em componente proprio, com barra de comandos fixa, cabecalho compacto, faixa de imagens bloqueadas e chips de anexo. - Divisor arrastavel entre lista e leitor (mouse, setas, duplo clique), largura persistida e limites que preservam os dois paineis. - Destinatarios em chips (Para, Cc, Cco): aceita nomes com virgula, colagem de listas, validacao e bloqueio de envio com endereco invalido. - Responder a todos, sem o proprio endereco e sem repetidos; In-Reply-To e References nas respostas; prefixo Re:/Fwd: sem repeticao. - Marcar como importante (estrela, filtro, linha da lista) e enviar com alta importancia (Importance/X-Priority), com etiqueta no leitor. - Sanitizador: a regexp de URI customizada descartava todo atributo que nao fosse link (width, colspan, bgcolor...), o que desmontava tabelas de e-mail; imagens de fundo agora sao bloqueadas de forma reversivel. Protocols - POST /api/v1/mail/flagged (\Flagged) com bump de modseq para IMAP. - Renderizacao expoe message_id, in_reply_to e important. - Cabecalhos de resposta validados contra injecao. Auth - As views do admin de dominio eram globais: dashboard, fila de saida, DMARC, preflight e diagnostico mostravam dados de todos os dominios. Agora todas respeitam o escopo da sessao; super admin segue vendo tudo. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b5baf33a63
commit
50a67531ba
30 files changed
+1810
-348
No files matched your search
@@ -209,3 +209,44 @@ func TestDeleteMovesToTrashThenExpungesFromTrash(t *testing.T) {
|
||||
t.Errorf("the message survived being emptied from Trash: %d rows", len(trashRows))
|
||||
}
|
||||
}
|
||||
|
||||
// Marking important round-trips through the list, can be undone, and cannot
|
||||
// reach a message the mailbox does not own.
|
||||
func TestMarkFlaggedRoundTripsAndStaysInsideTheMailbox(t *testing.T) {
|
||||
pool := testPool(t)
|
||||
repo := NewWebmailRepository(pool)
|
||||
mailboxID := seedMailbox(t, pool)
|
||||
ctx := context.Background()
|
||||
|
||||
uid := deliver(t, pool, mailboxID, "INBOX", "important one", false)
|
||||
|
||||
flaggedNow := func() bool {
|
||||
msgs, err := repo.ListMessages(ctx, mailboxID, "inbox", "", 50, 0)
|
||||
if err != nil || len(msgs) == 0 {
|
||||
t.Fatalf("list: %v (%d messages)", err, len(msgs))
|
||||
}
|
||||
return msgs[0].Flagged
|
||||
}
|
||||
|
||||
if err := repo.MarkFlagged(ctx, mailboxID, "inbox", uid, true); err != nil {
|
||||
t.Fatalf("flag: %v", err)
|
||||
}
|
||||
if !flaggedNow() {
|
||||
t.Error("message is not flagged after MarkFlagged(true)")
|
||||
}
|
||||
// Flagging twice is not an error and does not duplicate the flag.
|
||||
if err := repo.MarkFlagged(ctx, mailboxID, "inbox", uid, true); err != nil {
|
||||
t.Errorf("flagging twice: %v", err)
|
||||
}
|
||||
if err := repo.MarkFlagged(ctx, mailboxID, "inbox", uid, false); err != nil {
|
||||
t.Fatalf("unflag: %v", err)
|
||||
}
|
||||
if flaggedNow() {
|
||||
t.Error("message is still flagged after MarkFlagged(false)")
|
||||
}
|
||||
|
||||
other := seedMailbox(t, pool)
|
||||
if err := repo.MarkFlagged(ctx, other, "inbox", uid, true); err != ErrMessageNotFound {
|
||||
t.Errorf("another mailbox flagging this UID got %v, want ErrMessageNotFound", err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user