package auth import ( "encoding/hex" "errors" "fmt" "net/http" "time" "gopkg.in/dgrijalva/jwt-go.v3" ) type ContextIdentityType int const ContextIdentity ContextIdentityType = iota var ( // ErrInvalidCredentials is returned when auth fails because the credentials are // invalid. ErrInvalidCredentials = errors.New("invalid credentials") // ErrAccountidPointofsaleidIncompatible is returned when auth fails because the // credentials contains both a accountid and pointofsaleid. This error will // disappear in a later version. ErrAccountidPointofsaleidIncompatible = errors.New( "credentials cannot contain both accountid and pointofsaleid") // ErrAccountidCobrandidIncompatible is returned when auth fails because the // credentials contains both a accountid and cobrandid. This error will // disappear in a later version. ErrAccountidCobrandidIncompatible = errors.New( "credentials cannot contain both accountid and cobrandid") ) // TokenOptions holds options related to the JWT // //nolint:lll type TokenOptions struct { SecretOpt func(string) error `long:"token-secret" env:"AUTH_TOKEN_SECRET" ini-name:"secret" description:"hex HMAC256/AES256 secret for signing/verifying JWT & crypting (32 bytes)"` Secret []byte `no-flag:"t"` Expiration time.Duration `no-flag:"t"` ExpirationOpt func(string) error `long:"token-expiration" ini-name:"expiration" required:"false" description:"Expiration time of the generated tokens" default:"5m"` CookieDomain string `long:"cookie-domain" ini-name:"cookie-domain" description:"The domain used in cookies, must match the public-facing server host name"` CookieSecure bool `long:"cookie-secure" ini-name:"cookie-secure" description:"Set to true if the cookie must be over https only (recommended)"` CacheMaxSize int `long:"token-cache-max-size" ini-name:"cache-max-size" required:"false" description:"Maximum number of entries in the token cache" default:"10000"` CachePurgeDelay time.Duration `no-flag:"t"` CachePurgeDelayOpt func(string) error `long:"token-cache-purge-delay" ini-name:"cache-purge-delay" required:"false" description:"Delay between token cache purges" default:"10m"` } func durationOption(tgt *time.Duration) func(string) error { return func(duration string) error { d, err := time.ParseDuration(duration) if err != nil { return err } *tgt = d return nil } } func hexBytesOption(size int, tgt *[]byte) func(string) error { return func(s string) error { b, err := hex.DecodeString(s) if err != nil { return fmt.Errorf("invalid hex string: %w", err) } if len(b) != size { return fmt.Errorf("invalid option size. Expected %d bytes, got: %d", size, len(b)) } *tgt = b return nil } } // NewTokenOptions creates a TokenOptions. func NewTokenOptions() *TokenOptions { options := TokenOptions{} options.ExpirationOpt = durationOption(&options.Expiration) options.CachePurgeDelayOpt = durationOption(&options.CachePurgeDelay) options.SecretOpt = hexBytesOption(32, &options.Secret) return &options } type Claims interface { jwt.Claims SetExpiresAt(time.Time) } func (t TokenOptions) MakeToken( claims Claims, withCookie bool, ) (string, *http.Cookie, error) { expiration := t.Expiration if withCookie { expiration *= 2 } expiresAt := time.Now().Add(expiration) claims.SetExpiresAt(expiresAt) token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) stoken, err := token.SignedString(t.Secret) if err != nil { return "", nil, err } var cookie *http.Cookie if withCookie { cookie = &http.Cookie{ Name: "clad-auth", Value: stoken, Expires: expiresAt, Path: "/", Domain: t.CookieDomain, Secure: t.CookieSecure, } } return stoken, cookie, nil }