Lumi/plugins/lumi_ai/backend/access_control.js
2026-06-12 11:54:46 +02:00

80 lines
2.9 KiB
JavaScript

const { readJson, writeJson } = require("./config_manager");
const EMPTY = { users: {}, updated_at: null };
class AiAccessControl {
constructor(recordAudit = () => {}) {
this.recordAudit = recordAudit;
}
list() {
const store = readJson("ai_access.json", EMPTY);
const now = Date.now();
return Object.entries(store.users || {}).map(([userId, entry]) => ({
user_id: userId,
...entry,
active: entry.banned || (entry.timeout_until && new Date(entry.timeout_until).getTime() > now)
})).filter((entry) => entry.active);
}
check(userId, context = {}) {
if (!userId) return deny("anonymous", "AI access requires an identified user.");
const entry = readJson("ai_access.json", EMPTY).users?.[userId];
if (!entry) return { allowed: true };
if (entry.banned) {
this.audit(userId, "banned", context);
return deny("banned", entry.reason || "AI access is disabled for this user.", entry.silent);
}
if (entry.timeout_until) {
const until = new Date(entry.timeout_until).getTime();
if (Number.isFinite(until) && until > Date.now()) {
this.audit(userId, "timed_out", context);
return deny("timed_out", entry.reason || "AI access is temporarily unavailable for this user.", entry.silent, entry.timeout_until);
}
}
return { allowed: true };
}
set(userId, { action, timeoutUntil = null, reason = "", silent = false, actorId = null }) {
if (!userId) throw new Error("User id is required.");
const store = readJson("ai_access.json", EMPTY);
store.users ||= {};
if (action === "remove") {
delete store.users[userId];
} else if (action === "ban") {
store.users[userId] = {
banned: true, timeout_until: null, reason, silent: Boolean(silent),
updated_at: new Date().toISOString(), updated_by: actorId
};
} else if (action === "timeout") {
const parsed = new Date(timeoutUntil);
if (Number.isNaN(parsed.getTime()) || parsed.getTime() <= Date.now()) {
throw new Error("Timeout must be a future timestamp.");
}
store.users[userId] = {
banned: false, timeout_until: parsed.toISOString(), reason, silent: Boolean(silent),
updated_at: new Date().toISOString(), updated_by: actorId
};
} else {
throw new Error("Unknown access action.");
}
store.updated_at = new Date().toISOString();
writeJson("ai_access.json", store);
return store.users[userId] || null;
}
audit(userId, reason, context) {
this.recordAudit({
kind: "access", status: "denied", denial_reason: reason, user_id: userId,
platform: context.platform || context.origin || "webui",
channel_id: context.channel_id || null
});
}
}
function deny(reason, message, silent = false, until = null) {
return { allowed: false, reason, message, silent: Boolean(silent), until };
}
module.exports = { AiAccessControl };