fix(auth): offer a Sign in button instead of the raw API 401

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 <noreply@anthropic.com>
This commit is contained in:
StyleRef
2026-07-31 10:57:10 +02:00
co-authored by Claude Opus 5
parent cec7f9f9cd
commit 8beb0e19a8
2 changed files with 68 additions and 4 deletions
+14
View File
@@ -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(
+54 -4
View File
@@ -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) {