fix(usage): key live byApiKey stats by full api key to prevent team-key collision
maskApiKey kept only the first 8 characters of the key. API keys minted from the same machine id share that prefix, so every key of an instance collapsed into one sk-XXXXXXX*** bucket per model/provider, and the dashboard attributed one holder's usage to another. Key the live path by the full api key - matching the daily rollup (aggregateEntryToDay) and the lastUsed overlay - and keep the last 4 characters in maskApiKey so masked keys stay distinguishable in the UI.
This commit is contained in:
@@ -5,8 +5,9 @@ import { getMeta, setMeta } from "../helpers/metaStore.js";
|
|||||||
|
|
||||||
function maskApiKey(key) {
|
function maskApiKey(key) {
|
||||||
if (!key || typeof key !== "string") return null;
|
if (!key || typeof key !== "string") return null;
|
||||||
if (key.length <= 8) return key.charAt(0) + "***";
|
if (key.length <= 12) return key.charAt(0) + "***";
|
||||||
return key.slice(0, 8) + "***";
|
// Keep the tail: keys sharing a machine-id prefix (team keys) must not collide.
|
||||||
|
return key.slice(0, 8) + "***" + key.slice(-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
const PENDING_TIMEOUT_MS = 60 * 1000;
|
const PENDING_TIMEOUT_MS = 60 * 1000;
|
||||||
@@ -634,6 +635,8 @@ export async function getUsageStats(period = "all") {
|
|||||||
const keyInfo = apiKeyMap[r.apiKey];
|
const keyInfo = apiKeyMap[r.apiKey];
|
||||||
const keyName = keyInfo?.name || r.apiKey.slice(0, 8) + "...";
|
const keyName = keyInfo?.name || r.apiKey.slice(0, 8) + "...";
|
||||||
const apiKeyMasked = maskApiKey(r.apiKey);
|
const apiKeyMasked = maskApiKey(r.apiKey);
|
||||||
|
// Key by the FULL api key (same as the daily rollup + lastUsed overlay)
|
||||||
|
// — masking here collided all keys sharing a prefix into one bucket.
|
||||||
const akKey = `${r.apiKey}|${r.model}|${r.provider || "unknown"}`;
|
const akKey = `${r.apiKey}|${r.model}|${r.provider || "unknown"}`;
|
||||||
if (!stats.byApiKey[akKey]) {
|
if (!stats.byApiKey[akKey]) {
|
||||||
stats.byApiKey[akKey] = { requests: 0, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cost: 0, rawModel: r.model, provider: providerDisplayName, apiKeyMasked, keyName, apiKeyKey: apiKeyMasked, lastUsed: r.timestamp };
|
stats.byApiKey[akKey] = { requests: 0, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cost: 0, rawModel: r.model, provider: providerDisplayName, apiKeyMasked, keyName, apiKeyKey: apiKeyMasked, lastUsed: r.timestamp };
|
||||||
|
|||||||
Reference in New Issue
Block a user