Turn per-user token quota off by default; keep code for later

This commit is contained in:
2026-08-21 12:51:29 -07:00
parent f1200b7d79
commit fac6e7f356
4 changed files with 54 additions and 39 deletions

View File

@@ -120,7 +120,12 @@ function isRateLimited(ip) {
return hits.length > RATE_LIMIT_MAX;
}
/* ===== Per-user daily token quota =====
/* ===== Per-user daily token quota (off by default) =====
* On the back burner: real usage does not look large enough to justify
* running this. Left in place, disabled, in case that changes. Set
* TOKEN_LIMIT_PER_USER to a positive number to turn it back on; leave it
* unset or 0 for unlimited, which skips the check and the usage file.
*
* TOKEN_LIMIT_PER_USER caps combined input+output tokens per identified user,
* per UTC calendar day. Usage is written to TOKEN_USAGE_FILE after every AI
* call, so it survives a container restart. Mount that file's folder as a
@@ -132,7 +137,8 @@ function isRateLimited(ip) {
* tally past the cap by that one call's tokens; the next call is blocked.
* That is an accepted tradeoff, since real token cost of a call is only
* known once its response returns. */
const TOKEN_LIMIT_PER_USER = parseInt(process.env.TOKEN_LIMIT_PER_USER || '50000', 10);
const TOKEN_LIMIT_PER_USER = parseInt(process.env.TOKEN_LIMIT_PER_USER || '0', 10);
const TOKEN_LIMIT_ENABLED = TOKEN_LIMIT_PER_USER > 0;
const TOKEN_USAGE_FILE = process.env.TOKEN_USAGE_FILE || path.join(ROOT, 'data', 'token-usage.json');
function todayUTC() {
@@ -193,7 +199,7 @@ function mockClaudeResponse(res, user) {
content: [{ type: 'text', text: JSON.stringify(draft) }],
usage
};
addTokens(user, usage.input_tokens + usage.output_tokens);
if (TOKEN_LIMIT_ENABLED) addTokens(user, usage.input_tokens + usage.output_tokens);
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify(body));
}
@@ -225,13 +231,15 @@ function proxyToClaude(req, res, user) {
const text = await upstream.text();
res.writeHead(upstream.status, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(text);
try {
const parsed = JSON.parse(text);
if (parsed.usage) {
addTokens(user, (parsed.usage.input_tokens || 0) + (parsed.usage.output_tokens || 0));
if (TOKEN_LIMIT_ENABLED) {
try {
const parsed = JSON.parse(text);
if (parsed.usage) {
addTokens(user, (parsed.usage.input_tokens || 0) + (parsed.usage.output_tokens || 0));
}
} catch (err) {
// Response was not JSON, or had no usage field. Nothing to record.
}
} catch (err) {
// Response was not JSON, or had no usage field. Nothing to record.
}
} catch (err) {
sendJson(res, 502, { error: 'Could not reach the Anthropic API: ' + err.message });
@@ -283,12 +291,14 @@ const server = http.createServer((req, res) => {
sendJson(res, 429, { error: `Rate limit reached (${RATE_LIMIT_MAX} AI drafts per ${Math.round(RATE_LIMIT_WINDOW_MS / 60000)} min). Wait a bit and try again.` });
return;
}
const usedToday = usedTokensToday(loadUsage(), user);
if (usedToday >= TOKEN_LIMIT_PER_USER) {
sendJson(res, 429, {
error: `Daily token quota reached (${usedToday}/${TOKEN_LIMIT_PER_USER} tokens for "${user}"). Resets in ${secondsUntilUTCMidnight()}s, at UTC midnight.`
});
return;
if (TOKEN_LIMIT_ENABLED) {
const usedToday = usedTokensToday(loadUsage(), user);
if (usedToday >= TOKEN_LIMIT_PER_USER) {
sendJson(res, 429, {
error: `Daily token quota reached (${usedToday}/${TOKEN_LIMIT_PER_USER} tokens for "${user}"). Resets in ${secondsUntilUTCMidnight()}s, at UTC midnight.`
});
return;
}
}
proxyToClaude(req, res, user);
return;
@@ -309,5 +319,5 @@ server.listen(PORT, () => {
console.log(process.env.ANTHROPIC_API_KEY ? 'ANTHROPIC_API_KEY loaded: AI draft button is live.' : 'No ANTHROPIC_API_KEY found: AI draft button will return an error until you add one to .env.');
}
console.log(AUTH_REQUIRED ? `Login required: ${Object.keys(USER_MAP).length} named user(s) configured.` : 'No login required: no APP_USERS or APP_USERNAME/APP_PASSWORD are set.');
console.log(`Per-user daily token quota: ${TOKEN_LIMIT_PER_USER} tokens. Usage file: ${TOKEN_USAGE_FILE}`);
console.log(TOKEN_LIMIT_ENABLED ? `Per-user daily token quota: ${TOKEN_LIMIT_PER_USER} tokens. Usage file: ${TOKEN_USAGE_FILE}` : 'No per-user token quota: TOKEN_LIMIT_PER_USER is unset or 0 (unlimited, feature on the back burner).');
});