|
| 1 | +// Per-instance write-consent for connector tools (issue #90). Reads are allowed |
| 2 | +// once a connector is connected; writes require an explicit consent row. All queries |
| 3 | +// parameterized. See migration 0051. |
| 4 | +import type { Env } from "../types.js"; |
| 5 | + |
| 6 | +export type ConnectorScope = "write"; |
| 7 | + |
| 8 | +/** Does this instance have consent for <connector> <scope>? Fail-closed on any error. */ |
| 9 | +export async function hasConsent( |
| 10 | + env: Env, |
| 11 | + instanceId: string | undefined, |
| 12 | + connector: string, |
| 13 | + scope: ConnectorScope, |
| 14 | +): Promise<boolean> { |
| 15 | + if (!instanceId) return false; |
| 16 | + try { |
| 17 | + const row = await env.DB.prepare( |
| 18 | + "SELECT 1 AS ok FROM instance_connector_consent WHERE instance_id = ?1 AND connector = ?2 AND scope = ?3", |
| 19 | + ).bind(instanceId, connector, scope).first<{ ok: number }>(); |
| 20 | + return !!row; |
| 21 | + } catch { |
| 22 | + return false; |
| 23 | + } |
| 24 | +} |
| 25 | + |
| 26 | +export async function setConsent(env: Env, instanceId: string, userId: string, connector: string, scope: ConnectorScope): Promise<void> { |
| 27 | + await env.DB.prepare( |
| 28 | + `INSERT INTO instance_connector_consent (instance_id, user_id, connector, scope) |
| 29 | + VALUES (?1, ?2, ?3, ?4) |
| 30 | + ON CONFLICT(instance_id, connector, scope) DO NOTHING`, |
| 31 | + ).bind(instanceId, userId, connector, scope).run(); |
| 32 | +} |
| 33 | + |
| 34 | +export async function revokeConsent(env: Env, instanceId: string, connector: string, scope: ConnectorScope): Promise<void> { |
| 35 | + await env.DB.prepare( |
| 36 | + "DELETE FROM instance_connector_consent WHERE instance_id = ?1 AND connector = ?2 AND scope = ?3", |
| 37 | + ).bind(instanceId, connector, scope).run(); |
| 38 | +} |
| 39 | + |
| 40 | +export interface ConsentRow { instance_id: string; user_id: string; connector: string; scope: string; created_at: string } |
| 41 | + |
| 42 | +export async function listConsents(env: Env, instanceId: string): Promise<ConsentRow[]> { |
| 43 | + const res = await env.DB.prepare( |
| 44 | + "SELECT instance_id, user_id, connector, scope, created_at FROM instance_connector_consent WHERE instance_id = ?1", |
| 45 | + ).bind(instanceId).all<ConsentRow>(); |
| 46 | + return res.results ?? []; |
| 47 | +} |
| 48 | + |
| 49 | +/** Admin: every connector consent across all users (with owner login). */ |
| 50 | +export async function listAllConsents(env: Env): Promise<Array<ConsentRow & { owner_login: string | null }>> { |
| 51 | + const res = await env.DB.prepare( |
| 52 | + `SELECT c.instance_id, c.user_id, c.connector, c.scope, c.created_at, u.github_login AS owner_login |
| 53 | + FROM instance_connector_consent c LEFT JOIN users u ON u.id = c.user_id |
| 54 | + ORDER BY c.created_at DESC`, |
| 55 | + ).all<ConsentRow & { owner_login: string | null }>(); |
| 56 | + return res.results ?? []; |
| 57 | +} |
0 commit comments