xmidt-org/themis

View on GitHub
token/factory.go

Summary

Maintainability
A
0 mins
Test Coverage
package token

import (
    "context"
    "fmt"
    "sync/atomic"

    "github.com/xmidt-org/themis/key"

    jwt "github.com/dgrijalva/jwt-go"
)

const (
    DefaultAlg = "RS256"
)

// Request is a token creation request.  Clients can pass in arbitrary claims, typically things like "iss",
// to merge and override anything set on the factory via configuration.
type Request struct {
    // Claims holds the extra claims to add to tokens.  These claims will override any configured claims in a Factory,
    // but will not override time-based claims such as nbf or exp.
    Claims map[string]interface{}

    // Metadata holds non-claim information about the request, usually garnered from the original HTTP request.  This
    // metadata is available to lower levels of infrastructure used by the Factory.
    Metadata map[string]interface{}
}

// NewRequest returns an empty, fully initialized token Request
func NewRequest() *Request {
    return &Request{
        Claims:   make(map[string]interface{}),
        Metadata: make(map[string]interface{}),
    }
}

// Factory is a creation strategy for signed JWT tokens
type Factory interface {
    // NewToken uses a Request to produce a signed JWT token
    NewToken(context.Context, *Request) (string, error)
}

type factory struct {
    method       jwt.SigningMethod
    claimBuilder ClaimBuilder

    // pair is an atomic value so that future updates can implement key rotation
    pair atomic.Value
}

func (f *factory) NewToken(ctx context.Context, r *Request) (string, error) {
    merged := make(map[string]interface{}, len(r.Claims))
    if err := f.claimBuilder.AddClaims(ctx, r, merged); err != nil {
        return "", err
    }

    token := jwt.NewWithClaims(f.method, jwt.MapClaims(merged))
    pair := f.pair.Load().(key.Pair)
    token.Header["kid"] = pair.KID()
    return token.SignedString(pair.Sign())
}

// NewFactory creates a token Factory from a Descriptor.  The supplied Noncer is used if and only
// if d.Nonce is true.  Alternatively, supplying a nil Noncer will disable nonce creation altogether.
// The token's key pair is registered with the given key Registry.
func NewFactory(o Options, cb ClaimBuilder, kr key.Registry) (Factory, error) {
    if len(o.Alg) == 0 {
        o.Alg = DefaultAlg
    }

    f := &factory{
        method:       jwt.GetSigningMethod(o.Alg),
        claimBuilder: cb,
    }

    if f.method == nil {
        return nil, fmt.Errorf("No such signing method: %s", o.Alg)
    }

    pair, err := kr.Register(o.Key)
    if err != nil {
        return nil, err
    }

    f.pair.Store(pair)
    return f, nil
}