Files
zerobyte/app/server/modules/api-keys/api-keys.controller.ts
Nico d24167b520 refactor(auth): mark desktop sessions with auth source (#990)
* refactor(auth): mark desktop sessions with auth source

Makes it easier to filter out on session type in backend paths that
behave differently depending on the context

* chore: fix un-used import

* fix(auth): align desktop session guards

* refactor(auth): gate desktop sessions by runtime features
2026-06-15 21:39:32 +02:00

104 lines
2.9 KiB
TypeScript

import { Hono } from "hono";
import { validator } from "hono-openapi";
import {
createApiKeyBody,
createApiKeyDto,
deleteApiKeyDto,
getApiKeysDto,
type CreateApiKeyDto,
type ListApiKeysDto,
} from "./api-keys.dto";
import { MAX_API_KEYS_PER_USER, countActiveApiKeys, hasApiKey, listApiKeys } from "./api-keys.service";
import { requireAuth, requireBrowserSession, requireRuntimeFeature } from "../auth/auth.middleware";
import { auth } from "~/server/lib/auth";
import { isPasswordAuthSupported, userHasPassword, verifyUserPassword } from "../auth/helpers";
export const apiKeysController = new Hono()
.get(
"/api-keys",
requireAuth,
requireRuntimeFeature("apiKeys"),
requireBrowserSession,
getApiKeysDto,
async (c) => {
const user = c.get("user");
const organizationId = c.get("organizationId");
const apiKeys = await listApiKeys(user.id, organizationId);
return c.json<ListApiKeysDto>({ apiKeys, limit: MAX_API_KEYS_PER_USER });
},
)
.post(
"/api-keys",
requireAuth,
requireRuntimeFeature("apiKeys"),
requireBrowserSession,
createApiKeyDto,
validator("json", createApiKeyBody),
async (c) => {
const user = c.get("user");
const organizationId = c.get("organizationId");
const { expiresIn, name, password } = c.req.valid("json");
if (isPasswordAuthSupported()) {
const hasPassword = await userHasPassword(user.id);
if (!hasPassword) {
return c.json({ message: "A local password is required to create API keys" }, 403);
}
const isPasswordValid = await verifyUserPassword({ userId: user.id, password });
if (!isPasswordValid) {
return c.json({ message: "Invalid password" }, 401);
}
}
const apiKeyCount = await countActiveApiKeys(user.id);
if (apiKeyCount >= MAX_API_KEYS_PER_USER) {
return c.json({ message: "API key limit reached" }, 409);
}
const apiKey = await auth.api.createApiKey({
body: {
name,
expiresIn: expiresIn ?? undefined,
userId: user.id,
metadata: { organizationId },
rateLimitEnabled: false,
},
});
return c.json<CreateApiKeyDto>({
id: apiKey.id,
name: apiKey.name,
key: apiKey.key,
createdAt: apiKey.createdAt.toISOString(),
expiresAt: apiKey.expiresAt?.toISOString() ?? null,
lastRequestAt: apiKey.lastRequest?.toISOString() ?? null,
});
},
)
.delete(
"/api-keys/:keyId",
requireAuth,
requireRuntimeFeature("apiKeys"),
requireBrowserSession,
deleteApiKeyDto,
async (c) => {
const user = c.get("user");
const organizationId = c.get("organizationId");
const keyId = c.req.param("keyId");
const belongsToUserOrganization = await hasApiKey(user.id, organizationId, keyId);
if (!belongsToUserOrganization) {
return c.json({ message: "API key not found" }, 404);
}
await auth.api.deleteApiKey({
headers: c.req.raw.headers,
body: { keyId },
});
return c.json({ success: true });
},
);