From abdfa4b68bdb86b75f4a96d3cfd6231544f3fda1 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:29:00 -0500 Subject: [PATCH] Defer NIP-07 encryption permissions --- js/app.js | 14 ++++++--- js/nostr/client.js | 39 +++++++++++++++++------- js/nostr/defaultClient.js | 2 ++ js/nostr/nip07Permissions.js | 6 +++- js/services/hashtagPreferencesService.js | 30 +++++++++++++++--- js/subscriptions.js | 14 +++++++-- js/ui/moreMenuController.js | 2 ++ js/ui/profileModalController.js | 12 ++++++++ js/userBlocks.js | 29 +++++++++++++----- 9 files changed, 117 insertions(+), 31 deletions(-) diff --git a/js/app.js b/js/app.js index affa2227..804dc5a0 100644 --- a/js/app.js +++ b/js/app.js @@ -89,7 +89,7 @@ import { onActiveSignerChanged, } from "./nostrClientRegistry.js"; import { queueSignEvent } from "./nostr/signRequestQueue.js"; -import { DEFAULT_NIP07_PERMISSION_METHODS } from "./nostr/nip07Permissions.js"; +import { DEFAULT_NIP07_CORE_METHODS } from "./nostr/nip07Permissions.js"; import { initViewCounter, subscribeToVideoViewCount, @@ -2027,7 +2027,9 @@ class Application { case "nip04-missing": return "Your Nostr extension must support NIP-04 to manage private block lists."; case "extension-permission-denied": - return "Permission to update your block list was denied by your Nostr extension."; + return "Your Nostr extension denied the encryption permissions needed to update your block list."; + case "extension-encryption-permission-denied": + return "Your Nostr extension denied the encryption permissions needed to update your block list."; case "sign-event-missing": case "signer-missing": return "Connect a Nostr signer that can encrypt and sign updates before managing block lists."; @@ -2058,7 +2060,9 @@ class Application { case "hashtag-preferences-missing-signer": return "Connect a Nostr signer that supports encryption before managing hashtag preferences."; case "hashtag-preferences-extension-denied": - return "Permission to manage hashtag preferences was denied by your signer."; + return "Your signer denied the encryption permissions needed to manage hashtag preferences."; + case "hashtag-preferences-permission-required": + return "Your Nostr extension must grant encryption permissions to read hashtag preferences."; case "hashtag-preferences-no-decryptors": return "Unable to decrypt hashtag preferences with the active signer."; case "hashtag-preferences-decrypt-failed": @@ -2694,7 +2698,9 @@ class Application { } if (signer.type === "extension" && nostrClient.ensureExtensionPermissions) { - const permissionResult = await nostrClient.ensureExtensionPermissions(); + const permissionResult = await nostrClient.ensureExtensionPermissions( + DEFAULT_NIP07_CORE_METHODS, + ); if (!permissionResult?.ok) { userLogger.warn( "[Application] Signer permissions denied while publishing DM relays.", diff --git a/js/nostr/client.js b/js/nostr/client.js index dd1cc1a5..2c964634 100644 --- a/js/nostr/client.js +++ b/js/nostr/client.js @@ -137,6 +137,7 @@ import { resolveDmRelaySelection, } from "../services/dmNostrService.js"; import { + DEFAULT_NIP07_CORE_METHODS, DEFAULT_NIP07_ENCRYPTION_METHODS, DEFAULT_NIP07_PERMISSION_METHODS, NIP07_PRIORITY, @@ -2003,7 +2004,7 @@ export class NostrClient { } const permissionResult = await this.ensureExtensionPermissions( - DEFAULT_NIP07_PERMISSION_METHODS, + DEFAULT_NIP07_CORE_METHODS, ); if (!permissionResult.ok) { const denialMessage = @@ -4813,7 +4814,23 @@ export class NostrClient { activeSigner = getActiveSigner(); } - if (activeSigner) { + let extensionPermissionResult = null; + if ( + activeSigner?.type === "extension" && + typeof this.ensureExtensionPermissions === "function" + ) { + extensionPermissionResult = await this.ensureExtensionPermissions( + DEFAULT_NIP07_ENCRYPTION_METHODS, + ); + if (!extensionPermissionResult?.ok) { + devLogger.warn( + "[nostr] Extension encryption permissions missing for DM decryption.", + extensionPermissionResult?.error, + ); + } + } + + if (activeSigner && extensionPermissionResult?.ok !== false) { const capabilities = resolveSignerCapabilities(activeSigner); if ( capabilities.nip44 && @@ -5260,16 +5277,16 @@ export class NostrClient { if (shouldRequestExtensionPermissions(signer)) { const permissionResult = await this.ensureExtensionPermissions( - DEFAULT_NIP07_PERMISSION_METHODS, + DEFAULT_NIP07_ENCRYPTION_METHODS, ); if (!permissionResult.ok) { userLogger.warn( - "[nostr] Cannot send direct message without extension permissions.", + "[nostr] Cannot send direct message without encryption permissions.", permissionResult.error, ); return { ok: false, - error: "extension-permission-denied", + error: "extension-encryption-permission-denied", details: permissionResult.error, }; } @@ -6539,7 +6556,7 @@ export class NostrClient { if (shouldRequestExtensionPermissions(signer)) { const permissionResult = await this.ensureExtensionPermissions( - DEFAULT_NIP07_PERMISSION_METHODS, + DEFAULT_NIP07_CORE_METHODS, ); if (!permissionResult.ok) { userLogger.warn( @@ -6547,7 +6564,7 @@ export class NostrClient { permissionResult.error, ); const error = new Error( - "The active signer must grant decrypt and sign permissions before editing a video.", + "The active signer must allow signing before editing a video.", ); error.code = "extension-permission-denied"; error.cause = permissionResult.error; @@ -6718,7 +6735,7 @@ export class NostrClient { if (shouldRequestExtensionPermissions(signer)) { const permissionResult = await this.ensureExtensionPermissions( - DEFAULT_NIP07_PERMISSION_METHODS, + DEFAULT_NIP07_CORE_METHODS, ); if (!permissionResult.ok) { userLogger.warn( @@ -6726,7 +6743,7 @@ export class NostrClient { permissionResult.error, ); const error = new Error( - "The active signer must grant decrypt and sign permissions before reverting a video.", + "The active signer must allow signing before reverting a video.", ); error.code = "extension-permission-denied"; error.cause = permissionResult.error; @@ -7026,7 +7043,7 @@ export class NostrClient { if (shouldRequestExtensionPermissions(signer)) { const permissionResult = await this.ensureExtensionPermissions( - DEFAULT_NIP07_PERMISSION_METHODS, + DEFAULT_NIP07_CORE_METHODS, ); if (!permissionResult.ok) { userLogger.warn( @@ -7034,7 +7051,7 @@ export class NostrClient { permissionResult.error, ); const error = new Error( - "The active signer must grant decrypt and sign permissions before deleting a video.", + "The active signer must allow signing before deleting a video.", ); error.code = "extension-permission-denied"; error.cause = permissionResult.error; diff --git a/js/nostr/defaultClient.js b/js/nostr/defaultClient.js index 13363919..dd7cf776 100644 --- a/js/nostr/defaultClient.js +++ b/js/nostr/defaultClient.js @@ -2,6 +2,7 @@ import { NostrClient } from "./client.js"; import { + DEFAULT_NIP07_CORE_METHODS, DEFAULT_NIP07_ENCRYPTION_METHODS, DEFAULT_NIP07_PERMISSION_METHODS, } from "./nip07Permissions.js"; @@ -26,6 +27,7 @@ export function requestDefaultExtensionPermissions( export { NostrClient, + DEFAULT_NIP07_CORE_METHODS, DEFAULT_NIP07_ENCRYPTION_METHODS, DEFAULT_NIP07_PERMISSION_METHODS, }; diff --git a/js/nostr/nip07Permissions.js b/js/nostr/nip07Permissions.js index f85a3a03..f08edd87 100644 --- a/js/nostr/nip07Permissions.js +++ b/js/nostr/nip07Permissions.js @@ -21,12 +21,16 @@ export const DEFAULT_NIP07_ENCRYPTION_METHODS = Object.freeze([ "nip44.v2.decrypt", ]); -export const DEFAULT_NIP07_PERMISSION_METHODS = Object.freeze([ +export const DEFAULT_NIP07_CORE_METHODS = Object.freeze([ // Core auth + relay metadata "get_public_key", "sign_event", "read_relays", "write_relays", +]); + +export const DEFAULT_NIP07_PERMISSION_METHODS = Object.freeze([ + ...DEFAULT_NIP07_CORE_METHODS, ...DEFAULT_NIP07_ENCRYPTION_METHODS, ]); diff --git a/js/services/hashtagPreferencesService.js b/js/services/hashtagPreferencesService.js index bf674d80..538ceaf1 100644 --- a/js/services/hashtagPreferencesService.js +++ b/js/services/hashtagPreferencesService.js @@ -19,7 +19,11 @@ import { normalizeHashtag } from "../utils/hashtagNormalization.js"; import { profileCache } from "../state/profileCache.js"; import { DEFAULT_RELAY_URLS } from "../nostr/toolkit.js"; import { relayManager } from "../relayManager.js"; -import { runNip07WithRetry, NIP07_PRIORITY } from "../nostr/nip07Permissions.js"; +import { + DEFAULT_NIP07_ENCRYPTION_METHODS, + runNip07WithRetry, + NIP07_PRIORITY, +} from "../nostr/nip07Permissions.js"; import { relaySubscriptionService } from "./relaySubscriptionService.js"; const LOG_PREFIX = "[HashtagPreferences]"; @@ -901,12 +905,28 @@ class HashtagPreferencesService { return { ok: false, error }; } try { - await requestDefaultExtensionPermissions(); + const permissionResult = await requestDefaultExtensionPermissions( + DEFAULT_NIP07_ENCRYPTION_METHODS, + ); + if (!permissionResult?.ok) { + const error = + permissionResult?.error instanceof Error + ? permissionResult.error + : new Error( + "Extension encryption permissions are required to read hashtag preferences.", + ); + error.code = "hashtag-preferences-permission-required"; + return { ok: false, error }; + } } catch (error) { userLogger.warn( `${LOG_PREFIX} Extension permissions request failed while loading preferences`, error, ); + const wrapped = + error instanceof Error ? error : new Error(String(error)); + wrapped.code = "hashtag-preferences-permission-required"; + return { ok: false, error: wrapped }; } } @@ -1108,10 +1128,12 @@ class HashtagPreferencesService { } if (signer.type === "extension") { - const permissionResult = await requestDefaultExtensionPermissions(); + const permissionResult = await requestDefaultExtensionPermissions( + DEFAULT_NIP07_ENCRYPTION_METHODS, + ); if (!permissionResult?.ok) { const error = new Error( - "The active signer must allow encryption and signing before publishing preferences.", + "The active signer must allow encryption before publishing preferences.", ); error.code = "hashtag-preferences-extension-denied"; error.cause = permissionResult?.error || null; diff --git a/js/subscriptions.js b/js/subscriptions.js index a660f9ea..760cd616 100644 --- a/js/subscriptions.js +++ b/js/subscriptions.js @@ -31,7 +31,11 @@ import { devLogger, userLogger } from "./utils/logger.js"; import moderationService from "./services/moderationService.js"; import nostrService from "./services/nostrService.js"; import { profileCache } from "./state/profileCache.js"; -import { runNip07WithRetry, NIP07_PRIORITY } from "./nostr/nip07Permissions.js"; +import { + DEFAULT_NIP07_ENCRYPTION_METHODS, + runNip07WithRetry, + NIP07_PRIORITY, +} from "./nostr/nip07Permissions.js"; import { relaySubscriptionService } from "./services/relaySubscriptionService.js"; const SUBSCRIPTION_SET_KIND = @@ -883,7 +887,9 @@ class SubscriptionsManager { return { ok: false, error }; } try { - const permissionResult = await requestDefaultExtensionPermissions(); + const permissionResult = await requestDefaultExtensionPermissions( + DEFAULT_NIP07_ENCRYPTION_METHODS, + ); if (!permissionResult?.ok) { const error = permissionResult?.error instanceof Error @@ -1112,7 +1118,9 @@ class SubscriptionsManager { } if (signer.type === "extension") { - const permissionResult = await requestDefaultExtensionPermissions(); + const permissionResult = await requestDefaultExtensionPermissions( + DEFAULT_NIP07_ENCRYPTION_METHODS, + ); if (!permissionResult.ok) { userLogger.warn( "[SubscriptionsManager] Signer permissions denied while updating subscriptions.", diff --git a/js/ui/moreMenuController.js b/js/ui/moreMenuController.js index 601d89fe..7b2082f1 100644 --- a/js/ui/moreMenuController.js +++ b/js/ui/moreMenuController.js @@ -1520,6 +1520,8 @@ export default class MoreMenuController { const message = error?.code === "nip04-missing" ? "Your Nostr extension must support NIP-04 to manage private lists." + : error?.code === "extension-encryption-permission-denied" + ? "Your Nostr extension must allow encryption to update your block list." : "Failed to update your block list. Please try again."; this.callbacks.showError(message); } diff --git a/js/ui/profileModalController.js b/js/ui/profileModalController.js index dbae7054..17bd5056 100644 --- a/js/ui/profileModalController.js +++ b/js/ui/profileModalController.js @@ -3579,6 +3579,8 @@ export class ProfileModalController { return "We couldn’t create secure wrapper keys for NIP-17 delivery."; case "extension-permission-denied": return "Please grant your Nostr extension permission to send messages."; + case "extension-encryption-permission-denied": + return "Please grant your Nostr extension encryption permission to send messages."; case "missing-actor-pubkey": return "We couldn’t determine your public key to send this message."; case "nostr-uninitialized": @@ -9213,6 +9215,9 @@ export class ProfileModalController { const message = mutationResult?.error?.code === "nip04-missing" ? "Your Nostr extension must support NIP-04 to manage private lists." + : mutationResult?.error?.code === + "extension-encryption-permission-denied" + ? "Your Nostr extension must allow encryption to update your block list." : mutationResult?.error?.message || "Failed to update your block list. Please try again."; context.error = mutationResult?.error || null; @@ -9233,6 +9238,8 @@ export class ProfileModalController { const message = error?.code === "nip04-missing" ? "Your Nostr extension must support NIP-04 to manage private lists." + : error?.code === "extension-encryption-permission-denied" + ? "Your Nostr extension must allow encryption to update your block list." : "Failed to update your block list. Please try again."; this.showError(message); } @@ -9283,6 +9290,9 @@ export class ProfileModalController { const message = mutationResult.error.code === "nip04-missing" ? "Your Nostr extension must support NIP-04 to manage private lists." + : mutationResult.error.code === + "extension-encryption-permission-denied" + ? "Your Nostr extension must allow encryption to update your block list." : mutationResult.error.message || "Failed to update your block list. Please try again."; if (message) { @@ -9299,6 +9309,8 @@ export class ProfileModalController { const message = error?.code === "nip04-missing" ? "Your Nostr extension must support NIP-04 to manage private lists." + : error?.code === "extension-encryption-permission-denied" + ? "Your Nostr extension must allow encryption to update your block list." : "Failed to update your block list. Please try again."; this.showError(message); } diff --git a/js/userBlocks.js b/js/userBlocks.js index e82d9b92..0f4724f0 100644 --- a/js/userBlocks.js +++ b/js/userBlocks.js @@ -20,7 +20,11 @@ import { import { profileCache } from "./state/profileCache.js"; import { DEFAULT_RELAY_URLS } from "./nostr/toolkit.js"; import { relayManager } from "./relayManager.js"; -import { runNip07WithRetry, NIP07_PRIORITY } from "./nostr/nip07Permissions.js"; +import { + DEFAULT_NIP07_ENCRYPTION_METHODS, + runNip07WithRetry, + NIP07_PRIORITY, +} from "./nostr/nip07Permissions.js"; import { relaySubscriptionService } from "./services/relaySubscriptionService.js"; class TinyEventEmitter { @@ -962,17 +966,24 @@ class UserBlockListManager { let permissionError = null; if ((!signerHasNip44 && requiresNip44) || (!signerHasNip04 && requiresNip04)) { try { - const permissionResult = await requestDefaultExtensionPermissions(); + const permissionResult = await requestDefaultExtensionPermissions( + DEFAULT_NIP07_ENCRYPTION_METHODS, + ); if (!permissionResult?.ok) { - permissionError = + const error = permissionResult?.error instanceof Error ? permissionResult.error : new Error( - "Extension permissions are required to use the browser decryptor.", + "Extension encryption permissions are required to use the browser decryptor.", ); + error.code = "user-blocklist-permission-required"; + permissionError = error; } } catch (error) { - permissionError = error instanceof Error ? error : new Error(String(error)); + const wrapped = + error instanceof Error ? error : new Error(String(error)); + wrapped.code = "user-blocklist-permission-required"; + permissionError = wrapped; } } @@ -1981,16 +1992,18 @@ class UserBlockListManager { } if (signer.type === "extension") { - const permissionResult = await requestDefaultExtensionPermissions(); + const permissionResult = await requestDefaultExtensionPermissions( + DEFAULT_NIP07_ENCRYPTION_METHODS, + ); if (!permissionResult.ok) { userLogger.warn( "[UserBlockList] Signer permissions denied while updating the block list.", permissionResult.error, ); const err = new Error( - "The active signer must allow encryption and signing before updating the block list.", + "The active signer must allow encryption before updating the block list.", ); - err.code = "extension-permission-denied"; + err.code = "extension-encryption-permission-denied"; err.cause = permissionResult.error; throw err; }