- 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)
52 lines
1.9 KiB
Go
52 lines
1.9 KiB
Go
package server
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"git.lohmar.co.uk/cclohmar/NextMyDay/internal/core/recovery"
|
|
)
|
|
|
|
// recoveryModule is the `do` module carrying the SPEC-007 approve/deny actions.
|
|
// It rides a GUID path, so the door session-gates it (unlike the anonymous
|
|
// `auth` recover action).
|
|
const recoveryModule = "recovery"
|
|
|
|
// RegisterRecoveryFlow registers SPEC-007's surface:
|
|
//
|
|
// - the core `access` workflow (GET /access — the anonymous recovery entry),
|
|
// - the `auth` module's `recover` action (POST /access?do=auth&action=recover,
|
|
// anonymous, A15), and
|
|
// - the session-gated `recovery` approve/deny actions
|
|
// (POST /{GUID}?do=recovery&action=approve|deny).
|
|
func RegisterRecoveryFlow(reg *MapRegistry, a *recovery.Actions) {
|
|
reg.RegisterCore("access", adaptRecovery(a.Page))
|
|
|
|
// The recovery request rides the `auth` action module, exactly as SPEC-007
|
|
// §4 spells it: POST /access?do=auth&action=recover. The module may already
|
|
// exist (SPEC-006's enroll/login); extend it rather than replace it.
|
|
auth := reg.module(authModule)
|
|
if auth == nil {
|
|
auth = NewModule(authModule)
|
|
reg.RegisterModule(auth)
|
|
}
|
|
auth.AddAction("recover", "recover.request", adaptRecovery(a.Request))
|
|
|
|
reg.RegisterModule(NewModule(recoveryModule).
|
|
AddAction("approve", "approve", adaptRecoveryActor(a.Approve)).
|
|
AddAction("deny", "deny", adaptRecoveryActor(a.Deny)))
|
|
}
|
|
|
|
// adaptRecovery adapts an anonymous recovery handler to the door's Handler.
|
|
func adaptRecovery(f func(http.ResponseWriter, *http.Request) error) Handler {
|
|
return func(w http.ResponseWriter, r *http.Request, _ *RequestContext) error {
|
|
return f(w, r)
|
|
}
|
|
}
|
|
|
|
// adaptRecoveryActor adapts a session-gated decision handler: the door has
|
|
// authenticated the actor and put its id on the RequestContext.
|
|
func adaptRecoveryActor(f func(http.ResponseWriter, *http.Request, int64) error) Handler {
|
|
return func(w http.ResponseWriter, r *http.Request, rc *RequestContext) error {
|
|
return f(w, r, rc.UserID)
|
|
}
|
|
}
|