mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 13:01:56 +00:00
1f3f5c2e27
Co-authored-by: alejandrobailo <alejandrobailo94@gmail.com>
391 lines
11 KiB
TypeScript
391 lines
11 KiB
TypeScript
"use server";
|
||
|
||
import { revalidatePath } from "next/cache";
|
||
import { redirect } from "next/navigation";
|
||
|
||
import { apiBaseUrl, getAuthHeaders, getFormValue, wait } from "@/lib";
|
||
import { buildSecretConfig } from "@/lib/provider-credentials/build-credentials";
|
||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||
import { appendSanitizedProviderInFilters } from "@/lib/provider-filters";
|
||
import { handleApiError, handleApiResponse } from "@/lib/server-actions-helper";
|
||
import { ProvidersApiResponse, ProviderType } from "@/types/providers";
|
||
|
||
export const getProviders = async ({
|
||
page = 1,
|
||
query = "",
|
||
sort = "",
|
||
filters = {},
|
||
pageSize = 10,
|
||
}: {
|
||
page?: number;
|
||
query?: string;
|
||
sort?: string;
|
||
filters?: Record<string, string | string[] | undefined>;
|
||
pageSize?: number;
|
||
}): Promise<ProvidersApiResponse | undefined> => {
|
||
const headers = await getAuthHeaders({ contentType: false });
|
||
|
||
if (isNaN(Number(page)) || page < 1) redirect("/providers");
|
||
|
||
const url = new URL(`${apiBaseUrl}/providers?include=provider_groups`);
|
||
|
||
if (page) url.searchParams.append("page[number]", page.toString());
|
||
if (pageSize) url.searchParams.append("page[size]", pageSize.toString());
|
||
if (query) url.searchParams.append("filter[search]", query);
|
||
if (sort) url.searchParams.append("sort", sort);
|
||
|
||
appendSanitizedProviderInFilters(url, filters);
|
||
|
||
try {
|
||
const response = await fetch(url.toString(), {
|
||
headers,
|
||
});
|
||
|
||
return (await handleApiResponse(response)) as
|
||
| ProvidersApiResponse
|
||
| undefined;
|
||
} catch (error) {
|
||
console.error("Error fetching providers:", error);
|
||
return undefined;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Fetches all providers by iterating through all pages.
|
||
* This is useful when you need the complete list of providers without pagination limits,
|
||
* such as for dropdown menus or selection lists.
|
||
*/
|
||
export const getAllProviders = async ({
|
||
query = "",
|
||
sort = "",
|
||
filters = {},
|
||
}: {
|
||
query?: string;
|
||
sort?: string;
|
||
filters?: Record<string, string | string[] | undefined>;
|
||
} = {}): Promise<ProvidersApiResponse | undefined> => {
|
||
const headers = await getAuthHeaders({ contentType: false });
|
||
const pageSize = 100; // Use larger page size to minimize API calls
|
||
const maxPages = 50; // Safety limit: 50 pages × 100 = 5000 providers max
|
||
let currentPage = 1;
|
||
const allProviders: ProvidersApiResponse["data"] = [];
|
||
let lastResponse: ProvidersApiResponse | undefined;
|
||
let hasMorePages = true;
|
||
|
||
try {
|
||
while (hasMorePages && currentPage <= maxPages) {
|
||
const url = new URL(`${apiBaseUrl}/providers?include=provider_groups`);
|
||
url.searchParams.append("page[number]", currentPage.toString());
|
||
url.searchParams.append("page[size]", pageSize.toString());
|
||
|
||
if (query) url.searchParams.append("filter[search]", query);
|
||
if (sort) url.searchParams.append("sort", sort);
|
||
|
||
appendSanitizedProviderInFilters(url, filters);
|
||
|
||
const response = await fetch(url.toString(), { headers });
|
||
const data = (await handleApiResponse(response)) as
|
||
| ProvidersApiResponse
|
||
| undefined;
|
||
|
||
if (!data?.data || data.data.length === 0) {
|
||
hasMorePages = false;
|
||
continue;
|
||
}
|
||
|
||
allProviders.push(...data.data);
|
||
lastResponse = data;
|
||
|
||
// Check if we've fetched all pages
|
||
const totalPages = data.meta?.pagination?.pages || 1;
|
||
if (currentPage >= totalPages) {
|
||
hasMorePages = false;
|
||
} else {
|
||
currentPage++;
|
||
}
|
||
}
|
||
|
||
// Return combined response with all providers
|
||
if (lastResponse) {
|
||
return {
|
||
...lastResponse,
|
||
data: allProviders,
|
||
meta: {
|
||
...lastResponse.meta,
|
||
pagination: {
|
||
...lastResponse.meta?.pagination,
|
||
page: 1,
|
||
pages: 1,
|
||
count: allProviders.length,
|
||
},
|
||
},
|
||
};
|
||
}
|
||
|
||
return undefined;
|
||
} catch (error) {
|
||
console.error("Error fetching all providers:", error);
|
||
return undefined;
|
||
}
|
||
};
|
||
|
||
export const getProvider = async (formData: FormData) => {
|
||
const headers = await getAuthHeaders({ contentType: false });
|
||
const providerId = formData.get("id");
|
||
|
||
const url = new URL(`${apiBaseUrl}/providers/${providerId}`);
|
||
|
||
try {
|
||
const response = await fetch(url.toString(), {
|
||
headers,
|
||
});
|
||
|
||
return handleApiResponse(response);
|
||
} catch (error) {
|
||
return handleApiError(error);
|
||
}
|
||
};
|
||
|
||
export const updateProvider = async (formData: FormData) => {
|
||
const headers = await getAuthHeaders({ contentType: true });
|
||
const providerId = formData.get(ProviderCredentialFields.PROVIDER_ID);
|
||
const providerAlias = formData.get(ProviderCredentialFields.PROVIDER_ALIAS);
|
||
const url = new URL(`${apiBaseUrl}/providers/${providerId}`);
|
||
|
||
try {
|
||
const response = await fetch(url.toString(), {
|
||
method: "PATCH",
|
||
headers,
|
||
body: JSON.stringify({
|
||
data: {
|
||
type: "providers",
|
||
id: providerId,
|
||
attributes: { alias: providerAlias },
|
||
},
|
||
}),
|
||
});
|
||
|
||
return handleApiResponse(response, "/providers");
|
||
} catch (error) {
|
||
return handleApiError(error);
|
||
}
|
||
};
|
||
|
||
export const addProvider = async (formData: FormData) => {
|
||
const headers = await getAuthHeaders({ contentType: true });
|
||
|
||
const providerType = formData.get("providerType") as ProviderType;
|
||
const providerUid = formData.get("providerUid") as string;
|
||
const providerAlias = formData.get("providerAlias") as string;
|
||
|
||
const url = new URL(`${apiBaseUrl}/providers`);
|
||
|
||
try {
|
||
const bodyData = {
|
||
data: {
|
||
type: "providers",
|
||
attributes: {
|
||
provider: providerType,
|
||
uid: providerUid,
|
||
...(providerAlias?.trim() && { alias: providerAlias.trim() }),
|
||
},
|
||
},
|
||
};
|
||
|
||
const response = await fetch(url.toString(), {
|
||
method: "POST",
|
||
headers,
|
||
body: JSON.stringify(bodyData),
|
||
});
|
||
|
||
return handleApiResponse(response, "/providers");
|
||
} catch (error) {
|
||
return handleApiError(error);
|
||
}
|
||
};
|
||
|
||
export const addCredentialsProvider = async (formData: FormData) => {
|
||
const headers = await getAuthHeaders({ contentType: true });
|
||
const url = new URL(`${apiBaseUrl}/providers/secrets`);
|
||
|
||
const providerId = getFormValue(
|
||
formData,
|
||
ProviderCredentialFields.PROVIDER_ID,
|
||
);
|
||
const providerType = getFormValue(
|
||
formData,
|
||
ProviderCredentialFields.PROVIDER_TYPE,
|
||
) as ProviderType;
|
||
const providerUid = getFormValue(
|
||
formData,
|
||
ProviderCredentialFields.PROVIDER_UID,
|
||
) as string | undefined;
|
||
|
||
try {
|
||
// For IaC provider, fetch the provider data to get the repository URL from uid
|
||
if (providerType === "iac") {
|
||
const providerUrl = new URL(`${apiBaseUrl}/providers/${providerId}`);
|
||
const providerResponse = await fetch(providerUrl.toString(), {
|
||
headers: await getAuthHeaders({ contentType: false }),
|
||
});
|
||
|
||
if (providerResponse.ok) {
|
||
const providerData = await providerResponse.json();
|
||
const providerUid = providerData?.data?.attributes?.uid;
|
||
|
||
// Add the repository URL to formData using the provider's uid
|
||
if (providerUid) {
|
||
formData.append(ProviderCredentialFields.REPOSITORY_URL, providerUid);
|
||
}
|
||
}
|
||
}
|
||
|
||
const { secretType, secret } = buildSecretConfig(
|
||
formData,
|
||
providerType,
|
||
providerUid,
|
||
);
|
||
|
||
const response = await fetch(url.toString(), {
|
||
method: "POST",
|
||
headers,
|
||
body: JSON.stringify({
|
||
data: {
|
||
type: "provider-secrets",
|
||
attributes: { secret_type: secretType, secret },
|
||
relationships: {
|
||
provider: {
|
||
data: { id: providerId, type: "providers" },
|
||
},
|
||
},
|
||
},
|
||
}),
|
||
});
|
||
|
||
return handleApiResponse(response, "/providers");
|
||
} catch (error) {
|
||
return handleApiError(error);
|
||
}
|
||
};
|
||
|
||
export const updateCredentialsProvider = async (
|
||
credentialsId: string,
|
||
formData: FormData,
|
||
) => {
|
||
const headers = await getAuthHeaders({ contentType: true });
|
||
const url = new URL(`${apiBaseUrl}/providers/secrets/${credentialsId}`);
|
||
const providerType = getFormValue(
|
||
formData,
|
||
ProviderCredentialFields.PROVIDER_TYPE,
|
||
) as ProviderType;
|
||
|
||
try {
|
||
const { secretType, secret } = buildSecretConfig(formData, providerType);
|
||
const response = await fetch(url.toString(), {
|
||
method: "PATCH",
|
||
headers,
|
||
body: JSON.stringify({
|
||
data: {
|
||
type: "provider-secrets",
|
||
id: credentialsId,
|
||
attributes: { secret_type: secretType, secret },
|
||
},
|
||
}),
|
||
});
|
||
|
||
return handleApiResponse(response, "/providers");
|
||
} catch (error) {
|
||
return handleApiError(error);
|
||
}
|
||
};
|
||
|
||
export const checkConnectionProvider = async (formData: FormData) => {
|
||
const headers = await getAuthHeaders({ contentType: false });
|
||
const providerId = formData.get(ProviderCredentialFields.PROVIDER_ID);
|
||
const url = new URL(`${apiBaseUrl}/providers/${providerId}/connection`);
|
||
|
||
try {
|
||
const response = await fetch(url.toString(), { method: "POST", headers });
|
||
await wait(2000);
|
||
|
||
return handleApiResponse(response, "/providers");
|
||
} catch (error) {
|
||
return handleApiError(error);
|
||
}
|
||
};
|
||
|
||
export const deleteCredentials = async (secretId: string) => {
|
||
const headers = await getAuthHeaders({ contentType: false });
|
||
|
||
if (!secretId) {
|
||
return { error: "Secret ID is required" };
|
||
}
|
||
|
||
const url = new URL(`${apiBaseUrl}/providers/secrets/${secretId}`);
|
||
|
||
try {
|
||
const response = await fetch(url.toString(), {
|
||
method: "DELETE",
|
||
headers,
|
||
});
|
||
|
||
if (!response.ok) {
|
||
try {
|
||
const errorData = await response.json();
|
||
throw new Error(
|
||
errorData?.message || "Failed to delete the credentials",
|
||
);
|
||
} catch {
|
||
throw new Error("Failed to delete the credentials");
|
||
}
|
||
}
|
||
|
||
let data = null;
|
||
if (response.status !== 204) {
|
||
data = await response.json();
|
||
}
|
||
|
||
revalidatePath("/providers");
|
||
return data || { success: true };
|
||
} catch (error) {
|
||
handleApiError(error);
|
||
}
|
||
};
|
||
|
||
export const deleteProvider = async (formData: FormData) => {
|
||
const headers = await getAuthHeaders({ contentType: false });
|
||
const providerId = formData.get(ProviderCredentialFields.PROVIDER_ID);
|
||
|
||
if (!providerId) {
|
||
return { error: "Provider ID is required" };
|
||
}
|
||
|
||
const url = new URL(`${apiBaseUrl}/providers/${providerId}`);
|
||
|
||
try {
|
||
const response = await fetch(url.toString(), {
|
||
method: "DELETE",
|
||
headers,
|
||
});
|
||
|
||
if (!response.ok) {
|
||
try {
|
||
const errorData = await response.json();
|
||
throw new Error(errorData?.message || "Failed to delete the provider");
|
||
} catch {
|
||
throw new Error("Failed to delete the provider");
|
||
}
|
||
}
|
||
|
||
let data = null;
|
||
if (response.status !== 204) {
|
||
data = await response.json();
|
||
}
|
||
|
||
revalidatePath("/providers");
|
||
return data || { success: true };
|
||
} catch (error) {
|
||
handleApiError(error);
|
||
}
|
||
};
|