Documentation ¶
Overview ¶
Package basculehttp provides a token-based security workflow for HTTP handlers using bascule.
Index ¶
- Constants
- Variables
- func AsValidator[F bascule.ValidatorFunc[*http.Request]](f F) bascule.Validator[*http.Request]
- func BasicAuth(userName, password string) string
- func DefaultErrorMarshaler(_ *http.Request, err error) (contentType string, content []byte, marshalErr error)
- func DefaultErrorStatusCoder(_ *http.Request, err error) int
- func NewAuthenticator(opts ...bascule.AuthenticatorOption[*http.Request]) (*bascule.Authenticator[*http.Request], error)
- func NewAuthorizer(opts ...bascule.AuthorizerOption[*http.Request]) (*bascule.Authorizer[*http.Request], error)
- func UseStatusCode(statusCode int, err error) error
- type AuthorizationParser
- type AuthorizationParserOption
- type BasicToken
- type BasicTokenParser
- type Challenge
- type ChallengeParameters
- func (cp *ChallengeParameters) Len() (c int)
- func (cp *ChallengeParameters) Set(name, value string) (err error)
- func (cp *ChallengeParameters) SetCharset(value string) (err error)
- func (cp *ChallengeParameters) SetRealm(value string) (err error)
- func (cp *ChallengeParameters) String() string
- func (cp *ChallengeParameters) Write(dst *strings.Builder)
- type Challenges
- type ErrorMarshaler
- type ErrorStatusCoder
- type Middleware
- type MiddlewareOption
- func UseAuthenticator(authenticator *bascule.Authenticator[*http.Request], err error) MiddlewareOption
- func UseAuthorizer(authorizer *bascule.Authorizer[*http.Request], err error) MiddlewareOption
- func WithAuthenticator(authenticator *bascule.Authenticator[*http.Request]) MiddlewareOption
- func WithAuthorizer(authorizer *bascule.Authorizer[*http.Request]) MiddlewareOption
- func WithChallenges(ch ...Challenge) MiddlewareOption
- func WithErrorMarshaler(em ErrorMarshaler) MiddlewareOption
- func WithErrorStatusCoder(esc ErrorStatusCoder) MiddlewareOption
- type Scheme
- type UnsupportedSchemeError
Examples ¶
Constants ¶
const ( // WWWAuthenticateHeader is the HTTP header used for StatusUnauthorized challenges // when encountered by the Middleware. // // This value is used by default when no header is supplied to Challenges.WriteHeader. WWWAuthenticateHeader = "WWW-Authenticate" // RealmParameter is the name of the reserved parameter for realm. RealmParameter = "realm" // CharsetParameter is the name of the reserved parameter for charset. CharsetParameter = "charset" // Token68Parameter is the name of the reserved attribute for token68 encoding. // This package does not support token68 encoding. Token68Parameter = "token68" )
const ( // DefaultAuthorizationHeader is the default HTTP header used for authorization // tokens in an HTTP request. DefaultAuthorizationHeader = "Authorization" )
Variables ¶
var ( // ErrInvalidChallengeScheme indicates that a scheme was improperly formatted. Usually, // this methods the scheme was either blank or contained whitespace. ErrInvalidChallengeScheme = errors.New("Invalid challenge auth scheme") // ErrInvalidChallengeParameter indicates that an attempt was made to an a challenge // auth parameter that wasn't validly formatted. Usually, this means that the // name contained whitespace. ErrInvalidChallengeParameter = errors.New("Invalid challenge auth parameter") // ErrReservedChallengeParameter indicates that an attempt was made to add a // challenge auth parameter that was reserved by the RFC. // // Since this package explicitly does not support token68, trying to set a token68 // parameter results in this error. ErrReservedChallengeParameter = errors.New("Reserved challenge auth parameter") )
var ( // ErrInvalidAuthorization indicates an authorization header value did not // correspond to the standard. ErrInvalidAuthorization = errors.New("invalid authorization") )
var ( // ErrNoAuthenticator is returned by NewMiddleware to indicate that an Authorizer // was configured without an Authenticator. ErrNoAuthenticator = errors.New("An Authenticator is required if an Authorizer is configured") )
Functions ¶
func AsValidator ¶ added in v1.1.0
AsValidator an HTTP-specific version of bascule.AsValidator. This function eases the syntactic pain of using golang's generics.
func BasicAuth ¶ added in v1.0.0
BasicAuth produces the basic authorization string described by RFC 2617.
func DefaultErrorMarshaler ¶ added in v1.0.0
func DefaultErrorMarshaler(_ *http.Request, err error) (contentType string, content []byte, marshalErr error)
DefaultErrorMarshaler returns a plaintext representation of the error.
func DefaultErrorStatusCoder ¶ added in v1.0.0
DefaultErrorStatusCoder is the strategy used when no ErrorStatusCoder is supplied. The following tests are done in order:
(1) First, if err is nil, this method returns 0.
(2) If any error in the chain provides a 'StatusCode() int' method, the result from that method is returned.
(3) If err has bascule.ErrMissingCredentials in its chain, this function returns http.StatusUnauthorized.
(3) If err has bascule.ErrBadCredentials in its chain, this function returns http.StatusUnauthorized.
(4) If err has bascule.ErrUnauthorized in its chain, this function returns http.StatusForbidden.
(5) If err has bascule.ErrInvalidCredentials in its chain, this function returns http.StatusBadRequest.
(6) Otherwise, this method returns 0 to indicate that it doesn't know how to produce a status code from the error.
func NewAuthenticator ¶ added in v1.0.0
func NewAuthenticator(opts ...bascule.AuthenticatorOption[*http.Request]) (*bascule.Authenticator[*http.Request], error)
NewAuthenticator is a convenient wrapper around bascule.NewAuthenticator. This function eases the syntactical pain of generics when creating Middleware.
func NewAuthorizer ¶ added in v1.0.0
func NewAuthorizer(opts ...bascule.AuthorizerOption[*http.Request]) (*bascule.Authorizer[*http.Request], error)
NewAuthorizer is a convenient wrapper around bascule.NewAuthorizer. This function eases the syntactical pain of generics when creating Middleware.
func UseStatusCode ¶ added in v1.0.0
UseStatusCode associates an HTTP status code with the given error. This function will override any existing status code associated with err.
Types ¶
type AuthorizationParser ¶ added in v1.0.0
type AuthorizationParser struct {
// contains filtered or unexported fields
}
AuthorizationParsers is a bascule.TokenParser that handles the Authorization header.
By default, this parser will use the standard Authorization header, which can be changed via with WithAuthorizationHeader option.
func NewAuthorizationParser ¶ added in v1.0.0
func NewAuthorizationParser(opts ...AuthorizationParserOption) (*AuthorizationParser, error)
NewAuthorizationParser constructs an Authorization parser from a set of configuration options.
func (*AuthorizationParser) Parse ¶ added in v1.0.0
func (ap *AuthorizationParser) Parse(ctx context.Context, source *http.Request) (bascule.Token, error)
Parse extracts the appropriate header, Authorization by default, and parses the scheme and value. Schemes are case-insensitive, e.g. BASIC and Basic are the same scheme.
If no authorization header is found in the request, this method returns ErrMissingCredentials.
If a token parser is registered for the given scheme, that token parser is invoked. Otherwise, UnsupportedSchemeError is returned, indicating the scheme in question.
type AuthorizationParserOption ¶ added in v1.0.0
type AuthorizationParserOption interface {
// contains filtered or unexported methods
}
AuthorizationParserOption is a configurable option for an AuthorizationParser.
func WithAuthorizationHeader ¶ added in v1.0.0
func WithAuthorizationHeader(header string) AuthorizationParserOption
WithAuthorizationHeader changes the name of the header holding the token. By default, the header used is DefaultAuthorizationHeader.
func WithBasic ¶ added in v1.0.0
func WithBasic() AuthorizationParserOption
WithBasic is a shorthand for WithScheme that registers basic token parsing using the default scheme.
func WithScheme ¶ added in v1.0.0
func WithScheme(scheme Scheme, parser bascule.TokenParser[string]) AuthorizationParserOption
WithScheme registers a string-based token parser that handles a specific authorization scheme. Invocations to this option are cumulative and will overwrite any existing registration.
type BasicToken ¶ added in v1.0.0
BasicToken is the interface that Basic Auth tokens implement.
type BasicTokenParser ¶ added in v1.0.0
type BasicTokenParser struct{}
BasicTokenParser is a string-based bascule.TokenParser that produces BasicToken instances from strings.
func (BasicTokenParser) Parse ¶ added in v1.0.0
Parse assumes that value is of the format required by https://datatracker.ietf.org/doc/html/rfc7617. The returned Token will return the basic auth username from its Principal() method. The returned Token will also implement BasicToken.
type Challenge ¶ added in v1.0.0
type Challenge struct { // Scheme is the name of scheme supplied in the challenge. This field is required. Scheme Scheme // Parameters are the optional auth parameters. Parameters ChallengeParameters }
Challenge represets an HTTP authentication challenge, as defined by RFC 7235.
func NewBasicChallenge ¶ added in v1.0.0
NewBasicChallenge is a convenience for creating a Challenge for basic auth.
Although realm is optional, it is HIGHLY recommended to set it to something recognizable for a client.
type ChallengeParameters ¶ added in v1.0.0
type ChallengeParameters struct {
// contains filtered or unexported fields
}
ChallengeParameters holds the set of parameters. The zero value of this type is ready to use. This type handles writing parameters as well as provides commonly used parameter names for convenience.
It is not required by spec, but any realm parameter is always placed first. Additionally, the output of parameters is consistently ordered and will always follow the order in which the parameters were set, realm being the exception.
Token68 is not supported. Any attempt to set that parameter will result in an error.
func NewChallengeParameters ¶ added in v1.0.0
func NewChallengeParameters(s ...string) (cp ChallengeParameters, err error)
NewChallengeParameters creates a ChallengeParameters from a sequence of name/value pairs. The strings are expected to be in name1, value1, name2, value2, ..., nameN, valueN sequence. If the number of strings is odd, this method returns an error. If any duplicate names occur, only the last name/value pair is used.
If any error occurs while setting parameters, execution is halted and that error is returned.
func (*ChallengeParameters) Len ¶ added in v1.0.0
func (cp *ChallengeParameters) Len() (c int)
Len returns the number of name/value pairs contained in these parameters.
func (*ChallengeParameters) Set ¶ added in v1.0.0
func (cp *ChallengeParameters) Set(name, value string) (err error)
Set sets the value of a parameter. If a parameter was already set, it is ovewritten. The realm may be set via this method, but token68 will be rejected as invalid.
This method returns ErrInvalidChallengeParameter if passed a name or a value that is blank or contains whitespace.
func (*ChallengeParameters) SetCharset ¶ added in v1.1.0
func (cp *ChallengeParameters) SetCharset(value string) (err error)
SetCharset sets a charset auth parameter. Basic auth is the main scheme that uses this. The value cannot be blank or contain any whitespace.
func (*ChallengeParameters) SetRealm ¶ added in v1.1.0
func (cp *ChallengeParameters) SetRealm(value string) (err error)
SetRealm sets a realm auth parameter. The value cannot be blank or contain any whitespace.
func (*ChallengeParameters) String ¶ added in v1.0.0
func (cp *ChallengeParameters) String() string
String returns the RFC 7235 format of these parameters.
func (*ChallengeParameters) Write ¶ added in v1.0.0
func (cp *ChallengeParameters) Write(dst *strings.Builder)
Write formats this challenge to the given builder.
type Challenges ¶ added in v1.0.0
type Challenges []Challenge
Challenges represents a sequence of challenges to associated with a StatusUnauthorized response.
func (Challenges) Append ¶ added in v1.0.0
func (chs Challenges) Append(ch ...Challenge) Challenges
Append appends challenges to this set. The semantics of this method are the same as the built-in append.
func (Challenges) WriteHeader ¶ added in v1.0.0
func (chs Challenges) WriteHeader(dst http.Header) error
WriteHeader write one WWWAuthenticateHeader for each challenge in this set.
If any challenge returns an error during formatting, execution is halted and that error is returned.
func (Challenges) WriteHeaderCustom ¶ added in v1.1.0
func (chs Challenges) WriteHeaderCustom(dst http.Header, name string) error
WriteHeaderCustom inserts one HTTP authenticate header per challenge in this set. If this set is empty, the given http.Header is not modified.
The name is used as the header name for each header this method writes. Typically, this will be WWW-Authenticate or Proxy-Authenticate. The name parameter is required.
If any challenge returns an error during formatting, execution is halted and that error is returned.
type ErrorMarshaler ¶ added in v1.0.0
type ErrorMarshaler func(request *http.Request, err error) (contentType string, content []byte, marshalErr error)
ErrorMarshaler is a strategy for marshaling an error's contents, particularly to be used in an HTTP response body.
type ErrorStatusCoder ¶ added in v1.0.0
ErrorStatusCoder is a strategy for determining the HTTP response code for an error.
If this closure returns a value less than 100, which is the smallest valid HTTP response code, the caller should supply a useful default.
type Middleware ¶ added in v1.0.0
type Middleware struct {
// contains filtered or unexported fields
}
Middleware is an immutable HTTP workflow that can decorate multiple handlers.
A Middleware can have either or both of an Authenticator, which creates tokens from HTTP requests, and an Authorizer, which approves access to the resource identified by the request. The behavior of a Middleware depends mostly on these two components.
If both an authenticator and an authorizer are supplied, the full bascule workflow, including events, is implemented.
If an authenticator is supplied without an authorizer, only token creation is implemented. Without an authorizer, it is assumed that all tokens have access to all requests.
If no authenticator is supplied, but an authorizer IS supplied, then NewMiddleware returns an error. An authenticator is required in order to create tokens.
Finally, if neither an authenticator or an authorizer is supplied, then this Middleware is a noop. Any attempt to decorate handlers will result in those handlers being returned as is. This allows a Middleware to be turned off via configuration.
Example (Authentication) ¶
ExampleMiddleware_authentication shows how to authenticate a token.
tp, _ := NewAuthorizationParser( WithBasic(), ) m, _ := NewMiddleware( UseAuthenticator( NewAuthenticator( bascule.WithTokenParsers(tp), bascule.WithValidators( AsValidator( // the signature of validator closures is very flexible // see bascule.AsValidator func(token bascule.Token) error { if basic, ok := token.(BasicToken); ok && basic.Password() == "correct_password" { return nil } return bascule.ErrBadCredentials }, ), ), ), ), ) h := m.ThenFunc( func(response http.ResponseWriter, request *http.Request) { t, _ := bascule.GetFrom(request) fmt.Println("principal:", t.Principal()) }, ) requestForJoe := httptest.NewRequest("GET", "/", nil) requestForJoe.SetBasicAuth("joe", "correct_password") response := httptest.NewRecorder() h.ServeHTTP(response, requestForJoe) fmt.Println("we let joe in with the code:", response.Code) requestForCurly := httptest.NewRequest("GET", "/", nil) requestForCurly.SetBasicAuth("joe", "bad_password") response = httptest.NewRecorder() h.ServeHTTP(response, requestForCurly) fmt.Println("this isn't joe:", response.Code)
Output: principal: joe we let joe in with the code: 200 this isn't joe: 401
Example (Authorization) ¶
ExampleMiddleware_authorization shows how to set up custom authorization for tokens.
tp, _ := NewAuthorizationParser( WithBasic(), ) m, _ := NewMiddleware( UseAuthenticator( NewAuthenticator( bascule.WithTokenParsers(tp), ), ), UseAuthorizer( NewAuthorizer( bascule.WithApproverFuncs( // this can also be a type that implements the bascule.Approver interface, // when used with bascule.WithApprovers func(_ context.Context, resource *http.Request, token bascule.Token) error { if token.Principal() != "joe" { // only joe can access this resource return bascule.ErrUnauthorized } return nil // approved }, ), ), ), ) h := m.ThenFunc( func(response http.ResponseWriter, request *http.Request) { t, _ := bascule.GetFrom(request) fmt.Println("principal:", t.Principal()) }, ) requestForJoe := httptest.NewRequest("GET", "/", nil) requestForJoe.SetBasicAuth("joe", "password") response := httptest.NewRecorder() h.ServeHTTP(response, requestForJoe) fmt.Println("we let joe in with the code:", response.Code) requestForCurly := httptest.NewRequest("GET", "/", nil) requestForCurly.SetBasicAuth("curly", "another_password") response = httptest.NewRecorder() h.ServeHTTP(response, requestForCurly) fmt.Println("we didn't authorize curly:", response.Code)
Output: principal: joe we let joe in with the code: 200 we didn't authorize curly: 403
Example (Basicauth) ¶
ExampleMiddleware_basicauth illustrates how to use a basculehttp Middleware with just basic auth.
tp, _ := NewAuthorizationParser( WithBasic(), ) m, _ := NewMiddleware( UseAuthenticator( NewAuthenticator( bascule.WithTokenParsers(tp), ), ), ) // decorate a handler that needs authorization h := m.ThenFunc( func(response http.ResponseWriter, request *http.Request) { if t, ok := bascule.GetFrom(request); ok { fmt.Println("principal:", t.Principal()) } else { fmt.Println("no token found") } }, ) // what happens when no authorization is set? noAuth := httptest.NewRequest("GET", "/", nil) response := httptest.NewRecorder() h.ServeHTTP(response, noAuth) fmt.Println("no authorization response code:", response.Code) // what happens when a valid Basic token is set? withBasic := httptest.NewRequest("GET", "/", nil) withBasic.SetBasicAuth("joe", "password") response = httptest.NewRecorder() h.ServeHTTP(response, withBasic) fmt.Println("with basic auth response code:", response.Code)
Output: no authorization response code: 401 principal: joe with basic auth response code: 200
func NewMiddleware ¶ added in v1.0.0
func NewMiddleware(opts ...MiddlewareOption) (m *Middleware, err error)
NewMiddleware creates an immutable Middleware instance from a supplied set of options. No options will result in a Middleware with default behavior.
If no authenticator is configured, but an authorizer is, this function returns ErrNoAuthenticator.
Note that if no workflow components are configured, i.e. neither an authenticator nor an authorizer are supplied, then the returned Middleware is a noop.
func (*Middleware) Then ¶ added in v1.0.0
func (m *Middleware) Then(protected http.Handler) http.Handler
Then produces an http.Handler that uses this Middleware's workflow to protected a given handler.
func (*Middleware) ThenFunc ¶ added in v1.0.0
func (m *Middleware) ThenFunc(protected http.HandlerFunc) http.Handler
ThenFunc is like Then, but protects a handler function.
type MiddlewareOption ¶ added in v1.0.0
type MiddlewareOption interface {
// contains filtered or unexported methods
}
MiddlewareOption is a functional option for tailoring a Middleware.
func UseAuthenticator ¶ added in v1.0.0
func UseAuthenticator(authenticator *bascule.Authenticator[*http.Request], err error) MiddlewareOption
UseAuthenticator is a variant of WithAuthenticator that allows a caller to nest function calls a little easier. The output of NewAuthenticator can be passed directly to this option.
Note: If no authenticator is supplied, NewMiddeware returns an error.
func UseAuthorizer ¶ added in v1.0.0
func UseAuthorizer(authorizer *bascule.Authorizer[*http.Request], err error) MiddlewareOption
UseAuthorizer is a variant of WithAuthorizer that allows a caller to nest function calls a little easier. The output of NewAuthorizer can be passed directly to this option.
func WithAuthenticator ¶ added in v1.0.0
func WithAuthenticator(authenticator *bascule.Authenticator[*http.Request]) MiddlewareOption
WithAuthenticator supplies the Authenticator workflow for the middleware.
func WithAuthorizer ¶ added in v1.0.0
func WithAuthorizer(authorizer *bascule.Authorizer[*http.Request]) MiddlewareOption
WithAuthorizer supplies the Authorizer workflow for the middleware.
The Authorizer is optional. If no authorizer is supplied, then no authorization takes place and no authorization events are fired.
func WithChallenges ¶ added in v1.0.0
func WithChallenges(ch ...Challenge) MiddlewareOption
WithChallenges adds WWW-Authenticate challenges to be used when a StatusUnauthorized is detected. Multiple invocations of this option are cumulative. Each challenge results in a separate WWW-Authenticate header, in the order specified by this option.
func WithErrorMarshaler ¶ added in v1.0.0
func WithErrorMarshaler(em ErrorMarshaler) MiddlewareOption
WithErrorMarshaler sets the strategy used to marshal errors to HTTP response bodies. If this option is omitted or if esc is nil, DefaultErrorMarshaler is used.
func WithErrorStatusCoder ¶ added in v1.0.0
func WithErrorStatusCoder(esc ErrorStatusCoder) MiddlewareOption
WithErrorStatusCoder sets the strategy used to write errors to HTTP responses. If this option is omitted or if esc is nil, DefaultErrorStatusCoder is used.
type Scheme ¶ added in v1.0.0
type Scheme string
Scheme is the authorization header scheme, e.g. Basic, Bearer, etc.
func ParseAuthorization ¶ added in v1.0.0
ParseAuthorization parses an authorization value typically passed in the Authorization HTTP header.
The required format is <scheme><single space><credential value>. This function is strict: it requires no leading or trailing space and exactly (1) space as a separator. If the raw value does not adhere to this format, ErrInvalidAuthorization is returned.
type UnsupportedSchemeError ¶ added in v1.0.0
type UnsupportedSchemeError struct {
Scheme Scheme
}
UnsupportedSchemeError is used to indicate that a particular HTTP Authorization scheme is not supported by the server.
func (*UnsupportedSchemeError) Error ¶ added in v1.0.0
func (use *UnsupportedSchemeError) Error() string
func (*UnsupportedSchemeError) StatusCode ¶ added in v1.0.0
func (use *UnsupportedSchemeError) StatusCode() int
StatusCode marks this error as using the http.StatusUnauthorized code. This is appropriate for almost all cases, as this error occurs because the server does not accept or understand the scheme that the HTTP client supplied.
Source Files ¶
Directories ¶
Path | Synopsis |
---|---|
Package basculecaps provide a standard format for token capabilities in the context of HTTP-based workflow.
|
Package basculecaps provide a standard format for token capabilities in the context of HTTP-based workflow. |