Turn per-user token quota off by default; keep code for later
This commit is contained in:
42
server.js
42
server.js
@@ -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).');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user