feat: show Nostr npub alongside DID in onboarding

OnboardingDid.vue now fetches node.nostr-pubkey after DID is
retrieved and displays it with a copy button. Both identities
are cached in localStorage. Added missing copyNpub function.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-03-12 22:50:35 +00:00
parent d6bbe2c980
commit fa64b4302e
2 changed files with 45 additions and 4 deletions

View File

@ -474,7 +474,7 @@
- [x] **IDENT-01** — Auto-generate Nostr keypair during identity creation. In `core/archipelago/src/identity_manager.rs` `create()` method, after generating the Ed25519 keypair, immediately call `create_nostr_key()` on the same identity so every identity gets both Ed25519 (DID) and secp256k1 (Nostr) keys from creation. Update the `IdentityInfo` struct returned by `identity.create` and `identity.list` RPC to always include `nostr_pubkey` (hex) and `nostr_npub` (bech32) fields when present. **Acceptance**: Call `identity.create`, then `identity.get` — response includes both `did` and `nostr_npub`. Deploy and verify.
- [ ] **IDENT-02** — Update onboarding to show DID + npub. In `neode-ui/src/views/OnboardingDid.vue`, after fetching the node DID, also fetch `node.nostr-pubkey` (already exists as RPC endpoint). Display both: "Your DID: did:key:z..." and "Your Nostr ID: npub1..." with copy buttons for each. Add a brief explanation: DID for Web5/federation, npub for Nostr apps. Store `nostr_npub` in localStorage alongside `neode_did`. **Acceptance**: Fresh onboarding flow shows both DID and npub on the identity screen. Deploy and verify at http://192.168.1.228.
- [x] **IDENT-02** — Update onboarding to show DID + npub. In `neode-ui/src/views/OnboardingDid.vue`, after fetching the node DID, also fetch `node.nostr-pubkey` (already exists as RPC endpoint). Display both: "Your DID: did:key:z..." and "Your Nostr ID: npub1..." with copy buttons for each. Add a brief explanation: DID for Web5/federation, npub for Nostr apps. Store `nostr_npub` in localStorage alongside `neode_did`. **Acceptance**: Fresh onboarding flow shows both DID and npub on the identity screen. Deploy and verify at http://192.168.1.228.
- [ ] **IDENT-03** — Wire real signature verification in onboarding. In `neode-ui/src/views/OnboardingVerify.vue`, replace `generateMockSignature()` with a real call to `rpcClient.signChallenge(challenge)`. Generate a random challenge string, send it to the backend, display the real Ed25519 signature. Add a "Verify" button that calls `identity.verify` with the DID, challenge, and signature to prove the node controls its keys. Show green checkmark on success. **Acceptance**: Onboarding verify step shows real cryptographic signature and verification succeeds. Deploy and verify.

View File

@ -69,9 +69,30 @@
</svg>
</button>
</div>
<p class="text-base text-white/60">
This is your sovereign digital identity. It proves you are you, without any company in the middle.
</p>
<p class="text-xs text-white/50 mb-3">For Web5, federation, and verifiable credentials</p>
</div>
<!-- Nostr ID -->
<div v-if="nostrNpub" class="text-left mt-4">
<h3 class="text-sm font-semibold text-white/80 mb-2 uppercase tracking-wide">Your Nostr ID</h3>
<div class="bg-black/40 rounded-lg p-4 mb-3 backdrop-blur-sm border border-white/10 flex items-start gap-3">
<p class="text-white/95 font-mono text-sm break-all leading-relaxed flex-1">
{{ nostrNpub }}
</p>
<button
@click="copyNpub"
class="shrink-0 p-1.5 rounded hover:bg-white/10 transition-colors text-white/50 hover:text-white/90"
:title="npubCopied ? 'Copied!' : 'Copy npub'"
>
<svg v-if="!npubCopied" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
<svg v-else class="w-4 h-4 text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
</svg>
</button>
</div>
<p class="text-xs text-white/50">For Nostr social apps and NIP-07 signing</p>
</div>
</div>
</div>
@ -104,10 +125,12 @@ import { rpcClient } from '@/api/rpc-client'
const router = useRouter()
const generatedDid = ref<string>('')
const nostrNpub = ref<string>('')
const isGenerating = ref(false)
const waitingForServer = ref(false)
const autoAdvancing = ref(false)
const didCopied = ref(false)
const npubCopied = ref(false)
const elapsedSeconds = ref(0)
const elapsedDisplay = ref('0:00')
let retryTimer: ReturnType<typeof setTimeout> | null = null
@ -147,6 +170,15 @@ async function fetchDid() {
storeDidState(did, pubkey)
isGenerating.value = false
waitingForServer.value = false
// Fetch Nostr npub in parallel (non-blocking)
rpcClient.getNostrPubkey().then(({ nostr_npub }) => {
if (nostr_npub) {
nostrNpub.value = nostr_npub
localStorage.setItem('neode_nostr_npub', nostr_npub)
}
}).catch(() => { /* Nostr key may not exist yet */ })
autoAdvanceAfterDelay()
} catch {
isGenerating.value = false
@ -167,6 +199,8 @@ function autoAdvanceAfterDelay() {
onMounted(() => {
const cached = localStorage.getItem('neode_did')
const cachedNpub = localStorage.getItem('neode_nostr_npub')
if (cachedNpub) nostrNpub.value = cachedNpub
if (cached && !cached.includes('...')) {
generatedDid.value = cached
} else {
@ -194,6 +228,13 @@ function copyDid() {
didCopied.value = true
setTimeout(() => { didCopied.value = false }, 2000)
}
function copyNpub() {
if (!nostrNpub.value) return
navigator.clipboard.writeText(nostrNpub.value).catch(() => {})
npubCopied.value = true
setTimeout(() => { npubCopied.value = false }, 2000)
}
</script>
<style scoped>