Skip to content

feat: configurable Nostr profile (kind 0) from the dashboard - #13

Merged
sovITxyz merged 2 commits into
mainfrom
feat/profile-kind0
Jul 19, 2026
Merged

feat: configurable Nostr profile (kind 0) from the dashboard#13
sovITxyz merged 2 commits into
mainfrom
feat/profile-kind0

Conversation

@sovITxyz

Copy link
Copy Markdown
Owner

Closes #11.

What

Adds a /dashboard/profile page where a signed-in user edits and publishes their Nostr kind 0 profile metadata — name, display name, about, picture, banner, website, NIP-05, and the lud16 lightning address (the NIP-57 zaps prerequisite, #12).

  • src/views/main/profile.tsx + public/js/profile.js — form prefilled from the stored profile; the kind 0 is built and signed entirely client-side through the existing NbreadSigner (NIP-07/46/55/nsec), mirrored to /api/mirror, and broadcast to editorRelays() with NIP-42 AUTH — the exact editor.js publish contract, including the NIP-55/Amber redirect-resume + no-resign retry flow. Blossom uploads are wired to the picture/banner fields.
  • /api/mirror accepts kind 0 (option (a) from the issue): same session gate, ev.pubkey === sess.pubkey tenant isolation, rate limit, and blocked check; mirrorEvent already routed kind 0 to upsertProfile. The MAX_POSTS_PER_PUBKEY cap stays 30023-only — kind 0 is a single replaceable slot and cannot grow storage.
  • Migration 0006: profiles.lud16 + parsing in upsertProfile (open question 4 → column, since zaps must read it on the render path).
  • Replaceable semantics: the page passes the stored created_at as prevCreatedAt; the client publishes with max(now, prev+1) so an edit always wins the (pubkey, 0, '') slot.
  • Round-trip fidelity: unknown kind-0 content keys are preserved through config.extra and merged back on publish, so a save never erases custom metadata written by other clients. Prefill is faithful (no trim/caps — truncation would rewrite the canonical profile network-wide); caps still bound the D1 columns and form typing.
  • NIP-24 deprecated aliases (displayName/username) prefill their canonical field when it's absent and are dropped from the republish, per NIP-24.
  • nip05 suggestion: <handle>@nbread.lol pre-fills only a first profile (never resurrects a deliberately removed nip05); shown as a hint otherwise. The settings-About-overrides-profile-bio relationship is surfaced next to the About field.

Security

  • Apex-only (APEX_CSP already allows self scripts, wss:, Blossom hosts); blog pages stay JS-free.
  • No server-side key handling — the server only ever sees the signed event.
  • All prefill values are hostile-by-assumption relay data: attribute/text escaping via hono/jsx, config JSON <-escaped in a non-executable script tag, __proto__ content keys kept as data via null-prototype objects on both sides.
  • An adversarial multi-agent review (5 dimensions × 3-skeptic verification) ran over the diff; all four confirmed findings are fixed in the second commit.

Tests

873 passing (14 new): page auth/prefill/XSS, kind-0 mirror accept + cross-key 403 + newest-wins + lud16 caps, storedProfileContent fidelity/alias/__proto__ cases, and the editor kind-guard update.

sovITxyz added 2 commits July 19, 2026 06:55
- /dashboard/profile: edit name, display_name, about, picture, banner,
  website, nip05, lud16, lud06; prefilled from the stored kind 0 (columns +
  raw content JSON), unknown content keys preserved through config.extra
- public/js/profile.js: build + sign client-side via NbreadSigner, mirror to
  /api/mirror, NIP-42 broadcast to editorRelays, NIP-55 redirect/resume,
  Blossom uploads for picture/banner
- /api/mirror now accepts kind 0 (own-key only; same rate limit + blocked
  gates; single replaceable slot so no storage growth)
- profiles.lud16 column (migration 0006) + upsertProfile parsing — the
  NIP-57 zaps prerequisite
- suggested nip05 <handle>@nbread.lol; surfaced the settings-about override
… + __proto__ edge cases

Adversarial review of the #11 diff confirmed four defects, all fixed:

- NIP-24 deprecated aliases (displayName/username) no longer ride along in
  extra where they would fight every edit of the canonical field; they
  prefill the canonical field when it is absent and are dropped from the
  republish (NIP-24: ignored/removed when found in the wild)
- prefill is now faithful (no trim/caps): republishing must not silently
  truncate over-cap values published elsewhere; caps still bound the D1
  columns and the form maxlength still caps typing
- the suggested nip05 only prefills a FIRST profile (prevCreatedAt null),
  so a deliberately removed nip05 is not resurrected by an unrelated save
- extra (server) and the metadata object (client) are null-prototype, so a
  literal "__proto__" content key survives as data instead of vanishing
  into the inherited setter
@sovITxyz
sovITxyz merged commit 5a68646 into main Jul 19, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Configurable Nostr profile (kind 0): edit & publish metadata from the dashboard

1 participant