fix(ui): confirm a Slack install only from a Slack-typed resource

- Require a non-empty id, the integrations resource type and the slack
  integration kind before reporting the workspace as installed
This commit is contained in:
Pablo F.G
2026-08-18 17:38:40 +02:00
parent 434e5aaf03
commit 9d43aa0c33
2 changed files with 29 additions and 7 deletions
+9
View File
@@ -248,10 +248,19 @@ describe("exchangeSlackOAuthCode result shape", () => {
["an array", []],
["a bare string", "invalid"],
["a resource with no id", { type: "integrations", attributes: {} }],
["a resource with an empty id", { ...INTEGRATION, id: "" }],
["a resource of another type", { ...INTEGRATION, type: "tasks" }],
[
"a resource with no attributes",
{ id: INTEGRATION.id, type: "integrations" },
],
[
"another kind of integration",
{
...INTEGRATION,
attributes: { ...INTEGRATION.attributes, integration_type: "jira" },
},
],
])("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));
+20 -7
View File
@@ -12,7 +12,7 @@ import {
slackRateLimitMessage,
} from "@/lib/integrations/slack-errors";
import { handleApiError, handleApiResponse } from "@/lib/server-actions-helper";
import type { IntegrationProps } from "@/types/integrations";
import { INTEGRATION_TYPE, type IntegrationProps } from "@/types/integrations";
interface SlackUnavailable {
unavailable: true;
@@ -98,23 +98,36 @@ const isSlackAuthorizeUrl = (value: string): boolean => {
}
};
const INTEGRATIONS_RESOURCE_TYPE = "integrations";
/**
* 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.
* unreadable rather than as a connected workspace. Identity is part of that: an
* id the page cannot link to, another resource type, or another integration
* kind would each be shown as the Slack workspace just installed.
*/
const isIntegrationResource = (value: unknown): boolean => {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
return false;
}
const { id, attributes } = value as Record<string, unknown>;
const { id, type, attributes } = value as Record<string, unknown>;
if (
typeof id !== "string" ||
id === "" ||
type !== INTEGRATIONS_RESOURCE_TYPE ||
typeof attributes !== "object" ||
attributes === null ||
Array.isArray(attributes)
) {
return false;
}
return (
typeof id === "string" &&
typeof attributes === "object" &&
attributes !== null &&
!Array.isArray(attributes)
(attributes as Record<string, unknown>).integration_type ===
INTEGRATION_TYPE.SLACK
);
};