mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-08-19 09:30:21 +00:00
fix(ui): confirm a Slack install only from a readable integration
- Validate the exchange body as a minimal JSON:API resource before the cast - Report truthy-but-unreadable payloads as the existing unconfirmed result
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { SLACK_UNREADABLE_RESULT_MESSAGE } from "@/lib/integrations/slack-errors";
|
||||
import { SentryErrorSource, SentryErrorType } from "@/sentry";
|
||||
|
||||
const { captureExceptionMock, captureMessageMock, fetchMock } = vi.hoisted(
|
||||
@@ -221,3 +222,56 @@ describe("getSlackAuthorizeUrl authorize URL", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* The callback names the workspace and redirects on `integration` alone, so a
|
||||
* `2xx` body it cannot read back as an integration must not reach it.
|
||||
*/
|
||||
describe("exchangeSlackOAuthCode result shape", () => {
|
||||
const INTEGRATION = {
|
||||
id: "9b1f4c22-5e7a-4c2e-8f0d-6a3b1c9d7e42",
|
||||
type: "integrations",
|
||||
attributes: {
|
||||
integration_type: "slack",
|
||||
configuration: { team_name: "Prowler HQ" },
|
||||
},
|
||||
};
|
||||
|
||||
const exchangeResponse = (data: unknown) =>
|
||||
new Response(JSON.stringify({ data }), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/vnd.api+json" },
|
||||
});
|
||||
|
||||
it.each<[string, unknown]>([
|
||||
["an empty object", {}],
|
||||
["an array", []],
|
||||
["a bare string", "invalid"],
|
||||
["a resource with no id", { type: "integrations", attributes: {} }],
|
||||
[
|
||||
"a resource with no attributes",
|
||||
{ id: INTEGRATION.id, type: "integrations" },
|
||||
],
|
||||
])("cannot confirm the install from %s", async (_label, data) => {
|
||||
// Given — a 2xx whose `data` is truthy but is not an integration resource.
|
||||
fetchMock.mockResolvedValue(exchangeResponse(data));
|
||||
|
||||
// When
|
||||
const result = await exchange();
|
||||
|
||||
// Then — the answer for a body with no `data`: the install happened, only
|
||||
// its result is unknown.
|
||||
expect(result).toEqual({
|
||||
unconfirmed: true,
|
||||
message: SLACK_UNREADABLE_RESULT_MESSAGE,
|
||||
});
|
||||
});
|
||||
|
||||
it("hands over the workspace the API upserted", async () => {
|
||||
// Given
|
||||
fetchMock.mockResolvedValue(exchangeResponse(INTEGRATION));
|
||||
|
||||
// When / Then
|
||||
expect(await exchange()).toEqual({ integration: INTEGRATION });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -98,6 +98,26 @@ const isSlackAuthorizeUrl = (value: string): boolean => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The callback names the workspace and redirects on this value alone, so a `2xx`
|
||||
* payload that is not a JSON:API resource (`{}`, `[]`, `"invalid"`) must read as
|
||||
* unreadable rather than as a connected workspace.
|
||||
*/
|
||||
const isIntegrationResource = (value: unknown): boolean => {
|
||||
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { id, attributes } = value as Record<string, unknown>;
|
||||
|
||||
return (
|
||||
typeof id === "string" &&
|
||||
typeof attributes === "object" &&
|
||||
attributes !== null &&
|
||||
!Array.isArray(attributes)
|
||||
);
|
||||
};
|
||||
|
||||
const failureFrom = async (
|
||||
response: Response,
|
||||
fallback: string,
|
||||
@@ -205,7 +225,7 @@ export const exchangeSlackOAuthCode = async (
|
||||
revalidatePath("/integrations");
|
||||
revalidatePath("/integrations/slack");
|
||||
|
||||
if (!body?.data) {
|
||||
if (!isIntegrationResource(body?.data)) {
|
||||
return { unconfirmed: true, message: SLACK_UNREADABLE_RESULT_MESSAGE };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user