274 lines
7.9 KiB
TypeScript
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;
|
|
}
|