mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-08-19 09:30:21 +00:00
fix(ui): validate the input to the Slack exchange action
- Reject a malformed exchange argument before it reaches the API - Derive IntegrationType from a const object - Honor explicit width and height on the Slack icon - Pin the revalidated paths on a completed install
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
|
||||
import { apiBaseUrl, getAuthHeaders, parseStringify } from "@/lib";
|
||||
import {
|
||||
readSlackFailure,
|
||||
SLACK_GENERIC_ERROR_MESSAGE,
|
||||
SLACK_UNREADABLE_RESULT_MESSAGE,
|
||||
slackErrorMessage,
|
||||
slackRateLimitMessage,
|
||||
@@ -50,6 +52,11 @@ interface SlackExchangeInput {
|
||||
state: string;
|
||||
}
|
||||
|
||||
const slackExchangeInputSchema = z.object({
|
||||
code: z.string().min(1),
|
||||
state: z.string().min(1),
|
||||
});
|
||||
|
||||
interface SlackExchangeSuccess {
|
||||
integration: IntegrationProps;
|
||||
}
|
||||
@@ -134,10 +141,13 @@ export const getSlackAuthorizeUrl =
|
||||
* consumes the `state`, exchanges the single-use `code`, and upserts the
|
||||
* tenant's Slack integration.
|
||||
*/
|
||||
export const exchangeSlackOAuthCode = async ({
|
||||
code,
|
||||
state,
|
||||
}: SlackExchangeInput): Promise<SlackExchangeResult> => {
|
||||
export const exchangeSlackOAuthCode = async (
|
||||
input: SlackExchangeInput,
|
||||
): Promise<SlackExchangeResult> => {
|
||||
const parsed = slackExchangeInputSchema.safeParse(input);
|
||||
if (!parsed.success) return { error: SLACK_GENERIC_ERROR_MESSAGE };
|
||||
|
||||
const { code, state } = parsed.data;
|
||||
const headers = await getAuthHeaders({ contentType: true });
|
||||
const url = new URL(`${apiBaseUrl}/integrations/slack/oauth/exchange`);
|
||||
|
||||
|
||||
@@ -41,6 +41,10 @@ describe("returning from Slack", () => {
|
||||
// The code is single-use and the exchange runs from a render (design D4):
|
||||
// without the once-guard, a second call burns it and reports a failure.
|
||||
expect(harness.exchangeCallCount).toBe(1);
|
||||
// A completed install invalidates the cached "none connected".
|
||||
expect(harness.revalidatedPaths).toEqual(
|
||||
expect.arrayContaining(["/integrations", "/integrations/slack"]),
|
||||
);
|
||||
}, 30000);
|
||||
|
||||
it("does not report an install the API completed as failed when it answers no content", async () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { getIntegrations } from "@/actions/integrations/integrations";
|
||||
import { getSlackAuthorizeUrl } from "@/actions/integrations/slack";
|
||||
import { SlackIntegrationManager } from "@/components/integrations/slack/slack-integration-manager";
|
||||
import { GENERIC_SERVER_ERROR_MESSAGE } from "@/lib/helper";
|
||||
import type { IntegrationProps } from "@/types/integrations";
|
||||
import { INTEGRATION_TYPE, type IntegrationProps } from "@/types/integrations";
|
||||
|
||||
/**
|
||||
* `getIntegrations` throws a `>= 500` answer past its own catch, which covers
|
||||
@@ -26,7 +26,7 @@ const readSlackIntegrations = async (searchParams: URLSearchParams) => {
|
||||
*/
|
||||
export async function SlackIntegrationContent() {
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.set("filter[integration_type]", "slack");
|
||||
searchParams.set("filter[integration_type]", INTEGRATION_TYPE.SLACK);
|
||||
// One workspace per tenant, so one row is the whole result set.
|
||||
searchParams.set("page[size]", "1");
|
||||
|
||||
|
||||
@@ -763,10 +763,10 @@ export const SlackIcon: React.FC<IconSvgProps> = ({
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height={size || height}
|
||||
height={height ?? size}
|
||||
role="presentation"
|
||||
viewBox="0 0 48 48"
|
||||
width={size || width}
|
||||
width={width ?? size}
|
||||
className={className}
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
@@ -2,11 +2,15 @@ import { z } from "zod";
|
||||
|
||||
import type { TaskState } from "@/types/tasks";
|
||||
|
||||
export const INTEGRATION_TYPE = {
|
||||
AMAZON_S3: "amazon_s3",
|
||||
AWS_SECURITY_HUB: "aws_security_hub",
|
||||
JIRA: "jira",
|
||||
SLACK: "slack",
|
||||
} as const;
|
||||
|
||||
export type IntegrationType =
|
||||
| "amazon_s3"
|
||||
| "aws_security_hub"
|
||||
| "jira"
|
||||
| "slack";
|
||||
(typeof INTEGRATION_TYPE)[keyof typeof INTEGRATION_TYPE];
|
||||
|
||||
export const JIRA_DISPATCH_MODE = {
|
||||
INDIVIDUAL: "individual",
|
||||
|
||||
Reference in New Issue
Block a user