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:
@@ -109,6 +109,32 @@ print(EJSON.stringify({ ok: 1 }));
|
||||
await this.evalJs<{ ok: number }>(js);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether `user` authenticates against `database` with exactly `password` and holds `dbOwner`
|
||||
* there: checked by connecting as the consumer, the way it connects. Read-only. `false` only on an
|
||||
* authentication failure or a missing role; an unreachable server rejects (novox/hq issue 120).
|
||||
*/
|
||||
async canAuthenticateAs(database: string, user: string, password: string): Promise<boolean> {
|
||||
const uri =
|
||||
`mongodb://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${this.conn.host}:${this.conn.port}` +
|
||||
`/${encodeURIComponent(database)}?authSource=${encodeURIComponent(database)}&serverSelectionTimeoutMS=10000`;
|
||||
let stdout: string;
|
||||
try {
|
||||
({ stdout } = await run(
|
||||
"mongosh",
|
||||
[uri, "--quiet", "--eval",
|
||||
"print(EJSON.stringify(db.runCommand({ connectionStatus: 1 }).authInfo.authenticatedUserRoles))"],
|
||||
{ timeout: 30_000 },
|
||||
));
|
||||
} catch (err) {
|
||||
const text = `${(err as { stderr?: string }).stderr ?? ""}${(err as { stdout?: string }).stdout ?? ""}`;
|
||||
if (/Authentication failed|AuthenticationFailed/i.test(text)) return false;
|
||||
throw err;
|
||||
}
|
||||
const roles = JSON.parse(stdout.trim()) as { role: string; db: string }[];
|
||||
return roles.some((r) => r.role === "dbOwner" && r.db === database);
|
||||
}
|
||||
|
||||
/** Drop a database and its owning user, idempotently. Dropping the database evicts its data; the
|
||||
* user is removed first so a re-grant of the same login starts clean. */
|
||||
async dropDatabaseAndUser(database: string, user: string): Promise<void> {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@novox/mesh-sdk": "^0.1.0"
|
||||
"@novox/mesh-sdk": "^0.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
|
||||
@@ -45,4 +45,9 @@ runProvisioner("mongodb-database", {
|
||||
await mongo.dropDatabaseAndUser(p.as, p.as);
|
||||
await announce("module.mongodb.database.deprovisioned", { database: p.as });
|
||||
},
|
||||
// Asked every minute by the harness: whether the backend still holds this consumer exactly as
|
||||
// the mesh gave it, so a login lost behind the provisioner's back is made again (novox/hq issue 120).
|
||||
async holds(p: Provision): Promise<boolean> {
|
||||
return mongo.canAuthenticateAs(p.as, p.as, p.password);
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user