- 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)
70 lines
2.4 KiB
Go
70 lines
2.4 KiB
Go
package server
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
|
|
"git.lohmar.co.uk/cclohmar/NextMyDay/internal/core/routing"
|
|
"git.lohmar.co.uk/cclohmar/NextMyDay/internal/core/users"
|
|
)
|
|
|
|
// RequestContext is the in-process hand-over from the door to a module function
|
|
// (SPEC-003 §5, D-003). It carries IDENTIFICATION, never a credential — the
|
|
// Principal and the session cookie are separate concerns (D-001).
|
|
//
|
|
// Field names match the GMS `X-*-*` headers so a future process split is
|
|
// mechanical (D-003).
|
|
type RequestContext struct {
|
|
// Workflow is the PARAM1 classification: a reserved workflow ("home",
|
|
// "access", …) or the resolved GUID.
|
|
Workflow string
|
|
// UserID is the resolved user (0 when anonymous).
|
|
UserID int64
|
|
// Principal is "user:<id>" — IDENTIFICATION only, never a credential.
|
|
Principal string
|
|
// Function is the resolved module function / action.
|
|
Function string
|
|
// Page is the joined path remainder handed to the function.
|
|
Page string
|
|
// Correlation is the request's correlation id.
|
|
Correlation string
|
|
// ClientIP is the resolved client IP.
|
|
ClientIP string
|
|
// Location is IP-derived and AUDIT ONLY (D-031) — no tracking.
|
|
Location string
|
|
}
|
|
|
|
// newRequestContext builds the hand-over from a resolution.
|
|
func newRequestContext(res routing.Resolution, userID int64, correlation, clientIP string) *RequestContext {
|
|
principal := "anonymous"
|
|
if userID != 0 {
|
|
principal = "user:" + strconv.FormatInt(userID, 10)
|
|
}
|
|
return &RequestContext{
|
|
Workflow: res.Workflow,
|
|
UserID: userID,
|
|
Principal: principal,
|
|
Function: res.Function,
|
|
Page: strings.Join(res.Params, "/"),
|
|
Correlation: correlation,
|
|
ClientIP: clientIP,
|
|
}
|
|
}
|
|
|
|
// RoleGate decides whether an authenticated user's role may call a given
|
|
// (module, function) (SPEC-003 §4 step 5d).
|
|
//
|
|
// SPEC-003 does not fix the policy — that is SPEC-007/SPEC-008. The door
|
|
// enforces the gate; the default implementation admits any authenticated user
|
|
// (the tier model ripples down, D-034).
|
|
type RoleGate interface {
|
|
Allowed(role users.Role, module, function string) bool
|
|
}
|
|
|
|
// AllowAuthenticated is the default RoleGate: every authenticated user may call
|
|
// every registered function. The one-admin and role-above rules are enforced by
|
|
// the users service; per-module authorisation arrives with SPEC-007/008.
|
|
type AllowAuthenticated struct{}
|
|
|
|
// Allowed implements RoleGate.
|
|
func (AllowAuthenticated) Allowed(users.Role, string, string) bool { return true }
|