No description
  • TypeScript 98.1%
  • JavaScript 0.9%
  • CSS 0.4%
  • Shell 0.4%
  • Dockerfile 0.2%
Find a file
Steffen Roertgen bf24016995
All checks were successful
Build and Push Docker Image / build (push) Successful in 4m19s
feat(edufeed-accounts): custodial team accounts with NIP-46 delegation
Phase 0 + Phase 1 + Phase 3 + Phase 4 of the edufeed-accounts design
doc (../edufeed-accounts-design.md). Lays down a custodial signing
service for "team / functional account" use cases on top of the
multi-share fork. Existing self-mode keysmith and FROSTR multi-share
flows are untouched — discriminator is users.auth_mode = 'self' |
'custodial'.

Schema:
- ensureAuthModeColumn() adds users.auth_mode at runtime
  (createUserTable() also includes it for fresh installs)
- 20260611_0011_team_members_and_invites.sql adds
  team_members + invite_tokens + bunker_invites

Crypto + DB helpers:
- src/db/custodial-kek.ts — HKDF-SHA256(master_key, salt, user_id) for
  the per-user KEK, with 9 unit tests. Fails fast when
  EDUFEED_ACCOUNTS_MASTER_KEY is missing.
- src/db/teams.ts — pure CRUD + token/secret-hash store + consume
  helpers for the three new tables. SHA-256 of every token at rest,
  expiry checks inside transactions, no plaintext logging.
- src/db/database.ts — listCustodialUsers() for startup auto-boot;
  createUser() takes options.authMode.

Endpoints:
- POST /api/auth/signup — owner email+password, generates 1-of-1
  FROSTR keyset, encrypts under HKDF KEK, boots ShareRuntime,
  attaches NIP-46, returns bunker URI + session cookie. Optional
  import_secret_hex preserves an existing npub (browser decodes the
  bech32 nsec to hex so the bech32 string never crosses the wire).
- POST /api/team/invite — owner mints a one-shot accept-invite token
- POST /api/team/accept-invite — colleague consumes the token, mints
  a bunker_invites secret, returns scoped bunker URI (no auth — the
  token IS the auth)
- POST /api/team/revoke — owner revokes a colleague, flips
  nip46_sessions.status to 'revoked'
- GET  /api/team/members — owner-facing colleague list
- POST /api/admin/teams/create — dev-time fast-path that bypasses
  email+password (admin secret gated), useful for spike testing

NIP-46 service extensions:
- handleSocketRequest refuses requests from sessions with
  status='revoked' BEFORE upsertSession (which would otherwise flip
  status back to 'active' via ON CONFLICT)
- respondConnect looks up bunker_invites by SHA-256(secret); on
  match, links the connecting client_pubkey to the right
  team_members row and burns the invite
- shouldAutoApprove short-circuits to true for custodial users —
  the session being valid IS the authorization
- handleSignEvent solo bypass for threshold=1: decodeShare().seckey
  + nostr-tools.finalizeEvent (Bifrost's threshold protocol waits
  forever for peers that don't exist on a 1-of-1 keyset)
- computeSharedSecret solo bypass for threshold=1:
  secp256k1.ProjectivePoint.fromHex('02' + peer).multiply(seckey)
  returns the X coord as hex — drop-in for deriveSharedSecret. All
  NIP-44 + NIP-04 + NIP-59 gift-wrap flows get the fix automatically.

Runtime + startup:
- server.ts auto-boots every auth_mode='custodial' user's runtime on
  startup. Without this, every server restart silently kills every
  team account's signing service until the owner re-signs-up.
- ShareMonitor.expectsPeers flag (set false for threshold=1 keysets
  in ShareRegistry.add); checkActivityTimeout skips its "no peer
  activity → recreate node" branch when the flag is false.

Default relay: wss://relay.edufeed.org (was relay.damus.io —
damus rate-limits the Bifrost kind-20004 announces and the multi-user
auto-boot was tripping it).

234/234 existing tests still pass; 9 new tests for the KEK helper.
End-to-end demo verified 2026-06-12 against edufeed-app's
LoginWithBunker.svelte: signup → bunker URI paste → publish event →
event arrives on relay under the team's npub. Gift wrap unwrap works
via the same NIP-44 solo ECDH path.

See edufeed-accounts-design.md §3 "Known caveat" + §13 "Lessons
learned from the first end-to-end demo" for the four bugs caught live
during the first demo (auto-approve, auto-boot, expectsPeers, solo
sign+ECDH).
2026-06-12 11:30:03 +02:00
.factory chore: user-testing validation synthesis for nip46-fix-and-polish milestone 2026-05-28 12:27:46 -05:00
.forgejo/workflows fix(ci): drop registry buildcache (was sinking the run after push) 2026-06-09 15:32:23 +02:00
.github fix: harden release metadata and runtime contracts 2026-03-08 19:26:16 -05:00
docs docs(deploy): add Traefik reverse-proxy + keysmith pairing recipe 2026-06-07 13:02:02 +02:00
frontend feat(ui): surface multi-share status in the Signer view 2026-06-03 16:59:26 +02:00
llm fix: apply requested API and UI hardening updates 2026-02-28 12:28:20 -06:00
packages/umbrel/igloo fix: address review findings across routes, ui, and workflows 2026-02-26 18:01:37 -06:00
scripts fix: harden release metadata and runtime contracts 2026-03-08 19:26:16 -05:00
src feat(edufeed-accounts): custodial team accounts with NIP-46 delegation 2026-06-12 11:30:03 +02:00
static fix: apply requested API and UI hardening updates 2026-02-28 12:28:20 -06:00
tests/routes test(user/credentials): focused spec for the keysmith pairing endpoint 2026-06-08 16:33:18 +02:00
.dockerignore build(docker): copy only postinstall script in prod image; add .dockerignore 2025-10-11 17:03:23 -05:00
.gitignore fix: apply requested API and UI hardening updates 2026-02-28 12:28:20 -06:00
AGENTS.md docs updates and polish 2026-02-09 15:07:49 -06:00
bun.lock fix: override vulnerable dompurify 2026-03-05 14:50:00 -06:00
CHANGELOG.md fix: address latest coderabbit review findings 2026-03-06 16:25:34 -06:00
CLAUDE.md docs(multi-share): document feat/multi-share branch + fixed gotchas 2026-06-07 10:41:18 +02:00
compose.yml fix: apply requested API and UI hardening updates 2026-02-28 12:28:20 -06:00
CONTRIBUTING.md nitpicks 2026-02-09 15:44:18 -06:00
Dockerfile fix(docker): pin oven/bun:1.3.4 to dodge 1.3.10 JIT memory regression 2026-06-09 15:05:55 +02:00
env.example docs(multi-share): replace v1 limitations with per-share isolation guarantees 2026-06-03 16:26:32 +02:00
LICENSE init commit 2024-10-27 15:44:27 -05:00
package.json chore: suppress stale browserslist build warning 2026-03-08 19:31:50 -05:00
README.md log storage optimizations and options 2026-02-10 16:44:26 -06:00
tsconfig.json feat(env, db, crypto): tighten auth, restart on updates, keep CSP/RELAYS 2025-10-11 21:14:44 -05:00

Igloo Server

Serverbased signing device and personal ephemeral relay for the FROSTR protocol. Igloo provides an alwayson signing node with an optional web UI for configuration and monitoring. Built on @frostr/igloo-core.

Looking to deploy quickly? Start with the one-click options in docs/DEPLOY.md (Umbrel App Store or Docker/Compose).

▶ Watch the umbrel demo

What It Is

  • Threshold Schnorr signing for Nostr using your FROSTR shares (kofn). The full private key is never reconstructed.
  • Two modes: Database (multiuser, encrypted creds, web UI) or Headless (envonly, APIfirst, no UI).
  • Includes an inmemory relay for dev/tests; use production relays in real deployments.

Features

  • Alwayson signer built on igloocore with multirelay support
  • Web UI (React + Tailwind) for setup, monitoring, recovery
  • Persisted UI event log (DB mode) with pagination and NDJSON export download
  • REST + WebSocket APIs with APIkey, Basic, or session auth
  • Health monitor + autorestart on repeated failures
  • Works as a single node or part of a kofn signer group
  • Ephemeral relay for testing and local development (not for production data)

Documentation

Quick Start

Prerequisites

  • Bun runtime (uses Bun APIs like bun:sqlite).
  • FROSTR group/share credentials or add them later via UI/API.

Start Locally (Database mode default)

git clone https://github.com/FROSTR-ORG/igloo-server.git
cd igloo-server && bun install && bun run build
export ADMIN_SECRET=$(openssl rand -hex 32)
bun run start
# http://localhost:8002 → enter ADMIN_SECRET → create admin → Configure tab → add GROUP_CRED + SHARE_CRED

Start Locally (Headless)

export HEADLESS=true
export GROUP_CRED="bfgroup1..." ; export SHARE_CRED="bfshare1..."
export RELAYS='["wss://relay.primal.net","wss://relay.damus.io"]'
export AUTH_ENABLED=false ; export API_KEY=dev-local-key  # /api/env still requires auth
bun run start

Configure & Deploy Fast

Pick a Mode

  • Database (recommended): multiuser, AESencrypted creds, admin onboarding via ADMIN_SECRET, SQLite at ./data/igloo.db (override with DB_PATH).
  • Headless: envonly config, APIfirst, UI disabled. Supports PEER_POLICIES blocks (see docs/PEER_POLICIES.md) and API key auth.

Deployment Options

Umbrel (1.1.0+)

  1. In Umbrel, open the App Store → click the menu (topright) → Community App Stores.
  2. Add https://github.com/frostr-org/igloo-server-store and save, then install Igloo Server.
  3. First launch: Umbrel injects ADMIN_SECRET (its app password) and the package sets SKIP_ADMIN_SECRET_VALIDATION=true, so you go straight to creating the first admin user. Follow the instructions screen, create your admin, then add GROUP_CRED / SHARE_CRED.
  4. If the install reports data-dir permission errors, fix volume ownership once (see Umbrel note in docs/DEPLOY.md) and restart from the Umbrel dashboard.
  5. Updates arrive via the store; start/stop from the Umbrel dashboard.

Docker / Compose

# oneoff
docker build -t igloo-server .
docker run -p 8002:8002 \
  -e NODE_ENV=production -e HOST_NAME=0.0.0.0 \
  -e ADMIN_SECRET=... -e AUTH_ENABLED=true -e RATE_LIMIT_ENABLED=true \
  -v $(pwd)/data:/app/data igloo-server

# compose (see compose.yml)
docker compose up -d --build

Reverse proxy (nginx) and cloud steps are in docs/DEPLOY.md.

Production Checklist

  • NODE_ENV=production, persist /app/data, set strong ADMIN_SECRET (keep set after onboarding).
  • Explicit ALLOWED_ORIGINS (WebSocket Origin checks support @self for “whatever host the user connects through”; host match, port-agnostic), TRUST_PROXY=true behind a proxy; forward WS upgrade headers.
  • Auth on (AUTH_ENABLED=true), rate limit on (RATE_LIMIT_ENABLED=true); optional SESSION_SECRET (autogen if absent).
  • Timeouts: tune FROSTR_SIGN_TIMEOUT or SIGN_TIMEOUT_MS (1000120000ms).

API & Docs

  • Swagger UI: http://localhost:8002/api/docs (selfhosted; run bun run docs:vendor if assets missing).
  • OpenAPI: docs/openapi/openapi.yaml or /api/docs/openapi.{json|yaml}.
  • Auth: API Key, Basic, or session; WS /api/events supports subprotocol hints (apikey.<TOKEN>, bearer.<TOKEN>, session.<ID>).
  • Validate spec: bun run docs:validate.

API Keys

  • Headless: set a single API_KEY in env; HTTP cannot rotate it.
  • Database mode: manage multiple keys via the UI or admin endpoints. Admin APIs accept either Authorization: Bearer <ADMIN_SECRET> or an authenticated admin session.
    • CLI helpers: bun run api:test:get, bun run api:test:ws, bun run api:test:nip (see scripts/api/README.md).

Operations

  • Health monitor: periodic connectivity checks; autorecreate node on repeated failures; status at /api/status.
  • Error circuit breaker: ERROR_CIRCUIT_WINDOW_MS (default 60000), ERROR_CIRCUIT_THRESHOLD (default 10), ERROR_CIRCUIT_EXIT_CODE (default 1).
  • Headless env management requires auth even with AUTH_ENABLED=false (/api/env*).

Security Quick Setup

Production defaults:

AUTH_ENABLED=true
RATE_LIMIT_ENABLED=true
ALLOWED_ORIGINS=https://yourdomain.example
TRUST_PROXY=true
# Provide ADMIN_SECRET; SESSION_SECRET autogenerates if absent

Data directory hardening (example):

chmod 700 ./data
chmod 600 ./data/igloo.db ./data/.session-secret 2>/dev/null || true

See docs/SECURITY.md for hardening and CSP details.

Troubleshooting

  • UI assets missing/outdated: run bun run build to regenerate static/app.js and static/styles.css.
  • UI not updating: prod caches assets; rebuild + restart. Dev disables cache.
  • Cred/relay issues: verify bfgroup1... / bfshare1... and reachable relays.
  • More: docs/SECURITY.md (hardening), docs/DEPLOY.md (proxy/cloud), docs/openapi/openapi.yaml (API).

Development

bun run dev   # frontend watch
bun run start # server
bun test      # backend tests

Contributing & License

MIT (see LICENSE). PRs welcome—use Conventional Commits and verify: bun run build, bun test, bun run docs:validate.