diff --git a/crates/httpd/src/auth_middleware.rs b/crates/httpd/src/auth_middleware.rs index f4d10f9c5..2539eff68 100644 --- a/crates/httpd/src/auth_middleware.rs +++ b/crates/httpd/src/auth_middleware.rs @@ -155,10 +155,16 @@ pub async fn auth_gate( next.run(request).await } else { // Remote connections to other pages when auth is not - // configured yet: redirect to a static "setup required" - // page instead of passing through, which would cause a - // redirect loop between `/` and `/onboarding` (#350). - Redirect::to("/setup-required").into_response() + // configured yet: send them to /onboarding so they can + // complete first-time setup via the setup-code flow + // (#350, #646). The original redirect loop between `/` + // and `/onboarding` was fixed separately at the SPA + // template layer via `should_redirect_from_onboarding`, + // which keeps remote visitors on /onboarding while auth + // setup is pending. The setup code (printed to stdout) + // still prevents an unauthorized remote visitor from + // claiming the instance. + Redirect::to("/onboarding").into_response() } }, AuthResult::Unauthorized => { diff --git a/crates/httpd/tests/auth_middleware.rs b/crates/httpd/tests/auth_middleware.rs index f55f3f6c8..74eb3599f 100644 --- a/crates/httpd/tests/auth_middleware.rs +++ b/crates/httpd/tests/auth_middleware.rs @@ -1360,10 +1360,11 @@ async fn onboarding_passes_through_for_remote_during_setup() { } /// During setup (no password), a remote connection to / is redirected to -/// /setup-required (same as /onboarding). +/// /onboarding so the user can enter the setup code and complete first- +/// time setup via the wizard's AuthStep (#646). #[cfg(feature = "web-ui")] #[tokio::test] -async fn root_redirects_to_setup_required_for_remote() { +async fn root_redirects_to_onboarding_for_remote() { let (addr, _store, _state) = start_proxied_server().await; let client = reqwest::Client::builder() @@ -1383,13 +1384,15 @@ async fn root_redirects_to_setup_required_for_remote() { .and_then(|v| v.to_str().ok()) .unwrap_or(""); assert_eq!( - location, "/setup-required", - "remote / during setup must redirect to /setup-required" + location, "/onboarding", + "remote / during setup must redirect to /onboarding" ); } -/// /setup-required is a public path and serves content even for remote -/// connections during setup (no redirect loop). +/// /setup-required is still served as a public stale-bookmark fallback +/// even for remote connections during setup. It is no longer the default +/// redirect target, but direct navigation must still work and must not +/// redirect-loop. #[cfg(feature = "web-ui")] #[tokio::test] async fn setup_required_page_accessible_for_remote() { @@ -1414,8 +1417,12 @@ async fn setup_required_page_accessible_for_remote() { ); let body = resp.text().await.unwrap(); assert!( - body.contains("Authentication Not Configured"), - "/setup-required should contain the setup heading" + body.contains("First-time setup"), + "/setup-required should contain the new setup heading" + ); + assert!( + body.contains("href=\"/onboarding\""), + "/setup-required should link to /onboarding" ); } diff --git a/crates/web/src/templates.rs b/crates/web/src/templates.rs index 3cf10df1f..5a5fd15b5 100644 --- a/crates/web/src/templates.rs +++ b/crates/web/src/templates.rs @@ -844,12 +844,16 @@ mod tests { "should produce a full HTML document" ); assert!( - html.contains("Authentication Not Configured"), - "should contain the setup-required heading" + html.contains("First-time setup"), + "should contain the new setup heading" ); assert!( - html.contains("moltis auth reset-password"), - "should contain the CLI reset command" + html.contains("setup code"), + "should mention the one-time setup code" + ); + assert!( + html.contains("href=\"/onboarding\""), + "should link to the onboarding wizard" ); assert!( html.contains("/assets/v/test123/"), diff --git a/crates/web/src/templates/setup-required.html b/crates/web/src/templates/setup-required.html index 3c50df25c..b7358bd6c 100644 --- a/crates/web/src/templates/setup-required.html +++ b/crates/web/src/templates/setup-required.html @@ -12,18 +12,22 @@
-

Authentication Not Configured

-

This instance requires authentication to be set up before it can be accessed remotely.

-

Connect from the local machine or use the CLI:

-

moltis auth reset-password

+

First-time setup

+

This instance has not been configured yet. To finish setup remotely, use the one-time setup code printed to the server's standard output when the process started.

+

For a docker-compose deployment, find the code with:

+

docker compose logs moltis

+ Continue setup → +

Locked out of an existing instance with filesystem access? The moltis auth reset-password CLI command can recover it — do not use it on a fresh install.

diff --git a/crates/web/ui/e2e/specs/onboarding-auth.spec.js b/crates/web/ui/e2e/specs/onboarding-auth.spec.js index ffa75190d..258368a21 100644 --- a/crates/web/ui/e2e/specs/onboarding-auth.spec.js +++ b/crates/web/ui/e2e/specs/onboarding-auth.spec.js @@ -127,12 +127,11 @@ test.describe("Onboarding with forced auth (remote)", () => { test("completes auth and identity steps via WebSocket", async ({ page }) => { const pageErrors = watchPageErrors(page); - // Fresh runs should land on /onboarding (remote setup allows the - // onboarding page through for the setup-code auth flow). Retries - // can land on /login if a previous attempt already configured auth. - // Navigate directly to /onboarding since / redirects to - // /setup-required for remote connections during setup (#350). - await page.goto("/onboarding"); + // Fresh runs visiting `/` on a remote (proxied) connection should + // be redirected to /onboarding so the setup-code AuthStep is + // shown (#350, #646). Retries can land on /login if a previous + // attempt already configured auth. + await page.goto("/"); await expect .poll(() => new URL(page.url()).pathname, { timeout: 15_000 }) .toMatch(/^\/(?:onboarding|login|chats\/.+)$/);