Documentation
¶
Index ¶
- Variables
- type AddKidRequest
- type AddKidResponse
- type CleanExpiredRequest
- type CleanExpiredResponse
- type CompositeKeyLookup
- type DeleteKeyRequest
- type DeleteKeyResponse
- type EncryptedKeyStorage
- func (e *EncryptedKeyStorage) DeleteKey(ctx context.Context, req *DeleteKeyRequest) (*DeleteKeyResponse, error)
- func (e *EncryptedKeyStorage) GetKey(ctx context.Context, req *GetKeyRequest) (*GetKeyResponse, error)
- func (e *EncryptedKeyStorage) GetKeyByKid(ctx context.Context, req *GetKeyByKidRequest) (*GetKeyByKidResponse, error)
- func (e *EncryptedKeyStorage) ListKeyIDs(ctx context.Context, req *ListKeyIDsRequest) (*ListKeyIDsResponse, error)
- func (e *EncryptedKeyStorage) MigrateLegacy(ctx context.Context) (int, error)
- func (e *EncryptedKeyStorage) PutKey(ctx context.Context, req *PutKeyRequest) (*PutKeyResponse, error)
- type EncryptedKidStorage
- func (k *EncryptedKidStorage) Add(ctx context.Context, req *AddKidRequest) (*AddKidResponse, error)
- func (k *EncryptedKidStorage) CleanExpired(ctx context.Context, req *CleanExpiredRequest) (*CleanExpiredResponse, error)
- func (k *EncryptedKidStorage) GetKey(ctx context.Context, req *GetKeyRequest) (*GetKeyResponse, error)
- func (k *EncryptedKidStorage) GetKeyByKid(ctx context.Context, req *GetKeyByKidRequest) (*GetKeyByKidResponse, error)
- func (k *EncryptedKidStorage) Remove(ctx context.Context, req *RemoveKidRequest) (*RemoveKidResponse, error)
- type GetKeyByKidRequest
- type GetKeyByKidResponse
- type GetKeyRequest
- type GetKeyResponse
- type InMemoryKeyStore
- func (s *InMemoryKeyStore) DeleteKey(ctx context.Context, req *DeleteKeyRequest) (*DeleteKeyResponse, error)
- func (s *InMemoryKeyStore) GetKey(ctx context.Context, req *GetKeyRequest) (*GetKeyResponse, error)
- func (s *InMemoryKeyStore) GetKeyByKid(ctx context.Context, req *GetKeyByKidRequest) (*GetKeyByKidResponse, error)
- func (s *InMemoryKeyStore) ListKeyIDs(ctx context.Context, req *ListKeyIDsRequest) (*ListKeyIDsResponse, error)
- func (s *InMemoryKeyStore) PutKey(ctx context.Context, req *PutKeyRequest) (*PutKeyResponse, error)
- type JWKSHandler
- type JWKSKeyStore
- type JWKSOption
- type KeyLookup
- type KeyRecord
- type KeyStorage
- type KidStorage
- type KidStore
- func (s *KidStore) Add(ctx context.Context, req *AddKidRequest) (*AddKidResponse, error)
- func (s *KidStore) CleanExpired(ctx context.Context, req *CleanExpiredRequest) (*CleanExpiredResponse, error)
- func (s *KidStore) GetKey(ctx context.Context, req *GetKeyRequest) (*GetKeyResponse, error)
- func (s *KidStore) GetKeyByKid(ctx context.Context, req *GetKeyByKidRequest) (*GetKeyByKidResponse, error)
- func (s *KidStore) Len() int
- func (s *KidStore) Remove(ctx context.Context, req *RemoveKidRequest) (*RemoveKidResponse, error)
- type ListKeyIDsRequest
- type ListKeyIDsResponse
- type PutKeyRequest
- type PutKeyResponse
- type RemoveKidRequest
- type RemoveKidResponse
Constants ¶
This section is empty.
Variables ¶
var ( ErrKeyNotFound = fmt.Errorf("signing key not found") ErrAlgorithmMismatch = fmt.Errorf("algorithm mismatch") ErrKidNotFound = fmt.Errorf("key not found for kid") )
Common errors for key operations
var ErrKeyDecrypt = errors.New("keys: stored key could not be decrypted")
ErrKeyDecrypt is returned by GetKey and GetKeyByKid when a record that should be encrypted can't be opened: the master key is wrong, the bytes were altered, the ciphertext belongs to a different record, or it is plaintext and AllowLegacyPlaintext is off. The key is never returned in any of those cases.
Functions ¶
This section is empty.
Types ¶
type AddKidRequest ¶ added in v0.1.7
type AddKidRequest struct {
Kid string
Key any
Algorithm string
ClientID string
ExpiresAt time.Time
}
AddKidRequest is the input to KidStorage.Add. If ExpiresAt is zero, the kid has no expiry (current key).
type AddKidResponse ¶ added in v0.1.7
type AddKidResponse struct{}
AddKidResponse is the output of KidStorage.Add.
type CleanExpiredRequest ¶ added in v0.1.7
type CleanExpiredRequest struct{}
CleanExpiredRequest is the input to KidStorage.CleanExpired.
type CleanExpiredResponse ¶ added in v0.1.7
type CleanExpiredResponse struct {
// Removed is the number of expired records evicted by this call.
Removed int
}
CleanExpiredResponse is the output of KidStorage.CleanExpired.
type CompositeKeyLookup ¶
type CompositeKeyLookup struct {
Lookups []KeyLookup
}
CompositeKeyLookup tries multiple KeyLookups in order, returning the first successful result. Used to combine a KeyStorage's current-key lookup with a KidStore's grace-period entries.
func (*CompositeKeyLookup) GetKey ¶
func (c *CompositeKeyLookup) GetKey(ctx context.Context, req *GetKeyRequest) (*GetKeyResponse, error)
GetKey tries each lookup in order for a client_id match.
func (*CompositeKeyLookup) GetKeyByKid ¶
func (c *CompositeKeyLookup) GetKeyByKid(ctx context.Context, req *GetKeyByKidRequest) (*GetKeyByKidResponse, error)
GetKeyByKid tries each lookup in order for a kid match.
type DeleteKeyRequest ¶ added in v0.1.7
type DeleteKeyRequest struct {
ClientID string
}
DeleteKeyRequest is the input to KeyStorage.DeleteKey.
type DeleteKeyResponse ¶ added in v0.1.7
type DeleteKeyResponse struct{}
DeleteKeyResponse is the output of KeyStorage.DeleteKey.
type EncryptedKeyStorage ¶
type EncryptedKeyStorage struct {
// AllowLegacyPlaintext accepts encrypt-eligible records (HMAC secrets,
// private PEMs) that are stored in plaintext, as they were before
// encryption was turned on. Off by default: with it off, a plaintext
// secret in the store is ErrKeyDecrypt, so someone who can write the
// store can't swap in a key of their choosing. Turn it on only to read
// or MigrateLegacy such records.
AllowLegacyPlaintext bool
// contains filtered or unexported fields
}
EncryptedKeyStorage is a KeyStorage decorator that transparently encrypts sensitive key material at rest using AES-256-GCM. Two categories of input are encrypted on the way in (and decrypted on the way out):
- HMAC client secrets (HS256/HS384/HS512) — identified by Algorithm.
- PEM blocks whose header type contains "PRIVATE" — identified by content (e.g., "PRIVATE KEY", "RSA PRIVATE KEY", "EC PRIVATE KEY", "OPENSSH PRIVATE KEY"). The content-driven check matters because downstream consumers may persist private keys under non-JWT Algorithm strings (e.g., "ssh-ed25519" via the sshkeys submodule).
Public PEMs (which back JWKS exposure) and any other plaintext pass through unencrypted — they are not sensitive.
Because it wraps KeyStorage and operates on KeyRecord, it only needs to implement 5 methods — no manual forwarding of individual field accessors.
The kid is computed from plaintext key material in PutKey before encryption, so kid-based lookups work correctly even though the stored bytes are encrypted.
func NewEncryptedKeyStorage ¶
func NewEncryptedKeyStorage(inner KeyStorage, masterKeyHex string) (*EncryptedKeyStorage, error)
NewEncryptedKeyStorage creates an EncryptedKeyStorage wrapping inner. masterKeyHex must be exactly 64 hex characters (32 bytes).
func (*EncryptedKeyStorage) DeleteKey ¶
func (e *EncryptedKeyStorage) DeleteKey(ctx context.Context, req *DeleteKeyRequest) (*DeleteKeyResponse, error)
DeleteKey delegates to the inner store.
func (*EncryptedKeyStorage) GetKey ¶
func (e *EncryptedKeyStorage) GetKey(ctx context.Context, req *GetKeyRequest) (*GetKeyResponse, error)
GetKey retrieves and decrypts the key for the given clientID.
func (*EncryptedKeyStorage) GetKeyByKid ¶
func (e *EncryptedKeyStorage) GetKeyByKid(ctx context.Context, req *GetKeyByKidRequest) (*GetKeyByKidResponse, error)
GetKeyByKid retrieves and decrypts the key matching the given kid.
func (*EncryptedKeyStorage) ListKeyIDs ¶
func (e *EncryptedKeyStorage) ListKeyIDs(ctx context.Context, req *ListKeyIDsRequest) (*ListKeyIDsResponse, error)
ListKeyIDs delegates to the inner store.
func (*EncryptedKeyStorage) MigrateLegacy ¶ added in v0.1.46
func (e *EncryptedKeyStorage) MigrateLegacy(ctx context.Context) (int, error)
MigrateLegacy rewrites every legacy record in the current format and returns how many it rewrote. Unbound ciphertext from earlier versions is always migrated; plaintext records only when AllowLegacyPlaintext is on. Records already current, and public PEMs, are left alone. It stops at the first record it can't read or write.
func (*EncryptedKeyStorage) PutKey ¶
func (e *EncryptedKeyStorage) PutKey(ctx context.Context, req *PutKeyRequest) (*PutKeyResponse, error)
PutKey computes kid from plaintext, then encrypts HMAC secrets and PEM blocks of type "*PRIVATE*" before storing. Other inputs (notably public PEMs) are stored unmodified.
type EncryptedKidStorage ¶ added in v0.1.47
type EncryptedKidStorage struct {
// AllowLegacyPlaintext accepts encrypt-eligible records stored in
// plaintext, as they were before this decorator was added. Off by
// default, for the same reason as EncryptedKeyStorage's option.
AllowLegacyPlaintext bool
// contains filtered or unexported fields
}
EncryptedKidStorage is the KidStorage counterpart of EncryptedKeyStorage. A retired signing key kept for its grace period is as secret as it was before rotation, so persistent KidStorage backends (FS, GORM, GAE) get the same treatment: HMAC secrets and private PEMs are stored as `oaenc1:` AES-256-GCM records with AAD over client, alg and kid, a record that won't open is ErrKeyDecrypt, and plaintext is refused unless AllowLegacyPlaintext is on. Public keys pass through.
Use the same master key as the EncryptedKeyStorage the keys rotate out of; the two are independent otherwise.
func NewEncryptedKidStorage ¶ added in v0.1.47
func NewEncryptedKidStorage(inner KidStorage, masterKeyHex string) (*EncryptedKidStorage, error)
NewEncryptedKidStorage wraps inner. masterKeyHex must be exactly 64 hex characters (32 bytes), as for NewEncryptedKeyStorage.
func (*EncryptedKidStorage) Add ¶ added in v0.1.47
func (k *EncryptedKidStorage) Add(ctx context.Context, req *AddKidRequest) (*AddKidResponse, error)
Add encrypts an HMAC secret or private PEM before handing the record to inner; other keys are stored as given. The kid is the caller's, since KidStorage is indexed by it.
func (*EncryptedKidStorage) CleanExpired ¶ added in v0.1.47
func (k *EncryptedKidStorage) CleanExpired(ctx context.Context, req *CleanExpiredRequest) (*CleanExpiredResponse, error)
CleanExpired delegates to inner; expiry is stored in the clear.
func (*EncryptedKidStorage) GetKey ¶ added in v0.1.47
func (k *EncryptedKidStorage) GetKey(ctx context.Context, req *GetKeyRequest) (*GetKeyResponse, error)
GetKey delegates to inner, which answers ErrKeyNotFound: KidStorage is only meaningful by kid.
func (*EncryptedKidStorage) GetKeyByKid ¶ added in v0.1.47
func (k *EncryptedKidStorage) GetKeyByKid(ctx context.Context, req *GetKeyByKidRequest) (*GetKeyByKidResponse, error)
GetKeyByKid reads through inner and decrypts, or returns ErrKeyDecrypt. Unknown and expired kids keep inner's error.
func (*EncryptedKidStorage) Remove ¶ added in v0.1.47
func (k *EncryptedKidStorage) Remove(ctx context.Context, req *RemoveKidRequest) (*RemoveKidResponse, error)
Remove delegates to inner; nothing to decrypt.
type GetKeyByKidRequest ¶ added in v0.1.7
type GetKeyByKidRequest struct {
Kid string
}
GetKeyByKidRequest is the input to KeyLookup.GetKeyByKid.
type GetKeyByKidResponse ¶ added in v0.1.7
type GetKeyByKidResponse struct {
Record *KeyRecord
}
GetKeyByKidResponse is the output of KeyLookup.GetKeyByKid.
type GetKeyRequest ¶ added in v0.1.7
type GetKeyRequest struct {
ClientID string
}
GetKeyRequest is the input to KeyLookup.GetKey.
type GetKeyResponse ¶ added in v0.1.7
type GetKeyResponse struct {
Record *KeyRecord
}
GetKeyResponse is the output of KeyLookup.GetKey.
type InMemoryKeyStore ¶
type InMemoryKeyStore struct {
// contains filtered or unexported fields
}
InMemoryKeyStore is a thread-safe in-memory KeyStorage implementation. Suitable for testing and simple single-process deployments.
func NewInMemoryKeyStore ¶
func NewInMemoryKeyStore() *InMemoryKeyStore
NewInMemoryKeyStore creates a new empty InMemoryKeyStore.
func (*InMemoryKeyStore) DeleteKey ¶
func (s *InMemoryKeyStore) DeleteKey(ctx context.Context, req *DeleteKeyRequest) (*DeleteKeyResponse, error)
DeleteKey removes the key for the given clientID.
func (*InMemoryKeyStore) GetKey ¶
func (s *InMemoryKeyStore) GetKey(ctx context.Context, req *GetKeyRequest) (*GetKeyResponse, error)
GetKey returns the key record for the given clientID.
func (*InMemoryKeyStore) GetKeyByKid ¶
func (s *InMemoryKeyStore) GetKeyByKid(ctx context.Context, req *GetKeyByKidRequest) (*GetKeyByKidResponse, error)
GetKeyByKid returns the key record matching the given kid.
func (*InMemoryKeyStore) ListKeyIDs ¶
func (s *InMemoryKeyStore) ListKeyIDs(ctx context.Context, req *ListKeyIDsRequest) (*ListKeyIDsResponse, error)
ListKeyIDs returns all registered client IDs.
func (*InMemoryKeyStore) PutKey ¶
func (s *InMemoryKeyStore) PutKey(ctx context.Context, req *PutKeyRequest) (*PutKeyResponse, error)
PutKey stores a key record. Computes Kid from key material if not set.
type JWKSHandler ¶
type JWKSHandler struct {
KeyStore KeyStorage // needs ListKeyIDs() and GetKey()
KidStore *KidStore // optional: serves previous keys during grace period
CacheMaxAge int // Cache-Control max-age in seconds (default: 3600)
// TracerProvider opts the handler into SEP-414 (W3C Trace Context)
// observability. When non-nil, every ServeHTTP call:
// - extracts an inbound `traceparent` header (silently dropping a
// malformed value per W3C §3.2.2.5), and
// - emits a single `oneauth.jwks.serve` span with the response status
// and the number of asymmetric keys returned.
// Leaving this nil keeps the handler on the no-op fast path with no
// allocation cost — same opt-in shape as mcpkit's
// server.WithTracerProvider. See tests/keycloak/ and the SEP-414
// trace chain in issue #254 for the cross-process context.
TracerProvider trace.TracerProvider
}
JWKSHandler serves a JWKS (JSON Web Key Set) endpoint at /.well-known/jwks.json. Only asymmetric keys (RS256/ES256) are included — HS256 secrets are never exposed.
func (*JWKSHandler) ServeHTTP ¶
func (h *JWKSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
type JWKSKeyStore ¶
type JWKSKeyStore struct {
JWKSURL string
HTTPClient *http.Client
RefreshInterval time.Duration // default: 1 hour
MinRefreshGap time.Duration // default: 5 seconds
// TracerProvider opts the keystore into SEP-414 tracing. When set:
// - GetKeyByKid emits an `oneauth.jwks.key_lookup` span (attributes:
// `jwks.kid`, `jwks.cache_hit`).
// - refresh() emits an `oneauth.jwks.refresh` span and injects a
// W3C `traceparent` header on the outbound JWKS HTTP fetch so the
// upstream JWKS endpoint can stitch its own work into the trace.
// Nil keeps every span on the no-op fast path. Configured via
// WithTracerProvider.
TracerProvider trace.TracerProvider
// contains filtered or unexported fields
}
JWKSKeyStore implements KeyLookup (read-only) by fetching public keys from a remote JWKS endpoint. It caches keys locally and refreshes them periodically.
func NewJWKSKeyStore ¶
func NewJWKSKeyStore(jwksURL string, opts ...JWKSOption) *JWKSKeyStore
NewJWKSKeyStore creates a new JWKSKeyStore. Call Start() to begin fetching keys.
func (*JWKSKeyStore) GetKey ¶
func (s *JWKSKeyStore) GetKey(ctx context.Context, req *GetKeyRequest) (*GetKeyResponse, error)
GetKey returns ErrKeyNotFound — JWKSKeyStore only supports kid-based lookup. JWKS doesn't carry clientID→key mappings; use GetKeyByKid instead.
func (*JWKSKeyStore) GetKeyByKid ¶
func (s *JWKSKeyStore) GetKeyByKid(ctx context.Context, req *GetKeyByKidRequest) (*GetKeyByKidResponse, error)
GetKeyByKid returns the key record for the given kid. ClientID is empty since JWKS doesn't carry client_id metadata — the middleware skips the cross-app check in this case.
func (*JWKSKeyStore) Start ¶
func (s *JWKSKeyStore) Start() error
Start performs the initial JWKS fetch and starts background refresh.
func (*JWKSKeyStore) Stop ¶
func (s *JWKSKeyStore) Stop()
Stop stops the background refresh goroutine.
type JWKSOption ¶
type JWKSOption func(*JWKSKeyStore)
JWKSOption configures a JWKSKeyStore.
func WithHTTPClient ¶
func WithHTTPClient(c *http.Client) JWKSOption
WithHTTPClient sets the HTTP client for JWKS fetching.
func WithMinRefreshGap ¶
func WithMinRefreshGap(d time.Duration) JWKSOption
WithMinRefreshGap sets the minimum time between refreshes (prevents stampede).
func WithRefreshInterval ¶
func WithRefreshInterval(d time.Duration) JWKSOption
WithRefreshInterval sets how often keys are refreshed in the background.
func WithTracerProvider ¶ added in v0.1.14
func WithTracerProvider(tp trace.TracerProvider) JWKSOption
WithTracerProvider opts the keystore into SEP-414 tracing — spans on the GetKeyByKid hot path and outbound traceparent injection on the JWKS refresh request. Nil (the default) keeps tracing off and the no-op path zero-cost. See JWKSKeyStore.TracerProvider for the full contract.
type KeyLookup ¶
type KeyLookup interface {
// GetKey returns the key record for the given clientID.
// Returns ErrKeyNotFound if the client has no registered key.
GetKey(ctx context.Context, req *GetKeyRequest) (*GetKeyResponse, error)
// GetKeyByKid returns the key record matching the given kid.
// Returns ErrKidNotFound if no key matches or the key has expired.
GetKeyByKid(ctx context.Context, req *GetKeyByKidRequest) (*GetKeyByKidResponse, error)
}
KeyLookup provides read-only key lookup by clientID or kid. Implemented by all keystores, including read-only ones like JWKSKeyStore.
type KeyRecord ¶
type KeyRecord struct {
ClientID string // owning client/app
Key any // []byte for HMAC, PEM bytes for asymmetric
Algorithm string // "HS256", "RS256", "ES256", etc.
Kid string // key identifier, computed from key material
}
KeyRecord holds all fields for a stored signing key. All key operations work with this type rather than separate accessor methods.
type KeyStorage ¶
type KeyStorage interface {
KeyLookup
// PutKey stores a key record. If req.Record.Kid is empty, it is
// auto-computed from the key material and algorithm. Overwrites any
// existing key for the same ClientID.
PutKey(ctx context.Context, req *PutKeyRequest) (*PutKeyResponse, error)
// DeleteKey removes the key for the given clientID.
DeleteKey(ctx context.Context, req *DeleteKeyRequest) (*DeleteKeyResponse, error)
// ListKeyIDs returns all registered client IDs.
ListKeyIDs(ctx context.Context, req *ListKeyIDsRequest) (*ListKeyIDsResponse, error)
}
KeyStorage extends KeyLookup with write operations. Implemented by persistent backends (InMemory, GORM, FS, GAE).
type KidStorage ¶ added in v0.0.83
type KidStorage interface {
KeyLookup
// Add registers a kid→key mapping. If req.ExpiresAt is zero, the key
// has no expiry. Re-adding an existing kid overwrites it.
Add(ctx context.Context, req *AddKidRequest) (*AddKidResponse, error)
// Remove deletes a kid entry. Removing an absent kid is not an error.
Remove(ctx context.Context, req *RemoveKidRequest) (*RemoveKidResponse, error)
// CleanExpired removes all entries whose expiry has passed.
CleanExpired(ctx context.Context, req *CleanExpiredRequest) (*CleanExpiredResponse, error)
}
KidStorage is the write side of a kid-indexed key store. It extends the read-only KeyLookup with the grace-period operations KidStore provides, so retired keys can be persisted across process restarts by backends in stores/ (FS, GORM, GAE) rather than living only in process memory.
Unlike KeyStorage, KidStorage is keyed by kid (not clientID): GetKey by clientID always returns ErrKeyNotFound — only GetKeyByKid is meaningful.
type KidStore ¶
type KidStore struct {
// contains filtered or unexported fields
}
KidStore is an in-memory KidStorage that tracks kid→key mappings, including grace-period entries retained during key rotation.
Usage during rotation:
- kidStore.Add(ctx, &AddKidRequest{Kid: oldKid, Key: oldKey, Algorithm: alg, ClientID: clientID, ExpiresAt: time.Now().Add(gracePeriod)})
- keyStorage.PutKey(ctx, &PutKeyRequest{Record: newRecord}) // overwrites current
- kidStore holds the old key until grace period expires
func (*KidStore) Add ¶
func (s *KidStore) Add(ctx context.Context, req *AddKidRequest) (*AddKidResponse, error)
Add registers a kid→key mapping. If req.ExpiresAt is zero, the key has no expiry.
func (*KidStore) CleanExpired ¶
func (s *KidStore) CleanExpired(ctx context.Context, req *CleanExpiredRequest) (*CleanExpiredResponse, error)
CleanExpired removes all expired entries.
func (*KidStore) GetKey ¶
func (s *KidStore) GetKey(ctx context.Context, req *GetKeyRequest) (*GetKeyResponse, error)
GetKey always returns ErrKeyNotFound — KidStore only supports kid-based lookup.
func (*KidStore) GetKeyByKid ¶
func (s *KidStore) GetKeyByKid(ctx context.Context, req *GetKeyByKidRequest) (*GetKeyByKidResponse, error)
GetKeyByKid returns the key record for the given kid. Returns ErrKidNotFound if the kid is unknown or expired.
func (*KidStore) Remove ¶
func (s *KidStore) Remove(ctx context.Context, req *RemoveKidRequest) (*RemoveKidResponse, error)
Remove deletes a kid entry. Removing an absent kid is not an error.
type ListKeyIDsRequest ¶ added in v0.1.7
type ListKeyIDsRequest struct{}
ListKeyIDsRequest is the input to KeyStorage.ListKeyIDs.
type ListKeyIDsResponse ¶ added in v0.1.7
type ListKeyIDsResponse struct {
ClientIDs []string
}
ListKeyIDsResponse is the output of KeyStorage.ListKeyIDs.
type PutKeyRequest ¶ added in v0.1.7
type PutKeyRequest struct {
Record *KeyRecord
}
PutKeyRequest is the input to KeyStorage.PutKey.
type PutKeyResponse ¶ added in v0.1.7
type PutKeyResponse struct{}
PutKeyResponse is the output of KeyStorage.PutKey.
type RemoveKidRequest ¶ added in v0.1.7
type RemoveKidRequest struct {
Kid string
}
RemoveKidRequest is the input to KidStorage.Remove.
type RemoveKidResponse ¶ added in v0.1.7
type RemoveKidResponse struct{}
RemoveKidResponse is the output of KidStorage.Remove.