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 +109 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +97 -0
- package/dist/index.js.map +1 -0
- package/dist/pow.d.ts +14 -0
- package/dist/pow.d.ts.map +1 -0
- package/dist/pow.js +105 -0
- package/dist/pow.js.map +1 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +40 -0
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
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
package/dist/pow.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pow.js","sourceRoot":"","sources":["../src/pow.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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
|
+
}
|