Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/utils/db/apiKeys.db.server.ts
T

274 lines
7.9 KiB
TypeScript

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<LegacyApiKeyWithDetails, "key">;
export function redactApiKeyForDisplay(apiKey: LegacyApiKeyWithDetails): RedactedApiKeyWithDetails {
const redactedApiKey = { ...apiKey };
delete (redactedApiKey as Partial<LegacyApiKeyWithDetails>).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<RedactedApiKeyWithDetails[]> {
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<ApiKeyLogSimple[]> {
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<RedactedApiKeyWithDetails[]> {
const apiKeys = await getLegacyApiKeysForTenant(tenantId);
return redactApiKeysForDisplay(apiKeys);
}
export async function adminGetApiKeyById(id: string): Promise<RedactedApiKeyWithDetails | null> {
const apiKey = await adminFindLegacyApiKeyById(id);
return apiKey ? redactApiKeyForDisplay(apiKey) : null;
}
export async function getTenantApiKeyById(id: string, tenantId: string): Promise<RedactedApiKeyWithDetails | null> {
const apiKey = await findLegacyApiKeyById({ id, tenantId });
return apiKey ? redactApiKeyForDisplay(apiKey) : null;
}
export async function adminGetApiKeyByIdWithSecret(id: string): Promise<ApiKeyWithDetails | null> {
return adminFindLegacyApiKeyById(id);
}
export async function getTenantApiKeyByIdWithSecret(id: string, tenantId: string): Promise<ApiKeyWithDetails | null> {
return findLegacyApiKeyById({ id, tenantId });
}
export async function getApiKey(key: string): Promise<ApiKeySimple | null> {
return await cachified({
key: getApiKeyLookupCacheKey(key),
ttl: 1000 * 60 * 60,
getFreshValue: () => findLegacyApiKeyByKey(key),
});
}
export async function getApiKeyByAlias(tenantId: string, alias: string): Promise<ApiKeyWithDetails | null> {
return findLegacyApiKeyByAlias(tenantId, alias);
}
export async function getApiKeyLogs(id: string, tenantId: string): Promise<ApiKeyLogWithDetails[]> {
return getLegacyApiKeyLogsForKey({ id, tenantId });
}
export async function countApiKeyLogsForKey(id: string, tenantId: string): Promise<number> {
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;
}