media-connector-policy.mjs
105 lines 3.0 KB
Raw
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor ⚠ breaking 10 days ago
1 /**
2 * Per-vault media connector allowlist (Phase 2F-b-d-kn-b).
3 *
4 * Deny-by-default; holds enablement + display metadata only — no secrets.
5 *
6 * @see docs/MEDIA-WRITE-SURFACES-CONTRACT-2F-b-d-kn.md §5
7 */
8
9 import fs from 'fs';
10 import path from 'path';
11 import { randomUUID } from 'crypto';
12
13 const STORE_FILENAME = 'hub_media_connector_policy.json';
14
15 export const CONNECTOR_ID_RE = /^[a-z][a-z0-9_]{0,31}$/;
16
17 /**
18 * @typedef {Object} ConnectorPolicyRow
19 * @property {boolean} enabled
20 * @property {string} [display_name]
21 * @property {string} [updated]
22 */
23
24 /**
25 * @typedef {Object} MediaConnectorPolicyFile
26 * @property {'knowtation.media_connector_policy/v0'} schema
27 * @property {Record<string, { connectors?: Record<string, ConnectorPolicyRow> }>} vaults
28 */
29
30 /**
31 * @param {string} dataDir
32 * @returns {string}
33 */
34 export function getMediaConnectorPolicyPath(dataDir) {
35 return path.join(dataDir, STORE_FILENAME);
36 }
37
38 /**
39 * @param {string} dataDir
40 * @returns {MediaConnectorPolicyFile}
41 */
42 export function loadMediaConnectorPolicy(dataDir) {
43 const filePath = getMediaConnectorPolicyPath(dataDir);
44 if (!fs.existsSync(filePath)) {
45 return { schema: 'knowtation.media_connector_policy/v0', vaults: {} };
46 }
47 try {
48 const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
49 if (!parsed || typeof parsed !== 'object' || !parsed.vaults || typeof parsed.vaults !== 'object') {
50 return { schema: 'knowtation.media_connector_policy/v0', vaults: {} };
51 }
52 return /** @type {MediaConnectorPolicyFile} */ ({
53 schema: 'knowtation.media_connector_policy/v0',
54 vaults: parsed.vaults,
55 });
56 } catch {
57 return { schema: 'knowtation.media_connector_policy/v0', vaults: {} };
58 }
59 }
60
61 /**
62 * @param {string} dataDir
63 * @param {MediaConnectorPolicyFile} store
64 */
65 export function saveMediaConnectorPolicy(dataDir, store) {
66 const filePath = getMediaConnectorPolicyPath(dataDir);
67 const dir = path.dirname(filePath);
68 if (!fs.existsSync(dir)) {
69 fs.mkdirSync(dir, { recursive: true });
70 }
71 const payload = {
72 schema: 'knowtation.media_connector_policy/v0',
73 vaults: store.vaults ?? {},
74 };
75 const tmp = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
76 fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), 'utf8');
77 fs.renameSync(tmp, filePath);
78 }
79
80 /**
81 * @param {string} dataDir
82 * @param {string} vaultId
83 * @returns {Record<string, ConnectorPolicyRow>}
84 */
85 export function getVaultConnectors(dataDir, vaultId) {
86 const store = loadMediaConnectorPolicy(dataDir);
87 const vault = store.vaults?.[vaultId];
88 if (!vault || !vault.connectors || typeof vault.connectors !== 'object') {
89 return {};
90 }
91 return vault.connectors;
92 }
93
94 /**
95 * @param {string} dataDir
96 * @param {string} vaultId
97 * @param {string} connectorId
98 * @returns {ConnectorPolicyRow|null}
99 */
100 export function getEnabledConnector(dataDir, vaultId, connectorId) {
101 if (!CONNECTOR_ID_RE.test(connectorId)) return null;
102 const row = getVaultConnectors(dataDir, vaultId)[connectorId];
103 if (!row || row.enabled !== true) return null;
104 return row;
105 }
File History 1 commit
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor 10 days ago