2023-02-24 14:40:28 +01:00
|
|
|
package auth
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
|
2023-03-20 16:40:08 +01:00
|
|
|
"github.com/lestrrat-go/jwx/v2/jwk"
|
|
|
|
"github.com/lestrrat-go/jwx/v2/jws"
|
|
|
|
"github.com/lestrrat-go/jwx/v2/jwt"
|
2023-02-24 14:40:28 +01:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
2023-03-20 16:40:08 +01:00
|
|
|
const (
|
|
|
|
CookieName string = "edge-auth"
|
|
|
|
)
|
|
|
|
|
|
|
|
type GetKeySetFunc func() (jwk.Set, error)
|
|
|
|
|
|
|
|
func WithJWT(getKeySet GetKeySetFunc) OptionFunc {
|
2023-02-24 14:40:28 +01:00
|
|
|
return func(o *Option) {
|
2023-04-18 17:57:16 +02:00
|
|
|
o.GetClaims = func(ctx context.Context, r *http.Request, names ...string) ([]string, error) {
|
|
|
|
claim, err := getClaims[string](r, getKeySet, names...)
|
2023-02-24 14:40:28 +01:00
|
|
|
if err != nil {
|
2023-04-18 17:57:16 +02:00
|
|
|
return nil, errors.WithStack(err)
|
2023-02-24 14:40:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return claim, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-20 16:40:08 +01:00
|
|
|
func FindToken(r *http.Request, getKeySet GetKeySetFunc) (jwt.Token, error) {
|
|
|
|
authorization := r.Header.Get("Authorization")
|
|
|
|
|
|
|
|
// Retrieve token from Authorization header
|
|
|
|
rawToken := strings.TrimPrefix(authorization, "Bearer ")
|
|
|
|
|
|
|
|
// Retrieve token from ?edge-auth=<value>
|
|
|
|
if rawToken == "" {
|
|
|
|
rawToken = r.URL.Query().Get(CookieName)
|
|
|
|
}
|
|
|
|
|
2023-02-24 14:40:28 +01:00
|
|
|
if rawToken == "" {
|
2023-03-20 16:40:08 +01:00
|
|
|
cookie, err := r.Cookie(CookieName)
|
|
|
|
if err != nil && !errors.Is(err, http.ErrNoCookie) {
|
|
|
|
return nil, errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if cookie != nil {
|
|
|
|
rawToken = cookie.Value
|
|
|
|
}
|
2023-02-24 14:40:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if rawToken == "" {
|
2023-03-20 16:40:08 +01:00
|
|
|
return nil, errors.WithStack(ErrUnauthenticated)
|
2023-02-24 14:40:28 +01:00
|
|
|
}
|
|
|
|
|
2023-03-20 16:40:08 +01:00
|
|
|
keySet, err := getKeySet()
|
2023-02-24 14:40:28 +01:00
|
|
|
if err != nil {
|
2023-03-20 16:40:08 +01:00
|
|
|
return nil, errors.WithStack(err)
|
2023-02-24 14:40:28 +01:00
|
|
|
}
|
|
|
|
|
2023-03-28 20:38:29 +02:00
|
|
|
if keySet == nil {
|
|
|
|
return nil, errors.New("no keyset")
|
|
|
|
}
|
|
|
|
|
2023-03-20 16:40:08 +01:00
|
|
|
token, err := jwt.Parse([]byte(rawToken),
|
|
|
|
jwt.WithKeySet(keySet, jws.WithRequireKid(false)),
|
|
|
|
jwt.WithValidate(true),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.WithStack(err)
|
2023-02-24 14:40:28 +01:00
|
|
|
}
|
|
|
|
|
2023-03-20 16:40:08 +01:00
|
|
|
return token, nil
|
|
|
|
}
|
|
|
|
|
2023-04-18 17:57:16 +02:00
|
|
|
func getClaims[T any](r *http.Request, getKeySet GetKeySetFunc, names ...string) ([]T, error) {
|
2023-03-20 16:40:08 +01:00
|
|
|
token, err := FindToken(r, getKeySet)
|
|
|
|
if err != nil {
|
2023-04-18 17:57:16 +02:00
|
|
|
return nil, errors.WithStack(err)
|
2023-03-20 16:40:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ctx := r.Context()
|
|
|
|
|
|
|
|
mapClaims, err := token.AsMap(ctx)
|
|
|
|
if err != nil {
|
2023-04-18 17:57:16 +02:00
|
|
|
return nil, errors.WithStack(err)
|
2023-02-24 14:40:28 +01:00
|
|
|
}
|
|
|
|
|
2023-04-18 17:57:16 +02:00
|
|
|
claims := make([]T, len(names))
|
|
|
|
|
|
|
|
for idx, n := range names {
|
|
|
|
rawClaim, exists := mapClaims[n]
|
|
|
|
if !exists {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
claim, ok := rawClaim.(T)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.Errorf("unexpected claim '%s' to be of type '%T', got '%T'", n, new(T), rawClaim)
|
|
|
|
}
|
2023-02-24 14:40:28 +01:00
|
|
|
|
2023-04-18 17:57:16 +02:00
|
|
|
claims[idx] = claim
|
2023-02-24 14:40:28 +01:00
|
|
|
}
|
|
|
|
|
2023-04-18 17:57:16 +02:00
|
|
|
return claims, nil
|
2023-02-24 14:40:28 +01:00
|
|
|
}
|