alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
571 lines (477 loc) • 15.1 kB
text/typescript
import { randomUUID } from "node:crypto";
import { $inject, Alepha, z } from "alepha";
import { AlephaOrmPostgres } from "alepha/orm/postgres";
import { $issuer, $secure, AlephaSecurity } from "alepha/security";
import { $action, AlephaServer } from "alepha/server";
import { describe, expect, it } from "vitest";
import { AlephaApiKeys } from "../index.ts";
import { ApiKeyService } from "../services/ApiKeyService.ts";
describe("ApiKeyService", () => {
it("should create an API key", async () => {
class TestApp {
issuer = $issuer({
secret: "test-secret",
roles: [
{
name: "admin",
permissions: [{ name: "*" }],
},
],
});
}
const alepha = Alepha.create()
.with(AlephaOrmPostgres)
.with(AlephaServer)
.with(AlephaSecurity)
.with(AlephaApiKeys);
alepha.inject(TestApp);
const service = alepha.inject(ApiKeyService);
await alepha.start();
const userId = randomUUID();
const { apiKey, token } = await service.create({
userId,
name: "Test Key",
roles: ["admin"],
});
expect(apiKey).toBeDefined();
expect(apiKey.name).toBe("Test Key");
expect(apiKey.userId).toBe(userId);
expect(apiKey.roles).toEqual(["admin"]);
expect(token).toMatch(/^ak_/);
expect(apiKey.tokenPrefix).toBe("ak");
expect(apiKey.tokenSuffix).toBe(token.slice(-8));
});
it("should validate an API key", async () => {
class TestApp {
issuer = $issuer({
secret: "test-secret",
roles: [
{
name: "admin",
permissions: [{ name: "*" }],
},
],
});
}
const alepha = Alepha.create()
.with(AlephaOrmPostgres)
.with(AlephaServer)
.with(AlephaSecurity)
.with(AlephaApiKeys);
alepha.inject(TestApp);
const service = alepha.inject(ApiKeyService);
await alepha.start();
const userId = randomUUID();
const { token } = await service.create({
userId,
name: "Test Key",
roles: ["admin"],
});
const userInfo = await service.validate(token);
expect(userInfo).toBeDefined();
expect(userInfo?.id).toBe(userId);
expect(userInfo?.roles).toEqual(["admin"]);
});
it("should return null for invalid token", async () => {
class TestApp {
issuer = $issuer({
secret: "test-secret",
roles: [
{
name: "admin",
permissions: [{ name: "*" }],
},
],
});
}
const alepha = Alepha.create()
.with(AlephaOrmPostgres)
.with(AlephaServer)
.with(AlephaSecurity)
.with(AlephaApiKeys);
alepha.inject(TestApp);
const service = alepha.inject(ApiKeyService);
await alepha.start();
const userInfo = await service.validate("ak_invalid_token");
expect(userInfo).toBeNull();
});
it("should list API keys for a user", async () => {
class TestApp {
issuer = $issuer({
secret: "test-secret",
roles: [
{
name: "admin",
permissions: [{ name: "*" }],
},
],
});
}
const alepha = Alepha.create()
.with(AlephaOrmPostgres)
.with(AlephaServer)
.with(AlephaSecurity)
.with(AlephaApiKeys);
alepha.inject(TestApp);
const service = alepha.inject(ApiKeyService);
await alepha.start();
const userId = randomUUID();
await service.create({
userId,
name: "Key 1",
roles: ["admin"],
});
await service.create({
userId,
name: "Key 2",
roles: ["admin"],
});
const keys = await service.list(userId);
expect(keys).toHaveLength(2);
expect(keys.map((k) => k.name).sort()).toEqual(["Key 1", "Key 2"]);
});
it("should revoke an API key", async () => {
class TestApp {
issuer = $issuer({
secret: "test-secret",
roles: [
{
name: "admin",
permissions: [{ name: "*" }],
},
],
});
}
const alepha = Alepha.create()
.with(AlephaOrmPostgres)
.with(AlephaServer)
.with(AlephaSecurity)
.with(AlephaApiKeys);
alepha.inject(TestApp);
const service = alepha.inject(ApiKeyService);
await alepha.start();
const userId = randomUUID();
const { apiKey, token } = await service.create({
userId,
name: "Test Key",
roles: ["admin"],
});
// Key should be valid
const beforeRevoke = await service.validate(token);
expect(beforeRevoke).not.toBeNull();
// Revoke the key
await service.revoke(apiKey.id, userId);
// Key should be invalid after revocation
const afterRevoke = await service.validate(token);
expect(afterRevoke).toBeNull();
});
it("should create resolver for issuer integration", async () => {
class TestApp {
apiKeyService = $inject(ApiKeyService);
issuer = $issuer({
secret: "test-secret",
roles: [
{
name: "admin",
permissions: [{ name: "*" }],
},
],
});
// Action that can be accessed with API key
protected = $action({
use: [$secure()],
schema: {
response: z.object({ userId: z.string() }),
},
handler: (request) => {
return { userId: request.user.id };
},
});
}
const alepha = Alepha.create()
.with(AlephaOrmPostgres)
.with(AlephaServer)
.with(AlephaSecurity)
.with(AlephaApiKeys);
const app = alepha.inject(TestApp);
await alepha.start();
// Create an API key
const userId = randomUUID();
const { token } = await app.apiKeyService.create({
userId,
name: "Test Key",
roles: ["admin"],
});
// Register the API key resolver
app.issuer.registerResolver(app.apiKeyService.createResolver());
// Access the protected action using API key
const mockRequest = {
url: new URL(`http://localhost?api_key=${token}`),
headers: {},
};
const userInfo = await app.apiKeyService.validate(token);
expect(userInfo?.id).toBe(userId);
});
// ---------------------------------------------------------------------------
// Expired API keys validation
// ---------------------------------------------------------------------------
it("should return null for expired API key", async () => {
class TestApp {
issuer = $issuer({
secret: "test-secret",
roles: [{ name: "admin", permissions: [{ name: "*" }] }],
});
}
const alepha = Alepha.create()
.with(AlephaOrmPostgres)
.with(AlephaServer)
.with(AlephaSecurity)
.with(AlephaApiKeys);
alepha.inject(TestApp);
const service = alepha.inject(ApiKeyService);
await alepha.start();
const userId = randomUUID();
// Create key that expired 1 hour ago
const { token } = await service.create({
userId,
name: "Expired Key",
roles: ["admin"],
expiresAt: new Date(Date.now() - 60 * 60 * 1000),
});
const userInfo = await service.validate(token);
expect(userInfo).toBeNull();
});
it("should validate API key that has not yet expired", async () => {
class TestApp {
issuer = $issuer({
secret: "test-secret",
roles: [{ name: "admin", permissions: [{ name: "*" }] }],
});
}
const alepha = Alepha.create()
.with(AlephaOrmPostgres)
.with(AlephaServer)
.with(AlephaSecurity)
.with(AlephaApiKeys);
alepha.inject(TestApp);
const service = alepha.inject(ApiKeyService);
await alepha.start();
const userId = randomUUID();
// Create key that expires in 1 hour
const { token } = await service.create({
userId,
name: "Future Key",
roles: ["admin"],
expiresAt: new Date(Date.now() + 60 * 60 * 1000),
});
const userInfo = await service.validate(token);
expect(userInfo).not.toBeNull();
expect(userInfo?.id).toBe(userId);
});
// ---------------------------------------------------------------------------
// Concurrent revocation + validation
// ---------------------------------------------------------------------------
it("should handle concurrent validation and revocation", async () => {
class TestApp {
issuer = $issuer({
secret: "test-secret",
roles: [{ name: "admin", permissions: [{ name: "*" }] }],
});
}
const alepha = Alepha.create()
.with(AlephaOrmPostgres)
.with(AlephaServer)
.with(AlephaSecurity)
.with(AlephaApiKeys);
alepha.inject(TestApp);
const service = alepha.inject(ApiKeyService);
await alepha.start();
const userId = randomUUID();
const { apiKey, token } = await service.create({
userId,
name: "Concurrent Key",
roles: ["admin"],
});
// Run validation and revocation concurrently
const [validationResult] = await Promise.all([
service.validate(token),
service.revoke(apiKey.id, userId),
]);
// First validation may succeed (race condition)
// But subsequent validations should fail
const afterRevoke = await service.validate(token);
expect(afterRevoke).toBeNull();
});
// ---------------------------------------------------------------------------
// Cache behavior
// ---------------------------------------------------------------------------
it("should use cache for repeated validations", async () => {
class TestApp {
issuer = $issuer({
secret: "test-secret",
roles: [{ name: "admin", permissions: [{ name: "*" }] }],
});
}
const alepha = Alepha.create()
.with(AlephaOrmPostgres)
.with(AlephaServer)
.with(AlephaSecurity)
.with(AlephaApiKeys);
alepha.inject(TestApp);
const service = alepha.inject(ApiKeyService);
await alepha.start();
const userId = randomUUID();
const { token } = await service.create({
userId,
name: "Cache Test Key",
roles: ["admin"],
});
// First validation - cache miss
const first = await service.validate(token);
expect(first?.id).toBe(userId);
// Second validation - should hit cache
const second = await service.validate(token);
expect(second?.id).toBe(userId);
// Third validation - should still work
const third = await service.validate(token);
expect(third?.id).toBe(userId);
});
it("should invalidate cache on revocation", async () => {
class TestApp {
issuer = $issuer({
secret: "test-secret",
roles: [{ name: "admin", permissions: [{ name: "*" }] }],
});
}
const alepha = Alepha.create()
.with(AlephaOrmPostgres)
.with(AlephaServer)
.with(AlephaSecurity)
.with(AlephaApiKeys);
alepha.inject(TestApp);
const service = alepha.inject(ApiKeyService);
await alepha.start();
const userId = randomUUID();
const { apiKey, token } = await service.create({
userId,
name: "Cache Invalidation Key",
roles: ["admin"],
});
// Populate cache
const before = await service.validate(token);
expect(before?.id).toBe(userId);
// Revoke should invalidate cache
await service.revoke(apiKey.id, userId);
// Validation should fail (cache was invalidated)
const after = await service.validate(token);
expect(after).toBeNull();
});
// ---------------------------------------------------------------------------
// Invalid token formats
// ---------------------------------------------------------------------------
it("should return null for token without underscore", async () => {
class TestApp {
issuer = $issuer({
secret: "test-secret",
roles: [{ name: "admin", permissions: [{ name: "*" }] }],
});
}
const alepha = Alepha.create()
.with(AlephaOrmPostgres)
.with(AlephaServer)
.with(AlephaSecurity)
.with(AlephaApiKeys);
alepha.inject(TestApp);
const service = alepha.inject(ApiKeyService);
await alepha.start();
// Token without underscore is not an API key format
const result = await service.validate("invalidtokenwithoutunderscore");
expect(result).toBeNull();
});
it("should return null for empty token", async () => {
class TestApp {
issuer = $issuer({
secret: "test-secret",
roles: [{ name: "admin", permissions: [{ name: "*" }] }],
});
}
const alepha = Alepha.create()
.with(AlephaOrmPostgres)
.with(AlephaServer)
.with(AlephaSecurity)
.with(AlephaApiKeys);
alepha.inject(TestApp);
const service = alepha.inject(ApiKeyService);
await alepha.start();
const result = await service.validate("");
expect(result).toBeNull();
});
it("should return null for token with only prefix", async () => {
class TestApp {
issuer = $issuer({
secret: "test-secret",
roles: [{ name: "admin", permissions: [{ name: "*" }] }],
});
}
const alepha = Alepha.create()
.with(AlephaOrmPostgres)
.with(AlephaServer)
.with(AlephaSecurity)
.with(AlephaApiKeys);
alepha.inject(TestApp);
const service = alepha.inject(ApiKeyService);
await alepha.start();
const result = await service.validate("ak_");
expect(result).toBeNull();
});
it("should return null for JWT-like token", async () => {
class TestApp {
issuer = $issuer({
secret: "test-secret",
roles: [{ name: "admin", permissions: [{ name: "*" }] }],
});
}
const alepha = Alepha.create()
.with(AlephaOrmPostgres)
.with(AlephaServer)
.with(AlephaSecurity)
.with(AlephaApiKeys);
alepha.inject(TestApp);
const service = alepha.inject(ApiKeyService);
await alepha.start();
// JWT has dots, not underscores as separator
const result = await service.validate(
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature",
);
expect(result).toBeNull();
});
// ---------------------------------------------------------------------------
// Custom prefix
// ---------------------------------------------------------------------------
it("should support custom token prefix", async () => {
class TestApp {
issuer = $issuer({
secret: "test-secret",
roles: [{ name: "admin", permissions: [{ name: "*" }] }],
});
}
const alepha = Alepha.create()
.with(AlephaOrmPostgres)
.with(AlephaServer)
.with(AlephaSecurity)
.with(AlephaApiKeys);
alepha.inject(TestApp);
const service = alepha.inject(ApiKeyService);
await alepha.start();
const userId = randomUUID();
const { token } = await service.create({
userId,
name: "Custom Prefix Key",
roles: ["admin"],
prefix: "myapp",
});
expect(token).toMatch(/^myapp_/);
const userInfo = await service.validate(token);
expect(userInfo?.id).toBe(userId);
});
});