package setup import ( "html" "net/http" "net/url" "git.lohmar.co.uk/cclohmar/NextMyDay/internal/core/web" ) // renderHTML writes the wizard page inside the core shell (SPEC-009 §4). The // door renders no dynamic HTML itself (SPEC-003 §4); the wizard is the hand-off // target for the `setup` workflow. // // F-2: the bootstrap renders secrets and ceremony state — never cache it. func renderHTML(w http.ResponseWriter, status int, page web.Page) error { w.Header().Set("Cache-Control", "no-store") return web.Render(w, status, page) } // writeError writes a terse plain-text error. func writeError(w http.ResponseWriter, status int, msg string) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Header().Set("X-Content-Type-Options", "nosniff") w.WriteHeader(status) _, _ = w.Write([]byte(msg + "\n")) } // redirect sends the operator back to the wizard (post/redirect/get). func redirect(w http.ResponseWriter, r *http.Request, path string) { http.Redirect(w, r, path, http.StatusSeeOther) } // wizardPage renders the current step inside the shell. The wizard runs before // any identity exists, so the page is a GUEST screen: no nav (SPEC-009 §4). // Every form carries the signed pre-session CSRF token (A15). func wizardPage(st step, token, domain string) web.Page { c := html.EscapeString(token) return web.Page{ Title: "MyDay setup", Guest: true, CSRF: token, Content: web.Markup(`
Bring this instance to life: the domain, the top admin, then mail and locale.
` + stepBody(st, c, domain)), } } // totpPage renders the wizard's TOTP step (SPEC-005 §4) once the secret has // been issued: the otpauth URL, its human-readable secret for manual entry, and // the verify form. The wizard is server-rendered and works with no JavaScript, // so the secret is shown as text rather than encoded in a QR image; the page // renders inside the shell (no inline scripts) and the verify form carries the // signed pre-session CSRF token (A15). func totpPage(token, otpauthURL string) web.Page { c := html.EscapeString(token) u := html.EscapeString(otpauthURL) secret := html.EscapeString(secretFromOtpauth(otpauthURL)) return web.Page{ Title: "MyDay setup", Guest: true, CSRF: token, Content: web.Markup(`Add the authenticator, then enter a code from it to finish.
` + `A passkey can be added later, at first sign-in.
` + `Open this link in your authenticator app:
` + `` + `Or enter this secret by hand:
` + `` + secret + `
This instance is waiting for its first-run setup. Open the setup wizard with the one-time setup code that MyDay printed to its log when it started.
It is in MyDay's startup log:
journalctl -u mydaypct exec 11020 -- journalctl -u mydayLook for the line setup code.
The setup wizard is enabled on this instance.
`), } } // lockedPage is what `GET /` and `GET /setup` show when `setup: false` and no // admin exists yet (D-045): the instance is deliberately locked, not claimable. // It is a GUEST screen with no form. func lockedPage() web.Page { return web.Page{ Title: "MyDay is not set up", Guest: true, Content: web.Markup(`This instance has no administrator yet, and setup is disabled.
To run the first-run setup, set setup: true in config.yaml and
restart MyDay. Then open /setup.
Add an authenticator app. A passkey can be added later, at first sign-in.
` + `