- 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)
50 lines
1.8 KiB
Go
50 lines
1.8 KiB
Go
package server
|
|
|
|
import "git.lohmar.co.uk/cclohmar/NextMyDay/internal/core/users"
|
|
|
|
// Tiers is the D-034 RoleGate: simple tiers, ONE role per user, and the
|
|
// assigned role RIPPLES DOWN (admin > director > manager > team) — a director
|
|
// may do everything a manager and a team member may. There are no tick-boxes
|
|
// and no ACL.
|
|
//
|
|
// A function carries a MINIMUM required role. With no explicit requirement the
|
|
// floor is team, so every authenticated user is admitted — the door still
|
|
// enforces session + identity before this gate runs (SPEC-003 §4 step 5d).
|
|
//
|
|
// The role-ABOVE rules that need the *other* party (creation authority,
|
|
// recovery approval) do not live here: they cannot be decided from
|
|
// (role, module, function) alone, so the users service enforces them
|
|
// (SPEC-007 AC-1/AC-2).
|
|
type Tiers struct {
|
|
required map[string]users.Role
|
|
floor users.Role
|
|
}
|
|
|
|
// NewTiers returns the tiered gate with the default floor (team).
|
|
func NewTiers() *Tiers {
|
|
return &Tiers{required: make(map[string]users.Role), floor: users.RoleTeam}
|
|
}
|
|
|
|
// Require records the minimum role a (module, function) needs and returns the
|
|
// receiver for chaining. An unknown (module, function) keeps the floor.
|
|
func (t *Tiers) Require(module, function string, min users.Role) *Tiers {
|
|
if t.required == nil {
|
|
t.required = make(map[string]users.Role)
|
|
}
|
|
t.required[gateKey(module, function)] = min
|
|
return t
|
|
}
|
|
|
|
// Allowed implements RoleGate. The ripple-down rule is simply rank >= minimum;
|
|
// an invalid role (rank 0) never passes — fail closed.
|
|
func (t *Tiers) Allowed(role users.Role, module, function string) bool {
|
|
min, ok := t.required[gateKey(module, function)]
|
|
if !ok {
|
|
min = t.floor
|
|
}
|
|
return role.Rank() >= min.Rank()
|
|
}
|
|
|
|
func gateKey(module, function string) string { return module + "/" + function }
|
|
|
|
var _ RoleGate = (*Tiers)(nil)
|