Files
HiveGrid-Wireframes/hivegrid-cloud/apps/api/src/auth/scope.test.ts
T

131 lines
3.7 KiB
TypeScript

import type { LegacyApiKeySimple } from "@toolctrl/db/api-keys.server";
import { describe, expect, test } from "vitest";
import { createServicePrincipal } from "../auth.js";
import { authorizeApiScope, createStoredApiKeyPrincipal, type ApiScopeResult } from "./scope.js";
const NOW = new Date("2026-01-15T12:00:00.000Z");
function buildApiKey(overrides: Partial<LegacyApiKeySimple> = {}): LegacyApiKeySimple {
return {
id: "api-key-1",
alias: "Production key",
expires: new Date("2026-02-01T00:00:00.000Z"),
tenantId: "tenant-1",
active: true,
entities: [
{
entityId: "entity-1",
create: true,
read: true,
update: true,
delete: false,
},
],
tenant: {
id: "tenant-1",
name: "Tenant One",
slug: "tenant-one",
deactivatedReason: null,
},
...overrides,
};
}
function authorize(apiKey: LegacyApiKeySimple, overrides: Partial<Parameters<typeof authorizeApiScope>[1]> = {}): ApiScopeResult {
return authorizeApiScope(createStoredApiKeyPrincipal(apiKey), {
tenantId: "tenant-1",
entityId: "entity-1",
operation: "read",
now: NOW,
...overrides,
});
}
function expectRedactedDeny(result: ApiScopeResult, reason: string) {
expect(result).toEqual({
allowed: false,
principalType: "api-key",
reason,
code: "FORBIDDEN_API_KEY_SCOPE",
});
const serialized = JSON.stringify(result);
expect(serialized).not.toContain("plaintext-secret");
expect(serialized).not.toContain("hash-value");
expect(serialized).not.toContain("service-token");
}
describe("authorizeApiScope", () => {
test("allows service-token principals for every scoped operation", () => {
const principal = createServicePrincipal();
expect(
authorizeApiScope(principal, {
tenantId: "other-tenant",
entityId: "unknown-entity",
operation: "delete",
now: NOW,
}),
).toEqual({
allowed: true,
principalType: "service-token",
});
});
test("allows active stored API keys with matching tenant, entity, and CRUD permission", () => {
expect(authorize(buildApiKey(), { operation: "create" })).toEqual({
allowed: true,
principalType: "api-key",
});
});
test("denies inactive stored API keys", () => {
expectRedactedDeny(authorize(buildApiKey({ active: false })), "api_key_inactive");
});
test("denies expired stored API keys", () => {
expectRedactedDeny(authorize(buildApiKey({ expires: new Date("2026-01-01T00:00:00.000Z") })), "api_key_expired");
});
test("denies stored API keys for deactivated tenants", () => {
expectRedactedDeny(
authorize(
buildApiKey({
tenant: {
id: "tenant-1",
name: "Tenant One",
slug: "tenant-one",
deactivatedReason: "billing_issue",
},
}),
),
"tenant_deactivated",
);
});
test("denies stored API keys whose tenant does not match the request tenant", () => {
expectRedactedDeny(authorize(buildApiKey(), { tenantId: "tenant-2" }), "tenant_mismatch");
expectRedactedDeny(
authorize(
buildApiKey({
tenant: {
id: "tenant-2",
name: "Tenant Two",
slug: "tenant-two",
deactivatedReason: null,
},
}),
),
"tenant_mismatch",
);
});
test("denies stored API keys missing the requested entity permission", () => {
expectRedactedDeny(authorize(buildApiKey(), { entityId: "entity-2" }), "entity_permission_missing");
});
test("denies stored API keys missing the requested CRUD operation permission", () => {
expectRedactedDeny(authorize(buildApiKey(), { operation: "delete" }), "operation_permission_missing");
});
});