fix(api): sign invites with stored owner keypair instead of unsigned placeholder
Some checks failed
CI / Tests / 🧪 Test (push) Has been cancelled

Production /join on the broker (from feat 18c) rejects every invite
with invite_bad_signature because the web UI was emitting unsigned
payloads. This fixes that.

createMyMesh now generates ed25519 owner keypair + 32-byte root key
and stores all three on the mesh row. createMyInvite loads them,
signs the canonical invite bytes via crypto_sign_detached, and
emits a fully-signed payload matching what the broker expects:

  payload = {v, mesh_id, mesh_slug, broker_url, expires_at,
             mesh_root_key, role, owner_pubkey, signature}
  canonical = same fields minus signature, "|"-delimited
  signature = ed25519_sign(canonical, mesh.owner_secret_key)
  token = base64url(JSON(payload))   ← stored as invite.token

The base64url(JSON) token IS the DB lookup key — broker's /join
does `WHERE invite.token = <that string>`, then re-verifies the
signature it extracts from the decoded payload.

Also drops the sha256 derivePlaceholderRootKey() helper and the
encodeInviteLink helper, both replaced by inline logic.

backfill extended: the one-off script now populates owner_pubkey
AND owner_secret_key AND root_key together in a single pass. Query
condition is `WHERE any of the three IS NULL`, so running it
post-migration catches every row regardless of partial prior fills.

requires packages/api to depend on libsodium-wrappers + types
(added). 64/64 broker tests still green.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-04 23:12:04 +01:00
parent 1c773be577
commit 759a22e7c0
4 changed files with 177 additions and 58 deletions

View File

@@ -32,6 +32,7 @@
"ai": "catalog:",
"envin": "catalog:",
"hono": "4.10.4",
"libsodium-wrappers": "0.7.15",
"zod": "catalog:"
},
"devDependencies": {
@@ -39,6 +40,7 @@
"@turbostarter/prettier-config": "workspace:*",
"@turbostarter/tsconfig": "workspace:*",
"@turbostarter/vitest-config": "workspace:*",
"@types/libsodium-wrappers": "0.7.14",
"eslint": "catalog:",
"prettier": "catalog:",
"typescript": "catalog:",