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:
@@ -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) => ({
|
||||
|
||||
Reference in New Issue
Block a user