mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-30 13:20:54 +01:00
fix(slack): keep grant revocation enforced during install callbacks
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
785ae16699
commit
20534f871b
3 files changed
+10
-4
No files matched your search
@@ -116,7 +116,7 @@ export function slackChatRegistrationService(db: Db, options: SlackSetupOptions)
|
||||
if (typeof installerId !== "string" || !/^[UW][A-Z0-9]+$/.test(installerId) || installerId === result.bot_user_id) throw providerFailure("slack_install_account_missing");
|
||||
if (row.managerGrantId) {
|
||||
const grant = await managerGrants.get(row.managerGrantId, row.companyId, actor);
|
||||
if (installerId !== grant.slackUserId || teamId !== grant.workspaceId) throw providerFailure("slack_install_identity_mismatch");
|
||||
if (grant.status !== "active" || installerId !== grant.slackUserId || teamId !== grant.workspaceId) throw providerFailure("slack_install_identity_mismatch");
|
||||
}
|
||||
await endpoint(row.endpointId, actor);
|
||||
// Reauthorization reuses the runtime signing secret after staging was cleaned.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useLayoutEffect, useState } from "react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { ChatEndpointSetup } from "@/pages/apps/chat/ChatEndpointSetup";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ChatSetupSidebar } from "@/components/chat/ChatSetupNavigation";
|
||||
@@ -7,10 +8,11 @@ import { type ChatEndpoint } from "@/api/chatEndpoints";
|
||||
import { storybookAgents, storybookAuthSession } from "./paperclipData";
|
||||
import { defaultSlackAppConfiguration } from "@paperclipai/shared";
|
||||
|
||||
export type SlackSetupScenario = "choose" | "create" | "manual" | "install" | "declined" | "uncertain" | "manifest_pending" | "recovery" | "verify" | "avatar_failed" | "welcome_failed" | "success";
|
||||
export type SlackSetupScenario = "choose" | "create" | "manual" | "install" | "declined" | "approval_denied" | "uncertain" | "manifest_pending" | "recovery" | "verify" | "avatar_failed" | "welcome_failed" | "success";
|
||||
|
||||
/** Production wizard over a local provider fixture. No requests go to Slack. */
|
||||
export function SlackSetupFixture({ scenario = "create", managed = false, managedAvailable = true, workspaceCount = 1 }: { scenario?: SlackSetupScenario; managed?: boolean; managedAvailable?: boolean; workspaceCount?: number }) {
|
||||
const queryClient = useQueryClient();
|
||||
const [ready, setReady] = useState(false);
|
||||
const [signalVerification, setSignalVerification] = useState<() => void>(() => () => {});
|
||||
useLayoutEffect(() => {
|
||||
@@ -39,9 +41,10 @@ export function SlackSetupFixture({ scenario = "create", managed = false, manage
|
||||
endpoint.setup!.slackAccount = { externalUserId: "UPERSON", paperclipUserId: storybookAuthSession.user.id, status: "linked", welcomeStatus: scenario === "welcome_failed" ? "failed" : "sent", ...(scenario === "welcome_failed" ? {} : { dmChannelId: "DSTORY" }) };
|
||||
endpoint.status = "verifying"; endpoint.providerAccountId = "TSTORY"; endpoint.botExternalId = "USTORY"; endpoint.botUsername = "maya";
|
||||
};
|
||||
if (["install", "declined", "recovery", "manifest_pending"].includes(scenario)) created();
|
||||
if (["install", "declined", "approval_denied", "recovery", "manifest_pending"].includes(scenario)) created();
|
||||
if (scenario === "manifest_pending") endpoint.setup!.slackRegistration!.errorCode = "slack_manifest_update_pending";
|
||||
if (scenario === "declined") endpoint.setup!.slackRegistration!.errorCode = managed ? "slack_approval_pending" : "slack_install_declined";
|
||||
if (scenario === "approval_denied") endpoint.setup!.slackRegistration!.errorCode = "slack_approval_denied";
|
||||
if (scenario === "recovery") endpoint.setup!.slackRegistration = { ...endpoint.setup!.slackRegistration!, status: "credentials_saved", errorCode: "slack_configuration_incomplete" };
|
||||
if (scenario === "uncertain") endpoint.setup!.slackRegistration = { status: "uncertain", ...(managed ? { managerGrantId: grantId } : {}), errorCode: "slack_creation_uncertain", managementUrl: "https://api.slack.com/apps" };
|
||||
if (["verify", "avatar_failed", "welcome_failed", "success"].includes(scenario)) installed();
|
||||
@@ -57,6 +60,7 @@ export function SlackSetupFixture({ scenario = "create", managed = false, manage
|
||||
if (path === "/api/companies/company-storybook/chat-slack/setup-options") return Response.json({ managedAvailable: managed && managedAvailable, defaultMethod: managed && managedAvailable ? "managed" : "automatic", workspaces: managed && authorized ? Array.from({ length: Math.max(1, workspaceCount) }, (_, i) => ({ grantId: i ? "22222222-2222-4222-8222-222222222222" : grantId, workspaceId: `TSTORY${i}`, workspaceName: i ? "Research workspace" : "Paperclip", userId: "UPERSON" })) : [] });
|
||||
if (path === "/api/chat-endpoints/slack-story/slack/managed/authorize") {
|
||||
authorized = true;
|
||||
void queryClient.invalidateQueries({ queryKey: ["slack-managed-workspaces", endpoint.companyId] });
|
||||
return Response.json({ authorizationUrl: `${window.location.href.split("#")[0]}#simulated-manager-consent`, expiresAt: new Date(Date.now() + 600_000).toISOString() });
|
||||
}
|
||||
if (path === "/api/chat-endpoints/slack-story/slack/managed/provision") { installed(); return Response.json({}); }
|
||||
@@ -102,7 +106,7 @@ export function SlackSetupFixture({ scenario = "create", managed = false, manage
|
||||
};
|
||||
setReady(true);
|
||||
return () => { window.fetch = original; };
|
||||
}, [scenario, managed, managedAvailable, workspaceCount]);
|
||||
}, [scenario, managed, managedAvailable, workspaceCount, queryClient]);
|
||||
return <div className="space-y-6 p-6">
|
||||
<aside className="flex flex-wrap items-center gap-3 rounded-lg border border-border p-3 text-sm text-muted-foreground">
|
||||
Preview fixture: Slack consent, callbacks, and identity discovery are simulated.
|
||||
|
||||
@@ -22,6 +22,8 @@ export const MultipleWorkspaces: Story = { name: "02 · Add to Slack · Multiple
|
||||
export const Connect: Story = { name: "03 · Connect · Waiting for a message", args: { scenario: "verify" } };
|
||||
export const Success: Story = { name: "03 · Connect · Success", args: { scenario: "success" } };
|
||||
export const ApprovalPending: Story = { name: "Recovery · Slack approval pending", args: { scenario: "declined" } };
|
||||
export const ApprovalDeclined: Story = { name: "Recovery · Slack approval declined", args: { scenario: "approval_denied" } };
|
||||
export const EventConfigurationPending: Story = { name: "Recovery · App configuration pending", args: { scenario: "manifest_pending" } };
|
||||
export const UncertainCreation: Story = { name: "Recovery · Uncertain creation", args: { scenario: "uncertain" } };
|
||||
export const SavedCredentials: Story = { name: "Recovery · Saved credentials", args: { scenario: "recovery" } };
|
||||
export const Unavailable: Story = { name: "Recovery · Managed setup unavailable", args: { scenario: "install", managedAvailable: false } };
|
||||
|
||||
Reference in new issue
Block a user