adds config setting for PoW verification timeout

This commit is contained in:
nai-degen 2024-05-19 15:17:25 -05:00
parent 8d2ed23522
commit a3462e21bc
6 changed files with 43 additions and 32 deletions

View File

@ -127,6 +127,15 @@ type Config = {
* details on the available modes. * details on the available modes.
*/ */
powDifficultyLevel: "low" | "medium" | "high" | "extreme"; powDifficultyLevel: "low" | "medium" | "high" | "extreme";
/**
* Duration in minutes before a PoW challenge expires. Users' browsers must
* solve the challenge within this time frame or it will be rejected.
* Defaults to 30 minutes. It should be kept somewhat low to prevent abusive
* clients from working on many challenges in parallel, but you may need to
* increase this value for higher difficulty levels or older devices will not
* be able to solve the challenge in time.
*/
powChallengeTimeout: number;
/** Per-user limit for requests per minute to text and chat models. */ /** Per-user limit for requests per minute to text and chat models. */
textModelRateLimit: number; textModelRateLimit: number;
/** Per-user limit for requests per minute to image generation models. */ /** Per-user limit for requests per minute to image generation models. */
@ -322,6 +331,7 @@ export const config: Config = {
powTokenHours: getEnvWithDefault("POW_TOKEN_HOURS", 24), powTokenHours: getEnvWithDefault("POW_TOKEN_HOURS", 24),
powTokenMaxIps: getEnvWithDefault("POW_TOKEN_MAX_IPS", 2), powTokenMaxIps: getEnvWithDefault("POW_TOKEN_MAX_IPS", 2),
powDifficultyLevel: getEnvWithDefault("POW_DIFFICULTY_LEVEL", "low"), powDifficultyLevel: getEnvWithDefault("POW_DIFFICULTY_LEVEL", "low"),
powChallengeTimeout: getEnvWithDefault("POW_CHALLENGE_TIMEOUT", 30),
firebaseRtdbUrl: getEnvWithDefault("FIREBASE_RTDB_URL", undefined), firebaseRtdbUrl: getEnvWithDefault("FIREBASE_RTDB_URL", undefined),
firebaseKey: getEnvWithDefault("FIREBASE_KEY", undefined), firebaseKey: getEnvWithDefault("FIREBASE_KEY", undefined),
textModelRateLimit: getEnvWithDefault("TEXT_MODEL_RATE_LIMIT", 4), textModelRateLimit: getEnvWithDefault("TEXT_MODEL_RATE_LIMIT", 4),

View File

@ -16,7 +16,7 @@ const MODEL_FAMILY_FRIENDLY_NAME: { [f in ModelFamily]: string } = {
gpt4: "GPT-4", gpt4: "GPT-4",
"gpt4-32k": "GPT-4 32k", "gpt4-32k": "GPT-4 32k",
"gpt4-turbo": "GPT-4 Turbo", "gpt4-turbo": "GPT-4 Turbo",
"gpt4o": "GPT-4o", gpt4o: "GPT-4o",
"dall-e": "DALL-E", "dall-e": "DALL-E",
claude: "Claude (Sonnet)", claude: "Claude (Sonnet)",
"claude-opus": "Claude (Opus)", "claude-opus": "Claude (Opus)",
@ -146,11 +146,15 @@ This proxy keeps full logs of all prompts and AI responses. Prompt logs are anon
function getSelfServiceLinks() { function getSelfServiceLinks() {
if (config.gatekeeper !== "user_token") return ""; if (config.gatekeeper !== "user_token") return "";
const links = [
["Request a user token", "/user/captcha",], const links = [["Check your user token", "/user/lookup"]];
["Check your user token", "/user/lookup",] if (config.captchaMode !== "none") {
] links.unshift(["Request a user token", "/user/captcha"]);
return `<div style="font-size: 0.8em;">${links.map(([text, link]) => `<a target="_blank" href="${link}">${text}</a>`).join(" / ")}</div><hr />`; }
return `<div style="font-size: 0.8em;">${links
.map(([text, link]) => `<a target="_blank" href="${link}">${text}</a>`)
.join(" / ")}</div><hr />`;
} }
function getServerTitle() { function getServerTitle() {
@ -262,7 +266,7 @@ if (config.serviceInfoPassword?.length) {
}); });
infoPageRouter.use(checkIfUnlocked); infoPageRouter.use(checkIfUnlocked);
} }
infoPageRouter.get("/", handleInfoPage); infoPageRouter.get("/", (req, res) => res.sendStatus(204));
infoPageRouter.get("/status", (req, res) => { infoPageRouter.get("/status", (req, res) => {
res.json(buildInfo(req.protocol + "://" + req.get("host"), false)); res.json(buildInfo(req.protocol + "://" + req.get("host"), false));
}); });

View File

@ -3,7 +3,6 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="csrf-token" content="<%= csrfToken %>" /> <meta name="csrf-token" content="<%= csrfToken %>" />
<!-- prettier-ignore -->
<title><%= title %></title> <title><%= title %></title>
<style> <style>
body { body {
@ -112,5 +111,3 @@
</head> </head>
<body> <body>
<%- include("partials/shared_flash", { flashData: flash }) %> <%- include("partials/shared_flash", { flashData: flash }) %>
</body>
</html>

View File

@ -7,8 +7,6 @@ import { config } from "../../config";
/** HMAC key for signing challenges; regenerated on startup */ /** HMAC key for signing challenges; regenerated on startup */
const HMAC_KEY = crypto.randomBytes(32).toString("hex"); const HMAC_KEY = crypto.randomBytes(32).toString("hex");
/** Expiry time for a challenge in milliseconds */
const POW_EXPIRY = 1000 * 60 * 30; // 30 minutes
/** Lockout time after verification in milliseconds */ /** Lockout time after verification in milliseconds */
const LOCKOUT_TIME = 1000 * 60; // 60 seconds const LOCKOUT_TIME = 1000 * 60; // 60 seconds
@ -95,7 +93,7 @@ setInterval(() => {
} }
for (const [key, timestamp] of solves) { for (const [key, timestamp] of solves) {
if (now - timestamp > POW_EXPIRY) { if (now - timestamp > config.powChallengeTimeout * 1000 * 60) {
solves.delete(key); solves.delete(key);
} }
} }
@ -118,7 +116,7 @@ function generateChallenge(clientIp?: string, token?: string): Challenge {
m: argon2Params.ARGON2_MEMORY_KB, m: argon2Params.ARGON2_MEMORY_KB,
p: argon2Params.ARGON2_PARALLELISM, p: argon2Params.ARGON2_PARALLELISM,
d: targetValue.toString() + "n", d: targetValue.toString() + "n",
e: Date.now() + POW_EXPIRY, e: Date.now() + config.powChallengeTimeout * 1000 * 60,
ip: clientIp, ip: clientIp,
token, token,
}; };

View File

@ -71,25 +71,21 @@
</style> </style>
<div style="display: none" id="captcha-container"> <div style="display: none" id="captcha-container">
<p> <p>
Your device needs to perform a verification task before a user token will be issued. This verification might take Your device needs to perform a verification task before you can receive access. This might take anywhere from a few
anywhere from a few seconds to a few minutes, depending on your device and the proxy's security settings. seconds to a few minutes, depending on your device and the proxy's security settings.
</p> </p>
<p>Click the button below to start.</p> <p>Click the button below to start.</p>
<details> <details>
<summary>What is this?</summary> <summary>What is this?</summary>
<p> <p>
This is an anti-abuse measure to slow down automated requests. It requires your device's CPU to find a solution to This is an anti-abuse measure designed to slow down automated requests. It requires your device's CPU to find a
a cryptographic puzzle, after which a user token will be issued. solution to a cryptographic puzzle, after which a user token will be issued.
</p>
<p>
Your browser may slow down during the verification process. If you want to do something else while waiting, reduce
the number of workers to reduce the load on your device's CPU.
</p> </p>
</details> </details>
<details> <details>
<summary>How long does verification take?</summary> <summary>How long does verification take?</summary>
<p> <p>
The exact time depends on the device you're using and the server's difficulty setting (currently It on the device you're using and the verification task's difficulty level (currently
<strong><%= difficultyLevel %></strong>). It could take anywhere from a few seconds to a few minutes. <strong><%= difficultyLevel %></strong>). It could take anywhere from a few seconds to a few minutes.
</p> </p>
</details> </details>
@ -97,8 +93,19 @@
<summary>How often do I need to do this?</summary> <summary>How often do I need to do this?</summary>
<p>Once you've earned a user token, you can use it for <%= tokenLifetime %> hours before it expires.</p> <p>Once you've earned a user token, you can use it for <%= tokenLifetime %> hours before it expires.</p>
<p> <p>
You can refresh an expired token by returning to this page and completing the verification again, which will be You can refresh an expired token by returning to this page and verifying again. Subsequent verifications will go
faster than the first time. faster than the first one.
</p>
</details>
<details>
<summary>What is the "Workers" setting?</summary>
<p>
This controls how many CPU cores will be used to solve the verification task. By default, all of your device's
cores will be used to solve the task as quickly as possible.
</p>
<p>
If your device gets too hot or you want to use it for other tasks while verification is in progress, reduce the
number of workers to lower the CPU load at the cost of slower verification.
</p> </p>
</details> </details>
<details> <details>

View File

@ -18,9 +18,7 @@
</style> </style>
<h1>Request User Token</h1> <h1>Request User Token</h1>
<p> <p>You can request a temporary user token to use this proxy. The token will be valid for <%= tokenLifetime %> hours.</p>
You can request a temporary user token to use this proxy. The token will be valid for <%= tokenLifetime %> hours.
</p>
<% if (keyRequired) { %> <% if (keyRequired) { %>
<div> <div>
<p>You need to supply the proxy password to request or refresh a token.</p> <p>You need to supply the proxy password to request or refresh a token.</p>
@ -31,10 +29,7 @@
</div> </div>
<% } %> <% } %>
<div id="existing-token" style="display: none"> <div id="existing-token" style="display: none">
<p> <p>It looks like you might have an older temporary user token. If it has expired, you can try to refresh it.</p>
It looks like you might have an older temporary user token. You can refresh its expiration by completing a
faster verification challenge.
</p>
<strong id="existing-token-value">Existing token:</strong> <strong id="existing-token-value">Existing token:</strong>
</div> </div>
<div id="request-buttons"> <div id="request-buttons">