From 143ffeeb7eb050f622ff9896bc6d89be3c2a01db Mon Sep 17 00:00:00 2001 From: Catalin Pit Date: Tue, 18 Aug 2026 11:35:20 +0300 Subject: [PATCH] feat: track when API tokens were last used --- .../t.$teamUrl+/settings.tokens.tsx | 14 +++++++++++++ .../public-api/get-api-token-by-token.ts | 11 ++++++++++ .../server-only/public-api/get-api-tokens.ts | 1 + .../migration.sql | 2 ++ packages/prisma/schema.prisma | 21 ++++++++++--------- .../api-token-router/get-api-tokens.types.ts | 1 + 6 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 packages/prisma/migrations/20260818081941_add_api_token_last_used_property/migration.sql diff --git a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.tokens.tsx b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.tokens.tsx index 239137d0b..fa38b9766 100644 --- a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.tokens.tsx +++ b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.tokens.tsx @@ -68,6 +68,20 @@ export default function ApiTokensPage() { return i18n.date(row.original.expires); }, }, + { + header: t`Last Used`, + cell: ({ row }) => { + if (!row.original.lastUsedAt) { + return ( + + Never + + ); + } + + return {i18n.date(row.original.lastUsedAt)}; + }, + }, { header: t`Actions`, cell: ({ row }) => ( diff --git a/packages/lib/server-only/public-api/get-api-token-by-token.ts b/packages/lib/server-only/public-api/get-api-token-by-token.ts index d25b9b902..96309161d 100644 --- a/packages/lib/server-only/public-api/get-api-token-by-token.ts +++ b/packages/lib/server-only/public-api/get-api-token-by-token.ts @@ -96,6 +96,17 @@ export const getApiTokenByToken = async ({ token, bypassRateLimit = false }: Get }); } + void prisma.apiToken + .update({ + where: { + id: apiToken.id, + }, + data: { + lastUsedAt: new Date(), + }, + }) + .catch(() => null); + return { ...apiToken, user, diff --git a/packages/lib/server-only/public-api/get-api-tokens.ts b/packages/lib/server-only/public-api/get-api-tokens.ts index 06e014a10..a97ba809d 100644 --- a/packages/lib/server-only/public-api/get-api-tokens.ts +++ b/packages/lib/server-only/public-api/get-api-tokens.ts @@ -22,6 +22,7 @@ export const getApiTokens = async ({ userId, teamId }: GetApiTokensOptions) => { name: true, createdAt: true, expires: true, + lastUsedAt: true, }, orderBy: { createdAt: 'desc', diff --git a/packages/prisma/migrations/20260818081941_add_api_token_last_used_property/migration.sql b/packages/prisma/migrations/20260818081941_add_api_token_last_used_property/migration.sql new file mode 100644 index 000000000..9b9f88927 --- /dev/null +++ b/packages/prisma/migrations/20260818081941_add_api_token_last_used_property/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "ApiToken" ADD COLUMN "lastUsedAt" TIMESTAMP(3); diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 06be2eed9..13768e34f 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -220,16 +220,17 @@ enum ApiTokenAlgorithm { } model ApiToken { - id Int @id @default(autoincrement()) - name String - token String @unique - algorithm ApiTokenAlgorithm @default(SHA512) - expires DateTime? - createdAt DateTime @default(now()) - userId Int? - user User? @relation(fields: [userId], references: [id], onDelete: Cascade) - teamId Int - team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) + id Int @id @default(autoincrement()) + name String + token String @unique + algorithm ApiTokenAlgorithm @default(SHA512) + expires DateTime? + createdAt DateTime @default(now()) + lastUsedAt DateTime? + userId Int? + user User? @relation(fields: [userId], references: [id], onDelete: Cascade) + teamId Int + team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) } enum SubscriptionStatus { diff --git a/packages/trpc/server/api-token-router/get-api-tokens.types.ts b/packages/trpc/server/api-token-router/get-api-tokens.types.ts index 6a690066d..c3f06d916 100644 --- a/packages/trpc/server/api-token-router/get-api-tokens.types.ts +++ b/packages/trpc/server/api-token-router/get-api-tokens.types.ts @@ -9,6 +9,7 @@ export const ZGetApiTokensResponseSchema = z.array( name: true, createdAt: true, expires: true, + lastUsedAt: true, }), );