mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-08 01:57:31 +01:00
fix: keep callback handoff navigation single use
Include rendered screenshot evidence for the callback page layouts. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
03c5f8d452
commit
28c278d769
5 files changed
+13
-4
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
@@ -8,8 +8,8 @@ export function escapeCallbackHtml(value: string): string {
|
||||
export function oauthCallbackPage(input: {
|
||||
title: string;
|
||||
description: string;
|
||||
actionHref: string;
|
||||
actionLabel: string;
|
||||
actionHref?: string;
|
||||
actionLabel?: string;
|
||||
state?: "success" | "error" | "pending";
|
||||
head?: string;
|
||||
script?: string;
|
||||
@@ -19,7 +19,7 @@ export function oauthCallbackPage(input: {
|
||||
:root{color-scheme:light dark;--bg:#faf9f6;--card:#fff;--text:#242320;--muted:#68665f;--line:#e5e3dc;--accent:#345d45}
|
||||
@media(prefers-color-scheme:dark){:root{--bg:#191a18;--card:#22231f;--text:#f2f1eb;--muted:#b1b2a8;--line:#3b3d35;--accent:#a5d3b4}}
|
||||
*{box-sizing:border-box}body{margin:0;min-height:100svh;display:grid;place-items:center;padding:24px;background:var(--bg);color:var(--text);font:16px/1.6 system-ui,sans-serif}main{width:100%;max-width:460px;padding:36px;background:var(--card);border:1px solid var(--line);border-radius:16px}.brand{font-weight:650;letter-spacing:-.03em;margin-bottom:36px}.mark{display:inline-block;margin-right:8px;color:var(--accent)}.status{display:grid;place-items:center;width:44px;height:44px;border:1px solid var(--line);border-radius:50%;font-size:24px;color:var(--accent)}h1{font-size:26px;line-height:1.25;letter-spacing:-.025em;margin:20px 0 12px}p{color:var(--muted);margin:0 0 28px}a{display:inline-block;background:var(--text);color:var(--card);padding:10px 18px;border-radius:8px;text-decoration:none;font-weight:600}a:focus-visible{outline:3px solid var(--accent);outline-offset:4px}.note{font-size:13px;margin:24px 0 0}@media(max-width:480px){main{padding:24px}}
|
||||
</style></head><body><main><div class="brand"><span class="mark" aria-hidden="true">↗</span>Paperclip</div><div class="status" aria-hidden="true">${input.state === "success" ? "✓" : input.state === "error" ? "!" : "…"}</div><h1>${escape(input.title)}</h1><p>${escape(input.description)}</p><a href="${escape(input.actionHref)}">${escape(input.actionLabel)}</a><p class="note">You can close this window and return to Paperclip.</p></main>${input.script ? `<script>${input.script}</script>` : ""}</body></html>`;
|
||||
</style></head><body><main><div class="brand"><span class="mark" aria-hidden="true">↗</span>Paperclip</div><div class="status" aria-hidden="true">${input.state === "success" ? "✓" : input.state === "error" ? "!" : "…"}</div><h1>${escape(input.title)}</h1><p>${escape(input.description)}</p>${input.actionHref && input.actionLabel ? `<a href="${escape(input.actionHref)}">${escape(input.actionLabel)}</a>` : ""}<p class="note">You can close this window and return to Paperclip.</p></main>${input.script ? `<script>${input.script}</script>` : ""}</body></html>`;
|
||||
}
|
||||
|
||||
const callbackPaths = new Set([
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
cloudConnectorEnrollmentReturnPath,
|
||||
cloudConnectorEnrollmentOutcomeHtml,
|
||||
connectionIntentOAuthOutcomeHtml,
|
||||
oauthCallbackInterstitialHtml,
|
||||
} from "./tool-access.js";
|
||||
|
||||
describe("Cloud connector enrollment return path", () => {
|
||||
@@ -117,3 +118,10 @@ describe("inline enrollment completion", () => {
|
||||
expect(html).not.toContain("/apps/connect");
|
||||
});
|
||||
});
|
||||
|
||||
it("offers only one navigation for the single-use callback handoff", () => {
|
||||
const html = oauthCallbackInterstitialHtml("/api/tools/oauth/callback?state=one&code=two");
|
||||
expect(html.match(/http-equiv="refresh"/g)).toHaveLength(1);
|
||||
expect(html).not.toContain("<a ");
|
||||
expect(html).not.toContain("<script>");
|
||||
});
|
||||
@@ -224,7 +224,8 @@ export function oauthCallbackInterstitialHtml(continuePath: string): string {
|
||||
// two racing follow-ups would let the loser render an expired-state error.
|
||||
return oauthCallbackPage({
|
||||
title: "Finishing connection", description: "Please wait while Paperclip finishes connecting your app.",
|
||||
actionHref: continuePath, actionLabel: "Continue to Paperclip", state: "pending",
|
||||
// No link: a second navigation can race the single-use automatic refresh.
|
||||
state: "pending",
|
||||
head: `<meta http-equiv="refresh" content="0;url=${attribute}">`,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user