mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-14 14:57:28 +00:00
feat(credential-sharing): end-to-end-encrypted team credential sharing
Adds a `team` credential provider + team/member management so teammates can share secrets by email instead of passing .env files over chat. Fully E2E: the server only ever stores ciphertext, per-member sealed vault keys, and public keys — it never sees a plaintext value or the vault DEK. Plugin (@logicsrc/plugin-credential-sharing) - crypto.ts: X25519 identity keys, per-vault DEK (secretbox), DEK sealed to each member's pubkey (crypto_box_seal), value encrypt/decrypt (libsodium) - identity.ts: local ~/.logicsrc/identity.json (0600) holding the device key + API token; never uploads the secret key - client.ts: typed /api/credshare client - providers/team.ts: `team:<slug>/<vault>` CredentialProvider (inspect, readValues=decrypt, write=encrypt, rollback); fingerprints match env so env<->team diffs line up - fixes latent libsodium-wrappers ESM load bug (createRequire) here + in github-secrets Server (commandboard-api /api/credshare) - zero-knowledge router: email-code auth, keys, teams, members, invites, vaults, sealed grants, ciphertext secrets, audit; membership authz in app - CredShareStore abstraction: in-memory (dev/tests) + Supabase (prod) - Resend email transport for login codes + invites (no-op -> echoes locally) - supabase migration: credshare_* tables, deny-by-default RLS CLI - real `logicsrc login` (email code -> token + key upload) - `logicsrc teams create/list/invite/accept/members/vaults/grant/push/pull` Web (logicsrc.com/teams + /teams/accept) - management surface only (browser holds no private key, never decrypts): login, view teams/members/vaults, invite, accept Tests: crypto round-trip, server contract (invite->accept->push->grant->pull + authz boundaries), and a real HTTP+client+crypto E2E asserting the server never holds plaintext. Full workspace build + tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
257d581331
commit
f057589d66
28 changed files with 2869 additions and 17 deletions
|
|
@ -11,6 +11,10 @@ import { listSocialAccountProviders, socialAccountsPlugin } from "@logicsrc/plug
|
|||
import { uGigPlugin } from "@logicsrc/plugin-ugig";
|
||||
import { schemas, validate } from "@logicsrc/validators";
|
||||
import { buildAgentMailService, mailIdentity } from "./agentmail.js";
|
||||
import { createCredShareApi, type CredShareRequest } from "./credshare/router.js";
|
||||
import { createMemoryCredShareStore } from "./credshare/store.js";
|
||||
import { createSupabaseCredShareStore } from "./credshare/supabase-store.js";
|
||||
import { createResendEmailSender } from "./credshare/email.js";
|
||||
|
||||
const registry = createPluginRegistry([coinPayPlugin, uGigPlugin, sh1ptPlugin, c0mputePlugin, feedDiscoveryPlugin, socialAccountsPlugin, emailAccountsPlugin, agentMailPlugin]);
|
||||
|
||||
|
|
@ -57,6 +61,42 @@ const c0mputeWorkers = [
|
|||
{ id: "worker_pool_1", region: "us-west", status: "preview", capacity: "wip" }
|
||||
];
|
||||
|
||||
// Credential-sharing API: Supabase-backed when SUPABASE_URL + service key are
|
||||
// present, else an in-process memory store (local dev / tests). Zero-knowledge:
|
||||
// the server only ever relays ciphertext, wrapped keys, and public keys.
|
||||
const credShareApi = createCredShareApi({
|
||||
store: createSupabaseCredShareStore() ?? createMemoryCredShareStore(),
|
||||
email: createResendEmailSender(),
|
||||
webBaseUrl: process.env.LOGICSRC_WEB_URL || "https://logicsrc.com"
|
||||
});
|
||||
|
||||
const CREDSHARE_PREFIX = "/api/credshare";
|
||||
|
||||
async function handleCredShare(request: IncomingMessage, response: ServerResponse, url: URL) {
|
||||
const method = request.method ?? "GET";
|
||||
let body: unknown;
|
||||
if (method === "POST" || method === "PUT" || method === "PATCH") {
|
||||
try {
|
||||
body = await readJson(request);
|
||||
} catch {
|
||||
json(response, 400, { error: "Invalid JSON body" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
const authHeader = request.headers["authorization"];
|
||||
const header = Array.isArray(authHeader) ? authHeader[0] : authHeader;
|
||||
const token = header?.toLowerCase().startsWith("bearer ") ? header.slice(7).trim() : undefined;
|
||||
const req: CredShareRequest = {
|
||||
method,
|
||||
path: url.pathname.slice(CREDSHARE_PREFIX.length) || "/",
|
||||
query: url.searchParams,
|
||||
body,
|
||||
token
|
||||
};
|
||||
const result = await credShareApi.handle(req);
|
||||
json(response, result.status, result.body);
|
||||
}
|
||||
|
||||
class InvalidJsonBodyError extends Error {
|
||||
constructor() {
|
||||
super("Invalid JSON body");
|
||||
|
|
@ -85,7 +125,7 @@ async function route(request: IncomingMessage, response: ServerResponse) {
|
|||
json(response, 200, {
|
||||
ok: true,
|
||||
service: "commandboard-api",
|
||||
endpoints: ["/health", "/api/boards", "/api/tasks", "/api/plugins", "/api/schemas", "/api/accounts/providers", "/api/accounts", "/api/social/providers", "/api/email/providers", "/api/feeds/discover", "/api/feeds/providers", "/api/plugins/agentmail/mailboxes", "/api/plugins/agentmail/mailboxes/:mailbox/messages", "/api/plugins/agentmail/mailboxes/:mailbox/messages/:uid", "/api/plugins/agentmail/search", "/api/plugins/agentmail/messages", "/rss/discover/:keyword.xml", "/opml/discover/:keyword.xml", "/atom/discover/:keyword.xml", "/json-feed/discover/:keyword.json"]
|
||||
endpoints: ["/health", "/api/boards", "/api/tasks", "/api/plugins", "/api/schemas", "/api/accounts/providers", "/api/accounts", "/api/social/providers", "/api/email/providers", "/api/feeds/discover", "/api/feeds/providers", "/api/credshare/auth/request", "/api/credshare/auth/verify", "/api/credshare/keys", "/api/credshare/teams", "/api/credshare/teams/:slug/members", "/api/credshare/teams/:slug/invites", "/api/credshare/teams/:slug/vaults", "/api/credshare/invites/accept", "/api/credshare/vaults/:id/secrets", "/api/credshare/vaults/:id/grant", "/api/credshare/vaults/:id/grants", "/api/credshare/vaults/:id/audit", "/api/plugins/agentmail/mailboxes", "/api/plugins/agentmail/mailboxes/:mailbox/messages", "/api/plugins/agentmail/mailboxes/:mailbox/messages/:uid", "/api/plugins/agentmail/search", "/api/plugins/agentmail/messages", "/rss/discover/:keyword.xml", "/opml/discover/:keyword.xml", "/atom/discover/:keyword.xml", "/json-feed/discover/:keyword.json"]
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -220,6 +260,11 @@ async function route(request: IncomingMessage, response: ServerResponse) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (url.pathname === CREDSHARE_PREFIX || url.pathname.startsWith(`${CREDSHARE_PREFIX}/`)) {
|
||||
await handleCredShare(request, response, url);
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.pathname === "/api/plugins/agentmail" || url.pathname.startsWith("/api/plugins/agentmail/")) {
|
||||
await handleAgentMail(request, response, url);
|
||||
return;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue