76 lines
2.0 KiB
TypeScript
76 lines
2.0 KiB
TypeScript
|
|
import { ensureSchema, getRawDb } from "./mvp-db";
|
||
|
|
|
||
|
|
export type McpExportKind = "recovery" | "resources";
|
||
|
|
|
||
|
|
type ExportTokenRow = {
|
||
|
|
kind: McpExportKind;
|
||
|
|
payload: string;
|
||
|
|
expires_at: string;
|
||
|
|
};
|
||
|
|
|
||
|
|
function sqliteTimestamp(date: Date) {
|
||
|
|
return date.toISOString().replace("T", " ").slice(0, 19);
|
||
|
|
}
|
||
|
|
|
||
|
|
async function digest(value: string) {
|
||
|
|
const bytes = await crypto.subtle.digest(
|
||
|
|
"SHA-256",
|
||
|
|
new TextEncoder().encode(value),
|
||
|
|
);
|
||
|
|
return [...new Uint8Array(bytes)]
|
||
|
|
.map((byte) => byte.toString(16).padStart(2, "0"))
|
||
|
|
.join("");
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function issueMcpExportToken(
|
||
|
|
kind: McpExportKind,
|
||
|
|
payload: Record<string, unknown>,
|
||
|
|
ttlMinutes = 15,
|
||
|
|
) {
|
||
|
|
await ensureSchema();
|
||
|
|
const db = getRawDb();
|
||
|
|
const token = `${crypto.randomUUID().replaceAll("-", "")}${crypto
|
||
|
|
.randomUUID()
|
||
|
|
.replaceAll("-", "")}`;
|
||
|
|
const expiresAt = new Date(
|
||
|
|
Date.now() + Math.max(1, Math.min(60, ttlMinutes)) * 60_000,
|
||
|
|
);
|
||
|
|
await db.prepare("DELETE FROM mcp_export_tokens WHERE expires_at <= CURRENT_TIMESTAMP").run();
|
||
|
|
await db
|
||
|
|
.prepare(
|
||
|
|
`INSERT INTO mcp_export_tokens (token_hash, kind, payload, expires_at)
|
||
|
|
VALUES (?, ?, ?, ?)`,
|
||
|
|
)
|
||
|
|
.bind(await digest(token), kind, JSON.stringify(payload), sqliteTimestamp(expiresAt))
|
||
|
|
.run();
|
||
|
|
return { token, expiresAt: expiresAt.toISOString() };
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function consumeMcpExportToken(
|
||
|
|
token: string,
|
||
|
|
expectedKind: McpExportKind,
|
||
|
|
) {
|
||
|
|
if (!token || token.length < 32) return null;
|
||
|
|
await ensureSchema();
|
||
|
|
const db = getRawDb();
|
||
|
|
const tokenHash = await digest(token);
|
||
|
|
const row = await db
|
||
|
|
.prepare(
|
||
|
|
`SELECT kind, payload, expires_at
|
||
|
|
FROM mcp_export_tokens
|
||
|
|
WHERE token_hash = ? AND expires_at > CURRENT_TIMESTAMP`,
|
||
|
|
)
|
||
|
|
.bind(tokenHash)
|
||
|
|
.first<ExportTokenRow>();
|
||
|
|
if (!row || row.kind !== expectedKind) return null;
|
||
|
|
await db
|
||
|
|
.prepare("DELETE FROM mcp_export_tokens WHERE token_hash = ?")
|
||
|
|
.bind(tokenHash)
|
||
|
|
.run();
|
||
|
|
try {
|
||
|
|
return JSON.parse(row.payload) as Record<string, unknown>;
|
||
|
|
} catch {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|