@nifrajs/middleware 0.1.0-alpha.1

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.
Files changed (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +79 -0
  3. package/dist/_utils.d.ts +27 -0
  4. package/dist/_utils.d.ts.map +1 -0
  5. package/dist/_utils.js +141 -0
  6. package/dist/_utils.js.map +1 -0
  7. package/dist/basic-auth.d.ts +27 -0
  8. package/dist/basic-auth.d.ts.map +1 -0
  9. package/dist/basic-auth.js +64 -0
  10. package/dist/basic-auth.js.map +1 -0
  11. package/dist/body-limit.d.ts +19 -0
  12. package/dist/body-limit.d.ts.map +1 -0
  13. package/dist/body-limit.js +42 -0
  14. package/dist/body-limit.js.map +1 -0
  15. package/dist/cache-control.d.ts +23 -0
  16. package/dist/cache-control.d.ts.map +1 -0
  17. package/dist/cache-control.js +35 -0
  18. package/dist/cache-control.js.map +1 -0
  19. package/dist/cache.d.ts +60 -0
  20. package/dist/cache.d.ts.map +1 -0
  21. package/dist/cache.js +224 -0
  22. package/dist/cache.js.map +1 -0
  23. package/dist/combine.d.ts +7 -0
  24. package/dist/combine.d.ts.map +1 -0
  25. package/dist/combine.js +21 -0
  26. package/dist/combine.js.map +1 -0
  27. package/dist/compression.d.ts +29 -0
  28. package/dist/compression.d.ts.map +1 -0
  29. package/dist/compression.js +105 -0
  30. package/dist/compression.js.map +1 -0
  31. package/dist/context-storage.d.ts +14 -0
  32. package/dist/context-storage.d.ts.map +1 -0
  33. package/dist/context-storage.js +30 -0
  34. package/dist/context-storage.js.map +1 -0
  35. package/dist/cors.d.ts +25 -0
  36. package/dist/cors.d.ts.map +1 -0
  37. package/dist/cors.js +75 -0
  38. package/dist/cors.js.map +1 -0
  39. package/dist/csrf.d.ts +23 -0
  40. package/dist/csrf.d.ts.map +1 -0
  41. package/dist/csrf.js +68 -0
  42. package/dist/csrf.js.map +1 -0
  43. package/dist/etag.d.ts +14 -0
  44. package/dist/etag.d.ts.map +1 -0
  45. package/dist/etag.js +95 -0
  46. package/dist/etag.js.map +1 -0
  47. package/dist/healthcheck.d.ts +25 -0
  48. package/dist/healthcheck.d.ts.map +1 -0
  49. package/dist/healthcheck.js +41 -0
  50. package/dist/healthcheck.js.map +1 -0
  51. package/dist/idempotency.d.ts +83 -0
  52. package/dist/idempotency.d.ts.map +1 -0
  53. package/dist/idempotency.js +217 -0
  54. package/dist/idempotency.js.map +1 -0
  55. package/dist/index.d.ts +33 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +33 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/ip-restriction.d.ts +21 -0
  60. package/dist/ip-restriction.d.ts.map +1 -0
  61. package/dist/ip-restriction.js +161 -0
  62. package/dist/ip-restriction.js.map +1 -0
  63. package/dist/jwt.d.ts +82 -0
  64. package/dist/jwt.d.ts.map +1 -0
  65. package/dist/jwt.js +327 -0
  66. package/dist/jwt.js.map +1 -0
  67. package/dist/language.d.ts +20 -0
  68. package/dist/language.d.ts.map +1 -0
  69. package/dist/language.js +94 -0
  70. package/dist/language.js.map +1 -0
  71. package/dist/logger.d.ts +19 -0
  72. package/dist/logger.d.ts.map +1 -0
  73. package/dist/logger.js +28 -0
  74. package/dist/logger.js.map +1 -0
  75. package/dist/method-override.d.ts +28 -0
  76. package/dist/method-override.d.ts.map +1 -0
  77. package/dist/method-override.js +67 -0
  78. package/dist/method-override.js.map +1 -0
  79. package/dist/openapi.d.ts +83 -0
  80. package/dist/openapi.d.ts.map +1 -0
  81. package/dist/openapi.js +100 -0
  82. package/dist/openapi.js.map +1 -0
  83. package/dist/powered-by.d.ts +15 -0
  84. package/dist/powered-by.d.ts.map +1 -0
  85. package/dist/powered-by.js +23 -0
  86. package/dist/powered-by.js.map +1 -0
  87. package/dist/pretty-json.d.ts +18 -0
  88. package/dist/pretty-json.d.ts.map +1 -0
  89. package/dist/pretty-json.js +88 -0
  90. package/dist/pretty-json.js.map +1 -0
  91. package/dist/rate-limit.d.ts +92 -0
  92. package/dist/rate-limit.d.ts.map +1 -0
  93. package/dist/rate-limit.js +172 -0
  94. package/dist/rate-limit.js.map +1 -0
  95. package/dist/request-id.d.ts +18 -0
  96. package/dist/request-id.d.ts.map +1 -0
  97. package/dist/request-id.js +21 -0
  98. package/dist/request-id.js.map +1 -0
  99. package/dist/security-headers.d.ts +23 -0
  100. package/dist/security-headers.d.ts.map +1 -0
  101. package/dist/security-headers.js +36 -0
  102. package/dist/security-headers.js.map +1 -0
  103. package/dist/timing.d.ts +25 -0
  104. package/dist/timing.d.ts.map +1 -0
  105. package/dist/timing.js +101 -0
  106. package/dist/timing.js.map +1 -0
  107. package/dist/token-auth.d.ts +65 -0
  108. package/dist/token-auth.d.ts.map +1 -0
  109. package/dist/token-auth.js +90 -0
  110. package/dist/token-auth.js.map +1 -0
  111. package/dist/trailing-slash.d.ts +23 -0
  112. package/dist/trailing-slash.d.ts.map +1 -0
  113. package/dist/trailing-slash.js +70 -0
  114. package/dist/trailing-slash.js.map +1 -0
  115. package/package.json +68 -0
  116. package/src/_utils.ts +163 -0
  117. package/src/basic-auth.ts +109 -0
  118. package/src/body-limit.ts +53 -0
  119. package/src/cache-control.ts +45 -0
  120. package/src/cache.ts +277 -0
  121. package/src/combine.ts +24 -0
  122. package/src/compression.ts +111 -0
  123. package/src/context-storage.ts +36 -0
  124. package/src/cors.ts +90 -0
  125. package/src/csrf.ts +102 -0
  126. package/src/etag.ts +99 -0
  127. package/src/healthcheck.ts +59 -0
  128. package/src/idempotency.ts +313 -0
  129. package/src/index.ts +110 -0
  130. package/src/ip-restriction.ts +187 -0
  131. package/src/jwt.ts +441 -0
  132. package/src/language.ts +109 -0
  133. package/src/logger.ts +44 -0
  134. package/src/method-override.ts +87 -0
  135. package/src/openapi.ts +174 -0
  136. package/src/powered-by.ts +31 -0
  137. package/src/pretty-json.ts +97 -0
  138. package/src/rate-limit.ts +248 -0
  139. package/src/request-id.ts +30 -0
  140. package/src/security-headers.ts +51 -0
  141. package/src/timing.ts +133 -0
  142. package/src/token-auth.ts +172 -0
  143. package/src/trailing-slash.ts +94 -0
package/dist/index.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * `@nifrajs/middleware` — composable, dependency-free middleware + plugins applied via `app.use()`.
3
+ * The hardening set (`cors`, `rateLimit`, `securityHeaders`) returns a `@nifrajs/core` `Middleware`
4
+ * (a hook bundle, context-agnostic). The seeded plugins (`requestId`, `logger`, `etag`, `bearer`,
5
+ * `apiKey`) use the `definePlugin` convention — named (idempotent dedupe), and `requestId` threads
6
+ * `c.requestId` into the handler context.
7
+ */
8
+ export { basicAuth, } from "./basic-auth.js";
9
+ export { bodyLimit } from "./body-limit.js";
10
+ export { cache, MemoryResponseCache, responseCache, } from "./cache.js";
11
+ export { cacheControl } from "./cache-control.js";
12
+ export { combine, namedCombine } from "./combine.js";
13
+ export { compression } from "./compression.js";
14
+ export { cors } from "./cors.js";
15
+ export { createCsrfToken, csrf, verifyCsrfToken, } from "./csrf.js";
16
+ export { etag } from "./etag.js";
17
+ export { healthcheck } from "./healthcheck.js";
18
+ export { idempotency, MemoryIdempotencyStore, } from "./idempotency.js";
19
+ export { ipRestriction } from "./ip-restriction.js";
20
+ export { jwk, jwks, jwt, tryVerifyJwt, verifyJwt, } from "./jwt.js";
21
+ export { language, pickLanguage, } from "./language.js";
22
+ export { logger } from "./logger.js";
23
+ export { methodOverride } from "./method-override.js";
24
+ export { buildOpenApiDocument, openapi, } from "./openapi.js";
25
+ export { poweredBy } from "./powered-by.js";
26
+ export { prettyJson } from "./pretty-json.js";
27
+ export { MemoryStore, rateLimit, } from "./rate-limit.js";
28
+ export { requestId } from "./request-id.js";
29
+ export { securityHeaders } from "./security-headers.js";
30
+ export { timing } from "./timing.js";
31
+ export { apiKey, bearer, } from "./token-auth.js";
32
+ export { appendTrailingSlash, trimTrailingSlash, } from "./trailing-slash.js";
33
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAIL,SAAS,GACV,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAyB,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAClE,OAAO,EAGL,KAAK,EACL,mBAAmB,EAGnB,aAAa,GACd,MAAM,YAAY,CAAA;AACnB,OAAO,EAA4B,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAC3E,OAAO,EAAmB,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AACrE,OAAO,EAA2B,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACvE,OAAO,EAAoB,IAAI,EAAE,MAAM,WAAW,CAAA;AAClD,OAAO,EAEL,eAAe,EACf,IAAI,EACJ,eAAe,GAChB,MAAM,WAAW,CAAA;AAClB,OAAO,EAAoB,IAAI,EAAE,MAAM,WAAW,CAAA;AAClD,OAAO,EAA2B,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACvE,OAAO,EAKL,WAAW,EACX,sBAAsB,GAEvB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAA6C,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAC9F,OAAO,EAUL,GAAG,EACH,IAAI,EACJ,GAAG,EACH,YAAY,EAIZ,SAAS,GACV,MAAM,UAAU,CAAA;AACjB,OAAO,EAGL,QAAQ,EACR,YAAY,GACb,MAAM,eAAe,CAAA;AACtB,OAAO,EAAsB,MAAM,EAAyB,MAAM,aAAa,CAAA;AAC/E,OAAO,EAA8B,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACjF,OAAO,EACL,oBAAoB,EAMpB,OAAO,GAGR,MAAM,cAAc,CAAA;AACrB,OAAO,EAAyB,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAClE,OAAO,EAA0B,UAAU,EAAE,MAAM,kBAAkB,CAAA;AACrE,OAAO,EACL,WAAW,EAKX,SAAS,GACV,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAyB,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAClE,OAAO,EAA+B,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACpF,OAAO,EAA8D,MAAM,EAAE,MAAM,aAAa,CAAA;AAChG,OAAO,EAIL,MAAM,EAEN,MAAM,GACP,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,mBAAmB,EAEnB,iBAAiB,GAClB,MAAM,qBAAqB,CAAA"}
@@ -0,0 +1,21 @@
1
+ import type { Middleware } from "@nifrajs/core";
2
+ import { type MaybePromise } from "./_utils.js";
3
+ export type IpMatcher = string | ((ip: string, request: Request) => MaybePromise<boolean>);
4
+ export interface IpRestrictionOptions {
5
+ readonly allow?: readonly IpMatcher[];
6
+ readonly deny?: readonly IpMatcher[];
7
+ /** Preferred extraction hook when the adapter/app knows the peer address. */
8
+ readonly clientIp?: (request: Request) => MaybePromise<string | null | undefined>;
9
+ /** Trusted proxy count for `X-Forwarded-For` extraction. Default: 0, so XFF is ignored. */
10
+ readonly trustedProxies?: number;
11
+ /** Exact trusted single-IP header, e.g. an infra-set `x-real-ip`. Not used unless configured. */
12
+ readonly header?: string;
13
+ readonly error?: string;
14
+ }
15
+ /**
16
+ * IP allow/deny middleware. It fails closed when no trustworthy client IP can be derived. Configure
17
+ * `clientIp`, `trustedProxies`, or a trusted single-IP `header`; unconfigured X-Forwarded-For is never
18
+ * trusted.
19
+ */
20
+ export declare function ipRestriction(options: IpRestrictionOptions): Middleware;
21
+ //# sourceMappingURL=ip-restriction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ip-restriction.d.ts","sourceRoot":"","sources":["../src/ip-restriction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,EAAa,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAE1D,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,YAAY,CAAC,OAAO,CAAC,CAAC,CAAA;AAE1F,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,SAAS,EAAE,CAAA;IACrC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,SAAS,EAAE,CAAA;IACpC,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,YAAY,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;IACjF,2FAA2F;IAC3F,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,iGAAiG;IACjG,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAyID;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,UAAU,CA6BvE"}
@@ -0,0 +1,161 @@
1
+ import { jsonError } from "./_utils.js";
2
+ function parseIPv4(input) {
3
+ const parts = input.split(".");
4
+ if (parts.length !== 4)
5
+ return null;
6
+ let out = 0n;
7
+ for (const part of parts) {
8
+ if (!/^\d{1,3}$/.test(part))
9
+ return null;
10
+ const n = Number(part);
11
+ if (n > 255)
12
+ return null;
13
+ out = (out << 8n) | BigInt(n);
14
+ }
15
+ return out;
16
+ }
17
+ function parseIPv6(input) {
18
+ if (input.includes("%"))
19
+ return null;
20
+ let source = input.toLowerCase();
21
+ if (source.includes(".")) {
22
+ const lastColon = source.lastIndexOf(":");
23
+ if (lastColon < 0)
24
+ return null;
25
+ const ipv4 = parseIPv4(source.slice(lastColon + 1));
26
+ if (ipv4 === null)
27
+ return null;
28
+ const hi = Number((ipv4 >> 16n) & 0xffffn).toString(16);
29
+ const lo = Number(ipv4 & 0xffffn).toString(16);
30
+ source = `${source.slice(0, lastColon)}:${hi}:${lo}`;
31
+ }
32
+ const halves = source.split("::");
33
+ if (halves.length > 2)
34
+ return null;
35
+ const left = halves[0] === "" ? [] : halves[0].split(":");
36
+ const right = halves.length === 1 || halves[1] === "" ? [] : halves[1].split(":");
37
+ const groups = [...left, ...right];
38
+ if (groups.some((g) => !/^[0-9a-f]{1,4}$/.test(g)))
39
+ return null;
40
+ const missing = 8 - groups.length;
41
+ if (halves.length === 1) {
42
+ if (missing !== 0)
43
+ return null;
44
+ }
45
+ else if (missing < 1)
46
+ return null;
47
+ const expanded = halves.length === 1
48
+ ? groups
49
+ : [...left, ...Array.from({ length: missing }, () => "0"), ...right];
50
+ let out = 0n;
51
+ for (const group of expanded)
52
+ out = (out << 16n) | BigInt(Number.parseInt(group, 16));
53
+ return out;
54
+ }
55
+ function parseIp(input) {
56
+ const trimmed = input.trim();
57
+ const unbracketed = trimmed.startsWith("[") && trimmed.endsWith("]") ? trimmed.slice(1, -1) : trimmed;
58
+ const v4 = parseIPv4(unbracketed);
59
+ if (v4 !== null)
60
+ return { version: 4, value: v4 };
61
+ const v6 = parseIPv6(unbracketed);
62
+ return v6 === null ? null : { version: 6, value: v6 };
63
+ }
64
+ function parseRange(input) {
65
+ const slash = input.indexOf("/");
66
+ const address = slash < 0 ? input : input.slice(0, slash);
67
+ const ip = parseIp(address);
68
+ if (ip === null)
69
+ throw new Error(`ipRestriction: invalid IP/CIDR ${JSON.stringify(input)}`);
70
+ const bits = ip.version === 4 ? 32 : 128;
71
+ const prefix = slash < 0 ? bits : /^\d+$/.test(input.slice(slash + 1)) ? Number(input.slice(slash + 1)) : -1;
72
+ if (!Number.isInteger(prefix) || prefix < 0 || prefix > bits) {
73
+ throw new Error(`ipRestriction: invalid CIDR prefix ${JSON.stringify(input)}`);
74
+ }
75
+ const full = (1n << BigInt(bits)) - 1n;
76
+ const mask = prefix === 0 ? 0n : full ^ ((1n << BigInt(bits - prefix)) - 1n);
77
+ return { version: ip.version, network: ip.value & mask, mask };
78
+ }
79
+ function matchRange(ip, range) {
80
+ return ip.version === range.version && (ip.value & range.mask) === range.network;
81
+ }
82
+ function xForwardedClient(req, trustedProxies) {
83
+ if (trustedProxies <= 0)
84
+ return null;
85
+ const xff = req.headers.get("x-forwarded-for");
86
+ if (xff === null)
87
+ return null;
88
+ const parts = xff.split(",");
89
+ return parts[parts.length - trustedProxies]?.trim() || null;
90
+ }
91
+ async function resolveClientIp(req, options) {
92
+ const custom = await options.clientIp?.(req);
93
+ if (custom !== undefined && custom !== null)
94
+ return custom;
95
+ const fromXff = xForwardedClient(req, options.trustedProxies ?? 0);
96
+ if (fromXff !== null)
97
+ return fromXff;
98
+ if (options.header !== undefined) {
99
+ const value = req.headers.get(options.header);
100
+ if (value !== null && !value.includes(","))
101
+ return value.trim();
102
+ }
103
+ return null;
104
+ }
105
+ function compile(matchers) {
106
+ const ranges = [];
107
+ const fns = [];
108
+ for (const matcher of matchers ?? []) {
109
+ if (typeof matcher === "string")
110
+ ranges.push(parseRange(matcher));
111
+ else
112
+ fns.push(matcher);
113
+ }
114
+ return { ranges, fns };
115
+ }
116
+ async function matches(ipText, ip, compiled, req) {
117
+ if (compiled.ranges.some((range) => matchRange(ip, range)))
118
+ return true;
119
+ for (const fn of compiled.fns)
120
+ if (await fn(ipText, req))
121
+ return true;
122
+ return false;
123
+ }
124
+ /**
125
+ * IP allow/deny middleware. It fails closed when no trustworthy client IP can be derived. Configure
126
+ * `clientIp`, `trustedProxies`, or a trusted single-IP `header`; unconfigured X-Forwarded-For is never
127
+ * trusted.
128
+ */
129
+ export function ipRestriction(options) {
130
+ const trustedProxies = options.trustedProxies ?? 0;
131
+ if (!Number.isInteger(trustedProxies) || trustedProxies < 0) {
132
+ throw new Error("ipRestriction: trustedProxies must be a non-negative integer");
133
+ }
134
+ if (options.header !== undefined && options.header.trim() === "") {
135
+ throw new Error("ipRestriction: header must not be empty");
136
+ }
137
+ const allow = compile(options.allow);
138
+ const deny = compile(options.deny);
139
+ if (allow.ranges.length + allow.fns.length + deny.ranges.length + deny.fns.length === 0) {
140
+ throw new Error("ipRestriction: configure at least one allow or deny matcher");
141
+ }
142
+ const error = options.error ?? "ip_forbidden";
143
+ return {
144
+ name: "ip-restriction",
145
+ async onRequest(req) {
146
+ const ipText = await resolveClientIp(req, options);
147
+ if (ipText === null)
148
+ return jsonError(403, error);
149
+ const ip = parseIp(ipText);
150
+ if (ip === null)
151
+ return jsonError(403, error);
152
+ if (await matches(ipText, ip, deny, req))
153
+ return jsonError(403, error);
154
+ if (allow.ranges.length + allow.fns.length > 0 && !(await matches(ipText, ip, allow, req))) {
155
+ return jsonError(403, error);
156
+ }
157
+ return undefined;
158
+ },
159
+ };
160
+ }
161
+ //# sourceMappingURL=ip-restriction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ip-restriction.js","sourceRoot":"","sources":["../src/ip-restriction.ts"],"names":[],"mappings":"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"}
package/dist/jwt.d.ts ADDED
@@ -0,0 +1,82 @@
1
+ import { type NifraPlugin } from "@nifrajs/core";
2
+ import { type MaybePromise } from "./_utils.js";
3
+ export type JwtAlgorithm = "HS256" | "HS384" | "HS512" | "RS256" | "RS384" | "RS512";
4
+ export interface JwtHeader {
5
+ readonly alg: string;
6
+ readonly kid?: string;
7
+ readonly typ?: string;
8
+ readonly [key: string]: unknown;
9
+ }
10
+ export interface JwtClaims {
11
+ readonly iss?: string;
12
+ readonly sub?: string;
13
+ readonly aud?: string | readonly string[];
14
+ readonly exp?: number;
15
+ readonly nbf?: number;
16
+ readonly iat?: number;
17
+ readonly jti?: string;
18
+ readonly [claim: string]: unknown;
19
+ }
20
+ export interface VerifiedJwt<C extends JwtClaims = JwtClaims> {
21
+ readonly header: JwtHeader;
22
+ readonly claims: C;
23
+ readonly token: string;
24
+ }
25
+ export type VerifyJwtResult<C extends JwtClaims = JwtClaims> = {
26
+ readonly ok: true;
27
+ readonly data: VerifiedJwt<C>;
28
+ } | {
29
+ readonly ok: false;
30
+ readonly error: Error;
31
+ };
32
+ export interface JwkKey {
33
+ readonly kty?: string;
34
+ readonly kid?: string;
35
+ readonly alg?: string;
36
+ readonly use?: string;
37
+ readonly key_ops?: readonly string[];
38
+ readonly [key: string]: unknown;
39
+ }
40
+ export type JwtVerificationKey = string | Uint8Array | CryptoKey | JwkKey;
41
+ export type JwtKeyResolver = (header: JwtHeader, claims: JwtClaims) => MaybePromise<JwtVerificationKey | null | undefined>;
42
+ export interface VerifyJwtOptions {
43
+ readonly key: JwtVerificationKey | JwtKeyResolver;
44
+ readonly algorithms: readonly JwtAlgorithm[];
45
+ readonly issuer?: string | readonly string[];
46
+ readonly audience?: string | readonly string[];
47
+ readonly clockToleranceSec?: number;
48
+ readonly requiredClaims?: readonly string[];
49
+ readonly requireExpiration?: boolean;
50
+ readonly maxAgeSec?: number;
51
+ readonly now?: () => number;
52
+ }
53
+ export interface JwtOptions extends VerifyJwtOptions {
54
+ readonly realm?: string;
55
+ readonly optional?: boolean;
56
+ /** Header carrying the token. Default `"authorization"` (`Bearer <token>`). */
57
+ readonly header?: string;
58
+ /** Optional cookie fallback carrying the raw token. */
59
+ readonly cookie?: string;
60
+ }
61
+ export type JwtPlugin<C extends JwtClaims = JwtClaims> = NifraPlugin & {
62
+ claims(request: Request): C | null;
63
+ requireClaims(request: Request): C;
64
+ };
65
+ export interface JwksOptions {
66
+ readonly url: string | URL;
67
+ readonly fetch?: typeof fetch;
68
+ readonly cacheMs?: number;
69
+ /**
70
+ * Extra time to keep using the last successful key set when refresh fails.
71
+ * Defaults to one cache window; set `0` to fail closed immediately on refresh errors.
72
+ */
73
+ readonly staleMs?: number;
74
+ readonly timeoutMs?: number;
75
+ readonly maxBytes?: number;
76
+ }
77
+ export declare function verifyJwt<C extends JwtClaims = JwtClaims>(token: string, options: VerifyJwtOptions): Promise<VerifiedJwt<C>>;
78
+ export declare function tryVerifyJwt<C extends JwtClaims = JwtClaims>(token: string, options: VerifyJwtOptions): Promise<VerifyJwtResult<C>>;
79
+ export declare function jwt<C extends JwtClaims = JwtClaims>(options: JwtOptions): JwtPlugin<C>;
80
+ export declare function jwk(key: JwtVerificationKey): JwtKeyResolver;
81
+ export declare function jwks(options: JwksOptions): JwtKeyResolver;
82
+ //# sourceMappingURL=jwt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"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"}
package/dist/jwt.js ADDED
@@ -0,0 +1,327 @@
1
+ import { definePlugin } from "@nifrajs/core";
2
+ import { base64UrlDecode, jsonError, parseCookies, quotedHeaderValue, secretBytes, utf8Bytes, } from "./_utils.js";
3
+ const HASH = {
4
+ HS256: "SHA-256",
5
+ HS384: "SHA-384",
6
+ HS512: "SHA-512",
7
+ RS256: "SHA-256",
8
+ RS384: "SHA-384",
9
+ RS512: "SHA-512",
10
+ };
11
+ const HMAC_ALGS = new Set(["HS256", "HS384", "HS512"]);
12
+ const RSA_ALGS = new Set(["RS256", "RS384", "RS512"]);
13
+ const JSON_DECODER = new TextDecoder("utf-8", { fatal: true });
14
+ function isObject(v) {
15
+ return typeof v === "object" && v !== null && !Array.isArray(v);
16
+ }
17
+ function jwtError(message) {
18
+ return new Error(`jwt: ${message}`);
19
+ }
20
+ function decodeJsonSegment(segment) {
21
+ const bytes = base64UrlDecode(segment);
22
+ if (bytes === null)
23
+ throw jwtError("invalid base64url segment");
24
+ try {
25
+ return JSON.parse(JSON_DECODER.decode(bytes));
26
+ }
27
+ catch {
28
+ throw jwtError("invalid JSON segment");
29
+ }
30
+ }
31
+ function validateAlgorithms(algorithms) {
32
+ if (algorithms.length === 0)
33
+ throw new Error("jwt: algorithms must not be empty");
34
+ return new Set(algorithms);
35
+ }
36
+ function nonNegativeSeconds(value, name) {
37
+ if (value === undefined)
38
+ return 0;
39
+ if (!Number.isFinite(value) || value < 0)
40
+ throw jwtError(`${name} must be non-negative`);
41
+ return value;
42
+ }
43
+ function validateJwkForAlg(key, alg, kid) {
44
+ if (key.alg !== undefined && key.alg !== alg)
45
+ return false;
46
+ if (key.use !== undefined && key.use !== "sig")
47
+ return false;
48
+ if (key.key_ops !== undefined && !key.key_ops.includes("verify"))
49
+ return false;
50
+ if (kid !== undefined && key.kid !== undefined && key.kid !== kid)
51
+ return false;
52
+ if (HMAC_ALGS.has(alg))
53
+ return key.kty === "oct";
54
+ if (RSA_ALGS.has(alg))
55
+ return key.kty === "RSA";
56
+ return false;
57
+ }
58
+ async function importKey(key, alg, kid) {
59
+ const hash = HASH[alg];
60
+ if (key instanceof CryptoKey)
61
+ return key;
62
+ if (typeof key === "string" || key instanceof Uint8Array) {
63
+ if (!HMAC_ALGS.has(alg))
64
+ throw jwtError("symmetric key cannot verify an RSA token");
65
+ return crypto.subtle.importKey("raw", secretBytes(key, "jwt"), { name: "HMAC", hash }, false, [
66
+ "verify",
67
+ ]);
68
+ }
69
+ if (!validateJwkForAlg(key, alg, kid))
70
+ throw jwtError("JWK is not usable for this token");
71
+ const importJwk = crypto.subtle.importKey;
72
+ return importJwk.call(crypto.subtle, "jwk", key, RSA_ALGS.has(alg) ? { name: "RSASSA-PKCS1-v1_5", hash } : { name: "HMAC", hash }, false, ["verify"]);
73
+ }
74
+ function verifyAlgorithm(alg, allowed) {
75
+ if (alg === "none")
76
+ throw jwtError("alg none is not allowed");
77
+ if (!allowed.has(alg))
78
+ throw jwtError("algorithm is not allowed");
79
+ return alg;
80
+ }
81
+ function includesString(expected, actual) {
82
+ return expected === undefined
83
+ ? true
84
+ : typeof expected === "string"
85
+ ? expected === actual
86
+ : expected.includes(actual);
87
+ }
88
+ function validateClaims(claims, options) {
89
+ const now = options.now?.() ?? Math.floor(Date.now() / 1000);
90
+ if (!Number.isFinite(now))
91
+ throw jwtError("now must be finite");
92
+ const tolerance = nonNegativeSeconds(options.clockToleranceSec, "clockToleranceSec");
93
+ const requireExpiration = options.requireExpiration !== false;
94
+ if (requireExpiration && typeof claims.exp !== "number")
95
+ throw jwtError("exp is required");
96
+ if (claims.exp !== undefined && (!Number.isFinite(claims.exp) || claims.exp <= now - tolerance)) {
97
+ throw jwtError("token expired");
98
+ }
99
+ if (claims.nbf !== undefined && (!Number.isFinite(claims.nbf) || claims.nbf > now + tolerance)) {
100
+ throw jwtError("token is not active yet");
101
+ }
102
+ if (claims.iat !== undefined && (!Number.isFinite(claims.iat) || claims.iat > now + tolerance)) {
103
+ throw jwtError("iat is in the future");
104
+ }
105
+ if (options.maxAgeSec !== undefined) {
106
+ nonNegativeSeconds(options.maxAgeSec, "maxAgeSec");
107
+ if (typeof claims.iat !== "number" || !Number.isFinite(claims.iat))
108
+ throw jwtError("iat is required");
109
+ if (now - claims.iat > options.maxAgeSec + tolerance)
110
+ throw jwtError("token is too old");
111
+ }
112
+ if (options.issuer !== undefined) {
113
+ if (typeof claims.iss !== "string" || !includesString(options.issuer, claims.iss)) {
114
+ throw jwtError("issuer mismatch");
115
+ }
116
+ }
117
+ if (options.audience !== undefined) {
118
+ const got = claims.aud;
119
+ const gotList = typeof got === "string" ? [got] : Array.isArray(got) ? got : [];
120
+ const expected = typeof options.audience === "string" ? [options.audience] : options.audience;
121
+ if (!gotList.some((aud) => expected.includes(aud)))
122
+ throw jwtError("audience mismatch");
123
+ }
124
+ for (const claim of options.requiredClaims ?? []) {
125
+ if (!(claim in claims))
126
+ throw jwtError(`claim ${claim} is required`);
127
+ }
128
+ }
129
+ export async function verifyJwt(token, options) {
130
+ const allowed = validateAlgorithms(options.algorithms);
131
+ const parts = token.split(".");
132
+ if (parts.length !== 3 || parts.some((p) => p === ""))
133
+ throw jwtError("expected three segments");
134
+ const headerRaw = decodeJsonSegment(parts[0]);
135
+ const claimsRaw = decodeJsonSegment(parts[1]);
136
+ if (!isObject(headerRaw) || typeof headerRaw.alg !== "string")
137
+ throw jwtError("invalid header");
138
+ if (headerRaw.crit !== undefined)
139
+ throw jwtError("crit headers are not supported");
140
+ if (!isObject(claimsRaw))
141
+ throw jwtError("invalid claims");
142
+ const header = headerRaw;
143
+ const claims = claimsRaw;
144
+ const alg = verifyAlgorithm(header.alg, allowed);
145
+ const resolved = typeof options.key === "function" ? await options.key(header, claims) : options.key;
146
+ if (resolved === null || resolved === undefined)
147
+ throw jwtError("key not found");
148
+ const signature = base64UrlDecode(parts[2]);
149
+ if (signature === null)
150
+ throw jwtError("invalid signature encoding");
151
+ const key = await importKey(resolved, alg, header.kid);
152
+ const ok = await crypto.subtle.verify(HMAC_ALGS.has(alg) ? { name: "HMAC" } : { name: "RSASSA-PKCS1-v1_5" }, key, signature, utf8Bytes(`${parts[0]}.${parts[1]}`));
153
+ if (!ok)
154
+ throw jwtError("signature mismatch");
155
+ validateClaims(claims, options);
156
+ return { header, claims, token };
157
+ }
158
+ function toError(error) {
159
+ return error instanceof Error ? error : jwtError("verification failed");
160
+ }
161
+ export async function tryVerifyJwt(token, options) {
162
+ try {
163
+ return { ok: true, data: await verifyJwt(token, options) };
164
+ }
165
+ catch (error) {
166
+ return { ok: false, error: toError(error) };
167
+ }
168
+ }
169
+ function tokenFromRequest(req, header, cookie) {
170
+ const headerValue = req.headers.get(header);
171
+ if (headerValue !== null) {
172
+ if (header === "authorization") {
173
+ return headerValue.startsWith("Bearer ") ? headerValue.slice(7).trim() || null : null;
174
+ }
175
+ return headerValue.trim() || null;
176
+ }
177
+ return cookie === undefined ? null : (parseCookies(req.headers.get("cookie"))[cookie] ?? null);
178
+ }
179
+ function challenge(realm) {
180
+ return `Bearer realm="${quotedHeaderValue(realm)}"`;
181
+ }
182
+ function reject(realm) {
183
+ return jsonError(401, "unauthorized", { "www-authenticate": challenge(realm) });
184
+ }
185
+ export function jwt(options) {
186
+ validateAlgorithms(options.algorithms);
187
+ const realm = options.realm ?? "api";
188
+ const optional = options.optional === true;
189
+ const header = (options.header ?? "authorization").toLowerCase();
190
+ const store = new WeakMap();
191
+ const plugin = definePlugin("jwt", (app) => app.beforeHandle(async (c) => {
192
+ const token = tokenFromRequest(c.req, header, options.cookie);
193
+ if (token === null)
194
+ return optional ? undefined : reject(realm);
195
+ try {
196
+ const verified = await verifyJwt(token, options);
197
+ store.set(c.req, verified.claims);
198
+ return undefined;
199
+ }
200
+ catch {
201
+ return optional ? undefined : reject(realm);
202
+ }
203
+ }));
204
+ return Object.assign(plugin, {
205
+ claims: (request) => store.get(request) ?? null,
206
+ requireClaims: (request) => {
207
+ const claims = store.get(request);
208
+ if (claims === undefined)
209
+ throw reject(realm);
210
+ return claims;
211
+ },
212
+ });
213
+ }
214
+ export function jwk(key) {
215
+ return (header) => {
216
+ if (typeof key === "object" && !(key instanceof Uint8Array) && !(key instanceof CryptoKey)) {
217
+ if (header.kid !== undefined && key.kid !== undefined && key.kid !== header.kid)
218
+ return null;
219
+ }
220
+ return key;
221
+ };
222
+ }
223
+ function validateJwksUrl(url) {
224
+ const local = url.hostname === "localhost" ||
225
+ url.hostname === "127.0.0.1" ||
226
+ url.hostname === "::1" ||
227
+ url.hostname === "[::1]";
228
+ if (url.protocol !== "https:" && !local) {
229
+ throw new Error("jwks: url must be https unless it targets localhost");
230
+ }
231
+ }
232
+ async function readTextBounded(res, maxBytes) {
233
+ const declared = res.headers.get("content-length");
234
+ if (declared !== null && /^(?:0|[1-9]\d*)$/.test(declared) && Number(declared) > maxBytes) {
235
+ throw new Error("jwks: response too large");
236
+ }
237
+ const body = res.body;
238
+ if (body === null)
239
+ return "";
240
+ const reader = body.getReader();
241
+ const decoder = new TextDecoder();
242
+ let total = 0;
243
+ let text = "";
244
+ try {
245
+ for (;;) {
246
+ const { done, value } = await reader.read();
247
+ if (done)
248
+ return text + decoder.decode();
249
+ total += value.byteLength;
250
+ if (total > maxBytes) {
251
+ await reader.cancel();
252
+ throw new Error("jwks: response too large");
253
+ }
254
+ text += decoder.decode(value, { stream: true });
255
+ }
256
+ }
257
+ catch (err) {
258
+ if (err instanceof Error && err.message === "jwks: response too large")
259
+ throw err;
260
+ throw new Error("jwks: response read failed");
261
+ }
262
+ }
263
+ async function fetchJwks(options) {
264
+ const ctrl = new AbortController();
265
+ const timer = setTimeout(() => ctrl.abort(), options.timeoutMs);
266
+ try {
267
+ const res = await options.fetch(options.url, { signal: ctrl.signal });
268
+ if (!res.ok)
269
+ throw new Error(`jwks: fetch failed with ${res.status}`);
270
+ const text = await readTextBounded(res, options.maxBytes);
271
+ const parsed = JSON.parse(text);
272
+ if (!isObject(parsed) || !Array.isArray(parsed.keys))
273
+ throw new Error("jwks: invalid set");
274
+ return parsed.keys.filter(isObject);
275
+ }
276
+ finally {
277
+ clearTimeout(timer);
278
+ }
279
+ }
280
+ export function jwks(options) {
281
+ const url = new URL(String(options.url));
282
+ validateJwksUrl(url);
283
+ const fetcher = options.fetch ?? fetch;
284
+ const cacheMs = options.cacheMs ?? 300_000;
285
+ const staleMs = options.staleMs ?? cacheMs;
286
+ const timeoutMs = options.timeoutMs ?? 5_000;
287
+ const maxBytes = options.maxBytes ?? 65_536;
288
+ if (!Number.isFinite(cacheMs) || cacheMs < 0)
289
+ throw new Error("jwks: cacheMs must be non-negative");
290
+ if (!Number.isFinite(staleMs) || staleMs < 0)
291
+ throw new Error("jwks: staleMs must be non-negative");
292
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
293
+ throw new Error("jwks: timeoutMs must be positive");
294
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
295
+ throw new Error("jwks: maxBytes must be positive");
296
+ }
297
+ let cached = [];
298
+ let cachedAt = 0;
299
+ let pending;
300
+ const load = async () => {
301
+ const now = Date.now();
302
+ if (cached.length > 0 && now - cachedAt < cacheMs)
303
+ return cached;
304
+ pending ??= fetchJwks({ url, fetch: fetcher, timeoutMs, maxBytes }).finally(() => {
305
+ pending = undefined;
306
+ });
307
+ try {
308
+ cached = await pending;
309
+ cachedAt = Date.now();
310
+ return cached;
311
+ }
312
+ catch (error) {
313
+ if (cached.length > 0 && staleMs > 0 && Date.now() - cachedAt < cacheMs + staleMs) {
314
+ return cached;
315
+ }
316
+ throw error;
317
+ }
318
+ };
319
+ return async (header) => {
320
+ if (typeof header.kid !== "string" || header.kid === "")
321
+ return null;
322
+ const keys = await load();
323
+ const alg = header.alg;
324
+ return keys.find((key) => key.kid === header.kid && validateJwkForAlg(key, alg, header.kid));
325
+ };
326
+ }
327
+ //# sourceMappingURL=jwt.js.map