mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-13 22:37:29 +00:00
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>
134 lines
5.7 KiB
SQL
134 lines
5.7 KiB
SQL
-- LogicSRC team credential sharing (end-to-end encrypted).
|
|
--
|
|
-- The server is a zero-knowledge relay for secret VALUES: it stores member
|
|
-- public keys, per-member wrapped vault keys (sealed to those public keys), and
|
|
-- secret ciphertext + nonces. It never receives a plaintext secret or a vault
|
|
-- data-encryption key. The commandboard-api service uses the SERVICE ROLE key
|
|
-- and enforces membership authorization in application code. RLS here is
|
|
-- deny-by-default defense-in-depth for any non-service (anon/authenticated)
|
|
-- access path.
|
|
|
|
create extension if not exists "citext";
|
|
|
|
-- Members (identified by email) and their identity public keys.
|
|
create table if not exists credshare_users (
|
|
id uuid primary key default gen_random_uuid(),
|
|
email citext not null unique,
|
|
public_key text,
|
|
created_at timestamptz not null default now()
|
|
);
|
|
|
|
-- Short-lived email login codes (stored hashed).
|
|
create table if not exists credshare_login_codes (
|
|
email citext primary key,
|
|
code_hash text not null,
|
|
expires_at timestamptz not null,
|
|
created_at timestamptz not null default now()
|
|
);
|
|
|
|
-- Issued API bearer tokens (stored hashed).
|
|
create table if not exists credshare_tokens (
|
|
token_hash text primary key,
|
|
user_id uuid not null references credshare_users(id) on delete cascade,
|
|
created_at timestamptz not null default now(),
|
|
last_used_at timestamptz
|
|
);
|
|
create index if not exists credshare_tokens_user_idx on credshare_tokens(user_id);
|
|
|
|
create table if not exists credshare_teams (
|
|
id uuid primary key default gen_random_uuid(),
|
|
slug citext not null unique,
|
|
name text not null,
|
|
created_by uuid not null references credshare_users(id),
|
|
created_at timestamptz not null default now()
|
|
);
|
|
|
|
create table if not exists credshare_members (
|
|
id uuid primary key default gen_random_uuid(),
|
|
team_id uuid not null references credshare_teams(id) on delete cascade,
|
|
user_id uuid references credshare_users(id) on delete set null,
|
|
email citext not null,
|
|
role text not null default 'member' check (role in ('owner', 'admin', 'member')),
|
|
status text not null default 'invited' check (status in ('active', 'invited')),
|
|
invited_by uuid references credshare_users(id),
|
|
joined_at timestamptz,
|
|
created_at timestamptz not null default now(),
|
|
unique (team_id, email)
|
|
);
|
|
create index if not exists credshare_members_user_idx on credshare_members(user_id) where user_id is not null;
|
|
create index if not exists credshare_members_team_idx on credshare_members(team_id);
|
|
|
|
create table if not exists credshare_invites (
|
|
id uuid primary key default gen_random_uuid(),
|
|
team_id uuid not null references credshare_teams(id) on delete cascade,
|
|
email citext not null,
|
|
role text not null default 'member' check (role in ('owner', 'admin', 'member')),
|
|
token_hash text not null unique,
|
|
created_by uuid not null references credshare_users(id),
|
|
expires_at timestamptz not null,
|
|
accepted_at timestamptz,
|
|
created_at timestamptz not null default now()
|
|
);
|
|
create index if not exists credshare_invites_team_idx on credshare_invites(team_id);
|
|
|
|
create table if not exists credshare_vaults (
|
|
id uuid primary key default gen_random_uuid(),
|
|
team_id uuid not null references credshare_teams(id) on delete cascade,
|
|
name citext not null,
|
|
created_by uuid not null references credshare_users(id),
|
|
created_at timestamptz not null default now(),
|
|
unique (team_id, name)
|
|
);
|
|
|
|
-- Vault data-encryption key, sealed to each member's public key (one row per member).
|
|
create table if not exists credshare_vault_grants (
|
|
id uuid primary key default gen_random_uuid(),
|
|
vault_id uuid not null references credshare_vaults(id) on delete cascade,
|
|
user_id uuid not null references credshare_users(id) on delete cascade,
|
|
wrapped_dek text not null,
|
|
granted_by uuid not null references credshare_users(id),
|
|
created_at timestamptz not null default now(),
|
|
unique (vault_id, user_id)
|
|
);
|
|
|
|
-- Encrypted secrets. `ciphertext`/`nonce` decrypt only with the vault DEK, which
|
|
-- the server never sees. `fingerprint` is a salted hash used for redacted diffs.
|
|
create table if not exists credshare_secrets (
|
|
id uuid primary key default gen_random_uuid(),
|
|
vault_id uuid not null references credshare_vaults(id) on delete cascade,
|
|
name text not null,
|
|
nonce text not null,
|
|
ciphertext text not null,
|
|
fingerprint text not null,
|
|
version integer not null default 1,
|
|
updated_by uuid not null references credshare_users(id),
|
|
updated_at timestamptz not null default now(),
|
|
unique (vault_id, name)
|
|
);
|
|
|
|
create table if not exists credshare_audit (
|
|
id uuid primary key default gen_random_uuid(),
|
|
team_id uuid references credshare_teams(id) on delete set null,
|
|
vault_id uuid references credshare_vaults(id) on delete set null,
|
|
actor_user_id uuid not null references credshare_users(id),
|
|
action text not null,
|
|
key_name text,
|
|
fingerprint text,
|
|
created_at timestamptz not null default now()
|
|
);
|
|
create index if not exists credshare_audit_vault_idx on credshare_audit(vault_id, created_at desc);
|
|
|
|
-- Deny-by-default RLS: only the service role (which bypasses RLS) may touch
|
|
-- these tables. No anon/authenticated policies are defined, so every non-service
|
|
-- request is denied. Confidentiality of secret values comes from E2E encryption,
|
|
-- not from RLS.
|
|
alter table credshare_users enable row level security;
|
|
alter table credshare_login_codes enable row level security;
|
|
alter table credshare_tokens enable row level security;
|
|
alter table credshare_teams enable row level security;
|
|
alter table credshare_members enable row level security;
|
|
alter table credshare_invites enable row level security;
|
|
alter table credshare_vaults enable row level security;
|
|
alter table credshare_vault_grants enable row level security;
|
|
alter table credshare_secrets enable row level security;
|
|
alter table credshare_audit enable row level security;
|