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
@@ -245,6 +245,32 @@ func (m *mailAPI) handleSeen(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]bool{"ok": true})
|
||||
}
|
||||
|
||||
func (m *mailAPI) handleFlagged(w http.ResponseWriter, r *http.Request) {
|
||||
session, ok := m.authenticate(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var body struct {
|
||||
Folder string `json:"folder"`
|
||||
UID uint32 `json:"uid"`
|
||||
Flagged bool `json:"flagged"`
|
||||
}
|
||||
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4096)).Decode(&body); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "BAD_REQUEST", "Invalid JSON")
|
||||
return
|
||||
}
|
||||
if body.Folder == "" {
|
||||
body.Folder = "inbox"
|
||||
}
|
||||
|
||||
if err := m.useCase.SetFlagged(r.Context(), session.Email, body.Folder, body.UID, body.Flagged); err != nil {
|
||||
m.fail(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]bool{"ok": true})
|
||||
}
|
||||
|
||||
func (m *mailAPI) handleSend(w http.ResponseWriter, r *http.Request) {
|
||||
session, ok := m.authenticate(w, r)
|
||||
if !ok {
|
||||
@@ -411,6 +437,9 @@ func (m *mailAPI) decodeCompose(
|
||||
// DraftUID is the autosaved draft this message came from, so sending
|
||||
// can clear it. Without it the draft outlived the message it became.
|
||||
DraftUID uint32 `json:"draft_uid"`
|
||||
// Important sends with high priority; InReplyTo threads a reply.
|
||||
Important bool `json:"important"`
|
||||
InReplyTo string `json:"in_reply_to"`
|
||||
}
|
||||
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxComposeBytes)).Decode(&body); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "BAD_REQUEST", "Invalid JSON")
|
||||
@@ -426,14 +455,16 @@ func (m *mailAPI) decodeCompose(
|
||||
// letting the body choose From is how a webmail becomes an open relay for
|
||||
// spoofed mail.
|
||||
return usecase.ComposeInput{
|
||||
From: sender,
|
||||
To: body.To,
|
||||
Cc: body.Cc,
|
||||
Bcc: body.Bcc,
|
||||
Subject: body.Subject,
|
||||
Text: text,
|
||||
HTML: body.HTML,
|
||||
DraftUID: body.DraftUID,
|
||||
From: sender,
|
||||
To: body.To,
|
||||
Cc: body.Cc,
|
||||
Bcc: body.Bcc,
|
||||
Subject: body.Subject,
|
||||
Text: text,
|
||||
HTML: body.HTML,
|
||||
DraftUID: body.DraftUID,
|
||||
Important: body.Important,
|
||||
InReplyTo: body.InReplyTo,
|
||||
}, true
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user