import { createLegacyApiKeyLog, createLegacyApiKey, countLegacyApiKeyLogsForKey, deleteLegacyApiKey, adminFindLegacyApiKeyById, findFirstLegacyActiveApiKey, findLegacyApiKeyByAlias, findLegacyApiKeyById, findLegacyApiKeyByKey, getAllLegacyApiKeys, rotateLegacyApiKey, getLegacyApiKeyLogsForKey, getLegacyApiKeysForTenant, type LegacyApiKeyLogWithDetails, type LegacyApiKeySimple, type LegacyApiKeyWithDetails, updateLegacyApiKey, } from "@toolctrl/db/api-keys.server"; import { db } from "../db.server"; import { getClientIPAddress } from "~/utils/server/IpUtils"; import { PaginationDto } from "~/application/dtos/data/PaginationDto"; import { FiltersDto } from "~/application/dtos/data/FiltersDto"; import RowFiltersHelper from "../helpers/RowFiltersHelper"; import { cachified, clearCacheKey } from "../cache.server"; import { loadRequiredWebConfig } from "@toolctrl/config"; import { getApiKeyLookupCacheKey, serializeApiLogSearchParams } from "../api/apiKeySecurity.server"; export type ApiKeyWithDetails = LegacyApiKeyWithDetails; export type RedactedApiKeyWithDetails = Omit; export function redactApiKeyForDisplay(apiKey: LegacyApiKeyWithDetails): RedactedApiKeyWithDetails { const redactedApiKey = { ...apiKey }; delete (redactedApiKey as Partial).key; return redactedApiKey; } export function redactApiKeysForDisplay(apiKeys: LegacyApiKeyWithDetails[]): RedactedApiKeyWithDetails[] { return apiKeys.map(redactApiKeyForDisplay); } export type ApiKeyLogWithDetails = LegacyApiKeyLogWithDetails; export type ApiKeyLogSimple = { apiKeyId: string | null; status: number | null }; export type ApiKeySimple = LegacyApiKeySimple; export async function getAllApiKeys(): Promise { const apiKeys = await getAllLegacyApiKeys(); return redactApiKeysForDisplay(apiKeys); } export async function getAllApiKeyLogs( pagination: { page: number; pageSize: number }, filters: FiltersDto ): Promise<{ items: ApiKeyLogWithDetails[]; pagination: PaginationDto }> { const where = RowFiltersHelper.getFiltersCondition(filters); const items = await db.apiKeyLog.findMany({ take: pagination.pageSize, skip: pagination.pageSize * (pagination.page - 1), where, include: { apiKey: { include: { tenant: true }, }, }, orderBy: { createdAt: "desc", }, }); const totalItems = await db.apiKeyLog.count({ where, }); return { items, pagination: { page: pagination.page, pageSize: pagination.pageSize, totalItems, totalPages: Math.ceil(totalItems / pagination.pageSize), }, }; } export async function getAllApiKeyLogsSimple(tenantId?: string): Promise { let where: any = {}; if (tenantId) { where = { apiKey: { tenantId, }, }; } return await db.apiKeyLog.findMany({ where, select: { apiKeyId: true, status: true, }, orderBy: { createdAt: "desc", }, }); } export async function getTenantApiKeyLogs( tenantId: string, pagination: { page: number; pageSize: number }, filters: FiltersDto ): Promise<{ items: ApiKeyLogWithDetails[]; pagination: PaginationDto }> { const where = { AND: [RowFiltersHelper.getFiltersCondition(filters), { apiKey: { tenantId } }], }; const items = await db.apiKeyLog.findMany({ take: pagination.pageSize, skip: pagination.pageSize * (pagination.page - 1), where, include: { apiKey: { include: { tenant: true } } }, orderBy: { createdAt: "desc" }, }); const totalItems = await db.apiKeyLog.count({ where }); return { items, pagination: { page: pagination.page, pageSize: pagination.pageSize, totalItems, totalPages: Math.ceil(totalItems / pagination.pageSize), }, }; } export async function getApiKeys(tenantId: string): Promise { const apiKeys = await getLegacyApiKeysForTenant(tenantId); return redactApiKeysForDisplay(apiKeys); } export async function adminGetApiKeyById(id: string): Promise { const apiKey = await adminFindLegacyApiKeyById(id); return apiKey ? redactApiKeyForDisplay(apiKey) : null; } export async function getTenantApiKeyById(id: string, tenantId: string): Promise { const apiKey = await findLegacyApiKeyById({ id, tenantId }); return apiKey ? redactApiKeyForDisplay(apiKey) : null; } export async function adminGetApiKeyByIdWithSecret(id: string): Promise { return adminFindLegacyApiKeyById(id); } export async function getTenantApiKeyByIdWithSecret(id: string, tenantId: string): Promise { return findLegacyApiKeyById({ id, tenantId }); } export async function getApiKey(key: string): Promise { return await cachified({ key: getApiKeyLookupCacheKey(key), ttl: 1000 * 60 * 60, getFreshValue: () => findLegacyApiKeyByKey(key), }); } export async function getApiKeyByAlias(tenantId: string, alias: string): Promise { return findLegacyApiKeyByAlias(tenantId, alias); } export async function getApiKeyLogs(id: string, tenantId: string): Promise { return getLegacyApiKeyLogsForKey({ id, tenantId }); } export async function countApiKeyLogsForKey(id: string, tenantId: string): Promise { return countLegacyApiKeyLogsForKey({ id, tenantId }); } export async function getFirstApiKey(tenantId: string | null) { return findFirstLegacyActiveApiKey(tenantId); } export async function createApiKey( data: { tenantId: string; createdByUserId: string; alias: string; expires: Date | null; active: boolean; }, entities: { entityId: string; create: boolean; read: boolean; update: boolean; delete: boolean; }[] ) { return createLegacyApiKey(data, entities, undefined, getApiKeyHashSecret()); } export async function createApiKeyLog( request: Request, data: { tenantId: string | null; apiKeyId: string | null; endpoint: string; error?: string; status?: number; alias?: string; tenantName?: string; } ) { return await createLegacyApiKeyLog({ tenantId: data.tenantId, apiKeyId: data.apiKeyId, endpoint: data.endpoint, error: data.error, status: data.status, alias: data.alias, tenantName: data.tenantName, ip: getClientIPAddress(request.headers)?.toString() ?? "", method: request.method, params: serializeApiLogSearchParams(request), }); } export async function updateApiKey( id: string, data: { tenantId: string; alias: string; expires: Date | null; active: boolean; }, entities: { entityId: string; create: boolean; read: boolean; update: boolean; delete: boolean; }[] ) { const apiKey = await updateLegacyApiKey(id, data, entities); clearCacheKey(getApiKeyLookupCacheKey(apiKey.key)); return apiKey; } export async function rotateApiKey(id: string, tenantId: string) { const previousApiKey = await findLegacyApiKeyById({ id, tenantId }); if (!previousApiKey) { throw new Error(`API key ${id} not found`); } const apiKey = await rotateLegacyApiKey({ id, tenantId: previousApiKey.tenantId }, undefined, getApiKeyHashSecret()); if (previousApiKey?.key) { clearCacheKey(getApiKeyLookupCacheKey(previousApiKey.key)); } clearCacheKey(getApiKeyLookupCacheKey(apiKey.key)); return apiKey; } export async function deleteApiKey(id: string, tenantId: string) { const previousApiKey = await findLegacyApiKeyById({ id, tenantId }); if (!previousApiKey) { throw new Error(`API key ${id} not found`); } const apiKey = await deleteLegacyApiKey({ id, tenantId: previousApiKey.tenantId }); clearCacheKey(getApiKeyLookupCacheKey(apiKey.key)); return apiKey; } function getApiKeyHashSecret() { return loadRequiredWebConfig().jwtSecret; }