Move over nip05 and settings

This commit is contained in:
Jonathan Staab 2023-09-05 16:21:35 -07:00
parent 6af41363cd
commit 2597a18411
11 changed files with 196 additions and 40 deletions

View File

@ -1,4 +1,5 @@
import "./nip02"
import "./nip05"
import "./nip65"
import "./alerts"

View File

@ -0,0 +1,28 @@
import {tryFunc, Fetch} from "hurdak"
import {tryJson} from "src/util/misc"
import {people} from "src/engine2/state"
import {dufflepud} from "src/engine2/queries"
import {projections, updateKey} from "src/engine2/projections/core"
projections.addHandler(0, e => {
tryJson(async () => {
const {
kind0: {nip05: address},
} = JSON.parse(e.content)
if (!address) {
return
}
const profile = await tryFunc(() => Fetch.postJson(dufflepud("handle/info"), {handle: address}))
if (profile?.pubkey === e.pubkey) {
updateKey(people.key(e.pubkey), e.created_at, {
handle: {
profile,
address,
},
})
}
})
})

View File

@ -0,0 +1,23 @@
import {tryJson} from "src/util/misc"
import {Tags, appDataKeys} from "src/util/nostr"
import {settings} from "src/engine2/state"
import {getSetting, canSign, nip04, user} from "src/engine2/queries"
import {projections} from "src/engine2/projections/core"
projections.addHandler(30078, e => {
if (
canSign.get() &&
Tags.from(e).getMeta("d") === appDataKeys.USER_SETTINGS &&
e.created_at > getSetting("updated_at")
) {
tryJson(async () => {
const updates = await nip04.get().decryptAsUser(e.content, user.get().pubkey)
settings.update($settings => ({
...$settings,
...updates,
updated_at: e.created_at,
}))
})
}
})

View File

@ -1,4 +1,5 @@
export * from "./session"
export * from "./settings"
export * from "./nip02"
export * from "./nip65"
export * from "./alerts"

View File

@ -0,0 +1,5 @@
import {last} from "ramda"
import type {Handle} from "src/engine2/model"
export const displayHandle = (handle: Handle) =>
handle.address.startsWith("_@") ? last(handle.address.split("@")) : handle.address

View File

@ -1,31 +0,0 @@
import {switcherFn} from "hurdak"
import type {KeyState} from "src/engine2/model"
import {encryptFor, decryptFor} from "src/engine2/util/nip44"
export class Crypto {
constructor(readonly user: KeyState) {}
encryptWithKey(event, pk: string, sk: string) {
return encryptFor(sk, pk, JSON.stringify(event))
}
decryptWithKey(event, sk: string) {
return JSON.parse(decryptFor(sk, event.pubkey, event.content))
}
encryptAsUser(event, pk: string) {
const {method, privkey} = this.user
return switcherFn(method, {
privkey: () => this.encryptWithKey(event, pk, privkey),
})
}
decryptAsUser(event) {
const {method, privkey} = this.user
return switcherFn(method, {
privkey: () => this.decryptWithKey(event, privkey),
})
}
}

View File

@ -1,17 +1,23 @@
import {find, defaultTo, whereEq} from "ramda"
import type {KeyState} from "src/engine2/model"
import {derived} from "src/engine2/util/store"
import {pubkey, keys} from "src/engine2/state"
import {prepareNdk, ndkInstances} from "./ndk"
import {Signer} from "./signer"
import {Crypto} from "./crypto"
import {Nip04} from "./nip04"
import {Nip44} from "./nip44"
import {Wrapper} from "./wrapper"
export const stateKey = pubkey.derived(defaultTo("anonymous"))
export const user = derived([pubkey, keys], ([$pubkey, $keys]) => {
return find(whereEq({pubkey: $pubkey}), $keys)
return find(whereEq({pubkey: $pubkey}), $keys) as KeyState | null
})
export const canSign = user.derived(({method}) =>
["bunker", "privkey", "extension"].includes(method)
)
export const ndk = derived([user, ndkInstances], ([$user, $instances]) => {
if (!$user?.bunkerToken) {
return null
@ -24,11 +30,13 @@ export const ndk = derived([user, ndkInstances], ([$user, $instances]) => {
return $instances.get($user.pubkey)
})
export const crypto = derived([user], ([$user]) => new Crypto($user))
export const nip04 = derived([user, ndk], ([$user, $ndk]) => new Nip04($user, $ndk))
export const nip44 = derived([user], ([$user]) => new Nip44($user))
export const signer = derived([user, ndk], ([$user, $ndk]) => new Signer($user, $ndk))
export const wrapper = derived(
[user, crypto, signer],
([$user, $crypto, $signer]) => new Wrapper($user, $crypto, $signer)
[user, nip44, signer],
([$user, $nip44, $signer]) => new Wrapper($user, $nip44, $signer)
)

View File

@ -0,0 +1,63 @@
import {nip04} from "nostr-tools"
import type NDK from "@nostr-dev-kit/ndk"
import {switcherFn, sleep, tryFunc} from "hurdak"
import {withExtension} from "src/engine2/util/nip07"
import type {KeyState} from "src/engine2/model"
export class Nip04 {
constructor(readonly user: KeyState | null, readonly ndk: NDK | null) {}
async encrypt(message: string, pk: string, sk: string) {
return nip04.encrypt(sk, pk, message)
}
async decrypt(message: string, pk: string, sk: string) {
return tryFunc(() => nip04.decrypt(sk, pk, message)) || `<Failed to decrypt message>`
}
async encryptAsUser(message: string, pk: string) {
const {method, privkey} = this.user
return switcherFn(method, {
privkey: () => this.encrypt(message, pk, privkey),
extension: () => withExtension(ext => ext.nip04.encrypt(pk, message)),
bunker: async () => {
const user = this.ndk.getUser({hexpubkey: pk})
return this.ndk.signer.encrypt(user, message)
},
})
}
async decryptAsUser(message: string, pk: string) {
const {method, privkey} = this.user
return switcherFn(method, {
privkey: () => this.decrypt(message, pk, privkey),
extension: () =>
withExtension((ext: any) => {
return new Promise(async resolve => {
let result
// Alby gives us a bunch of bogus errors, try multiple times
for (let i = 0; i < 3; i++) {
result = await tryFunc(() => ext.nip04.decrypt(pk, message))
if (result) {
break
}
await sleep(30)
}
resolve(result || `<Failed to decrypt message>`)
})
}),
bunker: async () => {
const user = this.ndk.getUser({hexpubkey: pk})
return this.ndk.signer.decrypt(user, message)
},
})
}
}

View File

@ -0,0 +1,31 @@
import {switcherFn} from "hurdak"
import type {KeyState} from "src/engine2/model"
import {encryptFor, decryptFor} from "src/engine2/util/nip44"
export class Nip44 {
constructor(readonly user: KeyState) {}
encrypt(message: string, pk: string, sk: string) {
return encryptFor(sk, pk, message)
}
decrypt(message: string, pk: string, sk: string) {
return decryptFor(sk, pk, message)
}
encryptAsUser(message: string, pk: string) {
const {method, privkey} = this.user
return switcherFn(method, {
privkey: () => this.encrypt(message, pk, privkey),
})
}
decryptAsUser(message: string, pk: string) {
const {method, privkey} = this.user
return switcherFn(method, {
privkey: () => this.decrypt(message, pk, privkey),
})
}
}

View File

@ -1,9 +1,10 @@
import type {UnsignedEvent} from "nostr-tools"
import {getPublicKey} from "nostr-tools"
import {tryJson} from "src/util/misc"
import {nip59} from "src/engine2/util"
import type {KeyState} from "src/engine2/model"
import type {Signer} from "./signer"
import type {Crypto} from "./crypto"
import type {Nip44} from "./nip44"
export type WrapperParams = {
author?: string
@ -14,7 +15,7 @@ export type WrapperParams = {
}
export class Wrapper {
constructor(readonly user: KeyState, readonly crypto: Crypto, readonly signer: Signer) {}
constructor(readonly user: KeyState, readonly nip44: Nip44, readonly signer: Signer) {}
getAuthorPubkey(sk?: string) {
return sk ? getPublicKey(sk) : this.user.pubkey
@ -29,11 +30,18 @@ export class Wrapper {
}
encrypt(event, pk: string, sk?: string) {
return sk ? this.crypto.encryptWithKey(event, pk, sk) : this.crypto.encryptAsUser(event, pk)
const message = JSON.stringify(event)
return sk ? this.nip44.encrypt(message, pk, sk) : this.nip44.encryptAsUser(message, pk)
}
decrypt(event, sk?: string) {
return sk ? this.crypto.decryptWithKey(event, sk) : this.crypto.decryptAsUser(event)
const {pubkey, content} = event
const message = sk
? this.nip44.decrypt(content, pubkey, sk)
: this.nip44.decryptAsUser(content, pubkey)
return tryJson(() => JSON.parse(message))
}
getSeal(rumor, {author, wrap: {recipient}}: WrapperParams) {

View File

@ -0,0 +1,19 @@
import {settings} from "src/engine2/state"
export const getSetting = k => settings.get()[k]
export const imgproxy = (url: string, {w = 640, h = 1024} = {}) => {
const base = getSetting("imgproxy_url")
if (!url || url.match("gif$")) {
return url
}
try {
return base && url ? `${base}/x/s:${w}:${h}/${btoa(url)}` : url
} catch (e) {
return url
}
}
export const dufflepud = (path: string) => `${getSetting("dufflepud_url")}/${path}`