2016-06-07 01:27:44 +03:00
|
|
|
package request
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"net/http"
|
2022-08-19 14:59:36 +03:00
|
|
|
"strings"
|
2016-06-07 01:27:44 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
// Errors
|
|
|
|
var (
|
|
|
|
ErrNoTokenInRequest = errors.New("no token present in request")
|
|
|
|
)
|
|
|
|
|
2021-08-03 16:51:01 +03:00
|
|
|
// Extractor is an interface for extracting a token from an HTTP request.
|
2016-06-07 02:55:41 +03:00
|
|
|
// The ExtractToken method should return a token string or an error.
|
|
|
|
// If no token is present, you must return ErrNoTokenInRequest.
|
2016-06-07 01:27:44 +03:00
|
|
|
type Extractor interface {
|
|
|
|
ExtractToken(*http.Request) (string, error)
|
|
|
|
}
|
|
|
|
|
2021-08-03 16:51:01 +03:00
|
|
|
// HeaderExtractor is an extractor for finding a token in a header.
|
|
|
|
// Looks at each specified header in order until there's a match
|
2016-06-07 01:27:44 +03:00
|
|
|
type HeaderExtractor []string
|
|
|
|
|
|
|
|
func (e HeaderExtractor) ExtractToken(req *http.Request) (string, error) {
|
|
|
|
// loop over header names and return the first one that contains data
|
|
|
|
for _, header := range e {
|
|
|
|
if ah := req.Header.Get(header); ah != "" {
|
|
|
|
return ah, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "", ErrNoTokenInRequest
|
|
|
|
}
|
|
|
|
|
2021-08-03 16:51:01 +03:00
|
|
|
// ArgumentExtractor extracts a token from request arguments. This includes a POSTed form or
|
2016-06-07 02:55:41 +03:00
|
|
|
// GET URL arguments. Argument names are tried in order until there's a match.
|
2016-06-15 20:49:54 +03:00
|
|
|
// This extractor calls `ParseMultipartForm` on the request
|
2016-06-07 01:27:44 +03:00
|
|
|
type ArgumentExtractor []string
|
|
|
|
|
|
|
|
func (e ArgumentExtractor) ExtractToken(req *http.Request) (string, error) {
|
2023-03-31 14:20:59 +03:00
|
|
|
// Make sure form is parsed. We are explicitly ignoring errors at this point
|
|
|
|
_ = req.ParseMultipartForm(10e6)
|
2016-06-07 01:27:44 +03:00
|
|
|
|
|
|
|
// loop over arg names and return the first one that contains data
|
|
|
|
for _, arg := range e {
|
|
|
|
if ah := req.Form.Get(arg); ah != "" {
|
|
|
|
return ah, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", ErrNoTokenInRequest
|
|
|
|
}
|
|
|
|
|
2021-08-03 16:51:01 +03:00
|
|
|
// MultiExtractor tries Extractors in order until one returns a token string or an error occurs
|
2016-06-07 01:27:44 +03:00
|
|
|
type MultiExtractor []Extractor
|
|
|
|
|
|
|
|
func (e MultiExtractor) ExtractToken(req *http.Request) (string, error) {
|
|
|
|
// loop over header names and return the first one that contains data
|
|
|
|
for _, extractor := range e {
|
|
|
|
if tok, err := extractor.ExtractToken(req); tok != "" {
|
|
|
|
return tok, nil
|
2021-12-15 14:50:05 +03:00
|
|
|
} else if !errors.Is(err, ErrNoTokenInRequest) {
|
2016-06-07 01:27:44 +03:00
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "", ErrNoTokenInRequest
|
|
|
|
}
|
|
|
|
|
2021-08-03 16:51:01 +03:00
|
|
|
// PostExtractionFilter wraps an Extractor in this to post-process the value before it's handed off.
|
2016-06-07 02:55:41 +03:00
|
|
|
// See AuthorizationHeaderExtractor for an example
|
2016-06-07 01:27:44 +03:00
|
|
|
type PostExtractionFilter struct {
|
|
|
|
Extractor
|
|
|
|
Filter func(string) (string, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *PostExtractionFilter) ExtractToken(req *http.Request) (string, error) {
|
|
|
|
if tok, err := e.Extractor.ExtractToken(req); tok != "" {
|
|
|
|
return e.Filter(tok)
|
|
|
|
} else {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
2022-08-19 14:59:36 +03:00
|
|
|
|
|
|
|
// BearerExtractor extracts a token from the Authorization header.
|
|
|
|
// The header is expected to match the format "Bearer XX", where "XX" is the
|
|
|
|
// JWT token.
|
|
|
|
type BearerExtractor struct{}
|
|
|
|
|
|
|
|
func (e BearerExtractor) ExtractToken(req *http.Request) (string, error) {
|
|
|
|
tokenHeader := req.Header.Get("Authorization")
|
|
|
|
// The usual convention is for "Bearer" to be title-cased. However, there's no
|
|
|
|
// strict rule around this, and it's best to follow the robustness principle here.
|
|
|
|
if tokenHeader == "" || !strings.HasPrefix(strings.ToLower(tokenHeader), "bearer ") {
|
|
|
|
return "", ErrNoTokenInRequest
|
|
|
|
}
|
|
|
|
return tokenHeader[7:], nil
|
|
|
|
}
|