package bouncer import "context" type dispatchKey struct{} // Dispatch is the pre-resolved principal marker an in-process dispatcher // attaches to a request it builds itself (the MCP module calling one of a // plugin's routes on behalf of an already authenticated caller). The marker // is context-only: it lives under an unexported key, nothing reads a header, // cookie or query string for it, and an HTTP request can therefore never // carry one. Only an in-process dispatcher inside the framework may call // WithDispatch; plugins never set it. // // Guards lists the exact registry guard names (for example "api_token" or // "backend") whose middleware honours the marker; Registry.Middleware for a // name the marker does not list ignores it and authenticates the request as // usual. Principal is the identity the dispatcher resolved and Credential the // optional credential record stored next to it (WithCredential), nil for a // principal without one. type Dispatch struct { Guards []string Principal *Principal Credential any } // WithDispatch stores the marker on ctx. A nil ctx becomes a background // context, as WithUser does. Only an in-process dispatcher may call it. func WithDispatch(ctx context.Context, d *Dispatch) context.Context { if ctx == nil { ctx = context.Background() } return context.WithValue(ctx, dispatchKey{}, d) } // Dispatched returns the marker on ctx when it carries a principal and lists // guard among its Guards (exact name comparison, no wildcard). Any other // context, a marker without a principal or a marker for other guards answers // false, so the caller falls back to authenticating the request. func Dispatched(ctx context.Context, guard string) (*Dispatch, bool) { if ctx == nil || guard == "" { return nil, false } d, ok := ctx.Value(dispatchKey{}).(*Dispatch) if !ok || d == nil || d.Principal == nil { return nil, false } for _, name := range d.Guards { if name == guard { return d, true } } return nil, false }