Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/utils/db/apiKeys.db.server.test.ts
T

109 lines
3.3 KiB
TypeScript

// @vitest-environment node
import { describe, expect, test } from "vitest";
import type { ApiKeyWithDetails } from "./apiKeys.db.server";
import { getApiKeyLookupCacheKey, serializeApiLogSearchParams } from "../api/apiKeySecurity.server";
import { redactApiKeyForDisplay, redactApiKeysForDisplay } from "./apiKeys.db.server";
const RAW_SECRET = "raw-secret-value-that-must-not-leak";
function createApiKeyRow(overrides: Partial<ApiKeyWithDetails> = {}): ApiKeyWithDetails {
const now = new Date("2026-01-02T03:04:05.000Z");
return {
id: "api-key-1",
tenantId: "tenant-1",
createdByUserId: "user-1",
alias: "Primary integration",
key: RAW_SECRET,
keyHash: "hash-value",
keyPrefix: "raw-secr",
keyLastFour: "leak",
hashedAt: now,
keyVersion: 1,
expires: null,
active: true,
createdAt: now,
tenant: { id: "tenant-1", name: "Tenant", slug: "tenant", deactivatedReason: null },
createdByUser: {
id: "user-1",
email: "user@example.com",
firstName: "Ada",
lastName: "Lovelace",
phone: null,
locale: "en",
createdAt: now,
},
entities: [
{
id: "permission-1",
apiKeyId: "api-key-1",
entityId: "entity-1",
create: true,
read: true,
update: false,
delete: false,
entity: { id: "entity-1", title: "Contact", titlePlural: "Contacts", slug: "contacts" },
},
],
_count: { apiKeyLogs: 7 },
...overrides,
};
}
describe("API key display redaction", () => {
test("omits raw key while preserving display metadata for detail payloads", () => {
const source = createApiKeyRow();
const redacted = redactApiKeyForDisplay(source);
expect(redacted).not.toHaveProperty("key");
expect(redacted).toMatchObject({
id: source.id,
alias: source.alias,
active: source.active,
expires: source.expires,
keyPrefix: source.keyPrefix,
keyLastFour: source.keyLastFour,
hashedAt: source.hashedAt,
keyVersion: source.keyVersion,
entities: source.entities,
createdByUser: source.createdByUser,
tenant: source.tenant,
_count: source._count,
});
expect(JSON.stringify(redacted)).not.toContain(RAW_SECRET);
});
test("redacts list payloads without mutating source rows", () => {
const source = createApiKeyRow();
const redacted = redactApiKeysForDisplay([source]);
expect(redacted).toHaveLength(1);
expect(redacted[0]).not.toHaveProperty("key");
expect(JSON.stringify(redacted)).not.toContain(RAW_SECRET);
expect(source.key).toBe(RAW_SECRET);
expect(source).toHaveProperty("key", RAW_SECRET);
});
test("builds API-key cache keys without embedding raw secrets", () => {
const cacheKey = getApiKeyLookupCacheKey(RAW_SECRET);
expect(cacheKey).toMatch(/^apiKeyHash:[a-f0-9]{64}$/);
expect(cacheKey).not.toContain(RAW_SECRET);
});
test("redacts query params for API-key log writers", () => {
const request = new Request("https://example.com/api/site?token=raw-token&email=person@example.test&page=2&apiKey=sk_live_secret");
expect(serializeApiLogSearchParams(request)).toBe(
JSON.stringify({
token: "[REDACTED]",
email: "[REDACTED]",
page: "2",
apiKey: "[REDACTED]",
})
);
});
});