Skip to content
xmidt-orgPublic

About

A library that rate limits Callers using the Rate Capabilities in their Tokens.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

44 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tokenrate

A library that rate limits Callers using the Rate Capabilities in their Tokens.

Build Status codecov.io Apache V2 License GitHub Release GoDoc

Summary

A rate limiter whose subject and limits come from an authenticated Token. It limits how fast each Caller (the Token's principal) may make requests, using the Rate Capabilities the Token carries, e.g. prefix:rate:50/1m. A Token may carry several, such as 1000/1m and 100000/24h, and every one applies. A deployment's own policy, such as a ceiling for everyone or an override for one Caller, is a function it supplies with WithResolver.

tokenrate depends on nothing beyond the standard library: no bascule, no JWT library, no HTTP framework. A service passes in the principal and capability strings after authenticating a request, and decides how to apply the decision it gets back.

Install

go get github.com/xmidt-org/tokenrate

Usage

Place the middleware after the one that validates the request's credentials. It must never run before: the limiter keeps state per principal, so an unauthenticated caller who could choose the principal could fill memory with one request per name. It checks the Caller, writes one warning header per Capability Warning, and answers 429 Too Many Requests (with Retry-After) or 403 Forbidden itself. The only service-specific part is finding the principal and capabilities in the request. A Token with no rate is unlimited unless a Resolver says otherwise:

limiter, err := tokenrate.New(
	tokenrate.WithPrefixes("prefix:rate:"),
)
if err != nil {
	return err
}

handler := tokenrate.Middleware{
	Limiter: limiter,
	Extract: func(r *http.Request) (string, []string) {
		token, ok := bascule.Get(r.Context())
		if !ok {
			return "", nil // unauthenticated: Unrestricted unless the Resolver says otherwise
		}

		caps, _ := bascule.GetCapabilities(token)
		return token.Principal(), caps
	},
}.Wrap(next)

// With bascule's own middleware, which stores the Token in the context:
protected := basculeMiddleware.Then(handler)

Outside HTTP, call limiter.Check(ctx, principal, capabilities) and apply the Decision yourself.

A Resolver has the final say over the rates a Token counts as carrying, so policy lives in your code, not in options. The library always enforces what it returns: no rates means no limit, the zero rate refuses, and an error denies the request. DefaultResolver handles malformed rates and the Window Bounds; call it from yours to keep that.

tokenrate.WithResolver(func(ctx context.Context, principal string, provided []tokenrate.Rate) ([]tokenrate.Rate, error) {
	if override, ok := overrides[principal]; ok {
		return override, nil // replaces whatever the Token says
	}

	rates, _ := tokenrate.DefaultResolver(ctx, principal, provided)
	if len(rates) == 0 {
		return nil, errors.New("no rate capability") // every Token must carry a rate
	}

	return append(rates, ceiling), nil // nobody exceeds the ceiling
})

See the examples for all three.

Try it

examples/server is a small service limited by tokenrate, with a table of API keys standing in for credential validation, and examples/client makes a burst of requests and prints each answer. Both are standard library only.

go run ./examples/server
$ go run ./examples/client -key key-abc -n 7
 1  200 OK
 2  200 OK
 3  200 OK
 4  200 OK
 5  200 OK
 6  429 Too Many Requests  Retry-After: 12s
 7  429 Too Many Requests  Retry-After: 12s

key-ghi carries a malformed rate alongside a valid one, so every answer carries a warning; key-def carries no rate and is Unrestricted.

  • CONTEXT.md defines the terms.
  • docs/design.md describes the behavior.
  • ADR 0002 explains why a Caller is held to every rate that applies.
  • ADR 0003 explains why the library always enforces and a Resolver decides policy.

Code of Conduct

This project and everyone participating in it are governed by the XMiDT Code Of Conduct. By participating, you agree to this Code.

Contributing

Refer to CONTRIBUTING.md.

About

A library that rate limits Callers using the Rate Capabilities in their Tokens.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages