mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-04 19:16:16 +01:00
Return client errors for known OAuth reconnect states (#13794)
Map missing OAuth refresh credentials and terminal reauthorization to HTTP 422. Preserve reconnect instructions and reporting of unexpected provider failures. All 363 focused tests and server typecheck pass. Required CI and review checks passed; Greptile 5/5 with no unresolved comments. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
1f3ff75d33
commit
c496acb570
3 files changed
+21
-2
No files matched your search
@@ -483,6 +483,10 @@ These fields contain build identifiers; they add no tenant or user identity.
|
||||
refreshing its catalog returns 422 with `oauth_challenge` and the existing
|
||||
setup/reconnect links. Other upstream failures still return 502 and are
|
||||
reported, including an unexplained upstream HTTP 400.
|
||||
- Expired OAuth credentials without a refresh token, or a rejected refresh token
|
||||
that requires reauthorization. Discovery and health checks return 422 with
|
||||
`oauth_refresh_missing` or `oauth_reauthorization_required` and the existing
|
||||
reconnect instructions. Unexpected refresh failures remain reportable.
|
||||
- Slack's explicit response that its app has not enabled MCP access. Discovery
|
||||
and health checks return 422 with `slack_mcp_access_disabled` and setup
|
||||
instructions. This requires Slack's exact MCP endpoint and known error;
|
||||
|
||||
@@ -11973,6 +11973,7 @@ describeEmbeddedPostgres("tool access service", () => {
|
||||
details: expect.objectContaining({
|
||||
code: "oauth_reauthorization_required",
|
||||
}),
|
||||
status: 422,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -12245,7 +12246,7 @@ describeEmbeddedPostgres("tool access service", () => {
|
||||
expect(JSON.stringify(updated.config)).not.toContain("m2m-access-token");
|
||||
});
|
||||
|
||||
it("fails expired OAuth credentials without a refresh token and returns reconnect links", async () => {
|
||||
it.each(["catalog", "catalog/refresh", "health-check"])("returns reconnect instructions on %s for expired OAuth without a refresh token", async (path) => {
|
||||
vi.stubEnv("PAPERCLIP_TOOL_OAUTH_SLACK_CLIENT_ID", "slack-client-id");
|
||||
vi.stubEnv(
|
||||
"PAPERCLIP_TOOL_OAUTH_SLACK_CLIENT_SECRET",
|
||||
@@ -12328,7 +12329,7 @@ describeEmbeddedPostgres("tool access service", () => {
|
||||
actorId: "board",
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
status: 502,
|
||||
status: 422,
|
||||
details: expect.objectContaining({
|
||||
code: "oauth_refresh_missing",
|
||||
setupUrl: `/apps/${connect.connectionId}/permissions`,
|
||||
@@ -12336,6 +12337,18 @@ describeEmbeddedPostgres("tool access service", () => {
|
||||
connection: expect.objectContaining({ healthStatus: "failed" }),
|
||||
}),
|
||||
});
|
||||
await db.delete(toolCatalogEntries).where(eq(toolCatalogEntries.connectionId, connect.connectionId));
|
||||
const capture = vi.spyOn(sentry, "captureException").mockImplementation(() => {});
|
||||
const app = createRouteApp(db, boardSessionActor(company.id, "owner", "board"));
|
||||
const url = `/api/tool-connections/${connect.connectionId}/${path}`;
|
||||
const response = await (path === "catalog" ? request(app).get(url) : request(app).post(url));
|
||||
expect(response.status).toBe(422);
|
||||
expect(response.body).toMatchObject({
|
||||
code: "oauth_refresh_missing",
|
||||
error: "OAuth credentials have expired and need to be reconnected.",
|
||||
details: { setupUrl: `/apps/${connect.connectionId}/permissions`, reconnectUrl: `/apps/${connect.connectionId}/permissions` },
|
||||
});
|
||||
expect(capture).not.toHaveBeenCalled();
|
||||
expect(fetchMock).not.toHaveBeenCalled();
|
||||
const auditRows = await db
|
||||
.select()
|
||||
|
||||
@@ -2805,6 +2805,8 @@ function healthFailureHttpStatus(failure: {
|
||||
}): number {
|
||||
if (failure.status === "missing_secret") return 422;
|
||||
if (failure.code === "oauth_challenge") return 422;
|
||||
if (failure.code === "oauth_refresh_missing") return 422;
|
||||
if (failure.code === "oauth_reauthorization_required") return 422;
|
||||
if (failure.code === "slack_mcp_access_disabled") return 422;
|
||||
if (failure.code === "user_authorization_required") return 422;
|
||||
if (failure.code === "composio_broker_retired") return 422;
|
||||
|
||||
Reference in new issue
Block a user