Files
GibSend/apps/web/src/server/service/api-service.ts
T
2025-09-10 22:53:04 +10:00

108 lines
2.3 KiB
TypeScript

import { ApiPermission } from "@prisma/client";
import { db } from "../db";
import { randomBytes } from "crypto";
import { smallNanoid } from "../nanoid";
import { createSecureHash, verifySecureHash } from "../crypto";
import { logger } from "../logger/log";
export async function addApiKey({
name,
permission,
teamId,
domainId,
}: {
name: string;
permission: ApiPermission;
teamId: number;
domainId?: number;
}) {
try {
// Validate domain ownership if domainId is provided
if (domainId !== undefined) {
const domain = await db.domain.findUnique({
where: {
id: domainId,
teamId: teamId
},
select: { id: true },
});
if (!domain) {
throw new Error("DOMAIN_NOT_FOUND");
}
}
const clientId = smallNanoid(10);
const token = randomBytes(16).toString("hex");
const hashedToken = await createSecureHash(token);
const apiKey = `us_${clientId}_${token}`;
await db.apiKey.create({
data: {
name,
permission: permission,
teamId,
domainId,
tokenHash: hashedToken,
partialToken: `${apiKey.slice(0, 6)}...${apiKey.slice(-3)}`,
clientId,
},
});
return apiKey;
} catch (error) {
logger.error({ err: error }, "Error adding API key");
throw error;
}
}
export async function getTeamAndApiKey(apiKey: string) {
const [, clientId, token] = apiKey.split("_") as [string, string, string];
const apiKeyRow = await db.apiKey.findUnique({
where: {
clientId,
},
include: {
domain: {
select: { id: true, name: true },
},
},
});
if (!apiKeyRow) {
return null;
}
try {
const isValid = await verifySecureHash(token, apiKeyRow.tokenHash);
if (!isValid) {
return null;
}
const team = await db.team.findUnique({
where: {
id: apiKeyRow.teamId,
},
});
return { team, apiKey: apiKeyRow };
} catch (error) {
logger.error({ err: error }, "Error verifying API key");
return null;
}
}
export async function deleteApiKey(id: number) {
try {
await db.apiKey.delete({
where: {
id,
},
});
} catch (error) {
logger.error({ err: error }, "Error deleting API key");
throw error;
}
}