feat(bimi): logotipo do dominio validado, alojado e publicado no DNS

A foto de perfil nao viaja: nem o Gmail nem o Outlook leem uma imagem por
utilizador do servidor de quem envia, e nao existe protocolo que o permita. O
avatar que esses mostram vem dos contactos de quem recebe ou da conta Google
ou Microsoft do remetente.

O que eles leem e BIMI, que e por dominio. Fica implementado: validacao,
alojamento e o registo.

O validador reporta todas as violacoes de uma vez, e nao a primeira. Um
receptor que recusa a marca nao explica nada -- simplesmente nao desenha -- por
isso descobrir as regras uma viagem de cada vez seria a pior maneira possivel
de aprender este formato. Sao verificados o baseProfile e a versao que os
receptores procuram, o viewBox quadrado (uma marca nao quadrada e cortada de
forma diferente por cada cliente), exactamente um title, e a ausencia de tudo
o que seja activo ou externo: script, handlers de evento, links, imagens
rasterizadas, animacoes e referencias para fora do documento.

O registo so e publicado quando ha mesmo uma marca guardada. Um registo que
aponta para um logotipo inexistente e pior do que nenhum, porque o receptor
para de procurar assim que encontra um.

O logotipo e servido aberto, porque quem o vai buscar sao os fornecedores de
correio a avaliar uma mensagem e esses nunca terao sessao aqui.

Fica escrito no README que o Gmail e o Outlook so desenham a marca com um VMC,
e o painel avisa quando nao ha nenhum configurado.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Opus 5 committed 2026-09-07 08:12:11 -03:00
1 parent fcfee6a3f9
commit 6a602ab88f
16 files changed
+955 -5

No files matched your search

@@ -0,0 +1,79 @@
package postgres
import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
// BimiRepository stores the logo a domain publishes for BIMI.
type BimiRepository struct {
pool *pgxpool.Pool
}
func NewBimiRepository(pool *pgxpool.Pool) *BimiRepository {
return &BimiRepository{pool: pool}
}
// BimiLogo is a stored mark.
type BimiLogo struct {
SVG []byte
ETag string
VmcURL string
}
// Save replaces a domain's logo, returning the digest receivers can cache on.
func (r *BimiRepository) Save(ctx context.Context, domain string, svg []byte, vmcURL string) (string, error) {
sum := sha256.Sum256(svg)
etag := hex.EncodeToString(sum[:])
_, err := r.pool.Exec(ctx, `
INSERT INTO domain_bimi (domain_id, svg, etag, vmc_url, updated_at)
SELECT d.id, $2, $3, NULLIF($4, ''), NOW() FROM domains d WHERE lower(d.name) = lower($1)
ON CONFLICT (domain_id) DO UPDATE
SET svg = EXCLUDED.svg, etag = EXCLUDED.etag,
vmc_url = EXCLUDED.vmc_url, updated_at = NOW()
`, domain, svg, etag, vmcURL)
if err != nil {
return "", fmt.Errorf("save BIMI logo for %s: %w", domain, err)
}
return etag, nil
}
// Load returns a domain's logo, or nil when it has none.
func (r *BimiRepository) Load(ctx context.Context, domain string) (*BimiLogo, error) {
var logo BimiLogo
var vmc *string
err := r.pool.QueryRow(ctx, `
SELECT b.svg, b.etag, b.vmc_url
FROM domain_bimi b JOIN domains d ON d.id = b.domain_id
WHERE lower(d.name) = lower($1)
`, domain).Scan(&logo.SVG, &logo.ETag, &vmc)
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("load BIMI logo for %s: %w", domain, err)
}
if vmc != nil {
logo.VmcURL = *vmc
}
return &logo, nil
}
// Delete removes a domain's logo.
func (r *BimiRepository) Delete(ctx context.Context, domain string) error {
_, err := r.pool.Exec(ctx, `
DELETE FROM domain_bimi b USING domains d
WHERE b.domain_id = d.id AND lower(d.name) = lower($1)
`, domain)
if err != nil {
return fmt.Errorf("delete BIMI logo for %s: %w", domain, err)
}
return nil
}