pow-middleware 0.1.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 ADDED
@@ -0,0 +1,109 @@
1
+ # POW Middleware
2
+
3
+ A middleware for Node.js HTTP request and proof of work handling
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install pow-middleware
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import express from 'express'
15
+ import powMiddleware from 'pow-middleware'
16
+
17
+ const app = express()
18
+ app.use(powMiddleware())
19
+ ```
20
+
21
+ ## Solving challenges in browser
22
+
23
+ Here's a complete example showing how to handle PoW challenges with `fetch`:
24
+
25
+ ```typescript
26
+ // Step 1: Initial request (will fail with 403)
27
+ let response = await fetch(url)
28
+
29
+ if (response.status === 403) {
30
+ const error = await response.json()
31
+ console.log('Request blocked:', error) // { error: 'pow-required' }
32
+
33
+ // Step 2: Extract challenge from response headers
34
+ const challengeHeader = response.headers.get('x-pow-challenge')
35
+ if (!challengeHeader) {
36
+ throw new Error('No challenge provided')
37
+ }
38
+
39
+ const challenge = JSON.parse(challengeHeader)
40
+ console.log('Challenge received:', challenge)
41
+
42
+ // Step 3: Solve the challenge
43
+ const { hash, solvedChallenge } = await solveChallenge(challenge)
44
+ console.log('Challenge solved! Nonce:', solvedChallenge.nonce)
45
+
46
+ // Step 4: Retry request with PoW headers
47
+ response = await fetch(url, {
48
+ headers: {
49
+ 'x-pow': hash,
50
+ 'x-pow-challenge': JSON.stringify(solvedChallenge),
51
+ },
52
+ })
53
+ }
54
+ ```
55
+
56
+ ```typescript
57
+ // Helper function to solve the challenge
58
+ async function solveChallenge(challenge: any) {
59
+ let nonce = 0
60
+ let hash: string
61
+ while (true) {
62
+ challenge.nonce = nonce
63
+ hash = await computePoWHash(challenge)
64
+ if (checkLeadingZeroBits(hash, challenge.difficulty)) {
65
+ return { hash, solvedChallenge: challenge }
66
+ }
67
+ nonce++
68
+ }
69
+ throw new Error('Failed to solve challenge')
70
+ }
71
+
72
+ // SHA-256 hash function for browser
73
+ async function sha256(data: string): Promise<string> {
74
+ const encoder = new TextEncoder()
75
+ const buffer = await crypto.subtle.digest('SHA-256', encoder.encode(data))
76
+ return Array.from(new Uint8Array(buffer))
77
+ .map((b) => b.toString(16).padStart(2, '0'))
78
+ .join('')
79
+ }
80
+
81
+ // Compute PoW hash
82
+ async function computePoWHash(challenge: any): Promise<string> {
83
+ const seed = `${challenge.id}:${challenge.ts}:${challenge.difficulty}:${challenge.req}:${challenge.signature}:${challenge.nonce}`
84
+ return await sha256(seed)
85
+ }
86
+
87
+ // Check if hash has required leading zero bits
88
+ function checkLeadingZeroBits(hex: string, zeroBitsCount: number): boolean {
89
+ // Convert hex string to bytes
90
+ const bytes = new Uint8Array(hex.length / 2)
91
+ for (let i = 0; i < bytes.length; i++) {
92
+ bytes[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16)
93
+ }
94
+
95
+ // Check if first zeroBitsCount bits are 0
96
+ for (let bitIndex = 0; bitIndex < zeroBitsCount; bitIndex++) {
97
+ const byteIndex = Math.floor(bitIndex / 8)
98
+ if (byteIndex >= bytes.length) return false
99
+
100
+ const bitPosition = bitIndex % 8
101
+ const mask = 1 << (7 - bitPosition)
102
+
103
+ if ((bytes[byteIndex] & mask) !== 0) {
104
+ return false
105
+ }
106
+ }
107
+ return true
108
+ }
109
+ ```
@@ -0,0 +1,19 @@
1
+ import { PoWMiddleware, PoWMiddlewareOptions } from './types';
2
+ export declare const Constants: {
3
+ POW_HEADER: string;
4
+ POW_CHALLENGE_HEADER: string;
5
+ DEFAULT_DIFFICULTY: number;
6
+ DEFAULT_TTL: number;
7
+ DEFAULT_TRUST_PROXY: boolean;
8
+ ERRORS: {
9
+ POW_REQUIRED: string;
10
+ INVALID_CHALLENGE_FORMAT: string;
11
+ VALIDATION_FAILED: string;
12
+ CHALLENGE_EXPIRED: string;
13
+ };
14
+ };
15
+ export declare const powMiddleware: (options?: PoWMiddlewareOptions) => PoWMiddleware;
16
+ export type * from './types';
17
+ export * from './pow';
18
+ export default powMiddleware;
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAIL,aAAa,EACb,oBAAoB,EAErB,MAAM,SAAS,CAAA;AAEhB,eAAO,MAAM,SAAS;;;;;;;;;;;;CAYrB,CAAA;AAED,eAAO,MAAM,aAAa,GACxB,UAAU,oBAAoB,KAC7B,aAmFF,CAAA;AAGD,mBAAmB,SAAS,CAAA;AAC5B,cAAc,OAAO,CAAA;AACrB,eAAe,aAAa,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.powMiddleware = exports.Constants = void 0;
18
+ const pow_1 = require("./pow");
19
+ exports.Constants = {
20
+ POW_HEADER: 'x-pow',
21
+ POW_CHALLENGE_HEADER: 'x-pow-challenge',
22
+ DEFAULT_DIFFICULTY: 4,
23
+ DEFAULT_TTL: 5 * 60 * 1000, // 5 minutes
24
+ DEFAULT_TRUST_PROXY: false,
25
+ ERRORS: {
26
+ POW_REQUIRED: 'pow-required',
27
+ INVALID_CHALLENGE_FORMAT: 'invalid-challenge-format',
28
+ VALIDATION_FAILED: 'validation-failed',
29
+ CHALLENGE_EXPIRED: 'challenge-expired',
30
+ },
31
+ };
32
+ const powMiddleware = (options) => {
33
+ let { secret } = options || {};
34
+ const { difficulty = exports.Constants.DEFAULT_DIFFICULTY, trustProxy = exports.Constants.DEFAULT_TRUST_PROXY, ttl = exports.Constants.DEFAULT_TTL, powHeaderName = exports.Constants.POW_HEADER, powChallengeHeaderName = exports.Constants.POW_CHALLENGE_HEADER, } = options || {};
35
+ if (!secret) {
36
+ secret = (0, pow_1.randomSha256Hex)();
37
+ }
38
+ return (req, res, next) => {
39
+ const powHeader = req.headers[powHeaderName];
40
+ const powChallengeHeader = req.headers[powChallengeHeaderName];
41
+ const newChallenge = (0, pow_1.generateChallenge)((0, pow_1.randomUUID)(), Date.now(), difficulty, req, ttl, secret, trustProxy);
42
+ if (!powHeader || !powChallengeHeader) {
43
+ res.statusCode = 403;
44
+ res.setHeader('Content-Type', 'application/json');
45
+ res.setHeader(powChallengeHeaderName, JSON.stringify(newChallenge));
46
+ return next?.(new Error(exports.Constants.ERRORS.POW_REQUIRED));
47
+ }
48
+ if (typeof powHeader !== 'string' ||
49
+ typeof powChallengeHeader !== 'string') {
50
+ res.statusCode = 403;
51
+ res.setHeader('Content-Type', 'application/json');
52
+ res.setHeader(powChallengeHeaderName, JSON.stringify(newChallenge));
53
+ return next?.(new Error(exports.Constants.ERRORS.INVALID_CHALLENGE_FORMAT));
54
+ }
55
+ try {
56
+ let pow;
57
+ let challenge;
58
+ try {
59
+ pow = powHeader;
60
+ challenge = JSON.parse(powChallengeHeader);
61
+ }
62
+ catch (parseError) {
63
+ res.statusCode = 403;
64
+ res.setHeader('Content-Type', 'application/json');
65
+ res.setHeader(powChallengeHeaderName, JSON.stringify(newChallenge));
66
+ return next?.(new Error(exports.Constants.ERRORS.INVALID_CHALLENGE_FORMAT));
67
+ }
68
+ const now = Date.now();
69
+ if (now > challenge.ts + challenge.ttl) {
70
+ res.statusCode = 403;
71
+ res.setHeader('Content-Type', 'application/json');
72
+ res.setHeader(powChallengeHeaderName, JSON.stringify(newChallenge));
73
+ return next?.(new Error(exports.Constants.ERRORS.CHALLENGE_EXPIRED));
74
+ }
75
+ const isValid = (0, pow_1.validatePoW)(challenge, pow, secret, req, trustProxy);
76
+ if (isValid) {
77
+ next?.();
78
+ }
79
+ else {
80
+ res.statusCode = 403;
81
+ res.setHeader('Content-Type', 'application/json');
82
+ res.setHeader(powChallengeHeaderName, JSON.stringify(newChallenge));
83
+ return next?.(new Error(exports.Constants.ERRORS.VALIDATION_FAILED));
84
+ }
85
+ }
86
+ catch (error) {
87
+ res.statusCode = 403;
88
+ res.setHeader('Content-Type', 'application/json');
89
+ res.setHeader(powChallengeHeaderName, JSON.stringify(newChallenge));
90
+ return next?.(new Error(exports.Constants.ERRORS.VALIDATION_FAILED));
91
+ }
92
+ };
93
+ };
94
+ exports.powMiddleware = powMiddleware;
95
+ __exportStar(require("./pow"), exports);
96
+ exports.default = exports.powMiddleware;
97
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/pow.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { IncomingMessage } from 'http';
3
+ import { Challenge } from './types';
4
+ export { randomUUID };
5
+ export declare const randomSha256Hex: () => string;
6
+ export declare const hashmac: (data: string, key: string) => string;
7
+ export declare const sha256: (data: string) => string;
8
+ export declare function checkLeadingZeroBits(hex: string, zeroBitsCount: number): boolean;
9
+ export declare const hashRequest: (req: IncomingMessage, trustProxy: boolean) => string;
10
+ export declare const generateSignature: (id: string, ts: number, difficulty: number, reqHash: string, ttl: number, secret: string) => string;
11
+ export declare const generateChallenge: (id: string, ts: number, difficulty: number, req: IncomingMessage, ttl: number, secret: string, trustProxy: boolean) => Challenge;
12
+ export declare const computePoWHash: (challenge: Challenge) => string;
13
+ export declare const validatePoW: (challenge: Challenge, powHash: string, secret: string, req: IncomingMessage, trustProxy: boolean) => boolean;
14
+ //# sourceMappingURL=pow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pow.d.ts","sourceRoot":"","sources":["../src/pow.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAEnC,OAAO,EAAE,UAAU,EAAE,CAAA;AAErB,eAAO,MAAM,eAAe,QAAO,MAElC,CAAA;AAED,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,KAAG,MAEnD,CAAA;AAED,eAAO,MAAM,MAAM,GAAI,MAAM,MAAM,KAAG,MAErC,CAAA;AAED,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,aAAa,EAAE,MAAM,GACpB,OAAO,CAgBT;AAED,eAAO,MAAM,WAAW,GACtB,KAAK,eAAe,EACpB,YAAY,OAAO,KAClB,MAgBF,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,IAAI,MAAM,EACV,IAAI,MAAM,EACV,YAAY,MAAM,EAClB,SAAS,MAAM,EACf,KAAK,MAAM,EACX,QAAQ,MAAM,KACb,MAGF,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,IAAI,MAAM,EACV,IAAI,MAAM,EACV,YAAY,MAAM,EAClB,KAAK,eAAe,EACpB,KAAK,MAAM,EACX,QAAQ,MAAM,EACd,YAAY,OAAO,KAClB,SAYF,CAAA;AAED,eAAO,MAAM,cAAc,GAAI,WAAW,SAAS,KAAG,MAGrD,CAAA;AAED,eAAO,MAAM,WAAW,GACtB,WAAW,SAAS,EACpB,SAAS,MAAM,EACf,QAAQ,MAAM,EACd,KAAK,eAAe,EACpB,YAAY,OAAO,KAClB,OAqCF,CAAA"}
package/dist/pow.js ADDED
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.validatePoW = exports.computePoWHash = exports.generateChallenge = exports.generateSignature = exports.hashRequest = exports.sha256 = exports.hashmac = exports.randomSha256Hex = exports.randomUUID = void 0;
7
+ exports.checkLeadingZeroBits = checkLeadingZeroBits;
8
+ const crypto_1 = __importDefault(require("crypto"));
9
+ const crypto_2 = require("crypto");
10
+ Object.defineProperty(exports, "randomUUID", { enumerable: true, get: function () { return crypto_2.randomUUID; } });
11
+ const randomSha256Hex = () => {
12
+ return crypto_1.default.randomBytes(32).toString('hex');
13
+ };
14
+ exports.randomSha256Hex = randomSha256Hex;
15
+ const hashmac = (data, key) => {
16
+ return crypto_1.default.createHmac('sha256', key).update(data).digest('hex');
17
+ };
18
+ exports.hashmac = hashmac;
19
+ const sha256 = (data) => {
20
+ return crypto_1.default.createHash('sha256').update(data).digest('hex');
21
+ };
22
+ exports.sha256 = sha256;
23
+ function checkLeadingZeroBits(hex, zeroBitsCount) {
24
+ const buf = Buffer.from(hex, 'hex');
25
+ // Check if first zeroBitsCount bits are 0
26
+ for (let bitIndex = 0; bitIndex < zeroBitsCount; bitIndex++) {
27
+ const byteIndex = Math.floor(bitIndex / 8);
28
+ if (byteIndex >= buf.length)
29
+ return false;
30
+ const bitPosition = bitIndex % 8;
31
+ const mask = 1 << (7 - bitPosition);
32
+ if ((buf[byteIndex] & mask) !== 0) {
33
+ return false;
34
+ }
35
+ }
36
+ return true;
37
+ }
38
+ const hashRequest = (req, trustProxy) => {
39
+ const method = req.method || 'GET';
40
+ const url = req.url || '/';
41
+ // Get the client IP address
42
+ let ip = '';
43
+ if (trustProxy && req.headers['x-forwarded-for']) {
44
+ const forwardedFor = req.headers['x-forwarded-for'];
45
+ ip = Array.isArray(forwardedFor)
46
+ ? forwardedFor[0]
47
+ : forwardedFor.split(',')[0].trim();
48
+ }
49
+ else {
50
+ ip = req.socket.remoteAddress || '';
51
+ }
52
+ return (0, exports.sha256)(`${ip}:${method}:${url}`);
53
+ };
54
+ exports.hashRequest = hashRequest;
55
+ const generateSignature = (id, ts, difficulty, reqHash, ttl, secret) => {
56
+ const signatureData = `${id}:${ts}:${difficulty}:${reqHash}:${ttl}`;
57
+ return (0, exports.hashmac)(signatureData, secret);
58
+ };
59
+ exports.generateSignature = generateSignature;
60
+ const generateChallenge = (id, ts, difficulty, req, ttl, secret, trustProxy) => {
61
+ const reqHash = (0, exports.hashRequest)(req, trustProxy);
62
+ const signature = (0, exports.generateSignature)(id, ts, difficulty, reqHash, ttl, secret);
63
+ return {
64
+ id,
65
+ difficulty,
66
+ req: reqHash,
67
+ ts,
68
+ ttl,
69
+ signature,
70
+ nonce: null,
71
+ };
72
+ };
73
+ exports.generateChallenge = generateChallenge;
74
+ const computePoWHash = (challenge) => {
75
+ const seed = `${challenge.id}:${challenge.ts}:${challenge.difficulty}:${challenge.req}:${challenge.signature}:${challenge.nonce}`;
76
+ return (0, exports.sha256)(seed);
77
+ };
78
+ exports.computePoWHash = computePoWHash;
79
+ const validatePoW = (challenge, powHash, secret, req, trustProxy) => {
80
+ if (challenge.nonce === null || challenge.nonce === undefined) {
81
+ return false;
82
+ }
83
+ const expectedReqHash = (0, exports.hashRequest)(req, trustProxy);
84
+ if (challenge.req !== expectedReqHash) {
85
+ return false;
86
+ }
87
+ const expectedSignature = (0, exports.generateSignature)(challenge.id, challenge.ts, challenge.difficulty, challenge.req, challenge.ttl, secret);
88
+ if (challenge.signature !== expectedSignature) {
89
+ return false;
90
+ }
91
+ const now = Date.now();
92
+ if (Math.abs(now - challenge.ts) >= challenge.ttl) {
93
+ return false;
94
+ }
95
+ const expectedHash = (0, exports.computePoWHash)(challenge);
96
+ if (powHash !== expectedHash) {
97
+ return false;
98
+ }
99
+ if (!checkLeadingZeroBits(powHash, challenge.difficulty)) {
100
+ return false;
101
+ }
102
+ return true;
103
+ };
104
+ exports.validatePoW = validatePoW;
105
+ //# sourceMappingURL=pow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pow.js","sourceRoot":"","sources":["../src/pow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { IncomingMessage, ServerResponse } from 'http';
2
+ export interface PoWMiddlewareOptions {
3
+ secret: string;
4
+ difficulty?: number;
5
+ ttl?: number;
6
+ trustProxy?: boolean;
7
+ powHeaderName?: string;
8
+ powChallengeHeaderName?: string;
9
+ }
10
+ export type NextFunction = (err?: any) => void;
11
+ export interface PoWMiddleware {
12
+ (req: IncomingMessage, res: ServerResponse, next?: NextFunction): void;
13
+ }
14
+ export interface Challenge {
15
+ id: string;
16
+ difficulty: number;
17
+ req: string;
18
+ ts: number;
19
+ ttl: number;
20
+ signature: string;
21
+ nonce?: number | null;
22
+ }
23
+ export { IncomingMessage, ServerResponse };
24
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAA;AAEtD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,sBAAsB,CAAC,EAAE,MAAM,CAAA;CAChC;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,CAAA;AAC9C,MAAM,WAAW,aAAa;IAC5B,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,IAAI,CAAA;CACvE;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;IACX,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAED,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,CAAA"}
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ServerResponse = exports.IncomingMessage = void 0;
4
+ const http_1 = require("http");
5
+ Object.defineProperty(exports, "IncomingMessage", { enumerable: true, get: function () { return http_1.IncomingMessage; } });
6
+ Object.defineProperty(exports, "ServerResponse", { enumerable: true, get: function () { return http_1.ServerResponse; } });
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;AAAA,+BAAsD;AA0B7C,gGA1BA,sBAAe,OA0BA;AAAE,+FA1BA,qBAAc,OA0BA"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "pow-middleware",
3
+ "version": "0.1.0",
4
+ "description": "A middleware for Node.js HTTP request and proof of work handling",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "test": "jest",
10
+ "test:coverage": "jest --coverage"
11
+ },
12
+ "keywords": [
13
+ "middleware",
14
+ "http",
15
+ "nodejs"
16
+ ],
17
+ "author": "",
18
+ "license": "UNLICENSED",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/Dexon95/pow-middleware.git"
22
+ },
23
+ "devDependencies": {
24
+ "@types/express": "^5.0.6",
25
+ "@types/jest": "^30.0.0",
26
+ "@types/node": "^20.11.0",
27
+ "@types/supertest": "^6.0.3",
28
+ "express": "^5.2.1",
29
+ "jest": "^30.2.0",
30
+ "supertest": "^7.2.2",
31
+ "ts-jest": "^29.4.6",
32
+ "typescript": "^5.3.3"
33
+ },
34
+ "files": [
35
+ "dist"
36
+ ],
37
+ "engines": {
38
+ "node": ">=14.0.0"
39
+ }
40
+ }