feat: improve API keys and build workflows

This commit is contained in:
2026-10-04 20:05:13 +00:00 Unverified
parent 62f2362a2e
commit e4623efe86
27 changed files with 2080 additions and 235 deletions
+49
View File
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
import {
MemoryAuthStore,
hashToken,
normalizeApiKey,
tokenHashesEqual,
} from "../../server/auth";
import {
@@ -23,6 +24,54 @@ describe("authorization", () => {
});
describe("memory auth store", () => {
test("keeps non-expiring keys active and preserves finite expiry validation", async () => {
const store = new MemoryAuthStore();
await store.putUser({
username: "alice",
passwordHash: "hash",
roles: ["viewer"],
});
const key = {
id: "never-expiring-key-123",
tokenHash: hashToken("never"),
username: "alice",
capabilities: ["kubernetes:read" as const],
};
await store.createApiKey(key);
expect(await store.getApiKey(key.tokenHash)).toMatchObject(key);
expect((await store.listApiKeys("alice"))[0]?.expiresAt).toBeUndefined();
await store.createApiKey({
...key,
id: "finite-expiry-key-123",
tokenHash: hashToken("finite"),
expiresAt: "2020-01-01T00:00:00.000Z",
});
expect(await store.deleteExpiredApiKeys()).toBe(1);
expect(await store.getApiKey(key.tokenHash)).toMatchObject(key);
expect(
normalizeApiKey({ ...key, expiresAt: undefined }).expiresAt,
).toBeUndefined();
for (const expiresAt of ["none", "not-a-date", "2026-09-03", ""]) {
expect(() => normalizeApiKey({ ...key, expiresAt })).toThrow(
"ISO timestamp",
);
}
await expect(
store.createApiKey({
...key,
id: "duplicate-key-id-123",
tokenHash: hashToken("duplicate"),
}),
).resolves.toBeUndefined();
expect(await store.revokeApiKey("bob", key.id)).toBe(false);
expect(await store.revokeApiKey("alice", key.id)).toBe(true);
expect(await store.getApiKey(key.tokenHash)).toBeUndefined();
await expect(
store.createApiKey({ ...key, username: "missing" }),
).rejects.toThrow("not active");
await store.updateUser("alice", { disabled: true });
await expect(store.createApiKey({ ...key })).rejects.toThrow("not active");
});
test("maintains the API-key hash index across key mutations", async () => {
const store = new MemoryAuthStore();
await store.putUser({