feat: track agent API key usage

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-07-22 18:31:56 +00:00
1 parent a4aac781b7
commit 8b288dbc1e
7 files changed
+211 -50

No files matched your search

+26 -10
View File
@@ -1,6 +1,6 @@
import { createHash, timingSafeEqual } from "node:crypto";
import type { Request, RequestHandler } from "express";
import { and, eq, isNull } from "drizzle-orm";
import { and, eq, isNull, lt, or } from "drizzle-orm";
import type { Db } from "@paperclipai/db";
import {
activityLog,
@@ -20,6 +20,12 @@ import { boardAuthService } from "../services/board-auth.js";
import { ensureHumanRoleDefaultGrants } from "../services/principal-access-compatibility.js";
import { forbidden, unprocessable } from "../errors.js";
const AGENT_API_KEY_LAST_USED_THROTTLE_MS = 60_000;
function shouldUpdateAgentApiKeyLastUsed(lastUsedAt: Date | null, now: Date): boolean {
return lastUsedAt === null || now.getTime() - lastUsedAt.getTime() > AGENT_API_KEY_LAST_USED_THROTTLE_MS;
}
function hashToken(token: string) {
return createHash("sha256").update(token).digest("hex");
}
@@ -325,11 +331,6 @@ export function actorMiddleware(db: Db, opts: ActorMiddlewareOptions): RequestHa
return;
}
await db
.update(agentApiKeys)
.set({ lastUsedAt: new Date() })
.where(eq(agentApiKeys.id, key.id));
const agentRecord = await db
.select()
.from(agents)
@@ -356,6 +357,24 @@ export function actorMiddleware(db: Db, opts: ActorMiddlewareOptions): RequestHa
return;
}
const onBehalfOfMemberships = await loadResponsibleUserMemberships(db, {
companyId: key.companyId,
userId: responsibleUserId,
});
const authenticatedAt = new Date();
if (shouldUpdateAgentApiKeyLastUsed(key.lastUsedAt, authenticatedAt)) {
await db
.update(agentApiKeys)
.set({ lastUsedAt: authenticatedAt })
.where(and(
eq(agentApiKeys.id, key.id),
or(
isNull(agentApiKeys.lastUsedAt),
lt(agentApiKeys.lastUsedAt, new Date(authenticatedAt.getTime() - AGENT_API_KEY_LAST_USED_THROTTLE_MS)),
),
));
}
req.actor = {
type: "agent",
agentId: key.agentId,
@@ -363,10 +382,7 @@ export function actorMiddleware(db: Db, opts: ActorMiddlewareOptions): RequestHa
keyId: key.id,
keyScope: normalizeAgentApiKeyScope(key.scopeConfig),
onBehalfOfUserId: responsibleUserId,
onBehalfOfMemberships: await loadResponsibleUserMemberships(db, {
companyId: key.companyId,
userId: responsibleUserId,
}),
onBehalfOfMemberships,
runId: runIdHeader || undefined,
source: "agent_key",
};