From 069904f07aae2e04571e01f2cfd6fbf09416963a Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Wed, 30 Jul 2025 16:30:03 -0700 Subject: [PATCH] Only protect events if the relay will authenticate with the user --- CHANGELOG.md | 3 +- src/app/commands.ts | 51 ++++++++++++------- src/app/components/AlertDelete.svelte | 5 +- .../components/CalendarEventActions.svelte | 9 ++-- src/app/components/CalendarEventForm.svelte | 30 ++++++----- src/app/components/ChannelMessage.svelte | 9 ++-- .../ChannelMessageEmojiButton.svelte | 11 ++-- .../ChannelMessageMenuMobile.svelte | 11 ++-- src/app/components/ChatMessage.svelte | 4 +- .../components/ChatMessageEmojiButton.svelte | 2 +- .../components/ChatMessageMenuMobile.svelte | 2 +- src/app/components/CommentActions.svelte | 9 ++-- src/app/components/EventActions.svelte | 11 ++-- src/app/components/EventDeleteConfirm.svelte | 4 +- src/app/components/EventReply.svelte | 8 ++- src/app/components/EventReportDetails.svelte | 6 +-- src/app/components/GoalActions.svelte | 9 ++-- src/app/components/GoalCreate.svelte | 6 ++- src/app/components/NoteItem.svelte | 18 ++++--- src/app/components/ThreadActions.svelte | 9 ++-- src/app/components/ThreadCreate.svelte | 7 ++- src/routes/spaces/[relay]/[room]/+page.svelte | 14 +++-- src/routes/spaces/[relay]/chat/+page.svelte | 8 +-- 23 files changed, 159 insertions(+), 87 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af36526..c29c491 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Add `created_at` to event info dialog * Add signer status to profile page * Re-work bunker login flow +* Only protect events if relay authenticates # 1.2.2 @@ -25,7 +26,7 @@ * Fix sort order of thread comments * Fix link display when no title is available -* Fix making profiles non-protected +* Fix making profiles non-protect * Replace bunker url with relay claims for notifier auth * Add push notifications on all platforms * Add "mark all as read" on desktop diff --git a/src/app/commands.ts b/src/app/commands.ts index 9ec577e..63c2e4f 100644 --- a/src/app/commands.ts +++ b/src/app/commands.ts @@ -214,10 +214,19 @@ export const setInboxRelayPolicy = (url: string, enabled: boolean) => { // Relay access -export const attemptAuth = (url: string) => - Pool.get() - .get(url) - .auth.attemptAuth(e => signer.get()?.sign(e)) +export const attemptAuth = async (url: string) => { + const socket = Pool.get().get(url) + + await socket.auth.attemptAuth(e => signer.get()?.sign(e)) +} + +export const canEnforceNip70 = async (url: string) => { + const socket = Pool.get().get(url) + + await socket.auth.attemptAuth(e => signer.get()?.sign(e)) + + return socket.auth.status !== AuthStatus.None +} export const checkRelayAccess = async (url: string, claim = "") => { const socket = Pool.get().get(url) @@ -305,26 +314,29 @@ export const attemptRelayAccess = async (url: string, claim = "") => { // Actions -export const makeDelete = ({event, tags = []}: {event: TrustedEvent; tags?: string[][]}) => { +export type DeleteParams = { + protect: boolean + event: TrustedEvent + tags?: string[][] +} + +export const makeDelete = ({protect, event, tags = []}: DeleteParams) => { const thisTags = [["k", String(event.kind)], ...tagEvent(event), ...tags] const groupTag = getTag("h", event.tags) if (groupTag) { - thisTags.push(PROTECTED, groupTag) + thisTags.push(groupTag) + } + + if (protect) { + thisTags.push(PROTECTED) } return makeEvent(DELETE, {tags: thisTags}) } -export const publishDelete = ({ - relays, - event, - tags = [], -}: { - relays: string[] - event: TrustedEvent - tags?: string[][] -}) => publishThunk({event: makeDelete({event, tags}), relays}) +export const publishDelete = ({relays, ...params}: DeleteParams & {relays: string[]}) => + publishThunk({event: makeDelete(params), relays}) export type ReportParams = { event: TrustedEvent @@ -350,21 +362,24 @@ export const publishReport = ({ publishThunk({event: makeReport({event, reason, content}), relays}) export type ReactionParams = { + protect: boolean event: TrustedEvent content: string tags?: string[][] } -export const makeReaction = ({content, event, tags: paramTags = []}: ReactionParams) => { +export const makeReaction = ({protect, content, event, tags: paramTags = []}: ReactionParams) => { const tags = [...paramTags, ...tagEventForReaction(event)] - const groupTag = getTag("h", event.tags) if (groupTag) { - tags.push(PROTECTED) tags.push(groupTag) } + if (protect) { + tags.push(PROTECTED) + } + return makeEvent(REACTION, {content, tags}) } diff --git a/src/app/components/AlertDelete.svelte b/src/app/components/AlertDelete.svelte index 6f0830e..ba0878c 100644 --- a/src/app/components/AlertDelete.svelte +++ b/src/app/components/AlertDelete.svelte @@ -12,7 +12,10 @@ const {alert}: Props = $props() const confirm = () => { - publishDelete({event: alert.event, relays: [NOTIFIER_RELAY], tags: [["p", NOTIFIER_PUBKEY]]}) + const relays = [NOTIFIER_RELAY] + const tags = [["p", NOTIFIER_PUBKEY]] + + publishDelete({event: alert.event, relays, tags, protect: false}) pushToast({message: "Your alert has been deleted!"}) history.back() } diff --git a/src/app/components/CalendarEventActions.svelte b/src/app/components/CalendarEventActions.svelte index 7d71ccd..a932640 100644 --- a/src/app/components/CalendarEventActions.svelte +++ b/src/app/components/CalendarEventActions.svelte @@ -8,7 +8,7 @@ import EventActivity from "@app/components/EventActivity.svelte" import EventActions from "@app/components/EventActions.svelte" import CalendarEventEdit from "@app/components/CalendarEventEdit.svelte" - import {publishDelete, publishReaction} from "@app/commands" + import {publishDelete, publishReaction, canEnforceNip70} from "@app/commands" import {makeCalendarPath} from "@app/routes" import {pushModal} from "@app/modal" @@ -26,10 +26,11 @@ const editEvent = () => pushModal(CalendarEventEdit, {url, event}) - const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event}) + const deleteReaction = async (event: TrustedEvent) => + publishDelete({relays: [url], event, protect: await canEnforceNip70(url)}) - const createReaction = (template: EventContent) => - publishReaction({...template, event, relays: [url]}) + const createReaction = async (template: EventContent) => + publishReaction({...template, event, relays: [url], protect: await canEnforceNip70(url)})
diff --git a/src/app/components/CalendarEventForm.svelte b/src/app/components/CalendarEventForm.svelte index d3cdf41..8cc32e1 100644 --- a/src/app/components/CalendarEventForm.svelte +++ b/src/app/components/CalendarEventForm.svelte @@ -16,6 +16,7 @@ import {PROTECTED} from "@app/state" import {makeEditor} from "@app/editor" import {pushToast} from "@app/toast" + import {canEnforceNip70} from "@app/commands" type Props = { url: string @@ -63,19 +64,22 @@ } const ed = await editor - const event = makeEvent(EVENT_TIME, { - content: ed.getText({blockSeparator: "\n"}).trim(), - tags: [ - ["d", initialValues?.d || randomId()], - ["title", title], - ["location", location || ""], - ["start", start.toString()], - ["end", end.toString()], - ...daysBetween(start, end).map(D => ["D", D.toString()]), - ...ed.storage.nostr.getEditorTags(), - PROTECTED, - ], - }) + const content = ed.getText({blockSeparator: "\n"}).trim() + const tags = [ + ["d", initialValues?.d || randomId()], + ["title", title], + ["location", location || ""], + ["start", start.toString()], + ["end", end.toString()], + ...daysBetween(start, end).map(D => ["D", D.toString()]), + ...ed.storage.nostr.getEditorTags(), + ] + + if (await canEnforceNip70(url)) { + tags.push(PROTECTED) + } + + const event = makeEvent(EVENT_TIME, {content, tags}) pushToast({message: "Your event has been saved!"}) publishThunk({event, relays: [url]}) diff --git a/src/app/components/ChannelMessage.svelte b/src/app/components/ChannelMessage.svelte index 830c9e7..c6a1db4 100644 --- a/src/app/components/ChannelMessage.svelte +++ b/src/app/components/ChannelMessage.svelte @@ -16,7 +16,7 @@ import ChannelMessageMenuButton from "@app/components/ChannelMessageMenuButton.svelte" import ChannelMessageMenuMobile from "@app/components/ChannelMessageMenuMobile.svelte" import {colors, ENABLE_ZAPS} from "@app/state" - import {publishDelete, publishReaction} from "@app/commands" + import {publishDelete, publishReaction, canEnforceNip70} from "@app/commands" import {pushModal} from "@app/modal" interface Props { @@ -41,10 +41,11 @@ const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey, url}) - const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event}) + const deleteReaction = async (event: TrustedEvent) => + publishDelete({relays: [url], event, protect: await canEnforceNip70(url)}) - const createReaction = (template: EventContent) => - publishReaction({...template, event, relays: [url]}) + const createReaction = async (template: EventContent) => + publishReaction({...template, event, relays: [url], protect: await canEnforceNip70(url)}) - publishReaction({event, relays: [url], content: emoji.unicode}) + const onEmoji = async (emoji: NativeEmoji) => + publishReaction({ + event, + relays: [url], + content: emoji.unicode, + protect: await canEnforceNip70(url), + }) diff --git a/src/app/components/ChannelMessageMenuMobile.svelte b/src/app/components/ChannelMessageMenuMobile.svelte index 10dd013..44be192 100644 --- a/src/app/components/ChannelMessageMenuMobile.svelte +++ b/src/app/components/ChannelMessageMenuMobile.svelte @@ -9,7 +9,7 @@ import EventInfo from "@app/components/EventInfo.svelte" import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte" import {ENABLE_ZAPS} from "@app/state" - import {publishReaction} from "@app/commands" + import {publishReaction, canEnforceNip70} from "@app/commands" import {pushModal} from "@app/modal" type Props = { @@ -20,9 +20,14 @@ const {url, event, reply}: Props = $props() - const onEmoji = ((event: TrustedEvent, url: string, emoji: NativeEmoji) => { + const onEmoji = (async (event: TrustedEvent, url: string, emoji: NativeEmoji) => { history.back() - publishReaction({event, relays: [url], content: emoji.unicode}) + publishReaction({ + event, + relays: [url], + content: emoji.unicode, + protect: await canEnforceNip70(url), + }) }).bind(undefined, event, url) const showEmojiPicker = () => pushModal(EmojiPicker, {onClick: onEmoji}, {replaceState: true}) diff --git a/src/app/components/ChatMessage.svelte b/src/app/components/ChatMessage.svelte index 6b67d26..2464025 100644 --- a/src/app/components/ChatMessage.svelte +++ b/src/app/components/ChatMessage.svelte @@ -37,10 +37,10 @@ const reply = () => replyTo(event) const deleteReaction = (event: TrustedEvent) => - sendWrapped({template: makeDelete({event}), pubkeys}) + sendWrapped({template: makeDelete({event, protect: false}), pubkeys}) const createReaction = (template: EventContent) => - sendWrapped({template: makeReaction({event, ...template}), pubkeys}) + sendWrapped({template: makeReaction({event, protect: false, ...template}), pubkeys}) const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey}) diff --git a/src/app/components/ChatMessageEmojiButton.svelte b/src/app/components/ChatMessageEmojiButton.svelte index 159ef29..3abe04c 100644 --- a/src/app/components/ChatMessageEmojiButton.svelte +++ b/src/app/components/ChatMessageEmojiButton.svelte @@ -14,7 +14,7 @@ const {event, pubkeys}: Props = $props() const onEmoji = (emoji: NativeEmoji) => - sendWrapped({template: makeReaction({event, content: emoji.unicode}), pubkeys}) + sendWrapped({template: makeReaction({event, content: emoji.unicode, protect: false}), pubkeys}) diff --git a/src/app/components/ChatMessageMenuMobile.svelte b/src/app/components/ChatMessageMenuMobile.svelte index 5d147c7..1b977e2 100644 --- a/src/app/components/ChatMessageMenuMobile.svelte +++ b/src/app/components/ChatMessageMenuMobile.svelte @@ -20,7 +20,7 @@ const onEmoji = ((event: TrustedEvent, pubkeys: string[], emoji: NativeEmoji) => { history.back() - sendWrapped({template: makeReaction({event, content: emoji.unicode}), pubkeys}) + sendWrapped({template: makeReaction({event, content: emoji.unicode, protect: false}), pubkeys}) }).bind(undefined, event, pubkeys) const showEmojiPicker = () => pushModal(EmojiPicker, {onClick: onEmoji}, {replaceState: true}) diff --git a/src/app/components/CommentActions.svelte b/src/app/components/CommentActions.svelte index faf6ed7..e0b16bf 100644 --- a/src/app/components/CommentActions.svelte +++ b/src/app/components/CommentActions.svelte @@ -4,7 +4,7 @@ import ThunkStatusOrDeleted from "@app/components/ThunkStatusOrDeleted.svelte" import EventActivity from "@app/components/EventActivity.svelte" import EventActions from "@app/components/EventActions.svelte" - import {publishDelete, publishReaction} from "@app/commands" + import {publishDelete, publishReaction, canEnforceNip70} from "@app/commands" import {makeThreadPath} from "@app/routes" interface Props { @@ -17,10 +17,11 @@ const path = makeThreadPath(url, event.id) - const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event}) + const deleteReaction = async (event: TrustedEvent) => + publishDelete({relays: [url], event, protect: await canEnforceNip70(url)}) - const createReaction = (template: EventContent) => - publishReaction({...template, event, relays: [url]}) + const createReaction = async (template: EventContent) => + publishReaction({...template, event, relays: [url], protect: await canEnforceNip70(url)})
diff --git a/src/app/components/EventActions.svelte b/src/app/components/EventActions.svelte index a608eac..ae9c709 100644 --- a/src/app/components/EventActions.svelte +++ b/src/app/components/EventActions.svelte @@ -10,7 +10,7 @@ import EmojiButton from "@lib/components/EmojiButton.svelte" import EventMenu from "@app/components/EventMenu.svelte" import {ENABLE_ZAPS} from "@app/state" - import {publishReaction} from "@app/commands" + import {publishReaction, canEnforceNip70} from "@app/commands" type Props = { url: string @@ -26,8 +26,13 @@ const hidePopover = () => popover?.hide() - const onEmoji = (emoji: NativeEmoji) => - publishReaction({event, content: emoji.unicode, relays: [url]}) + const onEmoji = async (emoji: NativeEmoji) => + publishReaction({ + event, + content: emoji.unicode, + relays: [url], + protect: await canEnforceNip70(url), + }) let popover: Instance | undefined = $state() diff --git a/src/app/components/EventDeleteConfirm.svelte b/src/app/components/EventDeleteConfirm.svelte index 5239143..3ee9a95 100644 --- a/src/app/components/EventDeleteConfirm.svelte +++ b/src/app/components/EventDeleteConfirm.svelte @@ -1,7 +1,7 @@
diff --git a/src/app/components/GoalCreate.svelte b/src/app/components/GoalCreate.svelte index b1ae664..b51c3b5 100644 --- a/src/app/components/GoalCreate.svelte +++ b/src/app/components/GoalCreate.svelte @@ -13,6 +13,7 @@ import {pushToast} from "@app/toast" import {PROTECTED} from "@app/state" import {makeEditor} from "@app/editor" + import {canEnforceNip70} from "@app/commands" const {url} = $props() @@ -47,9 +48,12 @@ ["summary", summary], ["amount", String(amount)], ["relays", url], - PROTECTED, ] + if (await canEnforceNip70(url)) { + tags.push(PROTECTED) + } + publishThunk({ relays: [url], event: makeEvent(ZAP_GOAL, {content, tags}), diff --git a/src/app/components/NoteItem.svelte b/src/app/components/NoteItem.svelte index 50f2ca1..5da4918 100644 --- a/src/app/components/NoteItem.svelte +++ b/src/app/components/NoteItem.svelte @@ -6,17 +6,23 @@ import NoteContent from "@app/components/NoteContent.svelte" import NoteCard from "@app/components/NoteCard.svelte" import ReactionSummary from "@app/components/ReactionSummary.svelte" - import {publishDelete, publishReaction} from "@app/commands" + import {publishDelete, publishReaction, canEnforceNip70} from "@app/commands" const {url, event} = $props() - const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event}) + const deleteReaction = async (event: TrustedEvent) => + publishDelete({relays: [url], event, protect: await canEnforceNip70(url)}) - const createReaction = (template: EventContent) => - publishReaction({...template, event, relays: [url]}) + const createReaction = async (template: EventContent) => + publishReaction({...template, event, relays: [url], protect: await canEnforceNip70(url)}) - const onEmoji = (emoji: NativeEmoji) => - publishReaction({event, content: emoji.unicode, relays: [url]}) + const onEmoji = async (emoji: NativeEmoji) => + publishReaction({ + event, + content: emoji.unicode, + relays: [url], + protect: await canEnforceNip70(url), + }) diff --git a/src/app/components/ThreadActions.svelte b/src/app/components/ThreadActions.svelte index fa8e6cc..d26491a 100644 --- a/src/app/components/ThreadActions.svelte +++ b/src/app/components/ThreadActions.svelte @@ -4,7 +4,7 @@ import ThunkStatusOrDeleted from "@app/components/ThunkStatusOrDeleted.svelte" import EventActivity from "@app/components/EventActivity.svelte" import EventActions from "@app/components/EventActions.svelte" - import {publishDelete, publishReaction} from "@app/commands" + import {publishDelete, publishReaction, canEnforceNip70} from "@app/commands" import {makeThreadPath} from "@app/routes" interface Props { @@ -17,10 +17,11 @@ const path = makeThreadPath(url, event.id) - const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event}) + const deleteReaction = async (event: TrustedEvent) => + publishDelete({relays: [url], event, protect: await canEnforceNip70(url)}) - const createReaction = (template: EventContent) => - publishReaction({...template, event, relays: [url]}) + const createReaction = async (template: EventContent) => + publishReaction({...template, event, relays: [url], protect: await canEnforceNip70(url)})
diff --git a/src/app/components/ThreadCreate.svelte b/src/app/components/ThreadCreate.svelte index c0a77f3..1965b1e 100644 --- a/src/app/components/ThreadCreate.svelte +++ b/src/app/components/ThreadCreate.svelte @@ -12,6 +12,7 @@ import {pushToast} from "@app/toast" import {PROTECTED} from "@app/state" import {makeEditor} from "@app/editor" + import {canEnforceNip70} from "@app/commands" const {url} = $props() @@ -41,7 +42,11 @@ }) } - const tags = [...ed.storage.nostr.getEditorTags(), ["title", title], PROTECTED] + const tags = [...ed.storage.nostr.getEditorTags(), ["title", title]] + + if (await canEnforceNip70(url)) { + tags.push(PROTECTED) + } publishThunk({ relays: [url], diff --git a/src/routes/spaces/[relay]/[room]/+page.svelte b/src/routes/spaces/[relay]/[room]/+page.svelte index aa9b050..d6a1afe 100644 --- a/src/routes/spaces/[relay]/[room]/+page.svelte +++ b/src/routes/spaces/[relay]/[room]/+page.svelte @@ -39,7 +39,12 @@ REACTION_KINDS, } from "@app/state" import {setChecked, checked} from "@app/notifications" - import {addRoomMembership, removeRoomMembership, prependParent} from "@app/commands" + import { + addRoomMembership, + canEnforceNip70, + removeRoomMembership, + prependParent, + } from "@app/commands" import {PROTECTED} from "@app/state" import {makeFeed} from "@app/requests" import {popKey} from "@app/implicit" @@ -101,9 +106,12 @@ share = undefined } - const onSubmit = ({content, tags}: EventContent) => { + const onSubmit = async ({content, tags}: EventContent) => { tags.push(["h", room]) - tags.push(PROTECTED) + + if (await canEnforceNip70(url)) { + tags.push(PROTECTED) + } let template = {content, tags} diff --git a/src/routes/spaces/[relay]/chat/+page.svelte b/src/routes/spaces/[relay]/chat/+page.svelte index dedc858..1d01006 100644 --- a/src/routes/spaces/[relay]/chat/+page.svelte +++ b/src/routes/spaces/[relay]/chat/+page.svelte @@ -20,7 +20,7 @@ import ChannelComposeParent from "@app/components/ChannelComposeParent.svelte" import {userSettingValues, decodeRelay, getEventsForUrl} from "@app/state" import {setChecked, checked} from "@app/notifications" - import {prependParent} from "@app/commands" + import {prependParent, canEnforceNip70} from "@app/commands" import {PROTECTED, REACTION_KINDS} from "@app/state" import {makeFeed} from "@app/requests" import {popKey} from "@app/implicit" @@ -43,8 +43,10 @@ share = undefined } - const onSubmit = ({content, tags}: EventContent) => { - tags.push(PROTECTED) + const onSubmit = async ({content, tags}: EventContent) => { + if (await canEnforceNip70(url)) { + tags.push(PROTECTED) + } let template = {content, tags}