internal/server/api/principal.go
Ref: Size: 2.3 KiB History
package api
import (
"context"
"net/http"
"github.com/a73x/eitri/internal/server/store"
)
// Principal is the authenticated actor attached to every user-API request by
// userAuth. It is the F3 seam: userAuth resolves a PAT or session cookie to the
// credential's tenant; handlers consume the Principal without caring how it was
// resolved. Every credential is scoped to exactly one tenant — there is no
// fleet-wide principal and no fleet-wide user-facing operation (spec §3/§4).
type Principal struct {
// Tenant is the tenant scope this principal acts within.
Tenant string
}
// principalKey is the context key for the request principal.
type principalKey struct{}
// withPrincipal returns a request context carrying p.
func withPrincipal(ctx context.Context, p Principal) context.Context {
return context.WithValue(ctx, principalKey{}, p)
}
// principalFromContext returns the request's principal. A request that never
// passed userAuth yields the zero Principal — no tenant — so every check
// downstream fails closed.
func principalFromContext(r *http.Request) Principal {
p, _ := r.Context().Value(principalKey{}).(Principal)
return p
}
// TenantFromContext returns the tenant of the request's authenticated
// principal, or "" if the request never passed UserAuth. It is how a handler
// mounted outside the /api/v1 subtree — /mcp — reads the identity the auth
// middleware resolved.
func TenantFromContext(ctx context.Context) string {
p, _ := ctx.Value(principalKey{}).(Principal)
return p.Tenant
}
// mayActAs reports whether p may act on a resource owned by tenant. Strict
// scope-equality; an empty tenant on either side never matches (resources always
// have a tenant; a zero principal must not pair with a malformed resource via
// "" == "").
func mayActAs(p Principal, tenant string) bool {
return tenant != "" && p.Tenant == tenant
}
func filterVMs(p Principal, vms []store.VM) []store.VM {
out := make([]store.VM, 0, len(vms))
for _, vm := range vms {
if mayActAs(p, vm.Tenant) {
out = append(out, vm)
}
}
return out
}
// filterHosts is filterVMs for hosts.
func filterHosts(p Principal, hosts []store.Host) []store.Host {
out := make([]store.Host, 0, len(hosts))
for _, h := range hosts {
if mayActAs(p, h.Tenant) {
out = append(out, h)
}
}
return out
}