diff options
author | vnugent <public@vaughnnugent.com> | 2023-11-23 12:44:53 -0500 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2023-11-23 12:44:53 -0500 |
commit | 8434d8b8ecf54c6038a7e9ad7d57084a0865500c (patch) | |
tree | 719f5ee3beb9ea99c8d9e3d8e6279092f98e11ac /extension/src | |
parent | e272adcc3f32e31fe7668551453b8e34bc823c3e (diff) |
fixed signing and working note encryption!
Diffstat (limited to 'extension/src')
-rw-r--r-- | extension/src/entries/contentScript/primary/components/PromptPopup.vue | 24 | ||||
-rw-r--r-- | extension/src/entries/contentScript/primary/main.js | 4 | ||||
-rw-r--r-- | extension/src/entries/contentScript/primary/style.scss | 11 | ||||
-rw-r--r-- | extension/src/entries/contentScript/util.ts | 96 | ||||
-rw-r--r-- | extension/src/entries/nostr-provider.js | 132 | ||||
-rw-r--r-- | extension/src/features/nostr-api.ts | 2 | ||||
-rw-r--r-- | extension/src/features/server-api/index.ts | 5 |
7 files changed, 149 insertions, 125 deletions
diff --git a/extension/src/entries/contentScript/primary/components/PromptPopup.vue b/extension/src/entries/contentScript/primary/components/PromptPopup.vue index 381f7b3..d019b5d 100644 --- a/extension/src/entries/contentScript/primary/components/PromptPopup.vue +++ b/extension/src/entries/contentScript/primary/components/PromptPopup.vue @@ -1,12 +1,14 @@ <template> <div v-show="isOpen" id="nvault-ext-prompt" :class="{'dark': darkMode }"> - <div class="absolute top-0 bottom-0 left-0 right-0 text-white" style="z-index:9147483647 !important" > + <div class="fixed top-0 bottom-0 left-0 right-0 text-white" style="z-index:9147483647 !important" > + <div class="fixed inset-0 left-0 w-full h-full bg-black/50" @click.self="close" /> - <div class="relative w-full max-w-[28rem] mx-auto mt-36 mb-auto" ref="prompt"> - <div class="w-full p-5 bg-white border rounded-lg shadow-lg dark:bg-dark-900 dark:border-dark-500"> - <div v-if="loggedIn" class="text-gray-800 dark:text-gray-200"> + <div class="relative w-full max-w-[28rem] mx-auto mt-36 mb-auto" ref="prompt"> + <div class="w-full p-5 text-gray-800 bg-white border rounded-lg shadow-lg dark:bg-dark-900 dark:border-dark-500 dark:text-gray-200"> + + <div v-if="loggedIn" class=""> <div class="flex flex-row justify-between"> <div class=""> <div class="text-lg font-bold"> @@ -21,7 +23,7 @@ </span> </div> </div> - <div class=""> + <div class=""> <Popover class="relative"> <PopoverButton class=""> <fa-icon icon="circle-info" class="w-4 h-4" /> @@ -55,11 +57,19 @@ </div> </div> </div> + <div v-else class=""> - <h3 class="">Log in!</h3> + <div class=""> - You must log in before you can allow access. + <div class="text-lg font-bold"> + Log in + </div> </div> + + <div class="py-3 text-sm text-center"> + You must log in before you can allow access. + </div> + <div class="flex justify-end gap-2 mt-4"> <div> <button class="rounded btn xs" @click="close">Close</button> diff --git a/extension/src/entries/contentScript/primary/main.js b/extension/src/entries/contentScript/primary/main.js index dbfa07b..16cc6fc 100644 --- a/extension/src/entries/contentScript/primary/main.js +++ b/extension/src/entries/contentScript/primary/main.js @@ -15,8 +15,10 @@ import { runtime } from "webextension-polyfill"; import { createApp } from "vue"; +import { defer } from "lodash"; import { createPinia } from 'pinia'; import { useBackgroundPiniaPlugin, identityPlugin, originPlugin } from '../../store' +import { onLoad } from "../util"; import renderContent from "../renderContent"; import App from "./App.vue"; import Notification from '@kyvg/vue3-notification' @@ -25,8 +27,6 @@ import '@fontsource/noto-sans-masaram-gondi' //We need inline styles to inject into the shadow dom import tw from "~/assets/all.scss?inline"; import localStyle from './style.scss?inline' -import { onLoad } from "../util"; -import { defer } from "lodash"; /* FONT AWESOME CONFIG */ import { library } from '@fortawesome/fontawesome-svg-core' diff --git a/extension/src/entries/contentScript/primary/style.scss b/extension/src/entries/contentScript/primary/style.scss index bcdbbfd..e2c391d 100644 --- a/extension/src/entries/contentScript/primary/style.scss +++ b/extension/src/entries/contentScript/primary/style.scss @@ -1,15 +1,22 @@ #injected-root{ + + /* Reset all base fonts so host does not leak into shadow root */ + @apply font-sans text-base; .toaster{ @apply fixed top-10 right-2 z-[999999999] max-w-[250px]; } .vue-notification-template.vue-notification.error{ - @apply bg-red-500 text-white px-4 py-2; + @apply bg-red-500 text-white px-3 py-2; .notification-title{ - + @apply text-base font-bold; + } + + .notification-content{ + @apply text-sm; } } }
\ No newline at end of file diff --git a/extension/src/entries/contentScript/util.ts b/extension/src/entries/contentScript/util.ts index aa6aac3..09b515a 100644 --- a/extension/src/entries/contentScript/util.ts +++ b/extension/src/entries/contentScript/util.ts @@ -49,17 +49,16 @@ const registerWindowHandler = (store: Store, extName: string) => { const { selectedKey } = storeToRefs(store) const { nostr } = store.plugins; - //Only listen for messages if injection is enabled - window.addEventListener('message', async ({ source, data, origin }) => { + const onAsyncCall = async ({ source, data, origin } : MessageEvent<any>) => { //clean any junk/methods with json parse/stringify data = JSON.parse(JSON.stringify(data)) - const invokePrompt = async (cb:(...args:any) => Promise<any>) => { + const requestPermission = async (cb: (...args: any) => Promise<any>) => { //await propmt for user to allow the request const allow = await _promptHandler.invoke({ ...data, origin }) //send request to background - return response = allow ? await cb() : { error: 'User denied permission' } + return allow ? await cb() : { error: 'User denied permission' } } //Confirm the message format is correct @@ -71,45 +70,66 @@ const registerWindowHandler = (store: Store, extName: string) => { return } - // pass on to background - var response; - await apiCall(async () => { - switch (data.type) { - case 'getPublicKey': - return invokePrompt(async () => selectedKey.value?.PublicKey) - case 'signEvent': - return invokePrompt(async () => { - const event = data.payload.event - - //Set key id to selected key - event.KeyId = selectedKey.value!.Id - event.pubkey = selectedKey.value!.PublicKey; - - return await nostr.signEvent(event); - }) - //Check the public key against selected key - case 'getRelays': - return invokePrompt(async () => await nostr.getRelays()) - case 'nip04.encrypt': - return invokePrompt(async () => await nostr.nip04Encrypt({ + switch (data.type) { + case 'getPublicKey': + return requestPermission(async () => selectedKey.value?.PublicKey); + case 'signEvent': + return requestPermission(async () => { + const event = data.payload.event + return await nostr.signEvent({ + ...event, + KeyId: selectedKey.value!.Id, + pubkey: selectedKey.value!.PublicKey + }); + }) + //Check the public key against selected key + case 'getRelays': + return requestPermission(async () => await nostr.getRelays()) + case 'nip04.encrypt': + return requestPermission(async () => { + return await nostr.nip04Encrypt({ pubkey: data.payload.peer, content: data.payload.plaintext, - //Set selected key id as our desired decryption key - KeyId: selectedKey.value!.Id - })) - case 'nip04.decrypt': - return invokePrompt(async () => await nostr.nip04Decrypt({ - pubkey: data.payload.peer, - content: data.payload.ciphertext, - //Set selected key id as our desired decryption key + //Set selected key id as our desired encryption key KeyId: selectedKey.value!.Id - })) - default: - throw new Error('Unknown nostr message type') + }) + }) + case 'nip04.decrypt': + return requestPermission(async () => { + const result = await apiCall(async () => { + return await nostr.nip04Decrypt({ + pubkey: data.payload.peer, + content: data.payload.ciphertext, + //Set selected key id as our desired decryption key + KeyId: selectedKey.value!.Id + }) + }) + return isNil(result) ? 'Error: Failed to decrypt message' : result + }) + } + } + + //Only listen for messages if injection is enabled + window.addEventListener('message', async (args) => { + + //Wrap in apicall to display errors + await apiCall(async () => { + let response; + + try{ + response = await onAsyncCall(args) + } + catch(e: any){ + const error = JSON.stringify(e) + response = { error } + //rethrow for logging + throw e; + } + finally{ + // always return response message, must have the same id as the request + window.postMessage({ ext: extName, id: args.data.id, response }, origin); } }) - // return response message, must have the same id as the request - window.postMessage({ ext: extName, id: data.id, response }, origin); }); } diff --git a/extension/src/entries/nostr-provider.js b/extension/src/entries/nostr-provider.js index 7d8f3c5..9280b4d 100644 --- a/extension/src/entries/nostr-provider.js +++ b/extension/src/entries/nostr-provider.js @@ -13,78 +13,62 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. -const waiting = new Map(); - -const ext = '@vnuge/nvault-extension' - -const debugLog = (...args) => { - console.log(`[${ext}]`, ...args) -} - -const sendMessage = (type, payload) => new Promise((resolve, reject) => { - const id = Math.random().toString(36); - waiting.set(id, { resolve, reject }); - window.postMessage({ type, payload, id, ext }, '*'); -}); - -/** - * Listen for messages from the content script - */ -window.addEventListener('message', ({ data }) => { - - //Confirm the message format is correct - if (!data || !data.response || data.ext !== ext || !waiting.get(data.id)){ +(() => { + const ext = '@vnuge/nvault-extension' + const debugLog = (...args) => console.log(`[${ext}]`, ...args) + const sendMessage = (type, payload) => new Promise((resolve, reject) => { + const id = Math.random().toString(36); + waiting.set(id, { resolve, reject }); + window.postMessage({ type, payload, id, ext }, '*'); + }); + + const waiting = new Map(); + + /** + * Listen for messages from the content script + */ + window.addEventListener('message', ({ data }) => { + + //Confirm the message format is correct + if (!data || !data.response || data.ext !== ext || !waiting.get(data.id)) { return; - } - - debugLog(data) - - //Explode now valid - const { response, id } = data; - - const { resolve, reject } = waiting.get(id); - - if (response.error) { - - //Construct an error object from the resopnse message - const errorMessage = response.error.message ?? response.error; - - let error = new Error(`${ext}: ${errorMessage}`); - error.stack = response.error.stack; - - //Reject the promise as error - reject(error); - - } else { - //Resolve the promise as success - resolve(response); - } - - //Remove the waiter from the list - waiting.delete(id) -}); - - -//Expose the Nostr API to the window object -window.nostr = { - - //Redirect calls to the background script - getPublicKey(){ - return sendMessage('getPublicKey', {}) - } , - - async signEvent(event){ - const signed = await sendMessage('signEvent', { event }) - debugLog("Signed event", signed); - return signed - }, - - getRelays(){ - return sendMessage('getRelays', {}) - }, - - nip04: { - encrypt: (peer, plaintext) => sendMessage('nip04.encrypt', { peer, plaintext }), - decrypt: (peer, ciphertext) => sendMessage('nip04.decrypt', { peer, ciphertext }), - }, -};
\ No newline at end of file + } + + debugLog(data) + + //Explode now valid + const { response, id } = data; + const { resolve, reject } = waiting.get(id); + + if (response.error) { + //Reject the promise as error + reject(response.error); + + } else { + //Resolve the promise as success + resolve(response); + } + + //Remove the waiter from the list + waiting.delete(id) + }); + + //Expose the Nostr API to the window object + window.nostr = { + + //Redirect calls to the background script + getPublicKey: () => sendMessage('getPublicKey', {}), + getRelays: () => sendMessage('getRelays', {}), + + async signEvent(event) { + const signed = await sendMessage('signEvent', { event }) + debugLog("Signed event", signed); + return signed + }, + + nip04: { + encrypt: (peer, plaintext) => sendMessage('nip04.encrypt', { peer, plaintext }), + decrypt: (peer, ciphertext) => sendMessage('nip04.decrypt', { peer, ciphertext }), + }, + }; +})()
\ No newline at end of file diff --git a/extension/src/features/nostr-api.ts b/extension/src/features/nostr-api.ts index 81ef0c6..046ccea 100644 --- a/extension/src/features/nostr-api.ts +++ b/extension/src/features/nostr-api.ts @@ -13,12 +13,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. +import { cloneDeep } from "lodash"; import { Endpoints } from "./server-api"; import { type FeatureApi, type BgRuntime, type IFeatureExport, optionsOnly, exportForegroundApi } from "./framework"; import { type AppSettings } from "./settings"; import { useTagFilter } from "./tagfilter-api"; import type { NostrRelay, EncryptionRequest, NostrEvent } from './types'; -import { cloneDeep } from "lodash"; /** diff --git a/extension/src/features/server-api/index.ts b/extension/src/features/server-api/index.ts index fd8e65e..cd67242 100644 --- a/extension/src/features/server-api/index.ts +++ b/extension/src/features/server-api/index.ts @@ -133,7 +133,10 @@ export const useServerApi = (nostrUrl: Ref<string>, accUrl: Ref<string>): Server method:'POST', path: () => `${get(nostrUrl)}?type=encrypt`, onRequest: (data: EncryptionRequest) => Promise.resolve(data), - onResponse: async (response: WebMessage<string>) => response.getResultOrThrow() + onResponse: async (response: WebMessage<{ ciphertext:string, iv:string }>) =>{ + const { ciphertext, iv } = response.getResultOrThrow() + return `${ciphertext}?iv=${iv}` + } }) registerEndpoint({ |