-
Christophe de Vienne authoredChristophe de Vienne authored
auth.go 3.76 KiB
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
}