authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-05 11:13:07-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-05 15:36:02-07:00
log3873b6a5bbe78baba32102186b613d984331144c
treec05d4c7ab3d2b29fdc0bb816b0db06cc695edf08
parent036a0e63f8c0eaa73960243300cfd238c1553210
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

Hide guest providers on the password step and add back navigation

Show the selected account as a large blue heading. Capture only Astheno numeric error identifiers when token exchange is rejected. Assisted-by: gpt-6

3 files changed, 33 insertions(+), 12 deletions(-)

dashboard/src/guest.rs+27-7
...@@ -96,13 +96,13 @@ fn registration(auth: &auth::Store, provider: &str) -> Result<(String, String)>...@@ -96,13 +96,13 @@ fn registration(auth: &auth::Store, provider: &str) -> Result<(String, String)>
96}96}
9797
98async fn response_bytes(mut response: reqwest::Response) -> Result<Vec<u8>> {98async fn response_bytes(mut response: reqwest::Response) -> Result<Vec<u8>> {
99 if !response.status().is_success() || response.content_length().is_some_and(|n| n > 65536) {99 let status = response.status();
100 eprintln!(100 let endpoint = format!(
101 "guest provider response: {} {} status {}",101 "{}{}",
102 response.url().host_str().unwrap_or_default(),102 response.url().host_str().unwrap_or_default(),
103 response.url().path(),103 response.url().path()
104 response.status()104 );
105 );105 if response.content_length().is_some_and(|n| n > 65536) {
106 return Err(Error::new(106 return Err(Error::new(
107 502,107 502,
108 "The provider couldn't complete sign-in. Return to Shale and try again.",108 "The provider couldn't complete sign-in. Return to Shale and try again.",
...@@ -118,6 +118,26 @@ async fn response_bytes(mut response: reqwest::Response) -> Result<Vec<u8>> {...@@ -118,6 +118,26 @@ async fn response_bytes(mut response: reqwest::Response) -> Result<Vec<u8>> {
118 }118 }
119 data.extend_from_slice(&chunk);119 data.extend_from_slice(&chunk);
120 }120 }
121 if !status.is_success() {
122 let error: Value = serde_json::from_slice(&data).unwrap_or(Value::Null);
123 let code = string(&error["error_description"])
124 .split(':')
125 .next()
126 .unwrap_or_default();
127 let code = if code.starts_with('E')
128 && code.len() <= 8
129 && code[1..].bytes().all(|c| c.is_ascii_digit())
130 {
131 code
132 } else {
133 "unknown"
134 };
135 eprintln!("guest provider response: {endpoint} status {status} code {code}");
136 return Err(Error::new(
137 502,
138 "The provider couldn't complete sign-in. Return to Shale and try again.",
139 ));
140 }
121 Ok(data)141 Ok(data)
122}142}
123143
dashboard/web/pages/SignIn.css+3-3
...@@ -6,9 +6,9 @@ body { font: 16px "Name Sans", sans-serif; }...@@ -6,9 +6,9 @@ body { font: 16px "Name Sans", sans-serif; }
6.pf-v5-c-login__main [aria-disabled="true"], .pf-v5-c-login__main :disabled { opacity: .6; cursor: wait; }6.pf-v5-c-login__main [aria-disabled="true"], .pf-v5-c-login__main :disabled { opacity: .6; cursor: wait; }
7.setup-intro { margin-bottom: 1rem; text-align: center; }7.setup-intro { margin-bottom: 1rem; text-align: center; }
8.guest-providers { margin-bottom: 1.5rem; }8.guest-providers { margin-bottom: 1.5rem; }
9.sign-in-account { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; }9.sign-in-account { margin-bottom: 1.25rem; }
10.sign-in-account span { overflow-wrap: anywhere; }10.sign-in-account h2 { margin: 0; color: var(--primary); font-size: 28px; line-height: 1.2; font-weight: 500; text-align: center; text-transform: none; overflow-wrap: anywhere; }
11.sign-in-account button { background: none; border: 0; padding: 0; color: var(--text); text-decoration: underline; cursor: pointer; }11.sign-in-account button { display: inline-flex; align-items: center; gap: .375rem; margin-bottom: .75rem; background: none; border: 0; padding: 0; color: var(--primary); cursor: pointer; }
12.pf-v5-c-login__main .guest-providers a {12.pf-v5-c-login__main .guest-providers a {
13 display: grid;13 display: grid;
14 grid-template-columns: 20px minmax(0, 1fr) 20px;14 grid-template-columns: 20px minmax(0, 1fr) 20px;
dashboard/web/pages/SignIn.tsx+3-2
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1import { createEffect, createResource, createSignal, For, onCleanup, onMount, Show } from "solid-js";1import { createEffect, createResource, createSignal, For, onCleanup, onMount, Show } from "solid-js";
2import { authReason, authRequest } from "../auth.ts";2import { authReason, authRequest } from "../auth.ts";
3import ArrowLeft from "lucide-solid/icons/arrow-left";
3import Stone from "lucide-solid/icons/stone";4import Stone from "lucide-solid/icons/stone";
4import githubIcon from "../sso/github.svg";5import githubIcon from "../sso/github.svg";
5import theme from "../sso/css/styles.css?inline";6import theme from "../sso/css/styles.css?inline";
...@@ -86,7 +87,7 @@ export function SignIn() {...@@ -86,7 +87,7 @@ export function SignIn() {
86 <Show when={status.error || error()}><div class="alert-error pf-v5-c-alert" role="alert"><span class="pf-v5-c-alert__title">{status.error ? authReason(status.error) : error()}</span></div></Show>87 <Show when={status.error || error()}><div class="alert-error pf-v5-c-alert" role="alert"><span class="pf-v5-c-alert__title">{status.error ? authReason(status.error) : error()}</span></div></Show>
87 <Show when={notice()}><div class="alert-info pf-v5-c-alert" role="status"><span class="pf-v5-c-alert__title">{notice()}</span></div></Show>88 <Show when={notice()}><div class="alert-info pf-v5-c-alert" role="status"><span class="pf-v5-c-alert__title">{notice()}</span></div></Show>
88 <Show when={!status.error} fallback={<button class="pf-v5-c-button pf-m-primary pf-m-block" onClick={() => refetch()}>try again</button>}>89 <Show when={!status.error} fallback={<button class="pf-v5-c-button pf-m-primary pf-m-block" onClick={() => refetch()}>try again</button>}>
89 <Show when={!setup && status()?.providers?.length}>90 <Show when={!setup && !passwordStep() && status()?.providers?.length}>
90 <div id="kc-social-providers" class="guest-providers">91 <div id="kc-social-providers" class="guest-providers">
91 <For each={status()?.providers}>{(provider) => <a rel="external" data-provider={provider.id} class="pf-v5-c-button pf-m-primary pf-m-block" href={`/auth/guest/start/${provider.id}?next=${encodeURIComponent(params.get("next") ?? "")}`}>{provider.id === "astheno" ? <Stone size={18} aria-hidden="true" /> : <img src={githubIcon} width="18" height="18" alt="" />}<span>continue with {provider.name}</span></a>}</For>92 <For each={status()?.providers}>{(provider) => <a rel="external" data-provider={provider.id} class="pf-v5-c-button pf-m-primary pf-m-block" href={`/auth/guest/start/${provider.id}?next=${encodeURIComponent(params.get("next") ?? "")}`}>{provider.id === "astheno" ? <Stone size={18} aria-hidden="true" /> : <img src={githubIcon} width="18" height="18" alt="" />}<span>continue with {provider.name}</span></a>}</For>
92 </div>93 </div>
...@@ -94,7 +95,7 @@ export function SignIn() {...@@ -94,7 +95,7 @@ export function SignIn() {
94 <div id="kc-form"><div id="kc-form-wrapper">95 <div id="kc-form"><div id="kc-form-wrapper">
95 <form id="kc-form-login" onSubmit={(event) => { event.preventDefault(); void complete(); }}>96 <form id="kc-form-login" onSubmit={(event) => { event.preventDefault(); void complete(); }}>
96 <Show when={setup} fallback={97 <Show when={setup} fallback={
97 <Show when={!passwordStep()} fallback={<div class="sign-in-account"><span>{username()}</span><button type="button" onClick={() => { setPassword(""); setVisible(false); setError(""); setNotice(""); setPasswordStep(false); }}>change</button><input name="username" type="hidden" autocomplete="username" value={username()} /></div>}>98 <Show when={!passwordStep()} fallback={<div class="sign-in-account"><button type="button" onClick={() => { setPassword(""); setVisible(false); setError(""); setNotice(""); setPasswordStep(false); }}><ArrowLeft size={18} aria-hidden="true" />back</button><h2>{username()}</h2><input name="username" type="hidden" autocomplete="username" value={username()} /></div>}>
98 <div class="pf-v5-c-form__group">99 <div class="pf-v5-c-form__group">
99 <label for="username" class="pf-v5-c-form__label">username or email</label>100 <label for="username" class="pf-v5-c-form__label">username or email</label>
100 <input id="username" class="pf-v5-c-form-control" name="username" type="text" required autocomplete="username webauthn" value={username()} onInput={(event) => setUsername(event.currentTarget.value)} autofocus ref={(input) => queueMicrotask(() => input.focus())} />101 <input id="username" class="pf-v5-c-form-control" name="username" type="text" required autocomplete="username webauthn" value={username()} onInput={(event) => setUsername(event.currentTarget.value)} autofocus ref={(input) => queueMicrotask(() => input.focus())} />