fix(admin): dominio trocado ao criar, apelidos escondidos e quota a zero

Criar "lucas@cienciaembarcada.com.br" criava "lucas@lucasrguerra.dev.br". O
formulario partia o endereco e deitava fora a metade do dominio, e nao enviava
domain_id nenhum -- o servidor caia entao no dominio do admin com sessao
iniciada. Passa a ser parte local mais um dominio escolhido de uma lista: a
metade que decide o endereco deixa de poder ser descartada em silencio.

Apelidos de outros dominios estavam escondidos, e a culpa era minha: procurava
so no dominio onde a morada do proprio utilizador vive. Uma caixa recebe
postmaster, abuse, dmarc e tlsrpt de varios dominios ao mesmo tempo, por isso
o painel mostrava quatro onde entregavam oito. Ha um teste que falha com a
consulta antiga.

Os apelidos passam a poder ser criados e removidos dentro do modal. O dominio
sai do endereco escrito, para nao repetir a armadilha do formulario de cima.

USER, DOMAIN_ADMIN e SUPER_ADMIN eram constantes impressas no ecra, logo
ficavam em ingles em qualquer idioma. Um papel desconhecido continua a ser
mostrado tal como esta guardado, porque quem o ve precisa de saber o que a
base de dados tem.

A quota estava certa e mal mostrada. used_bytes vale 140771 -- os 137,5 KB
reais -- mas a pagina arredondava para megabytes inteiros, portanto qualquer
coisa abaixo de 512 KB lia-se "0 / 2 MB" e a barra ficava vazia, calculada
sobre os megabytes ja arredondados. Passa a formatar a partir dos bytes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Opus 5 committed 2026-08-24 11:02:02 -03:00
1 parent f801894f53
commit bb12e603a3
9 files changed
+308 -35

No files matched your search

+14 -7
View File
@@ -896,24 +896,31 @@ export async function listAliasesForMailbox(
mailboxId: string,
): Promise<unknown[] | null> {
const { sql, params } = scopeClause(scope, "m.domain_id");
const owner = await queryOne<{ email_address: string; domain_id: string }>(
`SELECT m.email_address, m.domain_id FROM mailboxes m WHERE m.id = $${params.length + 1} AND ${sql}`,
const owner = await queryOne<{ email_address: string }>(
`SELECT m.email_address FROM mailboxes m WHERE m.id = $${params.length + 1} AND ${sql}`,
[...params, mailboxId],
);
// Out of scope and non-existent are answered the same way, so the console
// of one domain cannot probe for ids belonging to another.
if (!owner) return null;
// Every domain in the caller's scope, not only the one the user's own
// address sits in. One mailbox commonly receives postmaster, abuse, dmarc
// and tlsrpt for several domains at once, and scoping this to the owner's
// domain hid all the others - the panel showed four aliases where eight
// were delivering.
const { sql: aliasSql, params: aliasParams } = scopeClause(scope, "a.domain_id");
return query(
`SELECT a.id, a.source_address, a.destination_addresses, a.is_catch_all, a.is_active
FROM aliases a
WHERE a.domain_id = $1
`SELECT a.id, a.source_address, a.destination_addresses, a.is_catch_all, a.is_active,
d.name AS domain_name
FROM aliases a JOIN domains d ON d.id = a.domain_id
WHERE ${aliasSql}
AND EXISTS (
SELECT 1 FROM unnest(a.destination_addresses) AS dest
WHERE lower(dest) = lower($2)
WHERE lower(dest) = lower($${aliasParams.length + 1})
)
ORDER BY a.source_address`,
[owner.domain_id, owner.email_address],
[...aliasParams, owner.email_address],
);
}
+20
View File
@@ -826,6 +826,26 @@ describeDb("auth API against a real database", () => {
expect(res.status).toBe(404);
});
// One mailbox commonly receives postmaster, abuse, dmarc and tlsrpt for
// several domains at once. Scoping the lookup to the domain the user's own
// address sits in hid every alias belonging to the others.
it("lists aliases from every domain, not only the user's own", async () => {
const mine = `cross-domain-alias@pagedtest-${PAGED_DOMAIN}.invalid`;
await query(
`INSERT INTO aliases (domain_id, source_address, destination_addresses)
VALUES ($1, $2, ARRAY[$3]) ON CONFLICT DO NOTHING`,
[PAGED_DOMAIN, mine, userEmail()],
);
const res = await get(`/api/v1/admin/mailboxes/${USER_ID}/aliases`, admin);
expect(res.status).toBe(200);
const sources = (res.body as unknown as { source_address: string }[]).map((a) => a.source_address);
// USER_ID lives in DOMAIN_ID; this alias lives in PAGED_DOMAIN and
// delivers to them all the same.
expect(sources).toContain(mine);
});
it("answers 404 for a user that does not exist rather than leaking the difference", async () => {
const res = await get(`/api/v1/admin/mailboxes/44444444-4444-4444-4444-444444444444/aliases`, admin);
expect(res.status).toBe(404);