mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-04 19:21:51 +00:00
326 lines
8.4 KiB
TypeScript
326 lines
8.4 KiB
TypeScript
"use server";
|
|
|
|
import { redirect } from "next/navigation";
|
|
|
|
import { getLatestFindings } from "@/actions/findings";
|
|
import { listOrganizationsSafe } from "@/actions/organizations/organizations";
|
|
import {
|
|
apiBaseUrl,
|
|
FINDINGS_FILTERED_SORT,
|
|
GENERIC_SERVER_ERROR_MESSAGE,
|
|
getAuthHeaders,
|
|
sanitizeErrorMessage,
|
|
} from "@/lib";
|
|
import { appendSanitizedProviderTypeFilters } from "@/lib/provider-filters";
|
|
import { handleApiResponse } from "@/lib/server-actions-helper";
|
|
import { OrganizationResource } from "@/types/organizations";
|
|
|
|
export const getResources = async ({
|
|
page = 1,
|
|
query = "",
|
|
sort = "",
|
|
filters = {},
|
|
pageSize = 10,
|
|
include = "",
|
|
fields = [],
|
|
}: {
|
|
page?: number;
|
|
query?: string;
|
|
sort?: string;
|
|
filters?: Record<string, string | string[] | undefined>;
|
|
pageSize?: number;
|
|
include?: string;
|
|
fields?: string[];
|
|
}) => {
|
|
const headers = await getAuthHeaders({ contentType: false });
|
|
|
|
if (isNaN(Number(page)) || page < 1) redirect("resources");
|
|
|
|
const url = new URL(`${apiBaseUrl}/resources`);
|
|
|
|
if (fields.length > 0) {
|
|
url.searchParams.append("fields[resources]", fields.join(","));
|
|
}
|
|
|
|
if (page) url.searchParams.append("page[number]", page.toString());
|
|
if (pageSize) url.searchParams.append("page[size]", pageSize.toString());
|
|
if (include) url.searchParams.append("include", include);
|
|
if (query) url.searchParams.append("filter[search]", query);
|
|
if (sort) url.searchParams.append("sort", sort);
|
|
|
|
appendSanitizedProviderTypeFilters(url, filters);
|
|
|
|
try {
|
|
const response = await fetch(url.toString(), {
|
|
headers,
|
|
});
|
|
|
|
return handleApiResponse(response);
|
|
} catch (error) {
|
|
console.error("Error fetching resources:", error);
|
|
return undefined;
|
|
}
|
|
};
|
|
|
|
export const getLatestResources = async ({
|
|
page = 1,
|
|
query = "",
|
|
sort = "",
|
|
include = "",
|
|
filters = {},
|
|
pageSize = 10,
|
|
fields = [],
|
|
}: {
|
|
page?: number;
|
|
query?: string;
|
|
sort?: string;
|
|
filters?: Record<string, string | string[] | undefined>;
|
|
pageSize?: number;
|
|
include?: string;
|
|
fields?: string[];
|
|
}) => {
|
|
const headers = await getAuthHeaders({ contentType: false });
|
|
|
|
if (isNaN(Number(page)) || page < 1) redirect("resources");
|
|
|
|
const url = new URL(`${apiBaseUrl}/resources/latest`);
|
|
|
|
if (fields.length > 0) {
|
|
url.searchParams.append("fields[resources]", fields.join(","));
|
|
}
|
|
|
|
if (page) url.searchParams.append("page[number]", page.toString());
|
|
if (pageSize) url.searchParams.append("page[size]", pageSize.toString());
|
|
if (include) url.searchParams.append("include", include);
|
|
if (query) url.searchParams.append("filter[search]", query);
|
|
if (sort) url.searchParams.append("sort", sort);
|
|
|
|
appendSanitizedProviderTypeFilters(url, filters);
|
|
|
|
try {
|
|
const response = await fetch(url.toString(), {
|
|
headers,
|
|
});
|
|
|
|
return handleApiResponse(response);
|
|
} catch (error) {
|
|
console.error("Error fetching latest resources:", error);
|
|
return undefined;
|
|
}
|
|
};
|
|
|
|
export const getMetadataInfo = async ({
|
|
query = "",
|
|
sort = "",
|
|
filters = {},
|
|
}: {
|
|
query?: string;
|
|
sort?: string;
|
|
filters?: Record<string, string | string[] | undefined>;
|
|
}) => {
|
|
const headers = await getAuthHeaders({ contentType: false });
|
|
|
|
const url = new URL(`${apiBaseUrl}/resources/metadata`);
|
|
|
|
if (query) url.searchParams.append("filter[search]", query);
|
|
if (sort) url.searchParams.append("sort", sort);
|
|
|
|
appendSanitizedProviderTypeFilters(url, filters);
|
|
|
|
try {
|
|
const response = await fetch(url.toString(), {
|
|
headers,
|
|
});
|
|
|
|
return handleApiResponse(response);
|
|
} catch (error) {
|
|
console.error("Error fetching metadata info:", error);
|
|
return undefined;
|
|
}
|
|
};
|
|
|
|
export const getLatestMetadataInfo = async ({
|
|
query = "",
|
|
sort = "",
|
|
filters = {},
|
|
}: {
|
|
query?: string;
|
|
sort?: string;
|
|
filters?: Record<string, string | string[] | undefined>;
|
|
}) => {
|
|
const headers = await getAuthHeaders({ contentType: false });
|
|
|
|
const url = new URL(`${apiBaseUrl}/resources/metadata/latest`);
|
|
|
|
if (query) url.searchParams.append("filter[search]", query);
|
|
if (sort) url.searchParams.append("sort", sort);
|
|
|
|
appendSanitizedProviderTypeFilters(url, filters);
|
|
|
|
try {
|
|
const response = await fetch(url.toString(), {
|
|
headers,
|
|
});
|
|
|
|
return handleApiResponse(response);
|
|
} catch (error) {
|
|
console.error("Error fetching latest metadata info:", error);
|
|
return undefined;
|
|
}
|
|
};
|
|
|
|
const SAFE_RESOURCE_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
|
|
export const getResourceEvents = async (
|
|
resourceId: string,
|
|
{
|
|
includeReadEvents = false,
|
|
lookbackDays = 90,
|
|
pageSize = 50,
|
|
}: {
|
|
includeReadEvents?: boolean;
|
|
lookbackDays?: number;
|
|
pageSize?: number;
|
|
} = {},
|
|
) => {
|
|
if (!SAFE_RESOURCE_ID_PATTERN.test(resourceId)) {
|
|
return { error: "Invalid resource ID format." };
|
|
}
|
|
|
|
const headers = await getAuthHeaders({ contentType: false });
|
|
|
|
const url = new URL(`${apiBaseUrl}/resources/${resourceId}/events`);
|
|
url.searchParams.append("include_read_events", String(includeReadEvents));
|
|
url.searchParams.append("lookback_days", String(lookbackDays));
|
|
url.searchParams.append("page[size]", String(pageSize));
|
|
|
|
try {
|
|
const response = await fetch(url.toString(), { headers });
|
|
|
|
if (!response.ok) {
|
|
const rawText = await response.text().catch(() => "");
|
|
const contentType =
|
|
response.headers.get("content-type")?.toLowerCase() || "";
|
|
const defaultError = "An error occurred while fetching events.";
|
|
const fallbackError = contentType.includes("text/html")
|
|
? GENERIC_SERVER_ERROR_MESSAGE
|
|
: response.statusText || defaultError;
|
|
try {
|
|
const errorData = rawText ? JSON.parse(rawText) : null;
|
|
const errorMessage =
|
|
errorData?.errors?.[0]?.detail ||
|
|
errorData?.error ||
|
|
errorData?.message ||
|
|
rawText ||
|
|
fallbackError;
|
|
return {
|
|
error: sanitizeErrorMessage(String(errorMessage), fallbackError),
|
|
status: response.status,
|
|
};
|
|
} catch {
|
|
return {
|
|
error: sanitizeErrorMessage(rawText || fallbackError, fallbackError),
|
|
status: response.status,
|
|
};
|
|
}
|
|
}
|
|
|
|
return handleApiResponse(response);
|
|
} catch (error) {
|
|
console.error("Error fetching resource events:", error);
|
|
return { error: "An unexpected error occurred." };
|
|
}
|
|
};
|
|
|
|
export const getResourceById = async (
|
|
id: string,
|
|
{
|
|
fields = [],
|
|
include = [],
|
|
}: {
|
|
fields?: string[];
|
|
include?: string[];
|
|
} = {},
|
|
) => {
|
|
const headers = await getAuthHeaders({ contentType: false });
|
|
|
|
const url = new URL(`${apiBaseUrl}/resources/${id}`);
|
|
|
|
if (fields.length > 0) {
|
|
url.searchParams.append("fields[resources]", fields.join(","));
|
|
}
|
|
|
|
if (include.length > 0) {
|
|
url.searchParams.append("include", include.join(","));
|
|
}
|
|
|
|
try {
|
|
const resource = await fetch(url.toString(), {
|
|
headers,
|
|
});
|
|
|
|
if (!resource.ok) {
|
|
throw new Error(`Error fetching resource: ${resource.status}`);
|
|
}
|
|
|
|
return handleApiResponse(resource);
|
|
} catch (error) {
|
|
console.error("Error fetching resource by ID:", error);
|
|
return undefined;
|
|
}
|
|
};
|
|
|
|
export const getResourceDrawerData = async ({
|
|
resourceId,
|
|
resourceUid,
|
|
providerId,
|
|
providerType,
|
|
page = 1,
|
|
pageSize = 10,
|
|
query = "",
|
|
}: {
|
|
resourceId: string;
|
|
resourceUid: string;
|
|
providerId: string;
|
|
providerType: string;
|
|
page?: number;
|
|
pageSize?: number;
|
|
query?: string;
|
|
}) => {
|
|
const isCloudEnv = process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true";
|
|
|
|
const [resourceData, findingsResponse, organizationsResponse] =
|
|
await Promise.all([
|
|
getResourceById(resourceId, { fields: ["tags"] }),
|
|
getLatestFindings({
|
|
page,
|
|
pageSize,
|
|
query,
|
|
sort: FINDINGS_FILTERED_SORT,
|
|
filters: {
|
|
"filter[resource_uid]": resourceUid,
|
|
"filter[status]": "FAIL",
|
|
},
|
|
}),
|
|
isCloudEnv && providerType === "aws"
|
|
? listOrganizationsSafe()
|
|
: Promise.resolve({ data: [] }),
|
|
]);
|
|
|
|
const providerOrg =
|
|
providerType === "aws"
|
|
? (organizationsResponse.data.find((organization: OrganizationResource) =>
|
|
organization.relationships?.providers?.data?.some(
|
|
(provider: { id: string }) => provider.id === providerId,
|
|
),
|
|
) ?? null)
|
|
: null;
|
|
|
|
return {
|
|
findings: findingsResponse?.data ?? [],
|
|
findingsMeta: findingsResponse?.meta ?? null,
|
|
providerOrg,
|
|
resourceTags: resourceData?.data?.attributes.tags ?? {},
|
|
};
|
|
};
|