core: add IP-range based rules for permissions and fifficulty

This commit is contained in:
Vaxry
2025-04-15 01:34:57 +01:00
parent 1bdc60b8d2
commit 321d1eb326
9 changed files with 602 additions and 20 deletions

View File

@@ -12,13 +12,22 @@ AI scrapers are everywhere. This will stop them. `robots.txt` won't.
- Protect your endpoint from AI bots with a cryptographic challenge
- Easy configuration in jsonc
- Support for cloudflare
- Support for IP-Range based rules (both ipv4 and ipv6)
- Minimal. The waiting page is tiny and light on network usage.
### Planned features
- Dynamic challenge amount (aka difficulty)
- Dynamic challenge amount (aka difficulty) based on traffic
- Detection of token overuse
- Better wait screen
- Better git integration (it's quite rudimentary right now)
## Caveats
If you are using this, it's almost certain search engines will stop indexing your site. Keep this in mind.
## Setup guide
1. Clone and build this repo. You will need `openssl`, `g++>=12`, and deps for `pistache`, `fmt` and `tinylates`.
2. Create a `config.jsonc`. An example one is in `example/`.
3. Adjust the config to your needs. Options are documented with comments in the example config.
4. Set up your IP rules if you want. These allow you to set up IPs that are automatically blocked, or allowed to access without a challenge. This is useful for e.g. search engine scrapers. Some IP ranges can be found in `example/index_bots.jsonc`.
5. Run checkpoint with your config: `./build/checkpoint -c config.jsonc`. How you run it long-term as a service is up to you.

View File

@@ -2,13 +2,14 @@
// where the html files are located
"html_dir": "./html",
// where the proxy should store its db (directory)
// where the proxy should store its private signing key (directory)
"data_dir": "./data",
// what port the proxy should listen on
"port": 3001,
// what address should the proxy pass after successful verification
// what address should the proxy pass after successful verification. DO NOT add a / at the end or http(s)://.
// this address should be local (127.0.0.1). Other configurations are not supported.
"forward_address": "127.0.0.1:3000",
// max request size of 10MB
@@ -18,5 +19,21 @@
"proxy_timeout_sec": 120,
// enables (a lot) more logging
"trace_logging": false
"trace_logging": false,
// the default difficulty for the challenge. 4 takes less than a second on a powerful desktop, and up to 15s on a low-powered phone.
// 5 is 16x slower.
// NOT recommended to set to anything below 4 or above 5.
"default_challenge_difficulty": 4,
// specific ip range configs.
"ip_configs": [
{
"action": "ALLOW",
"ip_ranges": [
"127.0.0.1/24",
"::1/128"
]
}
]
}

311
example/index_bots.jsonc Normal file
View File

@@ -0,0 +1,311 @@
// This jsonc file contains some known web crawlers used by search engines.
// You may pick and choose whichever you like.
[
// Google, gathered 15 IV 2025
{
"action": "Allow",
"ip_ranges": [
"2001:4860:4801:10::/64",
"2001:4860:4801:11::/64",
"2001:4860:4801:12::/64",
"2001:4860:4801:13::/64",
"2001:4860:4801:14::/64",
"2001:4860:4801:15::/64",
"2001:4860:4801:16::/64",
"2001:4860:4801:17::/64",
"2001:4860:4801:18::/64",
"2001:4860:4801:19::/64",
"2001:4860:4801:1a::/64",
"2001:4860:4801:1b::/64",
"2001:4860:4801:1c::/64",
"2001:4860:4801:1d::/64",
"2001:4860:4801:1e::/64",
"2001:4860:4801:1f::/64",
"2001:4860:4801:20::/64",
"2001:4860:4801:21::/64",
"2001:4860:4801:22::/64",
"2001:4860:4801:23::/64",
"2001:4860:4801:24::/64",
"2001:4860:4801:25::/64",
"2001:4860:4801:26::/64",
"2001:4860:4801:27::/64",
"2001:4860:4801:28::/64",
"2001:4860:4801:29::/64",
"2001:4860:4801:2::/64",
"2001:4860:4801:2a::/64",
"2001:4860:4801:2b::/64",
"2001:4860:4801:2c::/64",
"2001:4860:4801:2d::/64",
"2001:4860:4801:2e::/64",
"2001:4860:4801:2f::/64",
"2001:4860:4801:31::/64",
"2001:4860:4801:32::/64",
"2001:4860:4801:33::/64",
"2001:4860:4801:34::/64",
"2001:4860:4801:35::/64",
"2001:4860:4801:36::/64",
"2001:4860:4801:37::/64",
"2001:4860:4801:38::/64",
"2001:4860:4801:39::/64",
"2001:4860:4801:3a::/64",
"2001:4860:4801:3b::/64",
"2001:4860:4801:3c::/64",
"2001:4860:4801:3d::/64",
"2001:4860:4801:3e::/64",
"2001:4860:4801:40::/64",
"2001:4860:4801:41::/64",
"2001:4860:4801:42::/64",
"2001:4860:4801:43::/64",
"2001:4860:4801:44::/64",
"2001:4860:4801:45::/64",
"2001:4860:4801:46::/64",
"2001:4860:4801:47::/64",
"2001:4860:4801:48::/64",
"2001:4860:4801:49::/64",
"2001:4860:4801:4a::/64",
"2001:4860:4801:4b::/64",
"2001:4860:4801:4c::/64",
"2001:4860:4801:50::/64",
"2001:4860:4801:51::/64",
"2001:4860:4801:52::/64",
"2001:4860:4801:53::/64",
"2001:4860:4801:54::/64",
"2001:4860:4801:55::/64",
"2001:4860:4801:56::/64",
"2001:4860:4801:60::/64",
"2001:4860:4801:61::/64",
"2001:4860:4801:62::/64",
"2001:4860:4801:63::/64",
"2001:4860:4801:64::/64",
"2001:4860:4801:65::/64",
"2001:4860:4801:66::/64",
"2001:4860:4801:67::/64",
"2001:4860:4801:68::/64",
"2001:4860:4801:69::/64",
"2001:4860:4801:6a::/64",
"2001:4860:4801:6b::/64",
"2001:4860:4801:6c::/64",
"2001:4860:4801:6d::/64",
"2001:4860:4801:6e::/64",
"2001:4860:4801:6f::/64",
"2001:4860:4801:70::/64",
"2001:4860:4801:71::/64",
"2001:4860:4801:72::/64",
"2001:4860:4801:73::/64",
"2001:4860:4801:74::/64",
"2001:4860:4801:75::/64",
"2001:4860:4801:76::/64",
"2001:4860:4801:77::/64",
"2001:4860:4801:78::/64",
"2001:4860:4801:79::/64",
"2001:4860:4801:80::/64",
"2001:4860:4801:81::/64",
"2001:4860:4801:82::/64",
"2001:4860:4801:83::/64",
"2001:4860:4801:84::/64",
"2001:4860:4801:85::/64",
"2001:4860:4801:86::/64",
"2001:4860:4801:87::/64",
"2001:4860:4801:88::/64",
"2001:4860:4801:90::/64",
"2001:4860:4801:91::/64",
"2001:4860:4801:92::/64",
"2001:4860:4801:93::/64",
"2001:4860:4801:94::/64",
"2001:4860:4801:95::/64",
"2001:4860:4801:96::/64",
"2001:4860:4801:a0::/64",
"2001:4860:4801:a1::/64",
"2001:4860:4801:a2::/64",
"2001:4860:4801:a3::/64",
"2001:4860:4801:a4::/64",
"2001:4860:4801:a5::/64",
"2001:4860:4801:c::/64",
"2001:4860:4801:f::/64",
"192.178.5.0/27",
"192.178.6.0/27",
"192.178.6.128/27",
"192.178.6.160/27",
"192.178.6.192/27",
"192.178.6.32/27",
"192.178.6.64/27",
"192.178.6.96/27",
"34.100.182.96/28",
"34.101.50.144/28",
"34.118.254.0/28",
"34.118.66.0/28",
"34.126.178.96/28",
"34.146.150.144/28",
"34.147.110.144/28",
"34.151.74.144/28",
"34.152.50.64/28",
"34.154.114.144/28",
"34.155.98.32/28",
"34.165.18.176/28",
"34.175.160.64/28",
"34.176.130.16/28",
"34.22.85.0/27",
"34.64.82.64/28",
"34.65.242.112/28",
"34.80.50.80/28",
"34.88.194.0/28",
"34.89.10.80/28",
"34.89.198.80/28",
"34.96.162.48/28",
"35.247.243.240/28",
"66.249.64.0/27",
"66.249.64.128/27",
"66.249.64.160/27",
"66.249.64.224/27",
"66.249.64.32/27",
"66.249.64.64/27",
"66.249.64.96/27",
"66.249.65.0/27",
"66.249.65.128/27",
"66.249.65.160/27",
"66.249.65.192/27",
"66.249.65.224/27",
"66.249.65.32/27",
"66.249.65.64/27",
"66.249.65.96/27",
"66.249.66.0/27",
"66.249.66.128/27",
"66.249.66.160/27",
"66.249.66.192/27",
"66.249.66.224/27",
"66.249.66.32/27",
"66.249.66.64/27",
"66.249.66.96/27",
"66.249.68.0/27",
"66.249.68.128/27",
"66.249.68.32/27",
"66.249.68.64/27",
"66.249.68.96/27",
"66.249.69.0/27",
"66.249.69.128/27",
"66.249.69.160/27",
"66.249.69.192/27",
"66.249.69.224/27",
"66.249.69.32/27",
"66.249.69.64/27",
"66.249.69.96/27",
"66.249.70.0/27",
"66.249.70.128/27",
"66.249.70.160/27",
"66.249.70.192/27",
"66.249.70.224/27",
"66.249.70.32/27",
"66.249.70.64/27",
"66.249.70.96/27",
"66.249.71.0/27",
"66.249.71.128/27",
"66.249.71.160/27",
"66.249.71.192/27",
"66.249.71.224/27",
"66.249.71.32/27",
"66.249.71.64/27",
"66.249.71.96/27",
"66.249.72.0/27",
"66.249.72.128/27",
"66.249.72.160/27",
"66.249.72.192/27",
"66.249.72.224/27",
"66.249.72.32/27",
"66.249.72.64/27",
"66.249.72.96/27",
"66.249.73.0/27",
"66.249.73.128/27",
"66.249.73.160/27",
"66.249.73.192/27",
"66.249.73.224/27",
"66.249.73.32/27",
"66.249.73.64/27",
"66.249.73.96/27",
"66.249.74.0/27",
"66.249.74.128/27",
"66.249.74.160/27",
"66.249.74.192/27",
"66.249.74.32/27",
"66.249.74.64/27",
"66.249.74.96/27",
"66.249.75.0/27",
"66.249.75.128/27",
"66.249.75.160/27",
"66.249.75.192/27",
"66.249.75.224/27",
"66.249.75.32/27",
"66.249.75.64/27",
"66.249.75.96/27",
"66.249.76.0/27",
"66.249.76.128/27",
"66.249.76.160/27",
"66.249.76.192/27",
"66.249.76.224/27",
"66.249.76.32/27",
"66.249.76.64/27",
"66.249.76.96/27",
"66.249.77.0/27",
"66.249.77.128/27",
"66.249.77.160/27",
"66.249.77.192/27",
"66.249.77.224/27",
"66.249.77.32/27",
"66.249.77.64/27",
"66.249.77.96/27",
"66.249.78.0/27",
"66.249.78.32/27",
"66.249.79.0/27",
"66.249.79.128/27",
"66.249.79.160/27",
"66.249.79.192/27",
"66.249.79.224/27",
"66.249.79.32/27",
"66.249.79.64/27",
"66.249.79.96/27"
]
},
// Bing, retrieved 15 IV 2025
{
"action": "Allow",
"ip_ranges": [
"157.55.39.0/24",
"207.46.13.0/24",
"40.77.167.0/24",
"13.66.139.0/24",
"13.66.144.0/24",
"52.167.144.0/24",
"13.67.10.16/28",
"13.69.66.240/28",
"13.71.172.224/28",
"139.217.52.0/28",
"191.233.204.224/28",
"20.36.108.32/28",
"20.43.120.16/28",
"40.79.131.208/28",
"40.79.186.176/28",
"52.231.148.0/28",
"20.79.107.240/28",
"51.105.67.0/28",
"20.125.163.80/28",
"40.77.188.0/22",
"65.55.210.0/24",
"199.30.24.0/23",
"40.77.202.0/24",
"40.77.139.0/25",
"20.74.197.0/28",
"20.15.133.160/27",
"40.77.177.0/24",
"40.77.178.0/23"
]
},
{
// Kagi, retrieved 15 IV 2025
"action": "Allow",
"ip_ranges": [
"216.18.205.234/32",
"35.212.27.76/32",
"104.254.65.50/32",
"209.151.156.194/32"
]
}
]

View File

@@ -5,6 +5,18 @@
#include "../helpers/FsUtils.hpp"
#include "../GlobalState.hpp"
static CConfig::eConfigIPAction strToAction(const std::string& s) {
// TODO: allow any case I'm lazy it's 1am
if (s == "ALLOW" || s == "allow" || s == "Allow")
return CConfig::IP_ACTION_ALLOW;
if (s == "Deny" || s == "deny" || s == "Deny")
return CConfig::IP_ACTION_DENY;
if (s == "CHALLENGE" || s == "challenge" || s == "Challenge")
return CConfig::IP_ACTION_CHALLENGE;
throw std::runtime_error("Invalid ip config action");
}
CConfig::CConfig() {
auto json = glz::read_jsonc<SConfig>(
NFsUtils::readFileAsString(NFsUtils::isAbsolute(g_pGlobalState->configPath) ? g_pGlobalState->configPath : g_pGlobalState->cwd + "/" + g_pGlobalState->configPath).value());
@@ -13,4 +25,17 @@ CConfig::CConfig() {
throw std::runtime_error("No config / bad config format");
m_config = json.value();
// parse some datas
for (const auto& ic : m_config.ip_configs) {
SIPRangeConfigParsed parsed;
parsed.action = strToAction(ic.action);
parsed.difficulty = ic.difficulty;
for (const auto& ir : ic.ip_ranges) {
parsed.ip_ranges.emplace_back(CIPRange(ir));
}
m_parsedConfigDatas.ip_configs.emplace_back(std::move(parsed));
}
}

View File

@@ -3,20 +3,46 @@
#include <string>
#include <memory>
#include "IPRange.hpp"
class CConfig {
public:
CConfig();
enum eConfigIPAction : uint8_t {
IP_ACTION_DENY = 0,
IP_ACTION_ALLOW,
IP_ACTION_CHALLENGE
};
struct SIPRangeConfig {
std::string action = "";
std::vector<std::string> ip_ranges;
int difficulty = -1;
};
struct SIPRangeConfigParsed {
eConfigIPAction action = IP_ACTION_DENY;
std::vector<CIPRange> ip_ranges;
int difficulty = -1;
};
struct SConfig {
int port = 3001;
std::string forward_address = "127.0.0.1:3000";
std::string data_dir = "";
std::string html_dir = "";
unsigned long int max_request_size = 10000000; // 10MB
bool git_host = false;
unsigned long int proxy_timeout_sec = 120; // 2 minutes
bool trace_logging = false;
int port = 3001;
std::string forward_address = "127.0.0.1:3000";
std::string data_dir = "";
std::string html_dir = "";
unsigned long int max_request_size = 10000000; // 10MB
bool git_host = false;
unsigned long int proxy_timeout_sec = 120; // 2 minutes
bool trace_logging = false;
std::vector<SIPRangeConfig> ip_configs;
int default_challenge_difficulty = 4;
} m_config;
struct {
std::vector<SIPRangeConfigParsed> ip_configs;
} m_parsedConfigDatas;
};
inline std::unique_ptr<CConfig> g_pConfig;

129
src/config/IPRange.cpp Normal file
View File

@@ -0,0 +1,129 @@
#include "IPRange.hpp"
#include <algorithm>
#include <stdexcept>
CIP::CIP(const std::string& ip) {
if (std::count(ip.begin(), ip.end(), '.') == 3)
parseV4(ip);
else if (std::count(ip.begin(), ip.end(), ':') >= 2)
parseV6(ip);
else
throw std::runtime_error("IP not valid");
}
void CIP::parseV4(const std::string& ip) {
m_v6 = false;
std::string_view curr;
size_t lastPos = 0;
auto advance = [&]() {
size_t prev = lastPos ? lastPos + 1 : lastPos;
lastPos = ip.find('.', prev);
if (lastPos == std::string::npos)
curr = std::string_view{ip}.substr(prev);
else
curr = std::string_view{ip}.substr(prev, lastPos - prev);
};
for (size_t i = 0; i < 4; ++i) {
advance();
m_blocks.push_back(std::stoul(std::string{curr}));
if (m_blocks.back() > 0xFF)
throw std::runtime_error("Invalid IPv4 byte");
}
}
void CIP::parseV6(const std::string& ip) {
const auto COLONS = std::count(ip.begin(), ip.end(), ':');
m_v6 = true;
std::string_view curr;
size_t lastPos = 0;
bool first = true;
auto advance = [&]() {
size_t prev = !first ? lastPos + 1 : lastPos;
lastPos = ip.find(':', prev);
if (lastPos == std::string::npos)
curr = std::string_view{ip}.substr(prev);
else
curr = std::string_view{ip}.substr(prev, lastPos - prev);
first = false;
};
for (size_t i = 0; i < 8; ++i) {
advance();
if (curr.empty()) {
for (size_t j = 0; j < 8 - COLONS; ++j) {
i++;
m_blocks.push_back(0);
}
if (ip.starts_with("::") || ip.ends_with("::")) {
m_blocks.push_back(0);
advance();
} else
i--;
continue;
} else
m_blocks.push_back(std::stoul(std::string{curr}, nullptr, 16));
if (m_blocks.back() > 0xFFFF)
throw std::runtime_error("Invalid IPv6 byte");
}
}
CIPRange::CIPRange(const std::string& range) {
if (!range.contains('/'))
throw std::runtime_error("Range has no subnet");
m_subnet = std::stoul(range.substr(range.find('/') + 1));
m_ip = CIP(range.substr(0, range.find('/')));
}
bool CIPRange::ipMatches(const CIP& ip) const {
if (m_ip.m_v6 != ip.m_v6)
return false;
if (m_ip.m_v6)
return ipMatchesV6(ip);
return ipMatchesV4(ip);
}
bool CIPRange::ipMatchesV4(const CIP& ip) const {
uint32_t rangeMask = 0xFFFFFFFF << (32 - m_subnet);
uint32_t rangeIP =
(((uint32_t)m_ip.m_blocks.at(0)) << 24) | (((uint32_t)m_ip.m_blocks.at(1)) << 16) | (((uint32_t)m_ip.m_blocks.at(2)) << 8) | (((uint32_t)m_ip.m_blocks.at(3)) << 0);
uint32_t incomingIP =
(((uint32_t)ip.m_blocks.at(0)) << 24) | (((uint32_t)ip.m_blocks.at(1)) << 16) | (((uint32_t)ip.m_blocks.at(2)) << 8) | (((uint32_t)ip.m_blocks.at(3)) << 0);
return (rangeMask & rangeIP) == (rangeMask & incomingIP);
}
bool CIPRange::ipMatchesV6(const CIP& ip) const {
uint64_t rangeMaskLeft = 0xFFFFFFFFFFFFFFFF << (m_subnet > 64 ? 0 : 64 - m_subnet);
uint64_t rangeIPLeft =
(((uint64_t)m_ip.m_blocks.at(0)) << 48) | (((uint64_t)m_ip.m_blocks.at(1)) << 32) | (((uint64_t)m_ip.m_blocks.at(2)) << 16) | (((uint64_t)m_ip.m_blocks.at(3)) << 0);
uint64_t incomingIPLeft =
(((uint64_t)ip.m_blocks.at(0)) << 48) | (((uint64_t)ip.m_blocks.at(1)) << 32) | (((uint64_t)ip.m_blocks.at(2)) << 16) | (((uint64_t)ip.m_blocks.at(3)) << 0);
if ((rangeMaskLeft & rangeIPLeft) != (rangeMaskLeft & incomingIPLeft))
return false;
if (m_subnet <= 64)
return true;
uint64_t rangeMaskRight = 0xFFFFFFFFFFFFFFFF << (/* m_subnet > 64 */ 128 - m_subnet);
uint64_t rangeIPRight =
(((uint64_t)m_ip.m_blocks.at(4)) << 48) | (((uint64_t)m_ip.m_blocks.at(5)) << 32) | (((uint64_t)m_ip.m_blocks.at(6)) << 16) | (((uint64_t)m_ip.m_blocks.at(7)) << 0);
uint64_t incomingIPRight =
(((uint64_t)ip.m_blocks.at(4)) << 48) | (((uint64_t)ip.m_blocks.at(5)) << 32) | (((uint64_t)ip.m_blocks.at(6)) << 16) | (((uint64_t)ip.m_blocks.at(7)) << 0);
return (rangeMaskRight & rangeIPRight) == (rangeMaskRight & incomingIPRight);
}

33
src/config/IPRange.hpp Normal file
View File

@@ -0,0 +1,33 @@
#pragma once
#include <string>
#include <vector>
#include <cstdint>
class CIP {
public:
CIP() = default;
CIP(const std::string& ip);
bool m_v6 = false;
std::vector<uint16_t> m_blocks;
private:
void parseV4(const std::string& ip);
void parseV6(const std::string& ip);
};
// Accepts both ipv4 and ipv6
class CIPRange {
public:
CIPRange(const std::string& range);
bool ipMatches(const CIP& ip) const;
private:
CIP m_ip;
size_t m_subnet = 0;
bool ipMatchesV6(const CIP& ip) const;
bool ipMatchesV4(const CIP& ip) const;
};

View File

@@ -213,6 +213,40 @@ void CServerHandler::onRequest(const Pistache::Http::Request& req, Pistache::Htt
}
}
int challengeDifficulty = g_pConfig->m_config.default_challenge_difficulty;
if (!g_pConfig->m_parsedConfigDatas.ip_configs.empty()) {
const auto IP = CIP(req.address().host());
for (const auto& ic : g_pConfig->m_parsedConfigDatas.ip_configs) {
bool matched = false;
for (const auto& ipr : ic.ip_ranges) {
if (!ipr.ipMatches(IP))
continue;
matched = true;
break;
}
if (matched) {
if (ic.action == CConfig::IP_ACTION_ALLOW) {
Debug::log(LOG, " | Action: PASS (ip rule matched for {})", req.address().host());
proxyPass(req, response);
return;
} else if (ic.action == CConfig::IP_ACTION_DENY) {
Debug::log(LOG, " | Action: DENY (ip rule matched for {})", req.address().host());
response.send(Pistache::Http::Code::Forbidden, "Forbidden");
return;
}
// if it's challenge then it's default so just set the difficulty if applicable and proceed
if (ic.difficulty != -1)
challengeDifficulty = ic.difficulty;
break;
}
}
}
if (req.cookies().has(TOKEN_COOKIE_NAME)) {
// check the token
const auto TOKEN = CToken(req.cookies().get(TOKEN_COOKIE_NAME).value);
@@ -234,7 +268,7 @@ void CServerHandler::onRequest(const Pistache::Http::Request& req, Pistache::Htt
} else
Debug::log(LOG, " | Action: CHALLENGE (no token)");
serveStop(req, response);
serveStop(req, response, challengeDifficulty);
}
void CServerHandler::onTimeout(const Pistache::Http::Request& request, Pistache::Http::ResponseWriter response) {
@@ -274,18 +308,16 @@ void CServerHandler::challengeSubmitted(const Pistache::Http::Request& req, Pist
response.send(Pistache::Http::Code::Ok, "Ok");
}
void CServerHandler::serveStop(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response) {
void CServerHandler::serveStop(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response, int difficulty) {
static const auto PAGE_INDEX = NFsUtils::readFileAsString(NFsUtils::htmlPath("/index.min.html")).value();
static const auto PAGE_ROOT = PAGE_INDEX.substr(0, PAGE_INDEX.find_last_of("/") + 1);
CTinylates page(PAGE_INDEX);
page.setTemplateRoot(PAGE_ROOT);
const auto NONCE = generateNonce();
const auto DIFFICULTY = 4;
const auto NONCE = generateNonce();
const auto CHALLENGE = CChallenge(fingerprintForRequest(req), NONCE, difficulty);
const auto CHALLENGE = CChallenge(fingerprintForRequest(req), NONCE, DIFFICULTY);
page.add("challengeDifficulty", CTinylatesProp(std::to_string(DIFFICULTY)));
page.add("challengeDifficulty", CTinylatesProp(std::to_string(difficulty)));
page.add("challengeNonce", CTinylatesProp(NONCE));
page.add("challengeSignature", CTinylatesProp(CHALLENGE.signature()));
page.add("challengeFingerprint", CTinylatesProp(CHALLENGE.fingerprint()));

View File

@@ -16,7 +16,7 @@ class CServerHandler : public Pistache::Http::Handler {
void onTimeout(const Pistache::Http::Request& request, Pistache::Http::ResponseWriter response);
private:
void serveStop(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response);
void serveStop(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response, int difficulty);
void proxyPass(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response);
void challengeSubmitted(const Pistache::Http::Request& req, Pistache::Http::ResponseWriter& response);
std::string fingerprintForRequest(const Pistache::Http::Request& req);