Files
bitvid/js/utils/validateNpub.js
T
thePR0M3TH3ANandClaude Opus 4.8 e4e5c552c7 feat(forms): validate submitter npub on submission forms (#22)
The embed submission forms accepted any free-text identifier and published it,
so they were an easy spam vector. Add a shared, testable NIP-19 validator and
require a well-formed npub before a submission is sent.

- js/utils/validateNpub.js: validateNpubHex() decodes via nostr-tools nip19 and
  returns the 64-char hex pubkey, or null for anything that isn't a real npub
  (free text, wrong prefix, bad checksum, wrong NIP-19 entity). Also exposed as
  window.validateNpubHex for the classic iframe form scripts.
- Application form (required npub): submit is blocked unless valid; the decoded
  hex is written into the application body for canonical storage/matching.
- Feature-request / bug-fix / feedback (optional npub): stays optional, but a
  non-empty value must be a valid npub or submit is blocked with an inline error.
- Tests: tests/validate-npub.test.mjs (real nostr-tools, no implementation mirror).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-25 08:31:45 -04:00

61 lines
2.0 KiB
JavaScript

// Shared, anti-spam npub (NIP-19) validation for the embed submission forms
// (application / feature-request / bug-fix / feedback). A submission identifier
// is only accepted if it decodes to a canonical 64-char hex pubkey, so the forms
// stop publishing free-text garbage.
//
// Usable two ways:
// - import { validateNpubHex } from "../utils/validateNpub.js" (tests / app)
// - as a classic global in the iframe forms: window.validateNpubHex(value)
// (the module assigns the global below and resolves nostr-tools at call time
// from window.NostrTools, which the forms already load).
/**
* Decode a user-entered npub and return its hex pubkey, or null if it isn't a
* well-formed npub.
*
* @param {string} value Raw input (may include surrounding whitespace).
* @param {{ nip19?: { decode: Function } }} [deps] Inject a nip19 implementation
* (defaults to the global NostrTools bundle the embed forms load).
* @returns {string|null} 64-char lowercase hex pubkey, or null when invalid.
*/
export function validateNpubHex(value, { nip19 } = {}) {
const decoder =
nip19 ||
(typeof globalThis !== "undefined" ? globalThis.NostrTools?.nip19 : null);
if (typeof value !== "string") {
return null;
}
const trimmed = value.trim();
if (!trimmed.startsWith("npub1")) {
return null;
}
if (!decoder || typeof decoder.decode !== "function") {
return null;
}
try {
const decoded = decoder.decode(trimmed);
if (
decoded &&
decoded.type === "npub" &&
typeof decoded.data === "string" &&
/^[0-9a-f]{64}$/.test(decoded.data)
) {
return decoded.data;
}
} catch (error) {
return null;
}
return null;
}
if (typeof globalThis !== "undefined") {
// Expose to the iframe forms' classic inline scripts. Resolves NostrTools at
// call time, so load order relative to the nostr-tools bundle doesn't matter.
globalThis.validateNpubHex = (value) => validateNpubHex(value);
}
export default validateNpubHex;