From 8beb0e19a801141739bc034f96be6aa749d10649 Mon Sep 17 00:00:00 2001 From: StyleRef Date: Fri, 31 Jul 2026 10:57:10 +0200 Subject: [PATCH] fix(auth): offer a Sign in button instead of the raw API 401 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The style browser surfaced the REST 401 verbatim, which told people to send an `Authorization: Bearer` header and pointed at styleref.io/mcp — neither of which anyone can act on from inside ComfyUI. - A signed-out lane now renders one actionable row: a Sign in button running the same browser OAuth flow as the Login node, plus a link to the setup guide. Headless boxes still get the full instructions in the overlay, since they cannot just click. - /styleref/saved and /styleref/my-styles map an API 401 to `signedIn: false` rather than echoing the message. Expired or revoked stored credentials are the signed-out state, so the dialog should offer sign-in, not an error. Co-Authored-By: Claude Opus 5 --- server_routes.py | 14 ++++++++++++ web/styleref.js | 58 ++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/server_routes.py b/server_routes.py index 1cedb46..826674c 100644 --- a/server_routes.py +++ b/server_routes.py @@ -99,6 +99,13 @@ async def styleref_saved(request: web.Request) -> web.Response: offset=_int_param(request, "offset", 0), ) except StyleRefError as err: + # A 401 here means stored credentials that no longer work (expired, or + # revoked on styleref.io). That is the signed-out state as far as the UI + # is concerned, so report it as one and let the dialog offer Sign in — + # echoing the server's raw 401 told people to set an Authorization header, + # which is not something anyone can do from inside ComfyUI. + if err.needs_login: + return web.json_response({"styles": [], "signedIn": False}) return web.json_response({"styles": [], "signedIn": True, "error": err.message}) return web.json_response( @@ -124,6 +131,13 @@ async def styleref_my_styles(request: web.Request) -> web.Response: limit=_int_param(request, "limit", 8), cursor=cursor, query=query ) except StyleRefError as err: + # A 401 here means stored credentials that no longer work (expired, or + # revoked on styleref.io). That is the signed-out state as far as the UI + # is concerned, so report it as one and let the dialog offer Sign in — + # echoing the server's raw 401 told people to set an Authorization header, + # which is not something anyone can do from inside ComfyUI. + if err.needs_login: + return web.json_response({"styles": [], "signedIn": False}) return web.json_response({"styles": [], "signedIn": True, "error": err.message}) return web.json_response( diff --git a/web/styleref.js b/web/styleref.js index e7f3f59..7ed3003 100644 --- a/web/styleref.js +++ b/web/styleref.js @@ -211,6 +211,58 @@ function openPicker(initialQuery) { const message = (text) => el("div", "background:#26262a;border-radius:8px;padding:10px 12px;margin-bottom:6px;opacity:.75;font-size:13px;", text); + /** + * Signed-out state, as one actionable row rather than a wall of text. + * The old copy pasted the server's raw 401 — which told people to send an + * `Authorization: Bearer` header and linked the MCP install page, neither + * of which anyone can do from inside ComfyUI. Signing in is one click here. + */ + const signInPrompt = (out, what) => { + const box = el( + "div", + "background:#26262a;border-radius:8px;padding:12px;margin-bottom:6px;font-size:13px;", + ); + box.append( + el("div", "opacity:.8;margin-bottom:8px;", `Sign in to StyleRef to see ${what}.`), + ); + + const actions = el("div", "display:flex;gap:8px;align-items:center;"); + const signIn = el( + "button", + "background:#4f46e5;border:0;color:#fff;border-radius:6px;padding:6px 12px;cursor:pointer;font-size:13px;", + "Sign in", + ); + signIn.onclick = async () => { + signIn.disabled = true; + signIn.textContent = "Waiting for browser…"; + const res = await postJson("/styleref/auth/signin"); + if (res && res.ok === false && res.message) { + // Headless boxes get the full instructions — they can't just click. + showMessage(res.message); + signIn.disabled = false; + signIn.textContent = "Sign in"; + return; + } + // Both sections were rendered against the signed-out state. + renderMine(); + renderGallery(); + }; + actions.append(signIn); + + const help = el( + "a", + "color:#9b9bff;text-decoration:none;font-size:12px;opacity:.9;", + "Setup guide", + ); + help.href = "https://docs.styleref.io/connectors/comfyui/login-and-credits"; + help.target = "_blank"; + help.rel = "noopener"; + actions.append(help); + + box.append(actions); + out.append(box); + }; + function row(style) { const button = el( "button", @@ -350,7 +402,7 @@ function openPicker(initialQuery) { section(mineBox, "My styles", (out) => { if (mine.signedIn === false) { - out.append(message("Sign in (StyleRef Login node) to see your styles here.")); + signInPrompt(out, "your style library"); return; } if (mine.error) { @@ -412,9 +464,7 @@ function openPicker(initialQuery) { section(galleryBox, title, (out) => { if (gallery.signedIn === false) { - out.append( - message("Sign in (StyleRef Login node) to see the styles you saved."), - ); + signInPrompt(out, "the styles you saved"); return; } if (gallery.error) {