Every credential provider says whether it still holds a consumer (hq issue 120) #84
+22
-1
@@ -8,7 +8,7 @@
|
||||
// order requests were sent, which is what the queue below relies on.
|
||||
|
||||
import { createConnection, type Socket } from "node:net";
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { createHash, randomBytes } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
/** A parsed RESP value. Errors are surfaced as rejected commands, not as this type. */
|
||||
@@ -113,6 +113,27 @@ export class RedisClient {
|
||||
await this.command("ACL", "DELUSER", username);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an ACL user exists, is enabled, and accepts exactly this password. Read-only: it asks
|
||||
* `ACL GETUSER`, which answers nil for an unknown user and otherwise a flat list of fields, among
|
||||
* them `flags` and `passwords`, the latter as SHA-256 hex. This server keeps no ACL file, so its
|
||||
* users live in memory and a restart forgets them. This is how the provisioner notices
|
||||
* (novox/hq issue 120).
|
||||
*/
|
||||
async holdsAclUser(username: string, password: string): Promise<boolean> {
|
||||
const reply = await this.command("ACL", "GETUSER", username);
|
||||
if (!Array.isArray(reply)) return false;
|
||||
const field = (name: string): RespValue | undefined => {
|
||||
const i = reply.indexOf(name);
|
||||
return i >= 0 ? reply[i + 1] : undefined;
|
||||
};
|
||||
const flags = field("flags");
|
||||
const passwords = field("passwords");
|
||||
if (!Array.isArray(flags) || !flags.includes("on")) return false;
|
||||
if (!Array.isArray(passwords)) return false;
|
||||
return passwords.includes(createHash("sha256").update(password).digest("hex"));
|
||||
}
|
||||
|
||||
close(): void {
|
||||
if (this.socket) {
|
||||
this.socket.destroy();
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -43,4 +43,11 @@ runProvisioner("redis-cache", {
|
||||
await redis.deleteAclUser(p.as);
|
||||
await announce("module.redis.cache.deprovisioned", { username: p.as });
|
||||
},
|
||||
|
||||
// This server keeps its ACL users in memory only, so a restart of it forgets every consumer while
|
||||
// this provisioner keeps running. Asked every minute, so a forgotten user is made again instead
|
||||
// of every consumer failing to authenticate in silence (novox/hq issue 120).
|
||||
async holds(p: Provision): Promise<boolean> {
|
||||
return redis.holdsAclUser(p.as, p.password);
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user