@sebastienrousseau/crypto-middleware 0.0.7 → 0.0.9

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
@@ -142,16 +142,23 @@ lets you scope protection to specific endpoints.
142
142
 
143
143
  ### Configuration
144
144
 
145
- | Option | Type | Required | Description |
146
- | :----------- | :--------- | :------- | :------------------------------------------------ |
147
- | `key` | `string` | \* | Hex-encoded 256-bit key for encryption/decryption |
148
- | `routes` | `string[]` | No | Glob patterns for routes to apply middleware to |
149
- | `operations` | `string[]` | No | Operations to perform (see below) |
150
- | `hmacKey` | `string` | \* | Hex-encoded HMAC key for signature verification |
151
- | `jwtSecret` | `string` | \* | Secret for HS256 JWT verification |
145
+ | Option | Type | Required | Description |
146
+ | :------------ | :--------- | :------- | :------------------------------------------------ |
147
+ | `key` | `string` | \* | Hex-encoded 256-bit key for encryption/decryption |
148
+ | `routes` | `string[]` | No | Glob patterns for routes to apply middleware to |
149
+ | `operations` | `string[]` | No | Operations to perform (see below) |
150
+ | `hmacKey` | `string` | \* | Hex-encoded HMAC key for signature verification |
151
+ | `jwtSecret` | `string` | \* | Secret for HS256 JWT verification |
152
+ | `jwtIssuer` | `string` | No | Required `iss` claim for `verify-jwt` |
153
+ | `jwtAudience` | `string` | No | Required `aud` value for `verify-jwt` |
152
154
 
153
155
  \* Required when the corresponding operation is enabled.
154
156
 
157
+ `verify-jwt` accepts only HS256 (any other `alg`, including `none`, is
158
+ rejected), requires a numeric `exp` claim, checks `nbf` when present, and
159
+ checks `iss` and `aud` when `jwtIssuer` / `jwtAudience` are set. Set both
160
+ when the secret is shared with any other service.
161
+
155
162
  ### Operations
156
163
 
157
164
  | Operation | Description |
@@ -240,6 +247,8 @@ All 18 packages in the Crypto Service workspace maintain a **100% coverage floor
240
247
 
241
248
  Report vulnerabilities privately via [GitHub Security Advisories](https://github.com/sebastienrousseau/crypto-service/security/advisories) or according to [`SECURITY.md`](../../SECURITY.md). Never report security issues publicly.
242
249
 
250
+ The middleware verifies JWTs and HMAC signatures but does not rate-limit them. Register a rate limiter in the host application (for example [`@fastify/rate-limit`](https://github.com/fastify/fastify-rate-limit) or [`express-rate-limit`](https://github.com/express-rate-limit/express-rate-limit)) in front of the routes it protects, as `crypto-server` does.
251
+
243
252
  Cryptographic operations use the `@noble/*` libraries, Node.js `crypto` and OpenPGP.js. `@noble/post-quantum` has not been independently audited and does not guarantee constant-time execution, and no module in this suite is FIPS 140-3 validated. Key zeroization is limited: JavaScript strings and garbage-collected buffers cannot be reliably wiped. See [`SECURITY.md`](../../SECURITY.md).
244
253
 
245
254
  <p align="right"><a href="#contents">Back to Top</a></p>
package/dist/common.d.ts CHANGED
@@ -1,7 +1,13 @@
1
- import { JwtPayload } from "./types";
1
+ import { JwtPayload, MiddlewareConfig } from "./types";
2
2
  export declare function encryptPayload(key: string, data: unknown): string;
3
3
  export declare function decryptPayload(key: string, sealed: string): unknown;
4
4
  export declare function verifyHmacSignature(hmacKey: string, body: string, signature: string): boolean;
5
- export declare function verifyJwt(jwtSecret: string, token: string): JwtPayload;
5
+ export interface JwtVerifyOptions {
6
+ issuer?: string | undefined;
7
+ audience?: string | undefined;
8
+ }
9
+ export declare function verifyJwt(jwtSecret: string, token: string, options?: JwtVerifyOptions): JwtPayload;
10
+ export declare function verifyBearerJwt(config: MiddlewareConfig, authorization: string | undefined): JwtPayload;
11
+ export declare function verifyRequestSignature(config: MiddlewareConfig, headers: Record<string, string | string[] | undefined>, body: unknown): void;
6
12
  export declare function matchRoute(path: string, routes: string[]): boolean;
7
13
  //# sourceMappingURL=common.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAyB,UAAU,EAAE,MAAM,SAAS,CAAC;AAgB5D,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAIjE;AAgBD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAYnE;AAsBD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,OAAO,CA4BT;AA6BD,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,CAqGtE;AAoBD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAYlE"}
1
+ {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAyB,UAAU,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAgB9E,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAIjE;AAgBD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAYnE;AAsBD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,OAAO,CA4BT;AAWD,MAAM,WAAW,gBAAgB;IAK/B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAM5B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAiHD,wBAAgB,SAAS,CACvB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,gBAAqB,GAC7B,UAAU,CAcZ;AAUD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,gBAAgB,EACxB,aAAa,EAAE,MAAM,GAAG,SAAS,GAChC,UAAU,CAcZ;AAUD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,EACtD,IAAI,EAAE,OAAO,GACZ,IAAI,CAcN;AAoBD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAYlE"}
package/dist/common.js CHANGED
@@ -4,6 +4,8 @@ exports.encryptPayload = encryptPayload;
4
4
  exports.decryptPayload = decryptPayload;
5
5
  exports.verifyHmacSignature = verifyHmacSignature;
6
6
  exports.verifyJwt = verifyJwt;
7
+ exports.verifyBearerJwt = verifyBearerJwt;
8
+ exports.verifyRequestSignature = verifyRequestSignature;
7
9
  exports.matchRoute = matchRoute;
8
10
  const crypto_lib_1 = require("@sebastienrousseau/crypto-lib");
9
11
  const types_1 = require("./types");
@@ -42,26 +44,32 @@ function base64urlDecode(input) {
42
44
  const padded = input + "=".repeat((4 - (input.length % 4)) % 4);
43
45
  return Buffer.from(padded, "base64").toString("utf8");
44
46
  }
45
- function verifyJwt(jwtSecret, token) {
46
- if (!token) {
47
- throw new types_1.CryptoMiddlewareError("Missing JWT token", 401, "MISSING_TOKEN");
48
- }
47
+ function reject(message, code) {
48
+ throw new types_1.CryptoMiddlewareError(message, 401, code);
49
+ }
50
+ function splitToken(token) {
51
+ if (!token)
52
+ reject("Missing JWT token", "MISSING_TOKEN");
49
53
  const parts = token.split(".");
50
54
  if (parts.length !== 3) {
51
- throw new types_1.CryptoMiddlewareError("Malformed JWT: expected 3 parts", 401, "MALFORMED_TOKEN");
55
+ reject("Malformed JWT: expected 3 parts", "MALFORMED_TOKEN");
52
56
  }
53
- const [headerB64, payloadB64, signatureB64] = parts;
54
- let header;
57
+ return parts;
58
+ }
59
+ function decodeObject(segment, name) {
60
+ let value;
55
61
  try {
56
- header = JSON.parse(base64urlDecode(headerB64));
62
+ value = JSON.parse(base64urlDecode(segment));
57
63
  }
58
64
  catch {
59
- throw new types_1.CryptoMiddlewareError("Malformed JWT header", 401, "MALFORMED_TOKEN");
65
+ value = undefined;
60
66
  }
61
- if (header.alg !== "HS256") {
62
- throw new types_1.CryptoMiddlewareError(`Unsupported JWT algorithm: ${header.alg}. Only HS256 is supported.`, 401, "UNSUPPORTED_ALGORITHM");
67
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
68
+ reject(`Malformed JWT ${name}`, "MALFORMED_TOKEN");
63
69
  }
64
- const signingInput = `${headerB64}.${payloadB64}`;
70
+ return value;
71
+ }
72
+ function checkSignature(jwtSecret, signingInput, signatureB64) {
65
73
  const keyHex = Buffer.from(jwtSecret, "utf8").toString("hex");
66
74
  const computed = (0, crypto_lib_1.computeHmac)({
67
75
  algorithm: "sha256",
@@ -69,33 +77,71 @@ function verifyJwt(jwtSecret, token) {
69
77
  data: signingInput,
70
78
  });
71
79
  const sigPadded = signatureB64 + "=".repeat((4 - (signatureB64.length % 4)) % 4);
72
- const sigHex = Buffer.from(sigPadded, "base64").toString("hex");
80
+ const signatureBytes = Buffer.from(sigPadded, "base64");
73
81
  const computedBytes = Buffer.from(computed.mac, "hex");
74
- const signatureBytes = Buffer.from(sigHex, "hex");
75
82
  if (!(0, crypto_lib_1.timingSafeEqual)(computedBytes, signatureBytes)) {
76
- throw new types_1.CryptoMiddlewareError("Invalid JWT signature", 401, "INVALID_TOKEN");
83
+ reject("Invalid JWT signature", "INVALID_TOKEN");
77
84
  }
78
- let payload;
79
- try {
80
- payload = JSON.parse(base64urlDecode(payloadB64));
85
+ }
86
+ function checkTimes(payload) {
87
+ const now = Math.floor(Date.now() / 1000);
88
+ if (typeof payload.exp !== "number") {
89
+ reject("JWT has no numeric exp claim", "MISSING_EXPIRATION");
81
90
  }
82
- catch {
83
- throw new types_1.CryptoMiddlewareError("Malformed JWT payload", 401, "MALFORMED_TOKEN");
91
+ if (now >= payload.exp)
92
+ reject("JWT has expired", "TOKEN_EXPIRED");
93
+ if (payload.nbf === undefined)
94
+ return;
95
+ if (typeof payload.nbf !== "number") {
96
+ reject("JWT nbf claim is not a number", "MALFORMED_TOKEN");
84
97
  }
85
- if (payload.exp !== undefined) {
86
- const now = Math.floor(Date.now() / 1000);
87
- if (now >= payload.exp) {
88
- throw new types_1.CryptoMiddlewareError("JWT has expired", 401, "TOKEN_EXPIRED");
89
- }
98
+ if (now < payload.nbf)
99
+ reject("JWT is not yet valid", "TOKEN_NOT_YET_VALID");
100
+ }
101
+ function checkIssuerAudience(payload, options) {
102
+ if (options.issuer !== undefined && payload.iss !== options.issuer) {
103
+ reject("JWT issuer is not accepted", "INVALID_ISSUER");
90
104
  }
91
- if (payload.nbf !== undefined) {
92
- const now = Math.floor(Date.now() / 1000);
93
- if (now < payload.nbf) {
94
- throw new types_1.CryptoMiddlewareError("JWT is not yet valid", 401, "TOKEN_NOT_YET_VALID");
95
- }
105
+ if (options.audience === undefined)
106
+ return;
107
+ const aud = Array.isArray(payload.aud) ? payload.aud : [payload.aud];
108
+ if (!aud.includes(options.audience)) {
109
+ reject("JWT audience is not accepted", "INVALID_AUDIENCE");
96
110
  }
111
+ }
112
+ function verifyJwt(jwtSecret, token, options = {}) {
113
+ const [headerB64, payloadB64, signatureB64] = splitToken(token);
114
+ const header = decodeObject(headerB64, "header");
115
+ if (header["alg"] !== "HS256") {
116
+ reject(`Unsupported JWT algorithm: ${String(header["alg"])}. Only HS256 is supported.`, "UNSUPPORTED_ALGORITHM");
117
+ }
118
+ checkSignature(jwtSecret, `${headerB64}.${payloadB64}`, signatureB64);
119
+ const payload = decodeObject(payloadB64, "payload");
120
+ checkTimes(payload);
121
+ checkIssuerAudience(payload, options);
97
122
  return payload;
98
123
  }
124
+ function verifyBearerJwt(config, authorization) {
125
+ if (!config.jwtSecret) {
126
+ throw new types_1.CryptoMiddlewareError("jwtSecret is required for verify-jwt operation", 500, "MISSING_CONFIG");
127
+ }
128
+ const header = authorization ?? "";
129
+ const token = header.startsWith("Bearer ") ? header.slice(7) : "";
130
+ return verifyJwt(config.jwtSecret, token, {
131
+ issuer: config.jwtIssuer,
132
+ audience: config.jwtAudience,
133
+ });
134
+ }
135
+ function verifyRequestSignature(config, headers, body) {
136
+ if (!config.hmacKey) {
137
+ throw new types_1.CryptoMiddlewareError("hmacKey is required for verify-signature operation", 500, "MISSING_CONFIG");
138
+ }
139
+ const signature = headers["x-signature"] ??
140
+ headers["x-hub-signature-256"] ??
141
+ "";
142
+ const rawBody = typeof body === "string" ? body : JSON.stringify(body);
143
+ verifyHmacSignature(config.hmacKey, rawBody, signature);
144
+ }
99
145
  function matchRoute(path, routes) {
100
146
  if (routes.length === 0)
101
147
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAyB,MAAM,SAAS,CAAC;AA6BlE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,IAK3D,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,MAAM,YAAY,KACjB,IAAI,CA0FR"}
1
+ {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAyB,MAAM,SAAS,CAAC;AAwFlE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,IAK3D,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,MAAM,YAAY,KACjB,IAAI,CAqBR"}
package/dist/express.js CHANGED
@@ -3,6 +3,39 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createCryptoMiddleware = createCryptoMiddleware;
4
4
  const types_1 = require("./types");
5
5
  const common_1 = require("./common");
6
+ function requireKey(config, operation) {
7
+ if (!config.key) {
8
+ throw new types_1.CryptoMiddlewareError(`key is required for ${operation} operation`, 500, "MISSING_CONFIG");
9
+ }
10
+ return config.key;
11
+ }
12
+ function decryptBody(config, req) {
13
+ const key = requireKey(config, "decrypt-request");
14
+ if (req.body && typeof req.body === "object" && "encrypted" in req.body) {
15
+ req.body = (0, common_1.decryptPayload)(key, req.body.encrypted);
16
+ }
17
+ }
18
+ function encryptJsonResponses(config, res) {
19
+ const key = requireKey(config, "encrypt-response");
20
+ const originalJson = res.json.bind(res);
21
+ res.json = function encryptedJson(body) {
22
+ const sealed = (0, common_1.encryptPayload)(key, body);
23
+ return originalJson({ encrypted: sealed });
24
+ };
25
+ }
26
+ function applyOperations(config, operations, req, res) {
27
+ if (operations.includes("verify-jwt")) {
28
+ req.jwtPayload = (0, common_1.verifyBearerJwt)(config, req.headers.authorization);
29
+ }
30
+ if (operations.includes("verify-signature")) {
31
+ (0, common_1.verifyRequestSignature)(config, req.headers, req.body);
32
+ }
33
+ if (operations.includes("decrypt-request"))
34
+ decryptBody(config, req);
35
+ if (operations.includes("encrypt-response")) {
36
+ encryptJsonResponses(config, res);
37
+ }
38
+ }
6
39
  function createCryptoMiddleware(config) {
7
40
  const operations = config.operations ?? [];
8
41
  const routes = config.routes ?? [];
@@ -12,47 +45,7 @@ function createCryptoMiddleware(config) {
12
45
  return;
13
46
  }
14
47
  try {
15
- if (operations.includes("verify-jwt")) {
16
- if (!config.jwtSecret) {
17
- throw new types_1.CryptoMiddlewareError("jwtSecret is required for verify-jwt operation", 500, "MISSING_CONFIG");
18
- }
19
- const authHeader = req.headers.authorization ?? "";
20
- const token = authHeader.startsWith("Bearer ")
21
- ? authHeader.slice(7)
22
- : "";
23
- const payload = (0, common_1.verifyJwt)(config.jwtSecret, token);
24
- req.jwtPayload = payload;
25
- }
26
- if (operations.includes("verify-signature")) {
27
- if (!config.hmacKey) {
28
- throw new types_1.CryptoMiddlewareError("hmacKey is required for verify-signature operation", 500, "MISSING_CONFIG");
29
- }
30
- const signature = req.headers["x-signature"] ??
31
- req.headers["x-hub-signature-256"] ??
32
- "";
33
- const rawBody = typeof req.body === "string" ? req.body : JSON.stringify(req.body);
34
- (0, common_1.verifyHmacSignature)(config.hmacKey, rawBody, signature);
35
- }
36
- if (operations.includes("decrypt-request")) {
37
- if (!config.key) {
38
- throw new types_1.CryptoMiddlewareError("key is required for decrypt-request operation", 500, "MISSING_CONFIG");
39
- }
40
- if (req.body &&
41
- typeof req.body === "object" &&
42
- "encrypted" in req.body) {
43
- req.body = (0, common_1.decryptPayload)(config.key, req.body.encrypted);
44
- }
45
- }
46
- if (operations.includes("encrypt-response")) {
47
- if (!config.key) {
48
- throw new types_1.CryptoMiddlewareError("key is required for encrypt-response operation", 500, "MISSING_CONFIG");
49
- }
50
- const originalJson = res.json.bind(res);
51
- res.json = function encryptedJson(body) {
52
- const sealed = (0, common_1.encryptPayload)(config.key, body);
53
- return originalJson({ encrypted: sealed });
54
- };
55
- }
48
+ applyOperations(config, operations, req, res);
56
49
  next();
57
50
  }
58
51
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"fastify.d.ts","sourceRoot":"","sources":["../src/fastify.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAC;AAE7E,OAAO,EAAE,gBAAgB,EAAyB,MAAM,SAAS,CAAC;AAyBlE,iBAAe,gBAAgB,CAC7B,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,IAAI,CAAC,CAiHf;AAGD,eAAO,MAAM,YAAY,yBAGvB,CAAC"}
1
+ {"version":3,"file":"fastify.d.ts","sourceRoot":"","sources":["../src/fastify.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAC;AAE7E,OAAO,EAAE,gBAAgB,EAAyB,MAAM,SAAS,CAAC;AA8HlE,iBAAe,gBAAgB,CAC7B,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,IAAI,CAAC,CAgBf;AAGD,eAAO,MAAM,YAAY,yBAGvB,CAAC"}
package/dist/fastify.js CHANGED
@@ -7,94 +7,82 @@ exports.cryptoPlugin = void 0;
7
7
  const fastify_plugin_1 = __importDefault(require("fastify-plugin"));
8
8
  const types_1 = require("./types");
9
9
  const common_1 = require("./common");
10
- async function cryptoPluginImpl(fastify, opts) {
11
- const operations = opts.operations ?? [];
12
- const routes = opts.routes ?? [];
10
+ function sendMiddlewareError(reply, err) {
11
+ if (err instanceof types_1.CryptoMiddlewareError) {
12
+ reply.code(err.statusCode).send({
13
+ error: err.message,
14
+ code: err.code,
15
+ });
16
+ return;
17
+ }
18
+ throw err;
19
+ }
20
+ function registerVerification(fastify, opts, operations, routes) {
13
21
  fastify.addHook("onRequest", async (request, reply) => {
14
22
  if (!(0, common_1.matchRoute)(request.url, routes))
15
23
  return;
16
24
  try {
17
25
  if (operations.includes("verify-jwt")) {
18
- if (!opts.jwtSecret) {
19
- throw new types_1.CryptoMiddlewareError("jwtSecret is required for verify-jwt operation", 500, "MISSING_CONFIG");
20
- }
21
- const authHeader = request.headers.authorization ?? "";
22
- const token = authHeader.startsWith("Bearer ")
23
- ? authHeader.slice(7)
24
- : "";
25
- const payload = (0, common_1.verifyJwt)(opts.jwtSecret, token);
26
+ const payload = (0, common_1.verifyBearerJwt)(opts, request.headers.authorization);
26
27
  request.jwtPayload = payload;
27
28
  }
28
29
  if (operations.includes("verify-signature")) {
29
- if (!opts.hmacKey) {
30
- throw new types_1.CryptoMiddlewareError("hmacKey is required for verify-signature operation", 500, "MISSING_CONFIG");
31
- }
32
- const signature = request.headers["x-signature"] ??
33
- request.headers["x-hub-signature-256"] ??
34
- "";
35
- const rawBody = typeof request.body === "string"
36
- ? request.body
37
- : JSON.stringify(request.body);
38
- (0, common_1.verifyHmacSignature)(opts.hmacKey, rawBody, signature);
30
+ (0, common_1.verifyRequestSignature)(opts, request.headers, request.body);
39
31
  }
40
32
  }
41
33
  catch (err) {
42
- if (err instanceof types_1.CryptoMiddlewareError) {
43
- reply.code(err.statusCode).send({
44
- error: err.message,
45
- code: err.code,
46
- });
47
- return;
34
+ sendMiddlewareError(reply, err);
35
+ }
36
+ });
37
+ }
38
+ function registerDecryption(fastify, opts, routes) {
39
+ fastify.addHook("preHandler", async (request, reply) => {
40
+ if (!(0, common_1.matchRoute)(request.url, routes))
41
+ return;
42
+ if (!opts.key) {
43
+ reply.code(500).send({
44
+ error: "key is required for decrypt-request operation",
45
+ code: "MISSING_CONFIG",
46
+ });
47
+ return;
48
+ }
49
+ try {
50
+ const body = request.body;
51
+ if (body && typeof body === "object" && "encrypted" in body) {
52
+ request.body = (0, common_1.decryptPayload)(opts.key, body.encrypted);
48
53
  }
49
- throw err;
54
+ }
55
+ catch (err) {
56
+ sendMiddlewareError(reply, err);
50
57
  }
51
58
  });
59
+ }
60
+ function registerEncryption(fastify, opts, routes) {
61
+ fastify.addHook("preSerialization", async (request, _reply, payload) => {
62
+ if (!(0, common_1.matchRoute)(request.url, routes))
63
+ return payload;
64
+ if (!opts.key)
65
+ return payload;
66
+ if (payload !== null && typeof payload === "object") {
67
+ const sealed = (0, common_1.encryptPayload)(opts.key, payload);
68
+ return { encrypted: sealed };
69
+ }
70
+ return payload;
71
+ });
72
+ }
73
+ async function cryptoPluginImpl(fastify, opts) {
74
+ const operations = opts.operations ?? [];
75
+ const routes = opts.routes ?? [];
76
+ registerVerification(fastify, opts, operations, routes);
52
77
  if (operations.includes("decrypt-request")) {
53
- fastify.addHook("preHandler", async (request, reply) => {
54
- if (!(0, common_1.matchRoute)(request.url, routes))
55
- return;
56
- if (!opts.key) {
57
- reply.code(500).send({
58
- error: "key is required for decrypt-request operation",
59
- code: "MISSING_CONFIG",
60
- });
61
- return;
62
- }
63
- try {
64
- const body = request.body;
65
- if (body && typeof body === "object" && "encrypted" in body) {
66
- request.body =
67
- (0, common_1.decryptPayload)(opts.key, body.encrypted);
68
- }
69
- }
70
- catch (err) {
71
- if (err instanceof types_1.CryptoMiddlewareError) {
72
- reply.code(err.statusCode).send({
73
- error: err.message,
74
- code: err.code,
75
- });
76
- return;
77
- }
78
- throw err;
79
- }
80
- });
78
+ registerDecryption(fastify, opts, routes);
81
79
  }
82
80
  if (operations.includes("encrypt-response")) {
83
- fastify.addHook("preSerialization", async (request, _reply, payload) => {
84
- if (!(0, common_1.matchRoute)(request.url, routes))
85
- return payload;
86
- if (!opts.key)
87
- return payload;
88
- if (payload !== null && typeof payload === "object") {
89
- const sealed = (0, common_1.encryptPayload)(opts.key, payload);
90
- return { encrypted: sealed };
91
- }
92
- return payload;
93
- });
81
+ registerEncryption(fastify, opts, routes);
94
82
  }
95
83
  }
96
84
  exports.cryptoPlugin = (0, fastify_plugin_1.default)(cryptoPluginImpl, {
97
85
  name: "crypto-middleware",
98
- fastify: "4.x",
86
+ fastify: "4.x || 5.x",
99
87
  });
100
88
  //# sourceMappingURL=fastify.js.map
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export type { MiddlewareConfig, JwtPayload } from "./types";
2
2
  export { CryptoMiddlewareError } from "./types";
3
+ export type { JwtVerifyOptions } from "./common";
3
4
  export { encryptPayload, decryptPayload, verifyHmacSignature, verifyJwt, matchRoute, } from "./common";
4
5
  export { createCryptoMiddleware } from "./express";
5
6
  export { cryptoPlugin } from "./fastify";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAGhD,OAAO,EACL,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,SAAS,EACT,UAAU,GACX,MAAM,UAAU,CAAC;AAGlB,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAGnD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAChD,YAAY,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAGjD,OAAO,EACL,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,SAAS,EACT,UAAU,GACX,MAAM,UAAU,CAAC;AAGlB,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAGnD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC"}
package/dist/types.d.ts CHANGED
@@ -4,6 +4,8 @@ export interface MiddlewareConfig {
4
4
  operations?: Array<"decrypt-request" | "encrypt-response" | "verify-signature" | "verify-jwt">;
5
5
  hmacKey?: string;
6
6
  jwtSecret?: string;
7
+ jwtIssuer?: string;
8
+ jwtAudience?: string;
7
9
  }
8
10
  export interface JwtPayload {
9
11
  sub?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,gBAAgB;IAE/B,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAElB,UAAU,CAAC,EAAE,KAAK,CAChB,iBAAiB,GAAG,kBAAkB,GAAG,kBAAkB,GAAG,YAAY,CAC3E,CAAC;IAEF,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAcD,MAAM,WAAW,UAAU;IAEzB,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAExB,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAUD,qBAAa,qBAAsB,SAAQ,KAAK;IAE9C,SAAgB,UAAU,EAAE,MAAM,CAAC;IAEnC,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAGjB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAM9D"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,gBAAgB;IAE/B,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAElB,UAAU,CAAC,EAAE,KAAK,CAChB,iBAAiB,GAAG,kBAAkB,GAAG,kBAAkB,GAAG,YAAY,CAC3E,CAAC;IAEF,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAcD,MAAM,WAAW,UAAU;IAEzB,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAKxB,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAUD,qBAAa,qBAAsB,SAAQ,KAAK;IAE9C,SAAgB,UAAU,EAAE,MAAM,CAAC;IAEnC,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAGjB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAM9D"}
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@sebastienrousseau/crypto-middleware",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "Plug-and-play cryptographic middleware for Express and Fastify -- auto-decrypt requests, verify signatures, and encrypt responses.",
5
5
  "author": "Sebastien Rousseau <sebastienrousseau@users.noreply.github.com>",
6
6
  "license": "MIT OR Apache-2.0",
7
7
  "homepage": "https://crypto-service.co",
8
8
  "repository": {
9
- "directory": "packages/crypto-middleware",
10
9
  "type": "git",
11
- "url": "git@github.com:sebastienrousseau/crypto-service.git"
10
+ "url": "git+https://github.com/sebastienrousseau/crypto-service.git",
11
+ "directory": "packages/crypto-middleware"
12
12
  },
13
13
  "bugs": {
14
14
  "url": "https://github.com/sebastienrousseau/crypto-service/issues"
@@ -16,28 +16,30 @@
16
16
  "engines": {
17
17
  "node": ">=22.0.0"
18
18
  },
19
+ "type": "commonjs",
19
20
  "main": "./dist/index.js",
20
21
  "types": "./dist/index.d.ts",
21
- "sideEffects": false,
22
22
  "exports": {
23
23
  ".": {
24
24
  "types": "./dist/index.d.ts",
25
25
  "require": "./dist/index.js",
26
- "import": "./dist/index.js"
27
- }
26
+ "default": "./dist/index.js"
27
+ },
28
+ "./package.json": "./package.json"
28
29
  },
30
+ "sideEffects": false,
29
31
  "files": [
30
32
  "dist/**/*.js",
31
33
  "dist/**/*.d.ts",
32
34
  "dist/**/*.d.ts.map"
33
35
  ],
34
36
  "dependencies": {
35
- "@sebastienrousseau/crypto-lib": "0.0.7"
37
+ "fastify-plugin": "^6.0.0",
38
+ "@sebastienrousseau/crypto-lib": "0.0.9"
36
39
  },
37
40
  "peerDependencies": {
38
- "express": "^4.0.0",
39
- "fastify": "^4.0.0",
40
- "fastify-plugin": "^4.0.0"
41
+ "express": "^5.2.1",
42
+ "fastify": "^4.0.0 || ^5.0.0"
41
43
  },
42
44
  "peerDependenciesMeta": {
43
45
  "express": {
@@ -45,25 +47,21 @@
45
47
  },
46
48
  "fastify": {
47
49
  "optional": true
48
- },
49
- "fastify-plugin": {
50
- "optional": true
51
50
  }
52
51
  },
53
52
  "devDependencies": {
54
53
  "@sebastienrousseau/mocha-config": "^0.0.5",
55
- "@types/chai": "^4.3.7",
54
+ "@types/chai": "^5.2.3",
56
55
  "@types/chai-as-promised": "^8.0.2",
57
- "@types/express": "^4.17.21",
58
- "@types/mocha": "^10.0.2",
59
- "@types/node": "^26.4.1",
56
+ "@types/express": "^5.0.6",
57
+ "@types/mocha": "^10.0.10",
58
+ "@types/node": "^26.6.3",
60
59
  "c8": "^12.0.0",
61
- "chai": "^4.5.0",
62
- "chai-as-promised": "^7.1.2",
63
- "express": "^4.21.0",
60
+ "chai": "^6.3.0",
61
+ "chai-as-promised": "^8.0.2",
62
+ "express": "^5.2.1",
64
63
  "fastify": "^5.12.5",
65
- "fastify-plugin": "^6.0.0",
66
- "mocha": "^10.8.2",
64
+ "mocha": "^12.0.3",
67
65
  "rimraf": "^6.1.3",
68
66
  "ts-node": "^10.9.2",
69
67
  "typedoc": "^0.28.20",
@@ -88,8 +86,8 @@
88
86
  "clean": "rimraf ./coverage ./dist ./docs",
89
87
  "test": "c8 --check-coverage --lines 100 --branches 100 --functions 100 --reporter=lcov --reporter=text mocha --config=.mocharc.cjs",
90
88
  "docs": "typedoc",
91
- "lint": "eslint --ext .ts src __tests__",
92
- "lint:fix": "eslint --cache --fix --ext .ts src __tests__",
89
+ "lint": "eslint src __tests__",
90
+ "lint:fix": "eslint --cache --fix src __tests__",
93
91
  "format": "prettier --write src/**/*.ts",
94
92
  "format:check": "prettier --check \"src/**/*.ts\""
95
93
  }