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 };