Every credential provider says whether it still holds a consumer

holds() for postgres, mssql, mongodb, minio, lavinmq, mosquitto, mailu
and gitea, so the harness makes again a login the backend lost (hq issue
120). Each checks the mesh's password as the consumer presents it, or
compares it read-only, and returns false only when the backend says the
credential is absent or wrong; an unreachable backend throws.
This commit is contained in:
jochen
2026-09-26 01:09:52 +02:00
parent 3d271f72ea
commit 0cb0f814b4
24 changed files with 286 additions and 12 deletions
+29
View File
@@ -134,6 +134,35 @@ export class MailuClient {
await this.api("DELETE", `/user/${encodeURIComponent(email)}`);
}
/**
* Whether a mailbox exists, is enabled, and accepts exactly this password. Read-only. Existence
* from the admin API; the password from `doveadm auth test` in the imap container, which is how
* the mail server itself authenticates, and which exits 77 for a refused login. The password
* reaches doveadm through the exec's environment, never the host's argv. An unreachable API or
* container rejects (novox/hq issue 120).
*/
async holdsUser(email: string, password: string): Promise<boolean> {
const res = await fetch(`${this.baseUrl}/user/${encodeURIComponent(email)}`, {
headers: { Authorization: this.apiKey, Accept: "application/json" },
});
if (res.status === 404) return false;
if (!res.ok) throw new Error(`Mailu API GET /user/${email}: ${res.status} ${await res.text()}`);
const user = (await res.json()) as { enabled?: boolean };
if (user.enabled === false) return false;
try {
await run(
"docker",
["exec", "-e", "MESH_USER", "-e", "MESH_PW", this.imapContainer,
"sh", "-c", 'doveadm auth test "$MESH_USER" "$MESH_PW"'],
{ env: { ...process.env, MESH_USER: email, MESH_PW: password }, timeout: 30_000 },
);
return true;
} catch (err) {
if ((err as { code?: number }).code === 77) return false;
throw err;
}
}
async listAliases(): Promise<MailuAlias[]> {
const aliases = await this.api<any[]>("GET", "/alias");
return (aliases ?? []).map((a) => ({