allow signing manually with an external key.

This commit is contained in:
fiatjaf 2022-01-12 22:35:12 -03:00
parent 46ab2566b0
commit 161941a9c5
10 changed files with 89 additions and 53 deletions

View File

@ -15,7 +15,7 @@
"core-js": "^3.6.5", "core-js": "^3.6.5",
"identicon.js": "^2.3.3", "identicon.js": "^2.3.3",
"markdown-it": "^12.3.0", "markdown-it": "^12.3.0",
"nostr-tools": "^0.18.0", "nostr-tools": "^0.19.0",
"pouchdb-adapter-idb": "6", "pouchdb-adapter-idb": "6",
"pouchdb-core": "6", "pouchdb-core": "6",
"pouchdb-mapreduce": "6", "pouchdb-mapreduce": "6",

View File

@ -28,7 +28,6 @@
unelevated unelevated
type="submit" type="submit"
color="primary" color="primary"
:disable="!$store.state.keys.priv"
/> />
</div> </div>
</q-form> </q-form>
@ -48,9 +47,9 @@ export default {
} }
}, },
methods: { methods: {
sendPost() { async sendPost() {
this.$store.dispatch('sendPost', {message: this.text}) let ok = await this.$store.dispatch('sendPost', {message: this.text})
this.text = '' if (ok) this.text = ''
} }
} }
} }

View File

@ -10,14 +10,7 @@
</q-input> </q-input>
<div class="flex justify-end mt-2"> <div class="flex justify-end mt-2">
<q-btn <q-btn label="Reply" rounded unelevated type="submit" color="primary" />
:disable="!$store.state.keys.priv"
label="Reply"
rounded
unelevated
type="submit"
color="primary"
/>
</div> </div>
</q-form> </q-form>
</template> </template>
@ -39,7 +32,7 @@ export default {
}, },
methods: { methods: {
sendReply() { async sendReply() {
// build tags // build tags
let tags = [] let tags = []
@ -69,11 +62,12 @@ export default {
// remove ourselves // remove ourselves
tags = tags.filter(([_, v]) => v !== this.$store.state.keys.pub) tags = tags.filter(([_, v]) => v !== this.$store.state.keys.pub)
this.$store.dispatch('sendPost', { let ok = await this.$store.dispatch('sendPost', {
message: this.text, message: this.text,
tags tags
}) })
this.text = ''
if (ok) this.text = ''
} }
} }
} }

View File

@ -221,22 +221,21 @@
<div class="text-lg text-bold tracking-wide leading-relaxed py-2"> <div class="text-lg text-bold tracking-wide leading-relaxed py-2">
Initial Key Setup Initial Key Setup
</div> </div>
<p> <div class="mb-2">
Type your mnemonic seed from a previous Nostr account or generate a Type your private key from a previous Nostr account or generate a
new one. new one.
</p> </div>
<p> <div>
You can also type a raw private key or just a public key for a You can also type just a public key and later sign events manually
watch-only setup. or using a Nostr-capable browser extension.
</p> </div>
</q-card-section>
<q-card-section>
<q-form @submit="proceed"> <q-form @submit="proceed">
<q-input <q-input
v-model="key" v-model="key"
autogrow autogrow
autofocus autofocus
label="BIP39 Seed Words, private key or public key" label="Private key or public key"
class="text-lg" class="text-lg"
/> />
<q-toggle <q-toggle

View File

@ -103,23 +103,17 @@
<div class="text-lg text-bold tracking-wide leading-relaxed py-2"> <div class="text-lg text-bold tracking-wide leading-relaxed py-2">
Your keys <q-icon name="vpn_key" /> Your keys <q-icon name="vpn_key" />
</div> </div>
<p> <p v-if="$store.state.keys.priv">
Make sure you back up your private key! <br /> Make sure you back up your private key!
<small
>Posts are published using your private key. Others can see your
posts or follow you using only your public key.</small
>
</p> </p>
<p v-else>Your private key is not here!</p>
<div class="mt-1 text-xs">
Posts are published using your private key. Others can see your
posts or follow you using only your public key.
</div>
</q-card-section> </q-card-section>
<q-card-section> <q-card-section>
<p>Seed Words:</p>
<q-input
v-model="$store.state.keys.mnemonic"
class="mb-2"
readonly
filled
/>
<p>Private Key:</p> <p>Private Key:</p>
<q-input <q-input
v-model="$store.state.keys.priv" v-model="$store.state.keys.priv"

View File

@ -1,5 +1,36 @@
import {relayPool} from 'nostr-tools' import {relayPool} from 'nostr-tools'
import {Dialog} from 'quasar'
export const pool = relayPool() export const pool = relayPool()
pool.setPolicy('randomChoice', 3) pool.setPolicy('randomChoice', 3)
// this will try to sign either with window.nostr or using a manual prompt
export async function signAsynchronously(event) {
if (window.nostr) {
return window.nostr.signEvent(event)
} else {
return new Promise((resolve, reject) => {
Dialog.create({
class: 'px-6 py-1 overflow-hidden',
title: 'Sign this event manually',
message: `<pre class="font-mono">${JSON.stringify(
event,
null,
2
)}</pre>`,
html: true,
prompt: {
model: '',
type: 'text',
isValid: val => !!val.toLowerCase().match(/^[a-z0-9]{128}$/),
attrs: {autocomplete: 'off'},
label: 'Paste the signature here (as hex)'
}
})
.onOk(resolve)
.onCancel(() => reject('Canceled.'))
.onDismiss(() => reject('Canceled.'))
})
}
}

View File

@ -1,7 +1,7 @@
import {encrypt} from 'nostr-tools/nip04' import {encrypt} from 'nostr-tools/nip04'
import {Notify, LocalStorage} from 'quasar' import {Notify, LocalStorage} from 'quasar'
import {pool} from '../pool' import {pool, signAsynchronously} from '../pool'
import {dbSave, dbGetProfile, dbGetContactList} from '../db' import {dbSave, dbGetProfile, dbGetContactList} from '../db'
export function initKeys(store, keys) { export function initKeys(store, keys) {
@ -19,9 +19,11 @@ export async function launch(store) {
store.commit('haveReadNotifications') store.commit('haveReadNotifications')
} }
// now we already have a key // if we have already have a private key
if (store.state.keys.priv) { if (store.state.keys.priv) {
pool.setPrivateKey(store.state.keys.priv) pool.setPrivateKey(store.state.keys.priv)
} else {
pool.registerSigningFunction(signAsynchronously)
} }
// translate localStorage into a kind3 event -- or load relays and following from event // translate localStorage into a kind3 event -- or load relays and following from event
@ -138,15 +140,31 @@ export function restartMainSubscription(store) {
export async function sendPost(store, {message, tags = [], kind = 1}) { export async function sendPost(store, {message, tags = [], kind = 1}) {
if (message.length === 0) return if (message.length === 0) return
let event = await pool.publish({ let event
try {
event = await pool.publish({
pubkey: store.state.keys.pub, pubkey: store.state.keys.pub,
created_at: Math.floor(Date.now() / 1000), created_at: Math.floor(Date.now() / 1000),
kind, kind,
tags, tags,
content: message content: message
}) })
} catch (err) {
Notify.create({
message: `Did not publish: ${err}`,
color: 'red'
})
return
}
if (!event) {
// aborted
return
}
store.dispatch('addEvent', {event}) store.dispatch('addEvent', {event})
return true
} }
export async function setMetadata(store, metadata) { export async function setMetadata(store, metadata) {

View File

@ -20,7 +20,7 @@ export function setKeys(state, {mnemonic, priv, pub} = {}) {
pub = getPublicKey(priv) pub = getPublicKey(priv)
} }
state.keys = {mnemonic, priv, pub} state.keys = {priv, pub}
} }
export function setRelays(state, relays) { export function setRelays(state, relays) {

View File

@ -3,6 +3,7 @@ import {LocalStorage} from 'quasar'
export default function () { export default function () {
return { return {
keys: LocalStorage.getItem('keys') || {pub: '00'}, // { mnemonic, priv, pub } keys: LocalStorage.getItem('keys') || {pub: '00'}, // { mnemonic, priv, pub }
relays: { relays: {
// 'wss://nostr.rocks': {read: true, write: true}, // 'wss://nostr.rocks': {read: true, write: true},
'wss://relayer.fiatjaf.com': {read: true, write: true}, 'wss://relayer.fiatjaf.com': {read: true, write: true},

View File

@ -4769,10 +4769,10 @@ normalize-url@^6.0.1:
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
nostr-tools@^0.18.0: nostr-tools@^0.19.0:
version "0.18.0" version "0.19.0"
resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-0.18.0.tgz#d5c76ecd2ea6728a772de5daa5ab5d8110f3425c" resolved "https://registry.yarnpkg.com/nostr-tools/-/nostr-tools-0.19.0.tgz#3e086f4a1715607d8917912f56a16866a115efec"
integrity sha512-NOAC7JhNrAtFnEsr4vEG3PzSzhAcChzXr0SCcfrlFbXcwIFKHcrBGZ4K6qmhJPGpVvhuXwquixG3KlfMIoqc5g== integrity sha512-kn7+gy+Ncrf7WkIN/OTuNu/EZtfK2hVLQZw9CQOVcngeF5nlqMRVrgMQBo2CkrkFFV5G5tS71lH4imtiklh13g==
dependencies: dependencies:
"@noble/hashes" "^0.5.7" "@noble/hashes" "^0.5.7"
"@noble/secp256k1" "^1.3.0" "@noble/secp256k1" "^1.3.0"