aboutsummaryrefslogtreecommitdiff
path: root/extension
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-11-23 12:44:53 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2023-11-23 12:44:53 -0500
commit8434d8b8ecf54c6038a7e9ad7d57084a0865500c (patch)
tree719f5ee3beb9ea99c8d9e3d8e6279092f98e11ac /extension
parente272adcc3f32e31fe7668551453b8e34bc823c3e (diff)
fixed signing and working note encryption!
Diffstat (limited to 'extension')
-rw-r--r--extension/src/entries/contentScript/primary/components/PromptPopup.vue24
-rw-r--r--extension/src/entries/contentScript/primary/main.js4
-rw-r--r--extension/src/entries/contentScript/primary/style.scss11
-rw-r--r--extension/src/entries/contentScript/util.ts96
-rw-r--r--extension/src/entries/nostr-provider.js132
-rw-r--r--extension/src/features/nostr-api.ts2
-rw-r--r--extension/src/features/server-api/index.ts5
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({