middleware

package
v0.8.11 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 8, 2023 License: MIT Imports: 22 Imported by: 2

Documentation

Overview

The middleware package defines what a middleware is in trails and a set of basic middlewares.

The available middlewares are: - CORS - CurrentUser - ForceHTTPS - InjectSession - LogRequest - RateLimit - RequestID

Due to the amount of configuration required, middleware does not provide a default middleware chain Instead, the following can be copy-pasted:

vs := middleware.NewVisitors()
adpts := []middleware.Adapter{
	middleware.RateLimit(vs),
	middleware.ForceHTTPS(env),
	middleware.RequestID(requestIDKey),
	middleware.LogRequest(log),
	middleware.InjectSession(sessionStore, sessionKey),
	middleware.CurrentUser(responder, userStore, userKey),
}

Index

Constants

View Source
const (
	IdempotencyHeader = "Idempotency-Key"
)

Variables

This section is empty.

Functions

func Chain

func Chain(handler http.Handler, adapters ...Adapter) http.Handler

Chain glues the set of adapters to the handler.

func GetIPAddress

func GetIPAddress(hm http.Header) string

GetIPAddress parses "X-Forward-For" and "X-Real-Ip" headers for the IP address from the request.

GetIPAddress skips addresses from non-public ranges.

func NoopAdapter

func NoopAdapter(h http.Handler) http.Handler

NoopAdapter is a pass-through Adapter, often returned by Adapters available in this package when they are misconfigured.

func ReportPanic

func ReportPanic(env string) func(http.HandlerFunc) http.HandlerFunc

ReportPanic encloses the env and returns a function that when called, wraps the passed in http.HandlerFunc in sentryhttp.HandleFunc in order to recover and report panics.

Types

type Adapter

type Adapter func(http.Handler) http.Handler

An Adapter enables chaining middlewares together.

func CORS

func CORS(base string) Adapter

CORS sets "Access-Control-Allowed" style headers on a response. The handler including this middleware must also handle the http.MethodOptions method and not just the HTTP method it's designed for.

If base is it's zero-value, NoopAdapter returns and this middleware does nothing.

func CurrentUser

func CurrentUser(d *resp.Responder, storer UserStorer) Adapter

CurrentUser uses storer and the session.Session stored in a *http.Request.Context to check the current user has access and then stashes the current user in the *http.Request.Context under the trails.CurrentUserKey.

CurrentUser uses a *resp.Responder to handle cases where a current user cannot be retrieved or does not have access.

In such a case, CurrentUser toggles between *resp.Responder.Redirect or *resp.Responder.Json, choosing the latter if the *http.Request "Accept header MIME type is "application/json".

func ForceHTTPS

func ForceHTTPS(env trails.Environment) Adapter

ForceHTTPS redirects HTTP requests to HTTPS if the environment is not "development".

The "X-Forwarded-Proto" is used to check whether HTTP was requested due to a trails application running behind a proxy.

TODO(dlk): configurable headers to check.

func Idempotent added in v0.5.0

func Idempotent(cache IdempotencyCacher, hasher hash.Hash) Adapter

Idempotent returns a middleware.Adapter that enables features of idempotency on a POST endpoint. GET, DELETE, PUT, & PATCH are idempotent by definition.

Idempotent pulls a key (a UUID v4 string) from request headers to base the uniqueness of a POST request around.

If a previous request has not used that key, Idempotent pairs all of the following values to the key: - the body of the request - the body of the resulting response - the status code of the resulting response

If that key has been used before (and has not expired), Idempotent falls into one of these scenarios:

  • if a status code has not been set for that key, Idempotent responds with 409 since the idempotent request is still processing

  • if the newly requested resource (the URI) does not match the original, Idempotent responsds with 422

  • if the new request's body does not match the body of the original request's, Idempotent responds with 422

- Idempotent writes the status code and body set for the key

cache and hasher can be nil. Idempotent will use a default cache and implementation of hash.Hash, accordingly.

Idempotent implements the draft Idempotent HTTP Header Field specification: https://tools.ietf.org/id/draft-idempotency-header-01.html

func InjectIPAddress

func InjectIPAddress() Adapter

InjectIPAddress grabs the IP address in the *http.Request.Header and promotes it to *http.Request.Context under trails.IpAddrKey.

func InjectSession

func InjectSession(store session.SessionStorer) Adapter

InjectSession stores the session associated with the *http.Request in *http.Request.Context.

If store or key are their zero-values, NoopAdapter returns and this middleware does nothing.

func LogRequest

func LogRequest(ls *slog.Logger) Adapter

LogRequest logs the a LogRequestRecord using the provided handler.

For the LogRequestRecord.URI, LogRequest masks query params matching these keys with trails.LogMaskVal: - password

If handler is nil, NoopAdapter returns and this middleware does nothing.

func RateLimit

func RateLimit(visitors *Visitors) Adapter

RateLimit encloses the Visitors map and serves the http.Handler

NOTE: cribbed from https://www.alexedwards.net/blog/how-to-rate-limit-http-requests

If we need anything more sophisticated, check https://github.com/didip/tollbooth

func RequestID

func RequestID() Adapter

RequestID adds a UUID to the request context using trails.RequestIDKey.

TODO(dlk): use "X-Request-ID" or similar header for UUID value.

func RequireAuthed added in v0.3.2

func RequireAuthed(loginUrl, logoffUrl string) Adapter

RequireAuthed returns a middleware.Adapter that checks whether a User is authenticated, and requires they be authenticated. When the User is authenticated, then RequireAuthed hands off to the next part of the middleware chain.

Authenticated means a User is set in the request context with the provided key.

When the User is not authenticated, and the request's "Accept" header has "application/json" in it, RequireUnauthed writes 401 to the client. If the request does not have that value in it's header, RequireAuthed redirects to the provided login URL.

The URL originally requested is appended to as a "next" query param when the request method is GET and the endpoint is not the logoff URL.

func RequireUnauthed added in v0.3.2

func RequireUnauthed() Adapter

RequireUnauthed returns a middleware.Adapter that checks whether a user is authenticated and requires they not be authenticated. When they are not authenticated, RequireUnauthed hands off to the next part of the middleware chain.

Authenticated means a User is set in the request context with the provided key.

When the User is authenticated, and the request's "Accept" header has "application/json" in it, RequireUnauthed writes 400 to the client. If the request does not have that value in it's header, RequireUnauthed redirect to User's HomePath.

type AuthorizeApplicator added in v0.5.0

type AuthorizeApplicator[T any] struct {
	// contains filtered or unexported fields
}

An AuthorizeApplicator constructs Adapters that apply custom authorization rules for users, as specified by type T.

func NewAuthorizeApplicator added in v0.5.0

func NewAuthorizeApplicator[T any](d *resp.Responder) AuthorizeApplicator[T]

NewAuthorizeApplicator constructs an AuthorizeApplicator for type T. Apply methods for the constructed AuthorizeApplicator will use the Responder for redirects. Apply methods will use trails.CurrentUserKey to pull a user out of the request Context.

func (AuthorizeApplicator[T]) Apply added in v0.5.0

func (aa AuthorizeApplicator[T]) Apply(fn func(user T) (string, bool)) Adapter

Apply wraps a custom function validating the authorization of a user, whose type is specified by T.

Apply retrieves the value for the trails.CurrentUserKey from the request Context. Apply should not be used in a situation where the http.Request.Context in some cases stores the requisite value and others does not.

The provided custom function returns either true and an empty string - meaning the user is authorized - or false and a valid URL as a string.

If the custom function returns true, Apply passes the request to the next handler in the middleware stack.

If the custom function returns false, Apply does not pass the request to the next handler in the middleware stack.

Instead, Apply takes one of two actions depending on the "Accept" HTTP header of the request.

  • By default, Apply writes 401.
  • If "text/html" appears in the "Accept" header, though, Apply sets a "no access" flash on the session and redirects to the URL the custom function returns.

If fn is nil, Apply returns a NoopAdapter.

type IdemRes added in v0.5.0

type IdemRes struct {
	Body   *bytes.Buffer
	Req    []byte
	Status int
	URI    string
}

An IdemRes is data from an HTTP response that can be reused when another request matches the same idempotency key.

func NewIdemRes added in v0.5.0

func NewIdemRes(uri string, hashedBody []byte) IdemRes

NewIdemRes constructs a new *IdemRes.

func (*IdemRes) GobDecode added in v0.5.0

func (i *IdemRes) GobDecode(b []byte) error

GobDecode unmarshals the gob-encoded []byte into fields of the *IdemRes.

GobDecode implements gob.GobDecoder.

func (IdemRes) GobEncode added in v0.5.0

func (i IdemRes) GobEncode() ([]byte, error)

GobEncode marshals the fields of the IdemRes into a gob-encoded []byte.

GobEncode implements gob.GobEncoder.

type IdemResMap added in v0.5.0

type IdemResMap map[string]IdemResMapVal

An IdemResMap stores idempotency key, IdemRes value pairs in a map.

Server restarts reset this map. idemResMap ought not be used for production environments.

func NewIdemResMap added in v0.5.0

func NewIdemResMap() IdemResMap

NewIdemResMap constructs initializes an IdemResMap for use in an Idempotency middleware as a cache.

func (IdemResMap) Get added in v0.5.0

func (i IdemResMap) Get(ctx context.Context, key string) (IdemRes, bool)

Get retrieves the result of the request matching the idempotency key much like a regular map.

func (IdemResMap) Set added in v0.5.0

func (i IdemResMap) Set(ctx context.Context, key string, idemRes IdemRes)

Set overwrites the value paired to key in the map.

For each call to Set, keys older than 24 hours are evicted.

type IdemResMapVal added in v0.5.0

type IdemResMapVal struct {
	IdemRes
	// contains filtered or unexported fields
}

An IdemResMapVal is stored in an IdemResMap, wrapping an IdemRes.

type IdemResRedis added in v0.5.0

type IdemResRedis struct {
	// contains filtered or unexported fields
}

An IdemResRedis connects to a Redis backend for the purposes of caching idempotent responses.

func NewRedisCache added in v0.5.0

func NewRedisCache(opts *redis.Options) IdemResRedis

NewRedisCache constructs an IdemResRedis with the options passed in.

func (IdemResRedis) Get added in v0.5.0

func (i IdemResRedis) Get(ctx context.Context, key string) (IdemRes, bool)

Get retrieves the *IdemRes paired to key from the connected Redis backend.

func (IdemResRedis) Set added in v0.5.0

func (i IdemResRedis) Set(ctx context.Context, key string, idemRes IdemRes)

Set saves the *IdemRes by pairing it to the key in the Redis backend.

type IdempotencyCacher added in v0.5.0

type IdempotencyCacher interface {
	Get(ctx context.Context, key string) (IdemRes, bool)
	Set(ctx context.Context, key string, idemRes IdemRes)
}

An IdempotencyCacher can store responses paired to idempotency keys.

An IdempotencyCacher ought return newly initialized IdemRes when a key does not match an existing IdemRes

type LogRequestRecord added in v0.7.0

type LogRequestRecord struct {
	BodySize       int    `json:"bodySize"`
	Duration       int64  `json:"duration"`
	Host           string `json:"host"`
	ID             string `json:"id"`
	IPAddr         string `json:"remoteAddr"`
	Method         string `json:"method"`
	Path           string `json:"path"`
	Protocol       string `json:"protocol"`
	Referrer       string `json:"referrer"`
	ReqContentLen  int    `json:"contentLength"`
	ReqContentType string `json:"contentType"`
	Scheme         string `json:"scheme"`
	Status         int    `json:"status"`
	URI            string `json:"uri"`
	UserAgent      string `json:"userAgent"`
}

A LogRequestRecord represents the fields that a LogRequest

type User

type User interface {
	HasAccess() bool
	HomePath() string
}

The User defines attributes about a user in the context of middleware.

type UserStorer

type UserStorer func(id uint) (User, error)

UserStorer defines how to retrieve a User by an ID in the context of middleware.

type Visitor

type Visitor struct {
	LastSeen time.Time
	Limiter  *rate.Limiter
}

A Visitor tracks a rate limiter and last seen time.

type Visitors

type Visitors struct {
	sync.Mutex
	// contains filtered or unexported fields
}

A Visitors maps a Visitor to an IP address.

func NewVisitors

func NewVisitors() *Visitors

func (*Visitors) Fetch

func (vs *Visitors) Fetch(ip string) Visitor

Fetch retrieves the Visitor for the given ip creating a new Visitor if not seen.

Newly created visitors are limited to 5 requests every second with bursts of up to 20.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL