@nostrify/nostrify 0.47.1 → 0.48.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,19 +1,33 @@
1
-
2
-
3
- > @nostrify/nostrify@0.47.0 build /home/sid/repos/nostrify/packages/nostrify
4
- > npx tsc -p tsconfig.json && node ../../esbuild.config.js --package ./
5
-
6
- npm warn Unknown env config "verify-deps-before-run". This will stop working in the next major version of npm.
7
- npm warn Unknown env config "_jsr-registry". This will stop working in the next major version of npm.
8
- â ™Building with esbuild...
9
-
10
- dist/NRelay1.js 8.9kb
11
- dist/NSchema.js 5.8kb
12
- dist/NPool.js 5.0kb
13
- dist/test/TestRelayServer.js 4.4kb
14
- dist/NSet.js 4.1kb
15
- ...and 26 more output files...
16
-
17
- âš¡ Done in 25ms
18
- Copying source files...
19
- Done!
1
+
2
+ > @nostrify/nostrify@0.48.1 build /home/sid/repos/nostrify/packages/nostrify
3
+ > npx tsc -p tsconfig.json && node ../../esbuild.config.js --package ./
4
+
5
+ npm warn Unknown env config "verify-deps-before-run". This will stop working in the next major version of npm.
6
+ npm warn Unknown env config "_jsr-registry". This will stop working in the next major version of npm.
7
+ Building with esbuild...
8
+
9
+ dist/NRelay1.js 8.9kb
10
+ dist/NSchema.js 5.8kb
11
+ dist/NPool.js 5.0kb
12
+ dist/test/TestRelayServer.js 4.4kb
13
+ dist/NSet.js 4.1kb
14
+ dist/NConnectSigner.js 3.9kb
15
+ dist/ln/LNURL.js 3.3kb
16
+ dist/NIP98.js 2.7kb
17
+ dist/NBrowserSigner.js 2.6kb
18
+ dist/uploaders/BlossomUploader.js 1.9kb
19
+ dist/uploaders/NostrBuildUploader.js 1.9kb
20
+ dist/test/MockRelay.js 1.4kb
21
+ dist/BunkerURI.js 1.4kb
22
+ dist/NKinds.js 1.1kb
23
+ dist/NIP05.js 1.1kb
24
+ dist/NSecSigner.js 1.1kb
25
+ dist/utils/Machina.js 925b
26
+ dist/NCache.js 828b
27
+ dist/mod.js 815b
28
+ dist/test/mod.js 701b
29
+ ...and 11 more output files...
30
+
31
+ âš¡ Done in 31ms
32
+ Copying source files...
33
+ Done!
@@ -0,0 +1,6 @@
1
+
2
+ > @nostrify/nostrify@0.48.1 typecheck /home/sid/repos/nostrify/packages/nostrify
3
+ > npx tsc --noEmit
4
+
5
+ npm warn Unknown env config "verify-deps-before-run". This will stop working in the next major version of npm.
6
+ npm warn Unknown env config "_jsr-registry". This will stop working in the next major version of npm.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.48.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Implement type checking in CI for all libs.
8
+ - Updated dependencies
9
+ - @nostrify/types@0.36.8
10
+
11
+ ## 0.48.1
12
+
13
+ ### Patch Changes
14
+
15
+ - remove happy-dom dependency, fix improper test in NRelay1
16
+
17
+ ## 0.48.0
18
+
19
+ ### Minor Changes
20
+
21
+ - Remove all JSR imports
22
+
3
23
  ## 0.47.1
4
24
 
5
25
  ### Patch Changes
@@ -71,7 +91,8 @@
71
91
  All notable changes to this project will be documented in this file.
72
92
 
73
93
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
74
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
94
+ and this project adheres to
95
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
75
96
 
76
97
  ## 0.46.4 - 2025-07-15
77
98
 
@@ -83,7 +104,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
83
104
 
84
105
  ### Fixed
85
106
 
86
- - Fix fetch calls in browser by binding globalThis (NIP05, LNURL, BlossomUploader).
107
+ - Fix fetch calls in browser by binding globalThis (NIP05, LNURL,
108
+ BlossomUploader).
87
109
  - BlossomUploader: use a browser-supported file hashing method.
88
110
 
89
111
  ## 0.46.0 - 2025-05-10
@@ -108,7 +130,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
108
130
 
109
131
  ### Changed
110
132
 
111
- - BREAKING: Redesign the `LNURL` module to be a stateful class, more similar to `URL`.
133
+ - BREAKING: Redesign the `LNURL` module to be a stateful class, more similar to
134
+ `URL`.
112
135
 
113
136
  ## 0.44.0 - 2025-04-23
114
137
 
@@ -120,7 +143,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
120
143
 
121
144
  ### Changed
122
145
 
123
- - Moved `NSeedSigner`, `NPhraseSigner`, and `NCustodial` into a separate `@nostrify/seed` package.
146
+ - Moved `NSeedSigner`, `NPhraseSigner`, and `NCustodial` into a separate
147
+ `@nostrify/seed` package.
124
148
 
125
149
  ## 0.42.1 - 2025-04-10
126
150
 
@@ -155,7 +179,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
155
179
 
156
180
  ### Added
157
181
 
158
- - NRelay1: batch ids-only filters and filters for replaceable events by author/kind within the same event loop.
182
+ - NRelay1: batch ids-only filters and filters for replaceable events by
183
+ author/kind within the same event loop.
159
184
 
160
185
  ## 0.40.0 - 2025-03-26
161
186
 
@@ -254,7 +279,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
254
279
 
255
280
  ### Added
256
281
 
257
- - NRelay1: added `idleTimeout` option to automatically close inactive connections.
282
+ - NRelay1: added `idleTimeout` option to automatically close inactive
283
+ connections.
258
284
  - NRelay1: support `await using` syntax.
259
285
 
260
286
  ## 0.31.0 - 2024-09-09
@@ -262,11 +288,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
262
288
  ### Added
263
289
 
264
290
  - Added ReplyBotPolicy to block replies from bots in the same second.
265
- - Added AuthorPolicy to reject events from authors without a kind 0, or with a particular kind 0.
291
+ - Added AuthorPolicy to reject events from authors without a kind 0, or with a
292
+ particular kind 0.
266
293
 
267
294
  ### Changed
268
295
 
269
- - BREAKING: Moved moderation policies to the `@nostrify/policies` package. Replace `@nostrify/nostrify/policies` with `@nostrify/policies` in your project.
296
+ - BREAKING: Moved moderation policies to the `@nostrify/policies` package.
297
+ Replace `@nostrify/nostrify/policies` with `@nostrify/policies` in your
298
+ project.
270
299
 
271
300
  ### Fixed
272
301
 
@@ -306,7 +335,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
306
335
 
307
336
  ### Changed
308
337
 
309
- - BREAKING: NDatabase: add kind, pubkey, and created_at columns to nostr_tags table. These columns are non-nullable, so the old database will need to be deleted or manually migrated.
338
+ - BREAKING: NDatabase: add kind, pubkey, and created_at columns to nostr_tags
339
+ table. These columns are non-nullable, so the old database will need to be
340
+ deleted or manually migrated.
310
341
 
311
342
  ## [0.26.3] - 2024-07-18
312
343
 
@@ -330,7 +361,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
330
361
 
331
362
  ### Changed
332
363
 
333
- - BREAKING: NPool: remove `reqRelays` option, add `reqRouter`. Rename `eventRelays` to `eventRouter`.
364
+ - BREAKING: NPool: remove `reqRelays` option, add `reqRouter`. Rename
365
+ `eventRelays` to `eventRouter`.
334
366
 
335
367
  ## [0.25.0] - 2024-06-29
336
368
 
@@ -348,7 +380,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
348
380
 
349
381
  ### Fixed
350
382
 
351
- - NDatabase: add intrinsic limits to filters when applicable, skip 0 limit filters.
383
+ - NDatabase: add intrinsic limits to filters when applicable, skip 0 limit
384
+ filters.
352
385
 
353
386
  ## [0.23.2] - 2024-06-13
354
387
 
@@ -391,7 +424,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
391
424
 
392
425
  ### Fixed
393
426
 
394
- - NostrMetadata: added `website` field so `NSchema.metadata()` returns the correct type.
427
+ - NostrMetadata: added `website` field so `NSchema.metadata()` returns the
428
+ correct type.
395
429
 
396
430
  ### Changed
397
431
 
@@ -416,7 +450,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
416
450
 
417
451
  ### Added
418
452
 
419
- - `NUploader` interface and two uploader classes under `@nostrify/nostrify/uploaders`.
453
+ - `NUploader` interface and two uploader classes under
454
+ `@nostrify/nostrify/uploaders`.
420
455
  - Blossom uploader (`BlossomUploader`) to upload files to Blossom servers.
421
456
  - nostr.build uploader (`NostrBuildUploader`) to upload files to nostr.build.
422
457
  - `NIP98` module to verify NIP-98 Requests.
@@ -431,11 +466,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
431
466
 
432
467
  ### Added
433
468
 
434
- - NConnectSigner: NIP-44 encryption support by setting `{ encryption: 'nip44' }` in the constructor.
469
+ - NConnectSigner: NIP-44 encryption support by setting `{ encryption: 'nip44' }`
470
+ in the constructor.
435
471
 
436
472
  ### Fixed
437
473
 
438
- - NDatabase: Postgres FTS now correctly uses the `searchText` option to create the search index.
474
+ - NDatabase: Postgres FTS now correctly uses the `searchText` option to create
475
+ the search index.
439
476
 
440
477
  ## [0.20.0] - 2024-05-16
441
478
 
@@ -445,7 +482,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
445
482
 
446
483
  ### Changed
447
484
 
448
- - BREAKING: NDatabase `fts5` option has been renamed to `fts`, and now accepts a string of either `'sqlite'` or `'postgres'`.
485
+ - BREAKING: NDatabase `fts5` option has been renamed to `fts`, and now accepts a
486
+ string of either `'sqlite'` or `'postgres'`.
449
487
 
450
488
  ## [0.19.2] - 2024-05-16
451
489
 
@@ -457,14 +495,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
457
495
 
458
496
  ### Fixed
459
497
 
460
- - NDatabase: fix deleting everything for the author when they delete a single event.
498
+ - NDatabase: fix deleting everything for the author when they delete a single
499
+ event.
461
500
 
462
501
  ## [0.19.0] - 2024-05-13
463
502
 
464
503
  ### Fixed
465
504
 
466
505
  - Improved performance of NDatabase when querying replaceable events by author.
467
- - NConnect.signEvent now throws if the connect message was rejected by the relay.
506
+ - NConnect.signEvent now throws if the connect message was rejected by the
507
+ relay.
468
508
 
469
509
  ## [0.18.0] - 2024-05-04
470
510
 
@@ -474,13 +514,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
474
514
 
475
515
  ## [0.17.1] - 2024-04-29
476
516
 
477
- Redeploy to JSR (to hopefully fix [npm compatibility](https://github.com/jsr-io/jsr/issues/446)).
517
+ Redeploy to JSR (to hopefully fix
518
+ [npm compatibility](https://github.com/jsr-io/jsr/issues/446)).
478
519
 
479
520
  ## [0.17.0] - 2024-04-28
480
521
 
481
522
  ### Changed
482
523
 
483
- - BREAKING: change `NRelay.req` return type from an AsyncGenerator to an AsyncIterable.
524
+ - BREAKING: change `NRelay.req` return type from an AsyncGenerator to an
525
+ AsyncIterable.
484
526
 
485
527
  ## [0.16.0] - 2024-04-25
486
528
 
@@ -504,7 +546,8 @@ Redeploy to JSR (to hopefully fix [npm compatibility](https://github.com/jsr-io/
504
546
 
505
547
  ### Fixed
506
548
 
507
- - `NConnectSigner` - fixed race condition between sending messages and receiving responses.
549
+ - `NConnectSigner` - fixed race condition between sending messages and receiving
550
+ responses.
508
551
 
509
552
  ## [0.14.1] - 2024-04-24
510
553
 
@@ -516,7 +559,8 @@ Redeploy to JSR (to hopefully fix [npm compatibility](https://github.com/jsr-io/
516
559
 
517
560
  ### Fixed
518
561
 
519
- - BREAKING: fixed `NConnectSigner.connect` method signature (it doesn't need to accept a pubkey).
562
+ - BREAKING: fixed `NConnectSigner.connect` method signature (it doesn't need to
563
+ accept a pubkey).
520
564
 
521
565
  ## [0.13.0] - 2024-04-24
522
566
 
@@ -534,7 +578,8 @@ Redeploy to JSR (to hopefully fix [npm compatibility](https://github.com/jsr-io/
534
578
 
535
579
  ### Added
536
580
 
537
- - Added `AnyPolicy` policy, a pipeline policy which rejects only if all policies reject.
581
+ - Added `AnyPolicy` policy, a pipeline policy which rejects only if all policies
582
+ reject.
538
583
 
539
584
  ### Changed
540
585
 
@@ -544,7 +589,8 @@ Redeploy to JSR (to hopefully fix [npm compatibility](https://github.com/jsr-io/
544
589
 
545
590
  ### Added
546
591
 
547
- - Added moderation policies. Including the `NPolicy` interface and several policies.
592
+ - Added moderation policies. Including the `NPolicy` interface and several
593
+ policies.
548
594
 
549
595
  ## [0.10.2] - 2024-04-21
550
596
 
package/NIP98.test.ts CHANGED
@@ -1,8 +1,6 @@
1
1
  import { test } from "node:test";
2
2
  import { deepStrictEqual, rejects } from "node:assert";
3
3
  import { generateSecretKey } from "nostr-tools";
4
- import { ZodError } from "zod";
5
-
6
4
  import { NIP98 } from "./NIP98.ts";
7
5
  import { NSecSigner } from "./NSecSigner.ts";
8
6
  import { N64 } from "./utils/mod.ts";
@@ -78,7 +76,7 @@ await test("NIP98.verify fails with invalid token", async () => {
78
76
 
79
77
  await rejects(
80
78
  () => NIP98.verify(request),
81
- ZodError,
79
+ (e) => e instanceof Error && e.message === "Invalid token",
82
80
  );
83
81
  });
84
82
 
package/NIP98.ts CHANGED
@@ -1,8 +1,8 @@
1
- import type { NostrEvent } from '@nostrify/types';
2
- import { encodeHex } from '@std/encoding/hex';
3
- import { verifyEvent as _verifyEvent } from 'nostr-tools';
1
+ import type { NostrEvent } from "@nostrify/types";
2
+ import { toHex } from "@smithy/util-hex-encoding";
3
+ import { verifyEvent as _verifyEvent } from "nostr-tools";
4
4
 
5
- import { N64 } from './utils/N64.ts';
5
+ import { N64 } from "./utils/N64.ts";
6
6
 
7
7
  /** [NIP-98](https://github.com/nostr-protocol/nips/blob/master/98.md) HTTP auth. */
8
8
  export class NIP98 {
@@ -10,27 +10,27 @@ export class NIP98 {
10
10
  static async template(
11
11
  request: Request,
12
12
  opts?: { validatePayload?: boolean },
13
- ): Promise<Omit<NostrEvent, 'id' | 'pubkey' | 'sig'>> {
13
+ ): Promise<Omit<NostrEvent, "id" | "pubkey" | "sig">> {
14
14
  const {
15
- validatePayload = ['POST', 'PUT', 'PATCH'].includes(request.method),
15
+ validatePayload = ["POST", "PUT", "PATCH"].includes(request.method),
16
16
  } = opts ?? {};
17
17
  const { method, url } = request;
18
18
 
19
19
  const tags = [
20
- ['method', method],
21
- ['u', url],
20
+ ["method", method],
21
+ ["u", url],
22
22
  ];
23
23
 
24
24
  if (validatePayload) {
25
25
  const buffer = await request.clone().arrayBuffer();
26
- const digest = await crypto.subtle.digest('SHA-256', buffer);
26
+ const digest = await crypto.subtle.digest("SHA-256", buffer);
27
27
 
28
- tags.push(['payload', encodeHex(digest)]);
28
+ tags.push(["payload", toHex(new Uint8Array(digest))]);
29
29
  }
30
30
 
31
31
  return {
32
32
  kind: 27235,
33
- content: '',
33
+ content: "",
34
34
  tags,
35
35
  created_at: Math.floor(Date.now() / 1000),
36
36
  };
@@ -47,48 +47,62 @@ export class NIP98 {
47
47
  ): Promise<NostrEvent> {
48
48
  const {
49
49
  maxAge = 60_000,
50
- validatePayload = ['POST', 'PUT', 'PATCH'].includes(request.method),
50
+ validatePayload = ["POST", "PUT", "PATCH"].includes(request.method),
51
51
  verifyEvent = _verifyEvent,
52
52
  } = opts ?? {};
53
53
 
54
- const header = request.headers.get('authorization');
54
+ const header = request.headers.get("authorization");
55
55
  if (!header) {
56
- throw new Error('Missing Nostr authorization header');
56
+ throw new Error("Missing Nostr authorization header");
57
57
  }
58
58
 
59
59
  const token = header.match(/^Nostr (.+)$/)?.[1];
60
60
  if (!token) {
61
- throw new Error('Missing Nostr authorization token');
61
+ throw new Error("Missing Nostr authorization token");
62
+ }
63
+
64
+ let event: NostrEvent;
65
+ try {
66
+ event = N64.decodeEvent(token);
67
+ } catch (e) {
68
+ if (
69
+ e instanceof TypeError &&
70
+ e.message.includes("Incorrect padding on base64")
71
+ ) {
72
+ throw new Error("Invalid token");
73
+ } else {
74
+ throw e;
75
+ }
62
76
  }
63
77
 
64
- const event = N64.decodeEvent(token);
65
78
  if (!verifyEvent(event)) {
66
- throw new Error('Event signature is invalid');
79
+ throw new Error("Event signature is invalid");
67
80
  }
68
81
 
69
82
  const age = Date.now() - (event.created_at * 1_000);
70
- const u = event.tags.find(([name]) => name === 'u')?.[1];
71
- const method = event.tags.find(([name]) => name === 'method')?.[1];
72
- const payload = event.tags.find(([name]) => name === 'payload')?.[1];
83
+ const u = event.tags.find(([name]) => name === "u")?.[1];
84
+ const method = event.tags.find(([name]) => name === "method")?.[1];
85
+ const payload = event.tags.find(([name]) => name === "payload")?.[1];
73
86
 
74
87
  if (event.kind !== 27235) {
75
- throw new Error('Event must be kind 27235');
88
+ throw new Error("Event must be kind 27235");
76
89
  }
77
90
  if (u !== request.url) {
78
- throw new Error('Event URL does not match request URL');
91
+ throw new Error("Event URL does not match request URL");
79
92
  }
80
93
  if (method !== request.method) {
81
- throw new Error('Event method does not match HTTP request method');
94
+ throw new Error("Event method does not match HTTP request method");
82
95
  }
83
96
  if (age >= maxAge) {
84
- throw new Error('Event expired');
97
+ throw new Error("Event expired");
85
98
  }
86
99
  if (validatePayload && payload !== undefined) {
87
100
  const buffer = await request.clone().arrayBuffer();
88
- const digest = await crypto.subtle.digest('SHA-256', buffer);
101
+ const digest = await crypto.subtle.digest("SHA-256", buffer);
102
+ const hexed = toHex(new Uint8Array(digest));
89
103
 
90
- if (encodeHex(digest) !== payload) {
91
- throw new Error('Event payload does not match request body');
104
+ if (hexed !== payload) {
105
+ throw new Error("Event payload does not match request body");
92
106
  }
93
107
  }
94
108
 
package/NRelay1.test.ts CHANGED
@@ -157,9 +157,12 @@ await test("NRelay1 idleTimeout", async () => {
157
157
  await using relay = new NRelay1(server.url, { idleTimeout: 100 });
158
158
 
159
159
  await it("websocket opens", async () => {
160
- await new Promise((resolve) => setTimeout(resolve, 20));
160
+ await new Promise((resolve) =>
161
+ relay.socket.addEventListener(WebsocketEvent.open, resolve, {
162
+ once: true,
163
+ })
164
+ );
161
165
  deepStrictEqual(relay.socket.readyState, WebSocket.OPEN);
162
- await new Promise((resolve) => setTimeout(resolve, 20));
163
166
  });
164
167
 
165
168
  await it("websocket closes after idleTimeout", async () => {
package/dist/NIP98.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import type { NostrEvent } from '@nostrify/types';
1
+ import type { NostrEvent } from "@nostrify/types";
2
2
  /** [NIP-98](https://github.com/nostr-protocol/nips/blob/master/98.md) HTTP auth. */
3
3
  export declare class NIP98 {
4
4
  /** Generate an auth event template from a Request. */
5
5
  static template(request: Request, opts?: {
6
6
  validatePayload?: boolean;
7
- }): Promise<Omit<NostrEvent, 'id' | 'pubkey' | 'sig'>>;
7
+ }): Promise<Omit<NostrEvent, "id" | "pubkey" | "sig">>;
8
8
  /** Compare the auth event with the request, throwing a human-readable error if validation fails. */
9
9
  static verify(request: Request, opts?: {
10
10
  maxAge?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"NIP98.d.ts","sourceRoot":"","sources":["../NIP98.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAMlD,oFAAoF;AACpF,qBAAa,KAAK;IAChB,sDAAsD;WACzC,QAAQ,CACnB,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GACnC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,QAAQ,GAAG,KAAK,CAAC,CAAC;IA0BrD,oGAAoG;WACvF,MAAM,CACjB,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE;QACL,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC;KAC9C,GACA,OAAO,CAAC,UAAU,CAAC;CAkDvB"}
1
+ {"version":3,"file":"NIP98.d.ts","sourceRoot":"","sources":["../NIP98.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAMlD,oFAAoF;AACpF,qBAAa,KAAK;IAChB,sDAAsD;WACzC,QAAQ,CACnB,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GACnC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,QAAQ,GAAG,KAAK,CAAC,CAAC;IA0BrD,oGAAoG;WACvF,MAAM,CACjB,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE;QACL,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC;KAC9C,GACA,OAAO,CAAC,UAAU,CAAC;CAgEvB"}
package/dist/NIP98.js CHANGED
@@ -1,4 +1,4 @@
1
- import { encodeHex } from "@std/encoding/hex";
1
+ import { toHex } from "@smithy/util-hex-encoding";
2
2
  import { verifyEvent as _verifyEvent } from "nostr-tools";
3
3
  import { N64 } from "./utils/N64.js";
4
4
  class NIP98 {
@@ -15,7 +15,7 @@ class NIP98 {
15
15
  if (validatePayload) {
16
16
  const buffer = await request.clone().arrayBuffer();
17
17
  const digest = await crypto.subtle.digest("SHA-256", buffer);
18
- tags.push(["payload", encodeHex(digest)]);
18
+ tags.push(["payload", toHex(new Uint8Array(digest))]);
19
19
  }
20
20
  return {
21
21
  kind: 27235,
@@ -39,7 +39,16 @@ class NIP98 {
39
39
  if (!token) {
40
40
  throw new Error("Missing Nostr authorization token");
41
41
  }
42
- const event = N64.decodeEvent(token);
42
+ let event;
43
+ try {
44
+ event = N64.decodeEvent(token);
45
+ } catch (e) {
46
+ if (e instanceof TypeError && e.message.includes("Incorrect padding on base64")) {
47
+ throw new Error("Invalid token");
48
+ } else {
49
+ throw e;
50
+ }
51
+ }
43
52
  if (!verifyEvent(event)) {
44
53
  throw new Error("Event signature is invalid");
45
54
  }
@@ -62,7 +71,8 @@ class NIP98 {
62
71
  if (validatePayload && payload !== void 0) {
63
72
  const buffer = await request.clone().arrayBuffer();
64
73
  const digest = await crypto.subtle.digest("SHA-256", buffer);
65
- if (encodeHex(digest) !== payload) {
74
+ const hexed = toHex(new Uint8Array(digest));
75
+ if (hexed !== payload) {
66
76
  throw new Error("Event payload does not match request body");
67
77
  }
68
78
  }
package/dist/NIP98.ts CHANGED
@@ -1,8 +1,8 @@
1
- import type { NostrEvent } from '@nostrify/types';
2
- import { encodeHex } from '@std/encoding/hex';
3
- import { verifyEvent as _verifyEvent } from 'nostr-tools';
1
+ import type { NostrEvent } from "@nostrify/types";
2
+ import { toHex } from "@smithy/util-hex-encoding";
3
+ import { verifyEvent as _verifyEvent } from "nostr-tools";
4
4
 
5
- import { N64 } from './utils/N64.ts';
5
+ import { N64 } from "./utils/N64.ts";
6
6
 
7
7
  /** [NIP-98](https://github.com/nostr-protocol/nips/blob/master/98.md) HTTP auth. */
8
8
  export class NIP98 {
@@ -10,27 +10,27 @@ export class NIP98 {
10
10
  static async template(
11
11
  request: Request,
12
12
  opts?: { validatePayload?: boolean },
13
- ): Promise<Omit<NostrEvent, 'id' | 'pubkey' | 'sig'>> {
13
+ ): Promise<Omit<NostrEvent, "id" | "pubkey" | "sig">> {
14
14
  const {
15
- validatePayload = ['POST', 'PUT', 'PATCH'].includes(request.method),
15
+ validatePayload = ["POST", "PUT", "PATCH"].includes(request.method),
16
16
  } = opts ?? {};
17
17
  const { method, url } = request;
18
18
 
19
19
  const tags = [
20
- ['method', method],
21
- ['u', url],
20
+ ["method", method],
21
+ ["u", url],
22
22
  ];
23
23
 
24
24
  if (validatePayload) {
25
25
  const buffer = await request.clone().arrayBuffer();
26
- const digest = await crypto.subtle.digest('SHA-256', buffer);
26
+ const digest = await crypto.subtle.digest("SHA-256", buffer);
27
27
 
28
- tags.push(['payload', encodeHex(digest)]);
28
+ tags.push(["payload", toHex(new Uint8Array(digest))]);
29
29
  }
30
30
 
31
31
  return {
32
32
  kind: 27235,
33
- content: '',
33
+ content: "",
34
34
  tags,
35
35
  created_at: Math.floor(Date.now() / 1000),
36
36
  };
@@ -47,48 +47,62 @@ export class NIP98 {
47
47
  ): Promise<NostrEvent> {
48
48
  const {
49
49
  maxAge = 60_000,
50
- validatePayload = ['POST', 'PUT', 'PATCH'].includes(request.method),
50
+ validatePayload = ["POST", "PUT", "PATCH"].includes(request.method),
51
51
  verifyEvent = _verifyEvent,
52
52
  } = opts ?? {};
53
53
 
54
- const header = request.headers.get('authorization');
54
+ const header = request.headers.get("authorization");
55
55
  if (!header) {
56
- throw new Error('Missing Nostr authorization header');
56
+ throw new Error("Missing Nostr authorization header");
57
57
  }
58
58
 
59
59
  const token = header.match(/^Nostr (.+)$/)?.[1];
60
60
  if (!token) {
61
- throw new Error('Missing Nostr authorization token');
61
+ throw new Error("Missing Nostr authorization token");
62
+ }
63
+
64
+ let event: NostrEvent;
65
+ try {
66
+ event = N64.decodeEvent(token);
67
+ } catch (e) {
68
+ if (
69
+ e instanceof TypeError &&
70
+ e.message.includes("Incorrect padding on base64")
71
+ ) {
72
+ throw new Error("Invalid token");
73
+ } else {
74
+ throw e;
75
+ }
62
76
  }
63
77
 
64
- const event = N64.decodeEvent(token);
65
78
  if (!verifyEvent(event)) {
66
- throw new Error('Event signature is invalid');
79
+ throw new Error("Event signature is invalid");
67
80
  }
68
81
 
69
82
  const age = Date.now() - (event.created_at * 1_000);
70
- const u = event.tags.find(([name]) => name === 'u')?.[1];
71
- const method = event.tags.find(([name]) => name === 'method')?.[1];
72
- const payload = event.tags.find(([name]) => name === 'payload')?.[1];
83
+ const u = event.tags.find(([name]) => name === "u")?.[1];
84
+ const method = event.tags.find(([name]) => name === "method")?.[1];
85
+ const payload = event.tags.find(([name]) => name === "payload")?.[1];
73
86
 
74
87
  if (event.kind !== 27235) {
75
- throw new Error('Event must be kind 27235');
88
+ throw new Error("Event must be kind 27235");
76
89
  }
77
90
  if (u !== request.url) {
78
- throw new Error('Event URL does not match request URL');
91
+ throw new Error("Event URL does not match request URL");
79
92
  }
80
93
  if (method !== request.method) {
81
- throw new Error('Event method does not match HTTP request method');
94
+ throw new Error("Event method does not match HTTP request method");
82
95
  }
83
96
  if (age >= maxAge) {
84
- throw new Error('Event expired');
97
+ throw new Error("Event expired");
85
98
  }
86
99
  if (validatePayload && payload !== undefined) {
87
100
  const buffer = await request.clone().arrayBuffer();
88
- const digest = await crypto.subtle.digest('SHA-256', buffer);
101
+ const digest = await crypto.subtle.digest("SHA-256", buffer);
102
+ const hexed = toHex(new Uint8Array(digest));
89
103
 
90
- if (encodeHex(digest) !== payload) {
91
- throw new Error('Event payload does not match request body');
104
+ if (hexed !== payload) {
105
+ throw new Error("Event payload does not match request body");
92
106
  }
93
107
  }
94
108