package security import "context" // Identity represents the authenticated principal for a request. // // Identity is a value type — always copied, never a pointer — to prevent // nil-check burden and accidental mutation of a shared context value. // Construction follows a two-step pattern: NewIdentity populates authentication // data from the token (uid, name, email); WithTenant optionally enriches with a // tenant ID in a later middleware step, returning a new value without mutating // the original. type Identity struct { UID string TenantID string DisplayName string Email string } // authContextKey is the unexported context key used to store Identity values. // Using a private type prevents collisions with keys from other packages. type authContextKey struct{} var authKey = authContextKey{} // NewIdentity creates an Identity from token authentication data. // TenantID is left empty — populate it later with WithTenant once the enrichment // middleware has resolved it from the request context. func NewIdentity(uid, displayName, email string) Identity { return Identity{ UID: uid, DisplayName: displayName, Email: email, } } // WithTenant returns a copy of the Identity with TenantID set to id. // The receiver is not mutated — safe to call from concurrent middleware. func (i Identity) WithTenant(id string) Identity { i.TenantID = id return i } // SetInContext stores id in ctx as a [SecurityBag] and returns the enriched context. // Callers that need to attach additional request-level attributes should use // [SetBagInContext] directly. [FromContext] continues to work unchanged. func SetInContext(ctx context.Context, id Identity) context.Context { return SetBagInContext(ctx, NewSecurityBag(id)) } // FromContext retrieves the Identity stored by [SetInContext] or [SetBagInContext]. // Returns the zero-value Identity and false if no identity is present in ctx. func FromContext(ctx context.Context) (Identity, bool) { bag, ok := BagFromContext(ctx) if !ok { return Identity{}, false } return bag.Identity(), true } // SetBagInContext stores bag in ctx and returns the enriched context. // Use this when you need to attach request-level attributes beyond the Identity // (hardware IDs, grant codes, etc.) via [SecurityBag.With]. func SetBagInContext(ctx context.Context, bag SecurityBag) context.Context { return context.WithValue(ctx, authKey, bag) } // BagFromContext retrieves the [SecurityBag] stored by [SetBagInContext] or [SetInContext]. // Returns an empty SecurityBag and false if no bag is present in ctx. // Permission providers use this to access both the Identity and any extra attributes // injected during enrichment. func BagFromContext(ctx context.Context) (SecurityBag, bool) { bag, ok := ctx.Value(authKey).(SecurityBag) return bag, ok }