@riocrypto/common-server 1.0.2696 → 1.0.2698
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/build/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from "./middlewares/require-min-admin-role";
|
|
|
9
9
|
export * from "./middlewares/verify-csrf-token";
|
|
10
10
|
export * from "./middlewares/dynamic-rate-limiter";
|
|
11
11
|
export * from "./middlewares/auth-context";
|
|
12
|
+
export * from "./middlewares/verify-turnstile";
|
|
12
13
|
export * from "./services/apiKey";
|
|
13
14
|
export * from "./services/password";
|
|
14
15
|
export * from "./services/logger";
|
package/build/index.js
CHANGED
|
@@ -25,6 +25,7 @@ __exportStar(require("./middlewares/require-min-admin-role"), exports);
|
|
|
25
25
|
__exportStar(require("./middlewares/verify-csrf-token"), exports);
|
|
26
26
|
__exportStar(require("./middlewares/dynamic-rate-limiter"), exports);
|
|
27
27
|
__exportStar(require("./middlewares/auth-context"), exports);
|
|
28
|
+
__exportStar(require("./middlewares/verify-turnstile"), exports);
|
|
28
29
|
__exportStar(require("./services/apiKey"), exports);
|
|
29
30
|
__exportStar(require("./services/password"), exports);
|
|
30
31
|
__exportStar(require("./services/logger"), exports);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from "express";
|
|
2
|
+
interface TurnstileConfig {
|
|
3
|
+
/**
|
|
4
|
+
* The Turnstile secret key (from Cloudflare dashboard)
|
|
5
|
+
* Can be obtained from: https://dash.cloudflare.com/?to=/:account/turnstile
|
|
6
|
+
*/
|
|
7
|
+
secretKey: string;
|
|
8
|
+
/**
|
|
9
|
+
* Field name in request body that contains the turnstile token
|
|
10
|
+
* Default: "turnstileToken"
|
|
11
|
+
*/
|
|
12
|
+
tokenField?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Whether to skip verification in development/test environments
|
|
15
|
+
* Default: false
|
|
16
|
+
*/
|
|
17
|
+
skipInDevelopment?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Custom error message when verification fails
|
|
20
|
+
*/
|
|
21
|
+
errorMessage?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Logger function for errors
|
|
24
|
+
*/
|
|
25
|
+
logger?: {
|
|
26
|
+
warn: (msg: object) => void;
|
|
27
|
+
error: (msg: object) => void;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Creates a middleware that verifies Cloudflare Turnstile tokens
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const verifyTurnstile = createTurnstileMiddleware({
|
|
36
|
+
* secretKey: process.env.TURNSTILE_SECRET_KEY!,
|
|
37
|
+
* skipInDevelopment: process.env.NODE_ENV !== 'production',
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* app.post('/api/auth/send-code', verifyTurnstile, sendCodeHandler);
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function createTurnstileMiddleware(config: TurnstileConfig): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
44
|
+
export { TurnstileConfig };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.createTurnstileMiddleware = void 0;
|
|
16
|
+
const axios_1 = __importDefault(require("axios"));
|
|
17
|
+
const TURNSTILE_VERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
|
|
18
|
+
/**
|
|
19
|
+
* Normalize IP address (handle IPv4-mapped IPv6)
|
|
20
|
+
*/
|
|
21
|
+
function normalizeIp(ip) {
|
|
22
|
+
if (!ip)
|
|
23
|
+
return undefined;
|
|
24
|
+
if (ip.startsWith("::ffff:")) {
|
|
25
|
+
return ip.substring(7);
|
|
26
|
+
}
|
|
27
|
+
return ip;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get client IP from request, prioritizing Cloudflare headers
|
|
31
|
+
*/
|
|
32
|
+
function getClientIp(req) {
|
|
33
|
+
const cfConnectingIp = req.headers["cf-connecting-ip"];
|
|
34
|
+
const xForwardedFor = req.headers["x-forwarded-for"];
|
|
35
|
+
if (cfConnectingIp) {
|
|
36
|
+
return normalizeIp(cfConnectingIp);
|
|
37
|
+
}
|
|
38
|
+
if (xForwardedFor) {
|
|
39
|
+
return normalizeIp(xForwardedFor.split(",")[0].trim());
|
|
40
|
+
}
|
|
41
|
+
return normalizeIp(req.ip);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Creates a middleware that verifies Cloudflare Turnstile tokens
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* const verifyTurnstile = createTurnstileMiddleware({
|
|
49
|
+
* secretKey: process.env.TURNSTILE_SECRET_KEY!,
|
|
50
|
+
* skipInDevelopment: process.env.NODE_ENV !== 'production',
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* app.post('/api/auth/send-code', verifyTurnstile, sendCodeHandler);
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
function createTurnstileMiddleware(config) {
|
|
57
|
+
const { secretKey, tokenField = "turnstileToken", skipInDevelopment = false, errorMessage = "Human verification failed. Please try again.", logger, } = config;
|
|
58
|
+
if (!secretKey) {
|
|
59
|
+
throw new Error("[Turnstile] Secret key is required. Get it from https://dash.cloudflare.com/?to=/:account/turnstile");
|
|
60
|
+
}
|
|
61
|
+
return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
62
|
+
var _a;
|
|
63
|
+
// Skip in development if configured
|
|
64
|
+
if (skipInDevelopment) {
|
|
65
|
+
return next();
|
|
66
|
+
}
|
|
67
|
+
const token = (_a = req.body) === null || _a === void 0 ? void 0 : _a[tokenField];
|
|
68
|
+
if (!token) {
|
|
69
|
+
logger === null || logger === void 0 ? void 0 : logger.warn({
|
|
70
|
+
message: "Turnstile token missing from request",
|
|
71
|
+
path: req.path,
|
|
72
|
+
ip: getClientIp(req),
|
|
73
|
+
});
|
|
74
|
+
res.status(400).json({
|
|
75
|
+
error: "Verification token is required.",
|
|
76
|
+
});
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const remoteIp = getClientIp(req);
|
|
81
|
+
const response = yield axios_1.default.post(TURNSTILE_VERIFY_URL, new URLSearchParams(Object.assign({ secret: secretKey, response: token }, (remoteIp && { remoteip: remoteIp }))), {
|
|
82
|
+
headers: {
|
|
83
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
84
|
+
},
|
|
85
|
+
timeout: 10000, // 10 second timeout
|
|
86
|
+
});
|
|
87
|
+
if (response.data.success) {
|
|
88
|
+
// Verification successful
|
|
89
|
+
return next();
|
|
90
|
+
}
|
|
91
|
+
// Verification failed
|
|
92
|
+
logger === null || logger === void 0 ? void 0 : logger.warn({
|
|
93
|
+
message: "Turnstile verification failed",
|
|
94
|
+
path: req.path,
|
|
95
|
+
ip: remoteIp,
|
|
96
|
+
errorCodes: response.data["error-codes"],
|
|
97
|
+
});
|
|
98
|
+
res.status(403).json({
|
|
99
|
+
error: errorMessage,
|
|
100
|
+
codes: response.data["error-codes"],
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
logger === null || logger === void 0 ? void 0 : logger.error({
|
|
105
|
+
message: "Error verifying Turnstile token",
|
|
106
|
+
path: req.path,
|
|
107
|
+
ip: getClientIp(req),
|
|
108
|
+
error: error instanceof Error ? error.message : String(error),
|
|
109
|
+
});
|
|
110
|
+
// On error, we could either:
|
|
111
|
+
// 1. Block the request (more secure)
|
|
112
|
+
// 2. Allow it through (better UX but less secure)
|
|
113
|
+
// Here we block to be safe
|
|
114
|
+
res.status(500).json({
|
|
115
|
+
error: "Verification service unavailable. Please try again.",
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
exports.createTurnstileMiddleware = createTurnstileMiddleware;
|