"use strict"; const crypto = require("node:crypto"); const fs = require("node:fs"); const http = require("node:http"); const path = require("node:path"); const { WorkOrderManager } = require("./workorder-manager"); const { MapGate } = require("./map-gate"); const { sendSmtpMail } = require("./smtp-mailer"); const { executeRegisteredAction } = require("./action-client"); const DEFAULT_ACTIONS = Object.freeze({ "server-login": [ "read-navigation-map", "inspect-services", "health-check", "inspect-code-channel-owner-auth", ], "server-ops": [ "read-navigation-map", "inspect-services", "pull-registered-repo", "deploy-registered-service", "restart-registered-service", "health-check", "rollback-registered-service", "provision-approved-architecture", "push-repository", "restore-owner-password-login", ], "repo-push": ["read-navigation-map", "push-repository"], }); function createApp(options = {}) { const requestToken = options.requestToken || process.env.LAKE_LAMP_REQUEST_TOKEN || ""; const ownerEmail = options.ownerEmail || process.env.LAKE_LAMP_OWNER_EMAIL || ""; const approvers = options.approvers || loadApprovers(options.approversFile || process.env.LAKE_LAMP_APPROVERS_FILE || "", ownerEmail); const publicBaseUrl = String(options.publicBaseUrl || process.env.LAKE_LAMP_PUBLIC_URL || "").replace(/\/$/, ""); const targets = new Set(options.targets || splitCsv(process.env.LAKE_LAMP_TARGETS || "JD-FD-PRIMARY,BS-GZ-006")); const actions = options.actions || DEFAULT_ACTIONS; const manager = options.manager || new WorkOrderManager({ approvalTtl: Number(options.approvalTtl || process.env.LAKE_LAMP_APPROVAL_TTL || 3 * 60 * 60), sessionTtl: Number(options.sessionTtl || process.env.LAKE_LAMP_SESSION_TTL || 3 * 60 * 60), maxSessionLifetime: Number(options.maxSessionLifetime || process.env.LAKE_LAMP_MAX_SESSION_LIFETIME || 24 * 60 * 60), stateFile: Object.prototype.hasOwnProperty.call(options, "stateFile") ? options.stateFile : (process.env.LAKE_LAMP_STATE_FILE || "/var/lib/guanghu/lake-lamp-authz/state.json"), }); const sendEmail = options.sendEmail || (message => sendSmtpMail({ ...message, smtpHost: process.env.SMTP_HOST || "smtp.qq.com", smtpPort: Number(process.env.SMTP_PORT || 465), smtpUser: process.env.SMTP_USER || ownerEmail, smtpPass: process.env.QQ_SMTP_AUTH_CODE || "", })); const mapGate = options.mapGate || new MapGate({ mapsDir: options.mapsDir || process.env.LAKE_LAMP_MAPS_DIR || "/etc/guanghu/navigation-maps", stateFile: Object.prototype.hasOwnProperty.call(options, "mapStateFile") ? options.mapStateFile : (process.env.LAKE_LAMP_MAP_STATE_FILE || "/var/lib/guanghu/lake-lamp-authz/map-acks.json"), }); const repoGrantDir = options.repoGrantDir || process.env.LAKE_LAMP_REPO_GRANT_DIR || "/var/lib/guanghu/repo-authorizations"; const executeAction = options.executeAction || executeRegisteredAction; // Creating a powerless request must never become harder than the human mail // handoff. Keep at least three attempts per network each hour. const publicCreateLimit = Math.max(3, Number(options.publicCreateLimit || process.env.LAKE_LAMP_PUBLIC_CREATE_LIMIT || 24)); const publicCreateLimiter = options.publicCreateLimiter || new SlidingWindowLimiter(publicCreateLimit, 60 * 60); const publicCreateGlobalLimiter = options.publicCreateGlobalLimiter || new SlidingWindowLimiter(Number(options.publicCreateGlobalLimit || process.env.LAKE_LAMP_PUBLIC_CREATE_GLOBAL_LIMIT || 60), 60 * 60); // Owner handoff is a human recovery path, not a login endpoint. Always allow // at least three genuine mail attempts per network each hour, even if an old // deployment environment accidentally configures a lower value. const publicMailLimit = Math.max(3, Number(options.publicMailLimit || process.env.LAKE_LAMP_PUBLIC_MAIL_LIMIT || 12)); const publicMailLimiter = options.publicMailLimiter || new SlidingWindowLimiter(publicMailLimit, 60 * 60); const publicMailGlobalLimiter = options.publicMailGlobalLimiter || new SlidingWindowLimiter(Number(options.publicMailGlobalLimit || process.env.LAKE_LAMP_PUBLIC_MAIL_GLOBAL_LIMIT || 30), 60 * 60); async function sendApprovalEmail(handoffToken) { const issued = manager.issueApproval(handoffToken); if (!issued.ok) return issued; const approver = selectApprover(approvers, issued.order); if (!approver) { manager.failApprovalEmail(handoffToken); return { ok: false, reason: "no_registered_approver" }; } const approvalUrl = `${publicBaseUrl}/approve/${issued.approvalToken}`; const emailSent = await sendEmail({ to: approver.email, subject: `小湖灯授权请求 · ${issued.order.target}`, approvalUrl, order: issued.order, }); if (!emailSent) { manager.failApprovalEmail(handoffToken); return { ok: false, reason: "authorization_email_failed" }; } return { ok: true, order: issued.order }; } return http.createServer(async (req, res) => { try { const url = new URL(req.url, "http://localhost"); if (req.method === "GET" && url.pathname === "/health") return json(res, 200, { ok: true, service: "lake-lamp-authz", auth_mode: "email-link", approval_ttl: manager.approvalTtl, session_ttl: manager.sessionTtl, max_session_lifetime: manager.maxSessionLifetime, auto_renew_on_activity: true, }); if (req.method === "GET" && url.pathname === "/api/public/capabilities") return json(res, 200, { schema: "guanghu.lake-lamp-public-workorder/v1", create_workorder: `${publicBaseUrl}/api/public/workorders`, required_fields: ["persona_id", "target", "scope", "action"], optional_fields: ["persona_name", "description", "resource"], targets: [...targets], scopes: actions, owner_handoff: "open request_url and request pre-registered mailbox verification", approval_ttl: manager.approvalTtl, session_ttl: manager.sessionTtl, max_session_lifetime: manager.maxSessionLifetime, auto_renew_on_activity: true, limits: { create_per_network_per_hour: publicCreateLimit, email_per_network_per_hour: publicMailLimit, }, }); const requestMatch = url.pathname.match(/^\/request\/([A-Za-z0-9_-]{20,})$/); if (requestMatch && req.method === "GET") { const inspected = manager.inspectHandoff(requestMatch[1]); if (!inspected.ok) return html(res, 410, requestErrorPage(inspected.reason)); return html(res, 200, requestPage(inspected.order)); } if (requestMatch && req.method === "POST") { const inspected = manager.inspectHandoff(requestMatch[1]); if (!inspected.ok) return html(res, 410, requestErrorPage(inspected.reason)); // Refreshing or reopening an already-sent request must not consume a // second rate-limit slot. It also must not send a duplicate email. if (inspected.order.approval_email_sent) return html(res, 200, emailSentPage(inspected.order)); const source = clientAddress(req); if (!publicMailLimiter.take(source) || !publicMailGlobalLimiter.take("global")) return html(res, 429, requestErrorPage("rate_limited")); const sent = await sendApprovalEmail(requestMatch[1]); if (!sent.ok && sent.reason !== "approval_email_already_sent") return html(res, sent.reason === "authorization_email_failed" ? 502 : 410, requestErrorPage(sent.reason)); return html(res, 200, emailSentPage(sent.order)); } const approvalMatch = url.pathname.match(/^\/approve\/([A-Za-z0-9_-]{20,})$/); if (approvalMatch && req.method === "GET") { const inspected = manager.inspectApproval(approvalMatch[1]); if (!inspected.ok) return html(res, 410, approvalErrorPage(inspected.reason)); return html(res, 200, approvalPage(inspected.order, approvalMatch[1])); } if (approvalMatch && req.method === "POST") { const approved = manager.approve(approvalMatch[1]); if (!approved.ok) return html(res, 410, approvalErrorPage(approved.reason)); return html(res, 200, approvedPage(approved.order)); } if (req.method === "POST" && url.pathname === "/api/public/workorders") { const source = clientAddress(req); if (!publicCreateLimiter.take(source) || !publicCreateGlobalLimiter.take("global")) return json(res, 429, { error: "rate_limited", retry_after: 3600 }); const body = await readJson(req); if (!body) return json(res, 400, { error: "invalid_json" }); const validation = validateWorkorderBody(body, targets, actions); if (!validation.ok) return json(res, validation.status, { error: validation.error }); const created = manager.request(validation.request); return json(res, 201, { ok: true, workorder_id: created.id, claim_token: created.claimToken, request_url: `${publicBaseUrl}/request/${created.handoffToken}`, expires_in: created.expiresIn, status: "waiting_for_owner_handoff", }); } if (req.method === "POST" && url.pathname === "/api/workorders") { if (!bearerMatches(req, requestToken)) return json(res, 401, { error: "request_auth_required" }); const body = await readJson(req); if (!body) return json(res, 400, { error: "invalid_json" }); const validation = validateWorkorderBody(body, targets, actions); if (!validation.ok) return json(res, validation.status, { error: validation.error }); const expectedFingerprint = sha256(ownerEmail.toLowerCase()); if (!body.recipient_fingerprint || !safeEqual(body.recipient_fingerprint, expectedFingerprint)) return json(res, 403, { error: "owner_identity_mismatch" }); const created = manager.request(validation.request); const sent = await sendApprovalEmail(created.handoffToken); if (!sent.ok) return json(res, 502, { error: sent.reason }); return json(res, 201, { ok: true, workorder_id: created.id, claim_token: created.claimToken, expires_in: created.expiresIn, status: "waiting_for_owner" }); } const claimMatch = url.pathname.match(/^\/api\/workorders\/([0-9a-f-]{36})\/claim$/i); if (req.method === "POST" && claimMatch) { const token = bearer(req); const claimed = manager.claim(claimMatch[1], token); if (!claimed.ok) return json(res, claimed.reason === "approval_pending" ? 202 : 403, { error: claimed.reason }); return json(res, 200, { ok: true, session_token: claimed.sessionToken, expires_in: claimed.expiresIn, target: claimed.target, scope: claimed.scope, action: claimed.action, resource: claimed.resource || "" }); } if (req.method === "POST" && url.pathname === "/api/session/verify") { const body = await readJson(req); if (!body) return json(res, 400, { error: "invalid_json" }); const verified = manager.verifySession(bearer(req), { pid: String(body.persona_id || "") }, String(body.target || ""), String(body.scope || ""), String(body.action || ""), Date.now() / 1000, Object.prototype.hasOwnProperty.call(body, "resource") ? String(body.resource || "") : undefined); if (!verified.ok) return json(res, 403, { error: verified.reason }); if (body.action !== "read-navigation-map") { const map = mapGate.read(String(body.target || "")); const mapVerified = mapGate.verify(bearer(req), String(body.target || ""), map.hash); if (!mapVerified.ok) return json(res, 423, { error: mapVerified.reason, required_action: "read-navigation-map" }); } return json(res, 200, { ok: true, expires_at: verified.session.expiresAt }); } if (req.method === "POST" && url.pathname === "/api/session/renew") { const body = await readJson(req); if (!body) return json(res, 400, { error: "invalid_json" }); if (body.action || body.actions || body.target_override || body.scope_override || body.resource) return json(res, 400, { error: "renewal_cannot_expand_authority" }); const token = bearer(req); const target = String(body.target || ""); const scope = String(body.scope || ""); const renewed = manager.renewSession(token, { pid: String(body.persona_id || "") }, target, scope); if (!renewed.ok) return json(res, 403, { error: renewed.reason }); const map = mapGate.read(target); const acked = mapGate.ack(token, target, map.hash, Date.now() / 1000, Math.max(1, renewed.expiresAt - Date.now() / 1000)); if (!acked.ok) return json(res, 409, { error: acked.reason }); return json(res, 200, { ok: true, target, scope, expires_at: renewed.expiresAt, renewals: renewed.renewals, authority_expanded: false }); } if (req.method === "POST" && url.pathname === "/api/navigation-map/read") { const body = await readJson(req); if (!body) return json(res, 400, { error: "invalid_json" }); const verified = manager.verifySession(bearer(req), { pid: String(body.persona_id || "") }, String(body.target || ""), String(body.scope || ""), "read-navigation-map"); if (!verified.ok) return json(res, 403, { error: verified.reason }); const map = mapGate.read(String(body.target || "")); return json(res, 200, { ok: true, target: body.target, map_hash: map.hash, navigation_map: map.data }); } if (req.method === "POST" && url.pathname === "/api/navigation-map/ack") { const body = await readJson(req); if (!body) return json(res, 400, { error: "invalid_json" }); const token = bearer(req); const verified = manager.verifySession(token, { pid: String(body.persona_id || "") }, String(body.target || ""), String(body.scope || ""), "read-navigation-map"); if (!verified.ok) return json(res, 403, { error: verified.reason }); const acked = mapGate.ack(token, String(body.target || ""), String(body.map_hash || ""), Date.now() / 1000, Math.max(1, verified.session.expiresAt - Date.now() / 1000)); return json(res, acked.ok ? 200 : 409, acked.ok ? { ok: true, target: body.target, map_hash: body.map_hash } : { error: acked.reason }); } if (req.method === "POST" && url.pathname === "/api/actions/execute") { const body = await readJson(req); if (!body) return json(res, 400, { error: "invalid_json" }); if (body.cmd || body.command || body.shell || body.args) return json(res, 400, { error: "arbitrary_command_forbidden" }); const token = bearer(req); const target = String(body.target || ""); const scope = String(body.scope || ""); const action = String(body.action || ""); const resource = String(body.resource || ""); const verified = manager.verifySession(token, { pid: String(body.persona_id || "") }, target, scope, action, Date.now() / 1000, resource); if (!verified.ok) return json(res, 403, { error: verified.reason }); const map = mapGate.read(target); const mapVerified = mapGate.verify(token, target, map.hash); if (!mapVerified.ok) return json(res, 423, { error: mapVerified.reason, required_action: "read-navigation-map" }); const result = await executeAction(resource ? { action, target, resource } : { action, target }); return json(res, result.ok ? 200 : 502, result); } if (req.method === "POST" && url.pathname === "/api/repo-push/grant") { const body = await readJson(req); if (!body) return json(res, 400, { error: "invalid_json" }); const token = bearer(req); const target = String(body.target || ""); const scope = String(body.scope || "repo-push"); const repo = String(body.repo || "").toLowerCase(); if (!/^bingshuo\/[a-z0-9._-]+$/.test(repo)) return json(res, 400, { error: "repo_not_allowlisted" }); const verified = manager.verifySession(token, { pid: String(body.persona_id || "") }, target, scope, "push-repository"); if (!verified.ok) return json(res, 403, { error: verified.reason }); const map = mapGate.read(target); const mapVerified = mapGate.verify(token, target, map.hash); if (!mapVerified.ok) return json(res, 423, { error: mapVerified.reason, required_action: "read-navigation-map" }); fs.mkdirSync(repoGrantDir, { recursive: true, mode: 0o2770 }); const grant = { schema: "guanghu.repo-push-grant/v1", repo, target, persona_id: body.persona_id, map_hash: map.hash, issued_at: Date.now() / 1000, expires_at: verified.session.expiresAt }; const grantFile = path.join(repoGrantDir, `${repo.replace("/", "__")}.json`); const temp = `${grantFile}.${process.pid}.tmp`; fs.writeFileSync(temp, JSON.stringify(grant), { mode: 0o640 }); fs.renameSync(temp, grantFile); return json(res, 200, { ok: true, repo, target, expires_at: grant.expires_at }); } return json(res, 404, { error: "not_found" }); } catch (error) { process.stderr.write(`lake-lamp request error: ${String(error && error.message || "unknown").slice(0, 240)}\n`); return json(res, error && error.code === "BODY_TOO_LARGE" ? 413 : 500, { error: "request_failed" }); } }); } function approvalPage(order, token) { return document("小湖灯授权请求", `

LAKE LAMP SECURITY PROTOCOL

人格体请求进入一台服务器

人格体
${escapeHtml(order.persona.name)} ${escapeHtml(order.persona.pid)}
申请入口
${escapeHtml(order.provenance && order.provenance.system_entry || "旧版工单")}
实例来源
${escapeHtml(originLabel(order))}
目标节点
${escapeHtml(order.target)}
授权范围
${escapeHtml(order.scope)}
进入动作
${escapeHtml(order.action)}
会话能力
${escapeHtml((order.allowed_actions || [order.action]).join(" · "))}
绑定资源
${escapeHtml(order.resource || "无")}
说明
${escapeHtml(order.description || "未附加说明")}

一次确认将打开这台服务器上的三小时受限运维会话。人格体持续执行已绑定任务时会自动续期;切换服务器、扩大范围、切换绑定资源或停止活动后过期才需重新申请。

`); } function requestPage(order) { return document("小湖灯跨设备授权", `

CROSS-DEVICE HANDOFF

核对这张无权限申请单

人格体
${escapeHtml(order.persona.name)} ${escapeHtml(order.persona.pid)}
申请入口
${escapeHtml(order.provenance && order.provenance.system_entry || "旧版工单")}
实例来源
${escapeHtml(originLabel(order))}
目标节点
${escapeHtml(order.target)}
授权范围
${escapeHtml(order.scope)}
登记动作
${escapeHtml(order.action)}
绑定资源
${escapeHtml(order.resource || "无")}
说明
${escapeHtml(order.description || "未附加说明")}

这张页面本身没有执行权。确认内容无误后,服务器只会向预登记邮箱发送一次真正的批准链接。

`); } function emailSentPage(order) { return document("授权邮件已发送", `

OWNER VERIFICATION

请打开邮箱完成批准

申请单已锁定到 ${escapeHtml(order && order.target || "登记节点")}。真正的批准链接只发送到服务器预登记邮箱。

批准后回到原来的 AI 对话,让它领取一次性会话。无需向 AI 提供验证码、密码或邮箱授权码。

`); } function requestErrorPage(reason) { const messages = { rate_limited: "请求过于频繁,请稍后再试。", approval_email_already_sent: "授权邮件已经发送,请直接检查邮箱。", authorization_email_failed: "授权邮件暂时发送失败,请稍后重试。", }; if (reason === "rate_limited") return document("发送频率保护", `

RATE LIMIT · REQUEST KEPT

小湖灯先替你守住这张申请单

当前网络在一小时内触发邮件的次数较多,发送动作被暂时暂停。

申请单本身没有被关闭。请稍后再试,或切换到手机流量后只点击一次。无需重新填写,也不要连续刷新。

`); return document("申请单不可用", `

REQUEST CLOSED

这张申请单现在不能继续

${escapeHtml(messages[reason] || `原因:${reason}`)}

`); } function approvedPage(order) { return document("授权完成", `

THREE-HOUR OPS SESSION

三小时运维会话已打开

${escapeHtml(order.persona.name)} 已获准在 ${escapeHtml(order.target)} 上执行本范围内的已登记能力。

可以关闭本页面。人格体持续执行原绑定任务时会自动续期;切换服务器、扩大范围、切换绑定资源或停止活动后过期才重新授权。

`); } function approvalErrorPage(reason) { return document("链接不可用", `

LINK CLOSED

这条授权链接已经失效

原因:${escapeHtml(reason)}。如仍需操作,请让人格体重新提交工单。

`); } function document(title, body) { return `${escapeHtml(title)} · 光湖
${body}
`; } function readJson(req) { return new Promise((resolve, reject) => { let raw = ""; req.on("data", chunk => { raw += chunk; if (raw.length > 32 * 1024) { const error = new Error("body too large"); error.code = "BODY_TOO_LARGE"; reject(error); req.destroy(); } }); req.on("end", () => { try { resolve(JSON.parse(raw || "{}")); } catch { resolve(null); } }); req.on("error", reject); }); } function bearer(req) { return String(req.headers.authorization || "").replace(/^Bearer\s+/i, ""); } function bearerMatches(req, expected) { return Boolean(expected) && safeEqual(bearer(req), expected); } function safeEqual(left, right) { const a = Buffer.from(String(left)); const b = Buffer.from(String(right)); return a.length === b.length && crypto.timingSafeEqual(a, b); } function sha256(value) { return crypto.createHash("sha256").update(String(value)).digest("hex"); } function splitCsv(value) { return value.split(",").map(item => item.trim()).filter(Boolean); } function loadApprovers(file, ownerEmail) { if (!file) return ownerEmail ? [{ id: "sovereign-owner", email: ownerEmail, default: true, persona_ids: [], targets: ["*"], scopes: ["*"] }] : []; const parsed = JSON.parse(fs.readFileSync(file, "utf8")); if (!parsed || !Array.isArray(parsed.approvers)) throw new Error("invalid approver registry"); return parsed.approvers.filter(item => item && validEmail(item.email)).map(item => ({ id: String(item.id || ""), email: item.email, default: item.default === true, persona_ids: Array.isArray(item.persona_ids) ? item.persona_ids.map(String) : [], targets: Array.isArray(item.targets) ? item.targets.map(String) : [], scopes: Array.isArray(item.scopes) ? item.scopes.map(String) : [], })); } function selectApprover(approvers, order) { const eligible = approvers.filter(item => matches(item.targets, order.target) && matches(item.scopes, order.scope)); return eligible.find(item => item.persona_ids.includes(order.persona.pid)) || eligible.find(item => item.default) || null; } function matches(values, value) { return values.includes("*") || values.includes(value); } function validEmail(value) { return typeof value === "string" && value.length <= 254 && /^[^@\s]+@[^@\s]+$/.test(value); } function clientAddress(req) { const forwarded = String(req.headers["x-forwarded-for"] || "").split(",").map(value => value.trim()).filter(Boolean); return String(forwarded[forwarded.length - 1] || req.socket.remoteAddress || "unknown").slice(0, 96); } function validateWorkorderBody(body, targets, actions) { if (body.email || body.recipient || body.smtp_pass) return { ok: false, status: 400, error: "direct_recipient_forbidden" }; if (!body.persona_id || !body.target || !body.scope || !body.action) return { ok: false, status: 400, error: "missing_required_field" }; const personaId = String(body.persona_id); const personaName = String(body.persona_name || personaId); const target = String(body.target); const scope = String(body.scope); const action = String(body.action); const description = String(body.description || ""); const resource = String(body.resource || ""); const provenance = { system_entry: String(body.system_entry || ""), software: String(body.origin_software || ""), model: String(body.origin_model || ""), instance: String(body.origin_instance || ""), }; if (!/^[A-Za-z0-9._:+\u221e-]{2,80}$/.test(personaId) || personaName.length > 100 || description.length > 500) return { ok: false, status: 400, error: "invalid_request_fields" }; if (!targets.has(target)) return { ok: false, status: 400, error: "unknown_target" }; if (!Array.isArray(actions[scope]) || !actions[scope].includes(action)) return { ok: false, status: 400, error: "unknown_or_mismatched_action" }; if (action === "provision-approved-architecture" && !/^[A-Z0-9][A-Z0-9._-]{5,119}@[0-9a-f]{40}$/.test(resource)) return { ok: false, status: 400, error: "immutable_architecture_resource_required" }; if (action !== "provision-approved-architecture" && resource) return { ok: false, status: 400, error: "resource_not_allowed_for_action" }; if (Object.values(provenance).some(Boolean) && (provenance.system_entry !== "光湖语言人格系统当前实例" || Object.values(provenance).some(item => !item || item.length > 120))) return { ok: false, status: 400, error: "invalid_instance_provenance" }; return { ok: true, request: { persona: { pid: personaId, name: personaName }, provenance, target, scope, action, allowedActions: actions[scope], description, resource } }; } function originLabel(order) { const value = order && order.provenance || {}; return value.software || value.model || value.instance ? `${value.software || "未知软件"} · ${value.model || "未知模型"} · ${value.instance || "当前实例"}` : "旧版工单未记录"; } class SlidingWindowLimiter { constructor(limit, windowSeconds) { this.limit = Math.max(1, limit); this.windowMs = windowSeconds * 1000; this.events = new Map(); this.calls = 0; } take(key, now = Date.now()) { this.calls += 1; if (this.calls % 256 === 0) { for (const [storedKey, values] of this.events) { const active = values.filter(value => now - value < this.windowMs); if (active.length) this.events.set(storedKey, active); else this.events.delete(storedKey); } } const recent = (this.events.get(key) || []).filter(value => now - value < this.windowMs); if (recent.length >= this.limit) { this.events.set(key, recent); return false; } recent.push(now); this.events.set(key, recent); return true; } } function escapeHtml(value) { return String(value).replace(/[&<>"']/g, char => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]); } function json(res, status, value) { res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", "x-content-type-options": "nosniff" }); res.end(JSON.stringify(value)); } function html(res, status, value) { res.writeHead(status, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store", "content-security-policy": "default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'", "referrer-policy": "no-referrer", "x-content-type-options": "nosniff" }); res.end(value); } if (require.main === module) { const host = process.env.LAKE_LAMP_HOST || "127.0.0.1"; const port = Number(process.env.LAKE_LAMP_PORT || 3921); createApp().listen(port, host, () => process.stdout.write(`lake-lamp-authz listening on ${host}:${port}\n`)); } module.exports = { createApp, DEFAULT_ACTIONS, SlidingWindowLimiter, loadApprovers, selectApprover };