Files
supabase/apps/studio/data/analytics/api-keys-last-used-query.test.ts
Jordi EnricandJoshen Lim 3063679f1b feat(auth): restore key last-used timestamps FE-2462 FE-4315 (#50732)
## Problem

Studio expected aliased fields from the last-used API-key endpoint, but
the live endpoint returns OTEL attribute names. This kept legacy API-key
activity unavailable and prevented Studio from showing activity for new
JWT signing keys. Tracks FE-2462 and FE-4315.

## Fix

Normalize the endpoint response at the data boundary, keep the
`showApiKeysLastUsed` feature flag, and show activity from the past 24
hours for new JWT signing keys. Legacy HS256 signing keys remain blank
because the analytics response does not provide a stable signing-key
record ID for them. The request remains hosted-only, permission-gated,
and non-blocking, and the existing last-rotated column remains intact.

## How to test

- Make a request with a legacy anon or service-role API key, then open
Project Settings > API Keys and verify its last request appears.
- Make an Auth request signed by a new JWT signing key, then open JWT
Keys and verify the matching key shows a Last used timestamp.
- Verify a new key without activity shows No requests in the past 24
hours.
- Verify the legacy HS256 signing-key row leaves Last used blank.
- Expected result: legacy API keys and new JWT signing keys display
activity from the shared endpoint without changing self-hosted Studio.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a **Last used** column for JWT signing keys on supported
platforms.
* Displays usage timestamps, loading and error states, or when a key has
had no requests in the past 24 hours.
  * Usage tracking now includes both API keys and JWT signing keys.
* **Bug Fixes**
  * Improved handling of usage records for legacy and current keys.
* Usage details appear only on supported platforms and for users with
the required permissions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2026-09-23 13:46:00 +02:00

54 lines
1.7 KiB
TypeScript

import { describe, expect, test } from 'vitest'
import { apiKeysLastUsedSchema, getJWTSigningKeyLastUsedAt } from './api-keys-last-used-query'
describe('apiKeysLastUsedSchema', () => {
test('normalizes legacy API key and JWT signing key fields from the live endpoint', () => {
const rows = apiKeysLastUsedSchema.parse([
{
request_sb_apikey_apikey_hash: '',
request_sb_apikey_apikey_prefix: '',
request_sb_jwt_authorization_payload_algorithm: 'HS256',
request_sb_jwt_authorization_payload_key_id: '',
request_sb_jwt_authorization_payload_role: 'anon',
request_sb_jwt_authorization_payload_signature_prefix: 'legacy-signature',
timestamp: 100,
},
{
request_sb_apikey_apikey_hash: '',
request_sb_apikey_apikey_prefix: '',
request_sb_jwt_authorization_payload_algorithm: 'RS256',
request_sb_jwt_authorization_payload_key_id: 'signing-key-id',
request_sb_jwt_authorization_payload_role: 'authenticated',
request_sb_jwt_authorization_payload_signature_prefix: '',
timestamp: 200,
},
{
request_sb_jwt_authorization_payload_key_id: 'signing-key-id',
request_sb_jwt_authorization_payload_role: 'service_role',
timestamp: 300,
},
])
expect(rows).toEqual([
{
role: 'anon',
signaturePrefix: 'legacy-signature',
timestamp: 100,
},
{
keyId: 'signing-key-id',
role: 'authenticated',
timestamp: 200,
},
{
keyId: 'signing-key-id',
role: 'service_role',
timestamp: 300,
},
])
expect(getJWTSigningKeyLastUsedAt(rows, 'signing-key-id')).toBe(300)
})
})