- store: SQLite (WAL, foreign keys ON, CGO-free driver) + the embedded migration runner; each module's migrations are name-prefixed (D-012) - audit: the append-only audit log; the actor threads via context (R-1) - users: the user_id/GUID/email anchors (D-013) - config: the YAML settings file (D-035) + validation - httpx: cookies · loopback detection · trusted-proxy XFF · CSRF · security headers · redaction - middleware: correlation id · audit · rate limiting - mail: the invite/recovery mailer seam - server: the core DOOR — the resolution switch, the dispatch registry (RegisterModule), the role gates, /setup · enroll/login · recovery flows, the reserved `static` workflow, and the loopback-only bootstrap (D-025)
66 lines
2.7 KiB
Go
66 lines
2.7 KiB
Go
package server
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"git.lohmar.co.uk/cclohmar/NextMyDay/internal/core/web"
|
|
)
|
|
|
|
// The core handlers below are the door's minimal landing surfaces. SPEC-003 §4
|
|
// says the door renders no dynamic HTML — it resolves, gates, and hands off;
|
|
// these are the hand-off targets for the routes SPEC-003 must classify, and the
|
|
// real screens arrive with SPEC-005 (setup), SPEC-006 (enroll/login) and
|
|
// SPEC-007 (access). SPEC-009 §4 renders them inside the shared shell.
|
|
|
|
// Home is the neutral landing page. It is PUBLIC: it takes no session and
|
|
// touches no database (SPEC-003 §9 AC-2). It is registered both as the core
|
|
// `home` workflow (level 1, /home) and as the `home` module's dashboard (level
|
|
// 2, /{GUID}).
|
|
func Home(w http.ResponseWriter, _ *http.Request, _ *RequestContext) error {
|
|
return web.Render(w, http.StatusOK, web.Page{
|
|
Title: "MyDay",
|
|
Guest: true,
|
|
Content: web.Markup(`<h1>MyDay</h1>
|
|
<p>Welcome. Sign in with your personal link.</p>`),
|
|
})
|
|
}
|
|
|
|
// LoginPlaceholder stands in for the login screen (SPEC-006). It answers 200:
|
|
// a rendered page is a successful response — only a failed credential attempt
|
|
// is 401/400 (F-1).
|
|
func LoginPlaceholder(w http.ResponseWriter, _ *http.Request, _ *RequestContext) error {
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
w.WriteHeader(http.StatusOK)
|
|
_, err := w.Write([]byte(`<!doctype html><html><body><h1>Sign in</h1>` +
|
|
`<p>This link requires a valid session.</p></body></html>`))
|
|
return err
|
|
}
|
|
|
|
// EnrollPlaceholder stands in for the enrollment screen (SPEC-006). The GUID is
|
|
// found but not yet enrolled, so no session is required to see it.
|
|
func EnrollPlaceholder(w http.ResponseWriter, _ *http.Request, _ *RequestContext) error {
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
w.WriteHeader(http.StatusOK)
|
|
_, err := w.Write([]byte(`<!doctype html><html><body><h1>Enrollment</h1>` +
|
|
`<p>Passkey and TOTP enrollment.</p></body></html>`))
|
|
return err
|
|
}
|
|
|
|
// SetupPlaceholder stands in for the /setup wizard (SPEC-005). The door only
|
|
// reaches it in setup mode.
|
|
func SetupPlaceholder(w http.ResponseWriter, _ *http.Request, _ *RequestContext) error {
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
w.WriteHeader(http.StatusOK)
|
|
_, err := w.Write([]byte(`<!doctype html><html><body><h1>Setup</h1>` +
|
|
`<p>Bootstrap wizard.</p></body></html>`))
|
|
return err
|
|
}
|
|
|
|
// AccessPlaceholder stands in for the recovery entry (SPEC-007).
|
|
func AccessPlaceholder(w http.ResponseWriter, _ *http.Request, _ *RequestContext) error {
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
w.WriteHeader(http.StatusOK)
|
|
_, err := w.Write([]byte(`<!doctype html><html><body><h1>Account access</h1>` +
|
|
`<p>Recovery.</p></body></html>`))
|
|
return err
|
|
}
|