80 lines
2.9 KiB
JavaScript
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 };
|