mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-06-15 17:19:27 +02:00
refactor: use applesauce isSafeRelayURL for relay URL validation
Refactor relay URL validation to use applesauce's isSafeRelayURL helper which provides a fast regex-based check for valid websocket URLs. Changes: - Update isValidRelayURL in relay-url.ts to use isSafeRelayURL as fast path, with URL constructor fallback for IP addresses - Re-export isSafeRelayURL from relay-url.ts for convenience - Update loaders.ts to use isSafeRelayURL directly from applesauce - Add relay URL validation to: - nostr-utils.ts: getEventPointerFromQTag (q-tag relay hints) - zapstore-helpers.ts: getAppReferences (a-tag relay hints) - nip89-helpers.ts: getHandlerReferences (a-tag relay hints) - PublicChatsRenderer.tsx: extractGroups (group relay URLs) This ensures consistent validation across all relay URL extraction points using applesauce's battle-tested validation. https://claude.ai/code/session_01Ca2fKD2r4wHKRD8rcRohj9
This commit is contained in:
@@ -6,6 +6,7 @@ import { GroupLink } from "../GroupLink";
|
||||
import eventStore from "@/services/event-store";
|
||||
import pool from "@/services/relay-pool";
|
||||
import type { NostrEvent } from "@/types/nostr";
|
||||
import { isSafeRelayURL } from "applesauce-core/helpers/relays";
|
||||
|
||||
/**
|
||||
* Extract group references from a kind 10009 event
|
||||
@@ -19,9 +20,17 @@ function extractGroups(event: { tags: string[][] }): Array<{
|
||||
|
||||
for (const tag of event.tags) {
|
||||
if (tag[0] === "group" && tag[1] && tag[2]) {
|
||||
// Only include groups with valid relay URLs
|
||||
const relayUrl = tag[2];
|
||||
if (!isSafeRelayURL(relayUrl)) {
|
||||
console.warn(
|
||||
`[PublicChatsRenderer] Skipping group with invalid relay URL: ${relayUrl}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
groups.push({
|
||||
groupId: tag[1],
|
||||
relayUrl: tag[2],
|
||||
relayUrl,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NostrEvent } from "@/types/nostr";
|
||||
import { getTagValue } from "applesauce-core/helpers";
|
||||
import { isSafeRelayURL } from "applesauce-core/helpers/relays";
|
||||
import { AddressPointer } from "nostr-tools/nip19";
|
||||
|
||||
/**
|
||||
@@ -229,10 +230,13 @@ export function getHandlerReferences(event: NostrEvent): HandlerReference[] {
|
||||
|
||||
const relayHint = tag[2];
|
||||
const platform = tag[3];
|
||||
// Only include relay hint if it's a valid websocket URL
|
||||
const validRelayHint =
|
||||
relayHint && isSafeRelayURL(relayHint) ? relayHint : undefined;
|
||||
|
||||
references.push({
|
||||
address,
|
||||
relayHint: relayHint || undefined,
|
||||
relayHint: validRelayHint,
|
||||
platform: platform || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { NostrFilter } from "@/types/nostr";
|
||||
import { getNip10References } from "applesauce-common/helpers/threading";
|
||||
import { getCommentReplyPointer } from "applesauce-common/helpers/comment";
|
||||
import type { EventPointer, AddressPointer } from "nostr-tools/nip19";
|
||||
import { isSafeRelayURL } from "applesauce-core/helpers/relays";
|
||||
|
||||
export function derivePlaceholderName(pubkey: string): string {
|
||||
return `${pubkey.slice(0, 4)}:${pubkey.slice(-4)}`;
|
||||
@@ -81,6 +82,10 @@ export function getEventPointerFromQTag(
|
||||
const relayHint = tag[2];
|
||||
const authorHint = tag[3];
|
||||
|
||||
// Validate relay hint is a valid websocket URL before using
|
||||
const validRelayHint =
|
||||
relayHint && isSafeRelayURL(relayHint) ? relayHint : undefined;
|
||||
|
||||
// Check if it's an address coordinate (contains colons: kind:pubkey:d-tag)
|
||||
if (value.includes(":")) {
|
||||
const parts = value.split(":");
|
||||
@@ -91,8 +96,8 @@ export function getEventPointerFromQTag(
|
||||
|
||||
if (!isNaN(kind) && pubkey) {
|
||||
const pointer: AddressPointer = { kind, pubkey, identifier };
|
||||
if (relayHint) {
|
||||
pointer.relays = [relayHint];
|
||||
if (validRelayHint) {
|
||||
pointer.relays = [validRelayHint];
|
||||
}
|
||||
return pointer;
|
||||
}
|
||||
@@ -106,8 +111,8 @@ export function getEventPointerFromQTag(
|
||||
}
|
||||
|
||||
const pointer: EventPointer = { id: value };
|
||||
if (relayHint) {
|
||||
pointer.relays = [relayHint];
|
||||
if (validRelayHint) {
|
||||
pointer.relays = [validRelayHint];
|
||||
}
|
||||
if (authorHint && /^[0-9a-f]{64}$/i.test(authorHint)) {
|
||||
pointer.author = authorHint;
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import { normalizeURL as applesauceNormalizeURL } from "applesauce-core/helpers";
|
||||
import { isSafeRelayURL } from "applesauce-core/helpers/relays";
|
||||
|
||||
// Re-export applesauce's fast relay URL check for use in hot paths
|
||||
export { isSafeRelayURL };
|
||||
|
||||
/**
|
||||
* Check if a string is a valid relay URL
|
||||
* - Must have ws:// or wss:// protocol
|
||||
* - Must be a valid URL structure
|
||||
* - Must not contain invalid characters
|
||||
* - Must have a valid hostname
|
||||
*
|
||||
* Uses applesauce's isSafeRelayURL for fast validation of domain-based URLs,
|
||||
* with a fallback to URL constructor for IP addresses (which isSafeRelayURL doesn't support).
|
||||
*
|
||||
* @returns true if the URL is a valid relay URL, false otherwise
|
||||
*/
|
||||
@@ -21,8 +28,14 @@ export function isValidRelayURL(url: unknown): url is string {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fast path: use applesauce's regex-based validation for domain URLs
|
||||
if (isSafeRelayURL(trimmed)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback: use URL constructor for IP addresses and edge cases
|
||||
// isSafeRelayURL doesn't support IP addresses like 192.168.1.1 or 127.0.0.1
|
||||
try {
|
||||
// Must be a valid URL structure
|
||||
const parsed = new URL(trimmed);
|
||||
|
||||
// Protocol must be ws: or wss:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NostrEvent } from "@/types/nostr";
|
||||
import { getTagValue } from "applesauce-core/helpers";
|
||||
import { isSafeRelayURL } from "applesauce-core/helpers/relays";
|
||||
import { AddressPointer } from "nostr-tools/nip19";
|
||||
|
||||
/**
|
||||
@@ -227,9 +228,12 @@ export function getAppReferences(event: NostrEvent): AppReference[] {
|
||||
// Kind 32267 apps are expected in curation sets
|
||||
if (address.kind === 32267) {
|
||||
const relayHint = tag[2];
|
||||
// Only include relay hint if it's a valid websocket URL
|
||||
const validRelayHint =
|
||||
relayHint && isSafeRelayURL(relayHint) ? relayHint : undefined;
|
||||
references.push({
|
||||
address,
|
||||
relayHint: relayHint || undefined,
|
||||
relayHint: validRelayHint,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,11 @@ import {
|
||||
} from "applesauce-loaders/loaders";
|
||||
import type { EventPointer } from "nostr-tools/nip19";
|
||||
import { Observable } from "rxjs";
|
||||
import { getSeenRelays, mergeRelaySets } from "applesauce-core/helpers/relays";
|
||||
import {
|
||||
getSeenRelays,
|
||||
mergeRelaySets,
|
||||
isSafeRelayURL,
|
||||
} from "applesauce-core/helpers/relays";
|
||||
import {
|
||||
getEventPointerFromETag,
|
||||
getAddressPointerFromATag,
|
||||
@@ -16,7 +20,6 @@ import pool from "./relay-pool";
|
||||
import eventStore from "./event-store";
|
||||
import { relayListCache } from "./relay-list-cache";
|
||||
import type { NostrEvent } from "@/types/nostr";
|
||||
import { isValidRelayURL } from "@/lib/relay-url";
|
||||
|
||||
/**
|
||||
* Extract relay context from a Nostr event for comprehensive relay selection
|
||||
@@ -37,7 +40,9 @@ function extractRelayContext(event: NostrEvent): {
|
||||
const rTags = event.tags
|
||||
.filter((t) => t[0] === "r")
|
||||
.map((t) => t[1])
|
||||
.filter(isValidRelayURL);
|
||||
.filter(
|
||||
(url): url is string => typeof url === "string" && isSafeRelayURL(url),
|
||||
);
|
||||
|
||||
// Extract relay hints from all "e" tags using applesauce helper
|
||||
// Filter to only valid relay URLs
|
||||
@@ -48,7 +53,9 @@ function extractRelayContext(event: NostrEvent): {
|
||||
// v5: returns null for invalid tags instead of throwing
|
||||
return pointer?.relays?.[0]; // First relay hint from the pointer
|
||||
})
|
||||
.filter(isValidRelayURL);
|
||||
.filter(
|
||||
(url): url is string => typeof url === "string" && isSafeRelayURL(url),
|
||||
);
|
||||
|
||||
// Extract relay hints from all "a" tags (addressable event references)
|
||||
// This includes both lowercase "a" (reply) and uppercase "A" (root) tags
|
||||
@@ -59,7 +66,9 @@ function extractRelayContext(event: NostrEvent): {
|
||||
const pointer = getAddressPointerFromATag(tag);
|
||||
return pointer?.relays?.[0]; // First relay hint from the pointer
|
||||
})
|
||||
.filter(isValidRelayURL);
|
||||
.filter(
|
||||
(url): url is string => typeof url === "string" && isSafeRelayURL(url),
|
||||
);
|
||||
|
||||
// Extract first "p" tag as author hint using applesauce helper
|
||||
const authorHint = getTagValue(event, "p");
|
||||
|
||||
Reference in New Issue
Block a user