mirror of
https://github.com/tiennm99/llmapikey.git
synced 2026-09-05 08:17:40 +00:00
fix(keys): match reconcile to current key name via shared prefix constant
Reconcile filtered app keys by the old "llmapikey:" prefix while mint now names them "llmapikey/gh-<id>", so orphan/cost-leak detection silently missed every new key. Centralize the prefix in lib/keys/key-name.js (KEY_NAME_PREFIX + keyName) and use it in both mint and reconcile so they can't drift.
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Single source of truth for the OpenRouter key `name` this app mints. Used by
|
||||
* the mint path (to set the name) and the reconcile script (to recognize app
|
||||
* keys). Keep both sides on this constant so they can never drift apart.
|
||||
*
|
||||
* The name is opaque and non-PII: a fixed prefix + the numeric, immutable GitHub
|
||||
* id (never the mutable login).
|
||||
*/
|
||||
|
||||
/** Prefix on every key this app mints. Reconcile matches app keys by this. */
|
||||
export const KEY_NAME_PREFIX = "llmapikey/gh-";
|
||||
|
||||
/**
|
||||
* Build the OpenRouter key name for a GitHub user.
|
||||
*
|
||||
* @param {string} githubUserId numeric GitHub provider_id
|
||||
* @returns {string}
|
||||
*/
|
||||
export function keyName(githubUserId) {
|
||||
return `${KEY_NAME_PREFIX}${githubUserId}`;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import "server-only";
|
||||
|
||||
import { last4 } from "@/lib/keys/key-format";
|
||||
import { keyName } from "@/lib/keys/key-name";
|
||||
import * as repo from "@/lib/keys/api-keys-repository";
|
||||
import { createKey, deleteKey } from "@/lib/openrouter/provisioning-client";
|
||||
|
||||
@@ -29,7 +30,7 @@ export async function mintAndPersist(reservedId, githubUserId) {
|
||||
let mint;
|
||||
try {
|
||||
mint = await createKey({
|
||||
name: `llmapikey/gh-${githubUserId}`, // opaque numeric id — no PII into OpenRouter logs
|
||||
name: keyName(githubUserId), // opaque numeric id — no PII into OpenRouter logs
|
||||
limitUsd: numEnv("KEY_DAILY_LIMIT_USD", 10),
|
||||
resetPeriod: "daily",
|
||||
includeByok: true,
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Reconcile OpenRouter provisioned keys against the DB registry.
|
||||
*
|
||||
* Reports:
|
||||
* - Orphaned OpenRouter keys: minted by this app (name `llmapikey:*`) but with
|
||||
* no matching DB row → a billable cost leak to revoke.
|
||||
* - Orphaned OpenRouter keys: minted by this app (name starts with
|
||||
* KEY_NAME_PREFIX) but with no matching DB row → a billable cost leak to revoke.
|
||||
* - Dangling DB rows: an active row whose key was deleted out-of-band.
|
||||
* - Stale pending rows: a reservation from an interrupted mint (>10 min old)
|
||||
* that still has no key — would block that user until reclaimed.
|
||||
@@ -13,6 +13,7 @@
|
||||
*/
|
||||
import { getSql } from "../lib/db/postgres-client.js";
|
||||
import { listKeys } from "../lib/openrouter/provisioning-client.js";
|
||||
import { KEY_NAME_PREFIX } from "../lib/keys/key-name.js";
|
||||
|
||||
async function main() {
|
||||
const sql = getSql();
|
||||
@@ -24,7 +25,7 @@ async function main() {
|
||||
const dbHashes = new Set(dbRows.map((r) => r.openrouter_delete_hash).filter(Boolean));
|
||||
const orHashes = new Set(orKeys.map((k) => k.hash));
|
||||
|
||||
const appKeys = orKeys.filter((k) => typeof k.name === "string" && k.name.startsWith("llmapikey:"));
|
||||
const appKeys = orKeys.filter((k) => typeof k.name === "string" && k.name.startsWith(KEY_NAME_PREFIX));
|
||||
const orphans = appKeys.filter((k) => !dbHashes.has(k.hash));
|
||||
const dangling = dbRows.filter((r) => r.openrouter_delete_hash && !orHashes.has(r.openrouter_delete_hash));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user