76 lines
2.8 KiB
Go
76 lines
2.8 KiB
Go
|
|
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
|
||
|
|
}
|