NextMyDay/internal/core/server/requestcontext.go
Claus Lohmar d11e14295b feat(core): add the store, identity plumbing and the door (SPEC-003/004)
- 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)
2026-10-14 10:53:51 +00:00

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 }