mirror of
https://github.com/nicotsx/zerobyte.git
synced 2026-06-16 04:22:34 -04:00
* 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
104 lines
2.9 KiB
TypeScript
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 });
|
|
},
|
|
);
|