From 2597a18411cd9a662888d15c71682cf730ef7a38 Mon Sep 17 00:00:00 2001 From: Jonathan Staab Date: Tue, 5 Sep 2023 16:21:35 -0700 Subject: [PATCH] Move over nip05 and settings --- src/engine2/projections/index.ts | 1 + src/engine2/projections/nip05.ts | 28 ++++++++++++ src/engine2/projections/settings.ts | 23 ++++++++++ src/engine2/queries/index.ts | 1 + src/engine2/queries/nip05.ts | 5 ++ src/engine2/queries/session/crypto.ts | 31 ------------- src/engine2/queries/session/index.ts | 18 ++++++-- src/engine2/queries/session/nip04.ts | 63 ++++++++++++++++++++++++++ src/engine2/queries/session/nip44.ts | 31 +++++++++++++ src/engine2/queries/session/wrapper.ts | 16 +++++-- src/engine2/queries/settings.ts | 19 ++++++++ 11 files changed, 196 insertions(+), 40 deletions(-) create mode 100644 src/engine2/projections/nip05.ts create mode 100644 src/engine2/projections/settings.ts create mode 100644 src/engine2/queries/nip05.ts delete mode 100644 src/engine2/queries/session/crypto.ts create mode 100644 src/engine2/queries/session/nip04.ts create mode 100644 src/engine2/queries/session/nip44.ts create mode 100644 src/engine2/queries/settings.ts diff --git a/src/engine2/projections/index.ts b/src/engine2/projections/index.ts index 0149b90f..e6bbf5b7 100644 --- a/src/engine2/projections/index.ts +++ b/src/engine2/projections/index.ts @@ -1,4 +1,5 @@ import "./nip02" +import "./nip05" import "./nip65" import "./alerts" diff --git a/src/engine2/projections/nip05.ts b/src/engine2/projections/nip05.ts new file mode 100644 index 00000000..f5c38746 --- /dev/null +++ b/src/engine2/projections/nip05.ts @@ -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, + }, + }) + } + }) +}) diff --git a/src/engine2/projections/settings.ts b/src/engine2/projections/settings.ts new file mode 100644 index 00000000..d9d3ff97 --- /dev/null +++ b/src/engine2/projections/settings.ts @@ -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, + })) + }) + } +}) diff --git a/src/engine2/queries/index.ts b/src/engine2/queries/index.ts index fcc418cc..326733ff 100644 --- a/src/engine2/queries/index.ts +++ b/src/engine2/queries/index.ts @@ -1,4 +1,5 @@ export * from "./session" +export * from "./settings" export * from "./nip02" export * from "./nip65" export * from "./alerts" diff --git a/src/engine2/queries/nip05.ts b/src/engine2/queries/nip05.ts new file mode 100644 index 00000000..60d28942 --- /dev/null +++ b/src/engine2/queries/nip05.ts @@ -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 diff --git a/src/engine2/queries/session/crypto.ts b/src/engine2/queries/session/crypto.ts deleted file mode 100644 index 12b24930..00000000 --- a/src/engine2/queries/session/crypto.ts +++ /dev/null @@ -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), - }) - } -} diff --git a/src/engine2/queries/session/index.ts b/src/engine2/queries/session/index.ts index c42a625f..09f7a711 100644 --- a/src/engine2/queries/session/index.ts +++ b/src/engine2/queries/session/index.ts @@ -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) ) diff --git a/src/engine2/queries/session/nip04.ts b/src/engine2/queries/session/nip04.ts new file mode 100644 index 00000000..5b54e06f --- /dev/null +++ b/src/engine2/queries/session/nip04.ts @@ -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)) || `` + } + + 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 || ``) + }) + }), + bunker: async () => { + const user = this.ndk.getUser({hexpubkey: pk}) + + return this.ndk.signer.decrypt(user, message) + }, + }) + } +} diff --git a/src/engine2/queries/session/nip44.ts b/src/engine2/queries/session/nip44.ts new file mode 100644 index 00000000..db2c78f3 --- /dev/null +++ b/src/engine2/queries/session/nip44.ts @@ -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), + }) + } +} diff --git a/src/engine2/queries/session/wrapper.ts b/src/engine2/queries/session/wrapper.ts index edc3cadd..ed9aa57a 100644 --- a/src/engine2/queries/session/wrapper.ts +++ b/src/engine2/queries/session/wrapper.ts @@ -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) { diff --git a/src/engine2/queries/settings.ts b/src/engine2/queries/settings.ts new file mode 100644 index 00000000..8422301c --- /dev/null +++ b/src/engine2/queries/settings.ts @@ -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}`