package web // The trusted setting `operators` (novox/hq ADR 0440 ยง3, settlement 5 of design 59 milestone 1's prepare step): // the Keycloak identities (the ID token's preferred_username) let in. Nobody by default; a setting that cannot // be read, is empty, or holds something not its own shape lets nobody in either, and says why (issue 1262/AC4). import ( "encoding/json" "errors" "fmt" "os" "strings" ) // Env is where the mesh puts what the module reads, as the manifest's build env names it. type Env struct { Operators string // the trusted setting operators OIDC string // the oidc-client binding, rendered (issue 1262's oidc.json) Listen string // the port the mesh gave endpoint `web` (MESH_MAIL_WEB_LISTEN) Node string // this machine's own name (MESH_NODE), for the internal host mail..internal Modes string // the trusted setting modes (MESH_MAIL_WEB_MODES, issue 1859) } func envFromOS() Env { return Env{ Operators: os.Getenv("MESH_MAIL_WEB_OPERATORS"), OIDC: os.Getenv("MESH_MAIL_WEB_OIDC"), Listen: os.Getenv("MESH_MAIL_WEB_LISTEN"), Node: os.Getenv("MESH_NODE"), Modes: os.Getenv("MESH_MAIL_WEB_MODES"), } } // OperatorsFile is the shape of the setting as the mesh writes it. type OperatorsFile struct { Operators []string `json:"operators"` } // ReadOperators reads the operators setting (issue 1262/AC4): a setting missing, empty, or holding an unknown // field lets nobody in, and the reason is kept (never silently "everyone", never a panic). func ReadOperators(path string) ([]string, error) { if path == "" { return nil, errors.New("the module was started without its operators setting (MESH_MAIL_WEB_OPERATORS)") } raw, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("the operators setting cannot be read: %w", err) } dec := json.NewDecoder(strings.NewReader(string(raw))) dec.DisallowUnknownFields() var f OperatorsFile if err := dec.Decode(&f); err != nil { return nil, fmt.Errorf("the operators setting is not the module's: %w", err) } return f.Operators, nil } // Operators is the setting read fresh each time it is asked (never cached): it who may sign in, so a setting // that cannot be read grants nobody rather than whoever it last allowed. type Operators struct { Path string } // Allows says whether identity (the ID token's preferred_username) is let in, and why not when it is refused. func (o Operators) Allows(identity string) (bool, string) { if identity == "" { return false, "no identity was given" } names, err := ReadOperators(o.Path) if err != nil { return false, err.Error() } for _, n := range names { if n == identity { return true, "" } } return false, fmt.Sprintf("%q is not in the setting operators", identity) }