h3 1.2.1 → 1.3.0

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.
package/README.md CHANGED
@@ -148,6 +148,8 @@ H3 has a concept of composable utilities that accept `event` (from `eventHandler
148
148
  - `createError({ statusCode, statusMessage, data? })`
149
149
  - `sendProxy(event, { target, headers?, fetchOptions?, fetch?, sendStream? })`
150
150
  - `proxyRequest(event, { target, headers?, fetchOptions?, fetch?, sendStream? })`
151
+ - `fetchWithEvent(event, req, init, { fetch? }?)`
152
+ - `getProxyRequestHeaders(event)`
151
153
  - `sendNoContent(event, code = 204)`
152
154
  - `setResponseStatus(event, status)`
153
155
  - `getResponseStatus(event)`
package/dist/index.cjs CHANGED
@@ -5,6 +5,7 @@ const radix3 = require('radix3');
5
5
  const destr = require('destr');
6
6
  const cookieEs = require('cookie-es');
7
7
  const crypto = require('uncrypto');
8
+ const ironWebcrypto = require('iron-webcrypto');
8
9
 
9
10
  function useBase(base, handler) {
10
11
  base = ufo.withoutTrailingSlash(base);
@@ -394,13 +395,7 @@ async function proxyRequest(event, target, opts = {}) {
394
395
  if (PayloadMethods.has(method)) {
395
396
  body = await readRawBody(event).catch(() => void 0);
396
397
  }
397
- const headers = /* @__PURE__ */ Object.create(null);
398
- const reqHeaders = getRequestHeaders(event);
399
- for (const name in reqHeaders) {
400
- if (!ignoredHeaders.has(name)) {
401
- headers[name] = reqHeaders[name];
402
- }
403
- }
398
+ const headers = getProxyRequestHeaders(event);
404
399
  if (opts.fetchOptions?.headers) {
405
400
  Object.assign(headers, opts.fetchOptions.headers);
406
401
  }
@@ -418,13 +413,7 @@ async function proxyRequest(event, target, opts = {}) {
418
413
  });
419
414
  }
420
415
  async function sendProxy(event, target, opts = {}) {
421
- const _fetch = opts.fetch || globalThis.fetch;
422
- if (!_fetch) {
423
- throw new Error(
424
- "fetch is not available. Try importing `node-fetch-native/polyfill` for Node.js."
425
- );
426
- }
427
- const response = await _fetch(target, {
416
+ const response = await _getFetch(opts.fetch)(target, {
428
417
  headers: opts.headers,
429
418
  ...opts.fetchOptions
430
419
  });
@@ -439,22 +428,49 @@ async function sendProxy(event, target, opts = {}) {
439
428
  }
440
429
  event.node.res.setHeader(key, value);
441
430
  }
442
- try {
443
- if (response.body) {
444
- if (opts.sendStream === false) {
445
- const data = new Uint8Array(await response.arrayBuffer());
446
- event.node.res.end(data);
447
- } else {
448
- for await (const chunk of response.body) {
449
- event.node.res.write(chunk);
450
- }
451
- event.node.res.end();
452
- }
431
+ if (response._data !== void 0) {
432
+ return response._data;
433
+ }
434
+ if (opts.sendStream === false) {
435
+ const data = new Uint8Array(await response.arrayBuffer());
436
+ return event.node.res.end(data);
437
+ }
438
+ for await (const chunk of response.body) {
439
+ event.node.res.write(chunk);
440
+ }
441
+ return event.node.res.end();
442
+ }
443
+ function getProxyRequestHeaders(event) {
444
+ const headers = /* @__PURE__ */ Object.create(null);
445
+ const reqHeaders = getRequestHeaders(event);
446
+ for (const name in reqHeaders) {
447
+ if (!ignoredHeaders.has(name)) {
448
+ headers[name] = reqHeaders[name];
453
449
  }
454
- } catch (error) {
455
- event.node.res.end();
456
- throw error;
457
450
  }
451
+ return headers;
452
+ }
453
+ function fetchWithEvent(event, req, init, options) {
454
+ return _getFetch(options?.fetch)(req, {
455
+ ...init,
456
+ // @ts-ignore (context is used for unenv and local fetch)
457
+ context: init.context || event.context,
458
+ headers: {
459
+ ...getProxyRequestHeaders(event),
460
+ ...init?.headers
461
+ }
462
+ });
463
+ }
464
+ function _getFetch(_fetch) {
465
+ if (_fetch) {
466
+ return _fetch;
467
+ }
468
+ if (globalThis.fetch) {
469
+ return globalThis.fetch;
470
+ }
471
+ throw new Error(
472
+ "fetch is not available. Try importing `node-fetch-native/polyfill` for Node.js."
473
+ );
458
474
  }
459
475
 
460
476
  const defer = typeof setImmediate !== "undefined" ? setImmediate : (fn) => fn();
@@ -589,296 +605,6 @@ ${header}: ${value}`;
589
605
  }
590
606
  }
591
607
 
592
- const base64urlEncode = (value) => (Buffer.isBuffer(value) ? value : Buffer.from(value)).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
593
- const defaults = {
594
- encryption: {
595
- saltBits: 256,
596
- algorithm: "aes-256-cbc",
597
- iterations: 1,
598
- minPasswordlength: 32
599
- },
600
- integrity: {
601
- saltBits: 256,
602
- algorithm: "sha256",
603
- iterations: 1,
604
- minPasswordlength: 32
605
- },
606
- ttl: 0,
607
- timestampSkewSec: 60,
608
- localtimeOffsetMsec: 0
609
- };
610
- const clone = (options) => ({
611
- ...options,
612
- encryption: { ...options.encryption },
613
- integrity: { ...options.integrity }
614
- });
615
- const algorithms = {
616
- "aes-128-ctr": { keyBits: 128, ivBits: 128, name: "AES-CTR" },
617
- "aes-256-cbc": { keyBits: 256, ivBits: 128, name: "AES-CBC" },
618
- sha256: { keyBits: 256, name: "SHA-256" }
619
- };
620
- const macFormatVersion = "2";
621
- const macPrefix = `Fe26.${macFormatVersion}`;
622
- const randomBytes = (_crypto, size) => {
623
- const bytes = Buffer.allocUnsafe(size);
624
- _crypto.getRandomValues(bytes);
625
- return bytes;
626
- };
627
- const randomBits = (_crypto, bits) => {
628
- if (bits < 1) {
629
- throw new Error("Invalid random bits count");
630
- }
631
- const bytes = Math.ceil(bits / 8);
632
- return randomBytes(_crypto, bytes);
633
- };
634
- const pbkdf2 = async (_crypto, password, salt, iterations, keyLength, hash) => {
635
- const textEncoder = new TextEncoder();
636
- const passwordBuffer = textEncoder.encode(password);
637
- const importedKey = await _crypto.subtle.importKey(
638
- "raw",
639
- passwordBuffer,
640
- "PBKDF2",
641
- false,
642
- ["deriveBits"]
643
- );
644
- const saltBuffer = textEncoder.encode(salt);
645
- const params = { name: "PBKDF2", hash, salt: saltBuffer, iterations };
646
- const derivation = await _crypto.subtle.deriveBits(
647
- params,
648
- importedKey,
649
- keyLength * 8
650
- );
651
- return Buffer.from(derivation);
652
- };
653
- const generateKey = async (_crypto, password, options) => {
654
- if (password == null || password.length === 0) {
655
- throw new Error("Empty password");
656
- }
657
- if (options == null || typeof options !== "object") {
658
- throw new Error("Bad options");
659
- }
660
- if (!(options.algorithm in algorithms)) {
661
- throw new Error(`Unknown algorithm: ${options.algorithm}`);
662
- }
663
- const algorithm = algorithms[options.algorithm];
664
- const result = {};
665
- const hmac = options.hmac ?? false;
666
- const id = hmac ? { name: "HMAC", hash: algorithm.name } : { name: algorithm.name };
667
- const usage = hmac ? ["sign", "verify"] : ["encrypt", "decrypt"];
668
- if (typeof password === "string") {
669
- if (password.length < options.minPasswordlength) {
670
- throw new Error(
671
- `Password string too short (min ${options.minPasswordlength} characters required)`
672
- );
673
- }
674
- let { salt = "" } = options;
675
- if (!salt) {
676
- const { saltBits = 0 } = options;
677
- if (!saltBits) {
678
- throw new Error("Missing salt and saltBits options");
679
- }
680
- const randomSalt = randomBits(_crypto, saltBits);
681
- salt = randomSalt.toString("hex");
682
- }
683
- const derivedKey = await pbkdf2(
684
- _crypto,
685
- password,
686
- salt,
687
- options.iterations,
688
- algorithm.keyBits / 8,
689
- "SHA-1"
690
- );
691
- const importedEncryptionKey = await _crypto.subtle.importKey(
692
- "raw",
693
- derivedKey,
694
- id,
695
- false,
696
- usage
697
- );
698
- result.key = importedEncryptionKey;
699
- result.salt = salt;
700
- } else {
701
- if (password.length < algorithm.keyBits / 8) {
702
- throw new Error("Key buffer (password) too small");
703
- }
704
- result.key = await _crypto.subtle.importKey(
705
- "raw",
706
- password,
707
- id,
708
- false,
709
- usage
710
- );
711
- result.salt = "";
712
- }
713
- if (options.iv) {
714
- result.iv = options.iv;
715
- } else if ("ivBits" in algorithm) {
716
- result.iv = randomBits(_crypto, algorithm.ivBits);
717
- }
718
- return result;
719
- };
720
- const encrypt = async (_crypto, password, options, data) => {
721
- const key = await generateKey(_crypto, password, options);
722
- const textEncoder = new TextEncoder();
723
- const textBuffer = textEncoder.encode(data);
724
- const encrypted = await _crypto.subtle.encrypt(
725
- { name: algorithms[options.algorithm].name, iv: key.iv },
726
- key.key,
727
- textBuffer
728
- );
729
- return { encrypted: Buffer.from(encrypted), key };
730
- };
731
- const decrypt = async (_crypto, password, options, data) => {
732
- const key = await generateKey(_crypto, password, options);
733
- const decrypted = await _crypto.subtle.decrypt(
734
- { name: algorithms[options.algorithm].name, iv: key.iv },
735
- key.key,
736
- Buffer.isBuffer(data) ? data : Buffer.from(data)
737
- );
738
- const textDecoder = new TextDecoder();
739
- return textDecoder.decode(decrypted);
740
- };
741
- const hmacWithPassword = async (_crypto, password, options, data) => {
742
- const key = await generateKey(_crypto, password, { ...options, hmac: true });
743
- const textEncoder = new TextEncoder();
744
- const textBuffer = textEncoder.encode(data);
745
- const signed = await _crypto.subtle.sign(
746
- { name: "HMAC" },
747
- key.key,
748
- textBuffer
749
- );
750
- const digest = base64urlEncode(Buffer.from(signed));
751
- return { digest, salt: key.salt };
752
- };
753
- const normalizePassword = (password) => {
754
- if (typeof password === "object" && !Buffer.isBuffer(password)) {
755
- if ("secret" in password) {
756
- return {
757
- id: password.id,
758
- encryption: password.secret,
759
- integrity: password.secret
760
- };
761
- }
762
- return {
763
- id: password.id,
764
- encryption: password.encryption,
765
- integrity: password.integrity
766
- };
767
- }
768
- return { encryption: password, integrity: password };
769
- };
770
- const seal = async (_crypto, object, password, options) => {
771
- if (!password) {
772
- throw new Error("Empty password");
773
- }
774
- const opts = clone(options);
775
- const now = Date.now() + (opts.localtimeOffsetMsec || 0);
776
- const objectString = JSON.stringify(object);
777
- const pass = normalizePassword(password);
778
- const { id = "" } = pass;
779
- if (id && !/^\w+$/.test(id)) {
780
- throw new Error("Invalid password id");
781
- }
782
- const { encrypted, key } = await encrypt(
783
- _crypto,
784
- pass.encryption,
785
- opts.encryption,
786
- objectString
787
- );
788
- const encryptedB64 = base64urlEncode(encrypted);
789
- const iv = base64urlEncode(key.iv);
790
- const expiration = opts.ttl ? now + opts.ttl : "";
791
- const macBaseString = `${macPrefix}*${id}*${key.salt}*${iv}*${encryptedB64}*${expiration}`;
792
- const mac = await hmacWithPassword(
793
- _crypto,
794
- pass.integrity,
795
- opts.integrity,
796
- macBaseString
797
- );
798
- const sealed = `${macBaseString}*${mac.salt}*${mac.digest}`;
799
- return sealed;
800
- };
801
- const fixedTimeComparison = (a, b) => {
802
- let mismatch = a.length === b.length ? 0 : 1;
803
- if (mismatch) {
804
- b = a;
805
- }
806
- for (let i = 0; i < a.length; i += 1) {
807
- mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
808
- }
809
- return mismatch === 0;
810
- };
811
- const unseal = async (_crypto, sealed, password, options) => {
812
- if (!password) {
813
- throw new Error("Empty password");
814
- }
815
- const opts = clone(options);
816
- const now = Date.now() + (opts.localtimeOffsetMsec || 0);
817
- const parts = sealed.split("*");
818
- if (parts.length !== 8) {
819
- throw new Error("Incorrect number of sealed components");
820
- }
821
- const prefix = parts[0];
822
- const passwordId = parts[1];
823
- const encryptionSalt = parts[2];
824
- const encryptionIv = parts[3];
825
- const encryptedB64 = parts[4];
826
- const expiration = parts[5];
827
- const hmacSalt = parts[6];
828
- const hmac = parts[7];
829
- const macBaseString = `${prefix}*${passwordId}*${encryptionSalt}*${encryptionIv}*${encryptedB64}*${expiration}`;
830
- if (macPrefix !== prefix) {
831
- throw new Error("Wrong mac prefix");
832
- }
833
- if (expiration) {
834
- if (!/^\d+$/.test(expiration)) {
835
- throw new Error("Invalid expiration");
836
- }
837
- const exp = Number.parseInt(expiration, 10);
838
- if (exp <= now - opts.timestampSkewSec * 1e3) {
839
- throw new Error("Expired seal");
840
- }
841
- }
842
- if (typeof password === "undefined" || typeof password === "string" && password.length === 0) {
843
- throw new Error("Empty password");
844
- }
845
- let pass;
846
- if (typeof password === "object" && !Buffer.isBuffer(password)) {
847
- if (!((passwordId || "default") in password)) {
848
- throw new Error(`Cannot find password: ${passwordId}`);
849
- }
850
- pass = password[passwordId || "default"];
851
- } else {
852
- pass = password;
853
- }
854
- pass = normalizePassword(pass);
855
- const macOptions = opts.integrity;
856
- macOptions.salt = hmacSalt;
857
- const mac = await hmacWithPassword(
858
- _crypto,
859
- pass.integrity,
860
- macOptions,
861
- macBaseString
862
- );
863
- if (!fixedTimeComparison(mac.digest, hmac)) {
864
- throw new Error("Bad hmac value");
865
- }
866
- const encrypted = Buffer.from(encryptedB64, "base64");
867
- const decryptOptions = opts.encryption;
868
- decryptOptions.salt = encryptionSalt;
869
- decryptOptions.iv = Buffer.from(encryptionIv, "base64");
870
- const decrypted = await decrypt(
871
- _crypto,
872
- pass.encryption,
873
- decryptOptions,
874
- encrypted
875
- );
876
- if (decrypted) {
877
- return JSON.parse(decrypted);
878
- }
879
- return null;
880
- };
881
-
882
608
  const DEFAULT_NAME = "h3";
883
609
  const DEFAULT_COOKIE = {
884
610
  path: "/",
@@ -921,11 +647,11 @@ async function getSession(event, config) {
921
647
  session.id = (config.crypto || crypto).randomUUID();
922
648
  await updateSession(event, config);
923
649
  } else {
924
- const unsealed = await unseal(
650
+ const unsealed = await ironWebcrypto.unseal(
925
651
  config.crypto || crypto,
926
652
  reqCookie,
927
653
  config.password,
928
- config.seal || defaults
654
+ config.seal || ironWebcrypto.defaults
929
655
  );
930
656
  Object.assign(session, unsealed);
931
657
  }
@@ -940,11 +666,11 @@ async function updateSession(event, config, update) {
940
666
  if (update) {
941
667
  Object.assign(session.data, update);
942
668
  }
943
- const sealed = await seal(
669
+ const sealed = await ironWebcrypto.seal(
944
670
  config.crypto || crypto,
945
671
  session,
946
672
  config.password,
947
- config.seal || defaults
673
+ config.seal || ironWebcrypto.defaults
948
674
  );
949
675
  setCookie(event, sessionName, sealed, config.cookie || DEFAULT_COOKIE);
950
676
  return session;
@@ -1431,11 +1157,13 @@ exports.defineNodeMiddleware = defineNodeMiddleware;
1431
1157
  exports.deleteCookie = deleteCookie;
1432
1158
  exports.dynamicEventHandler = dynamicEventHandler;
1433
1159
  exports.eventHandler = eventHandler;
1160
+ exports.fetchWithEvent = fetchWithEvent;
1434
1161
  exports.fromNodeMiddleware = fromNodeMiddleware;
1435
1162
  exports.getCookie = getCookie;
1436
1163
  exports.getHeader = getHeader;
1437
1164
  exports.getHeaders = getHeaders;
1438
1165
  exports.getMethod = getMethod;
1166
+ exports.getProxyRequestHeaders = getProxyRequestHeaders;
1439
1167
  exports.getQuery = getQuery;
1440
1168
  exports.getRequestHeader = getRequestHeader;
1441
1169
  exports.getRequestHeaders = getRequestHeaders;
package/dist/index.d.ts CHANGED
@@ -1,56 +1,10 @@
1
1
  import { CookieSerializeOptions } from 'cookie-es';
2
+ import { SealOptions } from 'iron-webcrypto';
2
3
  import { IncomingMessage, ServerResponse, OutgoingMessage } from 'node:http';
3
4
  export { IncomingMessage as NodeIncomingMessage, ServerResponse as NodeServerResponse } from 'node:http';
4
5
  import * as ufo from 'ufo';
5
6
 
6
- /**
7
- * seal() method options.
8
- */
9
- interface SealOptionsSub {
10
- /**
11
- * The length of the salt (random buffer used to ensure that two identical objects will generate a different encrypted result). Defaults to 256.
12
- */
13
- saltBits: number;
14
- /**
15
- * The algorithm used. Defaults to 'aes-256-cbc' for encryption and 'sha256' for integrity.
16
- */
17
- algorithm: "aes-128-ctr" | "aes-256-cbc" | "sha256";
18
- /**
19
- * The number of iterations used to derive a key from the password. Defaults to 1.
20
- */
21
- iterations: number;
22
- /**
23
- * Minimum password size. Defaults to 32.
24
- */
25
- minPasswordlength: number;
26
- }
27
- /**
28
- * Options for customizing the key derivation algorithm used to generate encryption and integrity verification keys as well as the algorithms and salt sizes used.
29
- */
30
- interface SealOptions {
31
- /**
32
- * Encryption step options.
33
- */
34
- encryption: SealOptionsSub;
35
- /**
36
- * Integrity step options.
37
- */
38
- integrity: SealOptionsSub;
39
- /**
40
- * Sealed object lifetime in milliseconds where 0 means forever. Defaults to 0.
41
- */
42
- ttl: number;
43
- /**
44
- * Number of seconds of permitted clock skew for incoming expirations. Defaults to 60 seconds.
45
- */
46
- timestampSkewSec: number;
47
- /**
48
- * Local clock time offset, expressed in number of milliseconds (positive or negative). Defaults to 0.
49
- */
50
- localtimeOffsetMsec: number;
51
- }
52
-
53
- type SessionDataT = Record<string, string | number | boolean>;
7
+ type SessionDataT = Record<string, any>;
54
8
  type SessionData<T extends SessionDataT = SessionDataT> = T;
55
9
  interface Session<T extends SessionDataT = SessionDataT> {
56
10
  id: string;
@@ -338,8 +292,12 @@ interface ProxyOptions {
338
292
  fetch?: typeof fetch;
339
293
  sendStream?: boolean;
340
294
  }
341
- declare function proxyRequest(event: H3Event, target: string, opts?: ProxyOptions): Promise<void>;
342
- declare function sendProxy(event: H3Event, target: string, opts?: ProxyOptions): Promise<void>;
295
+ declare function proxyRequest(event: H3Event, target: string, opts?: ProxyOptions): Promise<any>;
296
+ declare function sendProxy(event: H3Event, target: string, opts?: ProxyOptions): Promise<any>;
297
+ declare function getProxyRequestHeaders(event: H3Event): any;
298
+ declare function fetchWithEvent(event: H3Event, req: RequestInfo | URL, init?: RequestInit, options?: {
299
+ fetch: typeof fetch;
300
+ }): Promise<Response>;
343
301
 
344
302
  declare function getQuery(event: H3Event): ufo.QueryObject;
345
303
  declare function getRouterParams(event: H3Event): H3Event["context"];
@@ -395,4 +353,4 @@ interface CreateRouterOptions {
395
353
  }
396
354
  declare function createRouter(opts?: CreateRouterOptions): Router;
397
355
 
398
- export { AddRouteShortcuts, App, AppOptions, AppUse, CacheConditions, CreateRouterOptions, DynamicEventHandler, Encoding, EventHandler, EventHandlerResponse, H3Error, H3Event, H3EventContext, H3Headers, H3Response, HTTPMethod, InputLayer, InputStack, Layer, LazyEventHandler, MIMES, Matcher, NodeEventContext, NodeListener, NodeMiddleware, NodePromisifiedHandler, ProxyOptions, RequestHeaders, Router, RouterMethod, RouterUse, Session, SessionConfig, SessionData, Stack, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getQuery, getRequestHeader, getRequestHeaders, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, isError, isEvent, isEventHandler, isMethod, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, toEventHandler, toNodeListener, updateSession, use, useBase, useSession, writeEarlyHints };
356
+ export { AddRouteShortcuts, App, AppOptions, AppUse, CacheConditions, CreateRouterOptions, DynamicEventHandler, Encoding, EventHandler, EventHandlerResponse, H3Error, H3Event, H3EventContext, H3Headers, H3Response, HTTPMethod, InputLayer, InputStack, Layer, LazyEventHandler, MIMES, Matcher, NodeEventContext, NodeListener, NodeMiddleware, NodePromisifiedHandler, ProxyOptions, RequestHeaders, Router, RouterMethod, RouterUse, Session, SessionConfig, SessionData, Stack, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, isError, isEvent, isEventHandler, isMethod, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, toEventHandler, toNodeListener, updateSession, use, useBase, useSession, writeEarlyHints };
package/dist/index.mjs CHANGED
@@ -3,6 +3,7 @@ import { createRouter as createRouter$1 } from 'radix3';
3
3
  import destr from 'destr';
4
4
  import { parse as parse$1, serialize } from 'cookie-es';
5
5
  import crypto from 'uncrypto';
6
+ import { unseal, defaults, seal } from 'iron-webcrypto';
6
7
 
7
8
  function useBase(base, handler) {
8
9
  base = withoutTrailingSlash(base);
@@ -392,13 +393,7 @@ async function proxyRequest(event, target, opts = {}) {
392
393
  if (PayloadMethods.has(method)) {
393
394
  body = await readRawBody(event).catch(() => void 0);
394
395
  }
395
- const headers = /* @__PURE__ */ Object.create(null);
396
- const reqHeaders = getRequestHeaders(event);
397
- for (const name in reqHeaders) {
398
- if (!ignoredHeaders.has(name)) {
399
- headers[name] = reqHeaders[name];
400
- }
401
- }
396
+ const headers = getProxyRequestHeaders(event);
402
397
  if (opts.fetchOptions?.headers) {
403
398
  Object.assign(headers, opts.fetchOptions.headers);
404
399
  }
@@ -416,13 +411,7 @@ async function proxyRequest(event, target, opts = {}) {
416
411
  });
417
412
  }
418
413
  async function sendProxy(event, target, opts = {}) {
419
- const _fetch = opts.fetch || globalThis.fetch;
420
- if (!_fetch) {
421
- throw new Error(
422
- "fetch is not available. Try importing `node-fetch-native/polyfill` for Node.js."
423
- );
424
- }
425
- const response = await _fetch(target, {
414
+ const response = await _getFetch(opts.fetch)(target, {
426
415
  headers: opts.headers,
427
416
  ...opts.fetchOptions
428
417
  });
@@ -437,22 +426,49 @@ async function sendProxy(event, target, opts = {}) {
437
426
  }
438
427
  event.node.res.setHeader(key, value);
439
428
  }
440
- try {
441
- if (response.body) {
442
- if (opts.sendStream === false) {
443
- const data = new Uint8Array(await response.arrayBuffer());
444
- event.node.res.end(data);
445
- } else {
446
- for await (const chunk of response.body) {
447
- event.node.res.write(chunk);
448
- }
449
- event.node.res.end();
450
- }
429
+ if (response._data !== void 0) {
430
+ return response._data;
431
+ }
432
+ if (opts.sendStream === false) {
433
+ const data = new Uint8Array(await response.arrayBuffer());
434
+ return event.node.res.end(data);
435
+ }
436
+ for await (const chunk of response.body) {
437
+ event.node.res.write(chunk);
438
+ }
439
+ return event.node.res.end();
440
+ }
441
+ function getProxyRequestHeaders(event) {
442
+ const headers = /* @__PURE__ */ Object.create(null);
443
+ const reqHeaders = getRequestHeaders(event);
444
+ for (const name in reqHeaders) {
445
+ if (!ignoredHeaders.has(name)) {
446
+ headers[name] = reqHeaders[name];
451
447
  }
452
- } catch (error) {
453
- event.node.res.end();
454
- throw error;
455
448
  }
449
+ return headers;
450
+ }
451
+ function fetchWithEvent(event, req, init, options) {
452
+ return _getFetch(options?.fetch)(req, {
453
+ ...init,
454
+ // @ts-ignore (context is used for unenv and local fetch)
455
+ context: init.context || event.context,
456
+ headers: {
457
+ ...getProxyRequestHeaders(event),
458
+ ...init?.headers
459
+ }
460
+ });
461
+ }
462
+ function _getFetch(_fetch) {
463
+ if (_fetch) {
464
+ return _fetch;
465
+ }
466
+ if (globalThis.fetch) {
467
+ return globalThis.fetch;
468
+ }
469
+ throw new Error(
470
+ "fetch is not available. Try importing `node-fetch-native/polyfill` for Node.js."
471
+ );
456
472
  }
457
473
 
458
474
  const defer = typeof setImmediate !== "undefined" ? setImmediate : (fn) => fn();
@@ -587,296 +603,6 @@ ${header}: ${value}`;
587
603
  }
588
604
  }
589
605
 
590
- const base64urlEncode = (value) => (Buffer.isBuffer(value) ? value : Buffer.from(value)).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
591
- const defaults = {
592
- encryption: {
593
- saltBits: 256,
594
- algorithm: "aes-256-cbc",
595
- iterations: 1,
596
- minPasswordlength: 32
597
- },
598
- integrity: {
599
- saltBits: 256,
600
- algorithm: "sha256",
601
- iterations: 1,
602
- minPasswordlength: 32
603
- },
604
- ttl: 0,
605
- timestampSkewSec: 60,
606
- localtimeOffsetMsec: 0
607
- };
608
- const clone = (options) => ({
609
- ...options,
610
- encryption: { ...options.encryption },
611
- integrity: { ...options.integrity }
612
- });
613
- const algorithms = {
614
- "aes-128-ctr": { keyBits: 128, ivBits: 128, name: "AES-CTR" },
615
- "aes-256-cbc": { keyBits: 256, ivBits: 128, name: "AES-CBC" },
616
- sha256: { keyBits: 256, name: "SHA-256" }
617
- };
618
- const macFormatVersion = "2";
619
- const macPrefix = `Fe26.${macFormatVersion}`;
620
- const randomBytes = (_crypto, size) => {
621
- const bytes = Buffer.allocUnsafe(size);
622
- _crypto.getRandomValues(bytes);
623
- return bytes;
624
- };
625
- const randomBits = (_crypto, bits) => {
626
- if (bits < 1) {
627
- throw new Error("Invalid random bits count");
628
- }
629
- const bytes = Math.ceil(bits / 8);
630
- return randomBytes(_crypto, bytes);
631
- };
632
- const pbkdf2 = async (_crypto, password, salt, iterations, keyLength, hash) => {
633
- const textEncoder = new TextEncoder();
634
- const passwordBuffer = textEncoder.encode(password);
635
- const importedKey = await _crypto.subtle.importKey(
636
- "raw",
637
- passwordBuffer,
638
- "PBKDF2",
639
- false,
640
- ["deriveBits"]
641
- );
642
- const saltBuffer = textEncoder.encode(salt);
643
- const params = { name: "PBKDF2", hash, salt: saltBuffer, iterations };
644
- const derivation = await _crypto.subtle.deriveBits(
645
- params,
646
- importedKey,
647
- keyLength * 8
648
- );
649
- return Buffer.from(derivation);
650
- };
651
- const generateKey = async (_crypto, password, options) => {
652
- if (password == null || password.length === 0) {
653
- throw new Error("Empty password");
654
- }
655
- if (options == null || typeof options !== "object") {
656
- throw new Error("Bad options");
657
- }
658
- if (!(options.algorithm in algorithms)) {
659
- throw new Error(`Unknown algorithm: ${options.algorithm}`);
660
- }
661
- const algorithm = algorithms[options.algorithm];
662
- const result = {};
663
- const hmac = options.hmac ?? false;
664
- const id = hmac ? { name: "HMAC", hash: algorithm.name } : { name: algorithm.name };
665
- const usage = hmac ? ["sign", "verify"] : ["encrypt", "decrypt"];
666
- if (typeof password === "string") {
667
- if (password.length < options.minPasswordlength) {
668
- throw new Error(
669
- `Password string too short (min ${options.minPasswordlength} characters required)`
670
- );
671
- }
672
- let { salt = "" } = options;
673
- if (!salt) {
674
- const { saltBits = 0 } = options;
675
- if (!saltBits) {
676
- throw new Error("Missing salt and saltBits options");
677
- }
678
- const randomSalt = randomBits(_crypto, saltBits);
679
- salt = randomSalt.toString("hex");
680
- }
681
- const derivedKey = await pbkdf2(
682
- _crypto,
683
- password,
684
- salt,
685
- options.iterations,
686
- algorithm.keyBits / 8,
687
- "SHA-1"
688
- );
689
- const importedEncryptionKey = await _crypto.subtle.importKey(
690
- "raw",
691
- derivedKey,
692
- id,
693
- false,
694
- usage
695
- );
696
- result.key = importedEncryptionKey;
697
- result.salt = salt;
698
- } else {
699
- if (password.length < algorithm.keyBits / 8) {
700
- throw new Error("Key buffer (password) too small");
701
- }
702
- result.key = await _crypto.subtle.importKey(
703
- "raw",
704
- password,
705
- id,
706
- false,
707
- usage
708
- );
709
- result.salt = "";
710
- }
711
- if (options.iv) {
712
- result.iv = options.iv;
713
- } else if ("ivBits" in algorithm) {
714
- result.iv = randomBits(_crypto, algorithm.ivBits);
715
- }
716
- return result;
717
- };
718
- const encrypt = async (_crypto, password, options, data) => {
719
- const key = await generateKey(_crypto, password, options);
720
- const textEncoder = new TextEncoder();
721
- const textBuffer = textEncoder.encode(data);
722
- const encrypted = await _crypto.subtle.encrypt(
723
- { name: algorithms[options.algorithm].name, iv: key.iv },
724
- key.key,
725
- textBuffer
726
- );
727
- return { encrypted: Buffer.from(encrypted), key };
728
- };
729
- const decrypt = async (_crypto, password, options, data) => {
730
- const key = await generateKey(_crypto, password, options);
731
- const decrypted = await _crypto.subtle.decrypt(
732
- { name: algorithms[options.algorithm].name, iv: key.iv },
733
- key.key,
734
- Buffer.isBuffer(data) ? data : Buffer.from(data)
735
- );
736
- const textDecoder = new TextDecoder();
737
- return textDecoder.decode(decrypted);
738
- };
739
- const hmacWithPassword = async (_crypto, password, options, data) => {
740
- const key = await generateKey(_crypto, password, { ...options, hmac: true });
741
- const textEncoder = new TextEncoder();
742
- const textBuffer = textEncoder.encode(data);
743
- const signed = await _crypto.subtle.sign(
744
- { name: "HMAC" },
745
- key.key,
746
- textBuffer
747
- );
748
- const digest = base64urlEncode(Buffer.from(signed));
749
- return { digest, salt: key.salt };
750
- };
751
- const normalizePassword = (password) => {
752
- if (typeof password === "object" && !Buffer.isBuffer(password)) {
753
- if ("secret" in password) {
754
- return {
755
- id: password.id,
756
- encryption: password.secret,
757
- integrity: password.secret
758
- };
759
- }
760
- return {
761
- id: password.id,
762
- encryption: password.encryption,
763
- integrity: password.integrity
764
- };
765
- }
766
- return { encryption: password, integrity: password };
767
- };
768
- const seal = async (_crypto, object, password, options) => {
769
- if (!password) {
770
- throw new Error("Empty password");
771
- }
772
- const opts = clone(options);
773
- const now = Date.now() + (opts.localtimeOffsetMsec || 0);
774
- const objectString = JSON.stringify(object);
775
- const pass = normalizePassword(password);
776
- const { id = "" } = pass;
777
- if (id && !/^\w+$/.test(id)) {
778
- throw new Error("Invalid password id");
779
- }
780
- const { encrypted, key } = await encrypt(
781
- _crypto,
782
- pass.encryption,
783
- opts.encryption,
784
- objectString
785
- );
786
- const encryptedB64 = base64urlEncode(encrypted);
787
- const iv = base64urlEncode(key.iv);
788
- const expiration = opts.ttl ? now + opts.ttl : "";
789
- const macBaseString = `${macPrefix}*${id}*${key.salt}*${iv}*${encryptedB64}*${expiration}`;
790
- const mac = await hmacWithPassword(
791
- _crypto,
792
- pass.integrity,
793
- opts.integrity,
794
- macBaseString
795
- );
796
- const sealed = `${macBaseString}*${mac.salt}*${mac.digest}`;
797
- return sealed;
798
- };
799
- const fixedTimeComparison = (a, b) => {
800
- let mismatch = a.length === b.length ? 0 : 1;
801
- if (mismatch) {
802
- b = a;
803
- }
804
- for (let i = 0; i < a.length; i += 1) {
805
- mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
806
- }
807
- return mismatch === 0;
808
- };
809
- const unseal = async (_crypto, sealed, password, options) => {
810
- if (!password) {
811
- throw new Error("Empty password");
812
- }
813
- const opts = clone(options);
814
- const now = Date.now() + (opts.localtimeOffsetMsec || 0);
815
- const parts = sealed.split("*");
816
- if (parts.length !== 8) {
817
- throw new Error("Incorrect number of sealed components");
818
- }
819
- const prefix = parts[0];
820
- const passwordId = parts[1];
821
- const encryptionSalt = parts[2];
822
- const encryptionIv = parts[3];
823
- const encryptedB64 = parts[4];
824
- const expiration = parts[5];
825
- const hmacSalt = parts[6];
826
- const hmac = parts[7];
827
- const macBaseString = `${prefix}*${passwordId}*${encryptionSalt}*${encryptionIv}*${encryptedB64}*${expiration}`;
828
- if (macPrefix !== prefix) {
829
- throw new Error("Wrong mac prefix");
830
- }
831
- if (expiration) {
832
- if (!/^\d+$/.test(expiration)) {
833
- throw new Error("Invalid expiration");
834
- }
835
- const exp = Number.parseInt(expiration, 10);
836
- if (exp <= now - opts.timestampSkewSec * 1e3) {
837
- throw new Error("Expired seal");
838
- }
839
- }
840
- if (typeof password === "undefined" || typeof password === "string" && password.length === 0) {
841
- throw new Error("Empty password");
842
- }
843
- let pass;
844
- if (typeof password === "object" && !Buffer.isBuffer(password)) {
845
- if (!((passwordId || "default") in password)) {
846
- throw new Error(`Cannot find password: ${passwordId}`);
847
- }
848
- pass = password[passwordId || "default"];
849
- } else {
850
- pass = password;
851
- }
852
- pass = normalizePassword(pass);
853
- const macOptions = opts.integrity;
854
- macOptions.salt = hmacSalt;
855
- const mac = await hmacWithPassword(
856
- _crypto,
857
- pass.integrity,
858
- macOptions,
859
- macBaseString
860
- );
861
- if (!fixedTimeComparison(mac.digest, hmac)) {
862
- throw new Error("Bad hmac value");
863
- }
864
- const encrypted = Buffer.from(encryptedB64, "base64");
865
- const decryptOptions = opts.encryption;
866
- decryptOptions.salt = encryptionSalt;
867
- decryptOptions.iv = Buffer.from(encryptionIv, "base64");
868
- const decrypted = await decrypt(
869
- _crypto,
870
- pass.encryption,
871
- decryptOptions,
872
- encrypted
873
- );
874
- if (decrypted) {
875
- return JSON.parse(decrypted);
876
- }
877
- return null;
878
- };
879
-
880
606
  const DEFAULT_NAME = "h3";
881
607
  const DEFAULT_COOKIE = {
882
608
  path: "/",
@@ -1404,4 +1130,4 @@ function createRouter(opts = {}) {
1404
1130
  return router;
1405
1131
  }
1406
1132
 
1407
- export { H3Error, H3Event, H3Headers, H3Response, MIMES, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getQuery, getRequestHeader, getRequestHeaders, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, isError, isEvent, isEventHandler, isMethod, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, toEventHandler, toNodeListener, updateSession, use, useBase, useSession, writeEarlyHints };
1133
+ export { H3Error, H3Event, H3Headers, H3Response, MIMES, appendHeader, appendHeaders, appendResponseHeader, appendResponseHeaders, assertMethod, callNodeListener, clearSession, createApp, createAppEventHandler, createError, createEvent, createRouter, defaultContentType, defineEventHandler, defineLazyEventHandler, defineNodeListener, defineNodeMiddleware, deleteCookie, dynamicEventHandler, eventHandler, fetchWithEvent, fromNodeMiddleware, getCookie, getHeader, getHeaders, getMethod, getProxyRequestHeaders, getQuery, getRequestHeader, getRequestHeaders, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getRouterParam, getRouterParams, getSession, handleCacheHeaders, isError, isEvent, isEventHandler, isMethod, isStream, lazyEventHandler, parseCookies, promisifyNodeListener, proxyRequest, readBody, readMultipartFormData, readRawBody, send, sendError, sendNoContent, sendProxy, sendRedirect, sendStream, setCookie, setHeader, setHeaders, setResponseHeader, setResponseHeaders, setResponseStatus, toEventHandler, toNodeListener, updateSession, use, useBase, useSession, writeEarlyHints };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "h3",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "Tiny JavaScript Server",
5
5
  "repository": "unjs/h3",
6
6
  "license": "MIT",
@@ -22,16 +22,17 @@
22
22
  "dependencies": {
23
23
  "cookie-es": "^0.5.0",
24
24
  "destr": "^1.2.2",
25
+ "iron-webcrypto": "^0.4.0",
25
26
  "radix3": "^1.0.0",
26
27
  "ufo": "^1.0.1",
27
28
  "uncrypto": "^0.1.2"
28
29
  },
29
30
  "devDependencies": {
30
31
  "0x": "^5.4.1",
31
- "@types/express": "^4.17.16",
32
- "@types/node": "^18.11.18",
32
+ "@types/express": "^4.17.17",
33
+ "@types/node": "^18.13.0",
33
34
  "@types/supertest": "^2.0.12",
34
- "@vitest/coverage-c8": "^0.28.3",
35
+ "@vitest/coverage-c8": "^0.28.4",
35
36
  "autocannon": "^7.10.0",
36
37
  "changelogen": "^0.4.1",
37
38
  "connect": "^3.7.0",
@@ -42,11 +43,11 @@
42
43
  "jiti": "^1.16.2",
43
44
  "listhen": "^1.0.2",
44
45
  "node-fetch-native": "^1.0.1",
45
- "prettier": "^2.8.3",
46
+ "prettier": "^2.8.4",
46
47
  "supertest": "^6.3.3",
47
48
  "typescript": "^4.9.5",
48
49
  "unbuild": "^1.1.1",
49
- "vitest": "^0.28.3"
50
+ "vitest": "^0.28.4"
50
51
  },
51
52
  "packageManager": "pnpm@7.26.3",
52
53
  "scripts": {