Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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"`
cookieBasename string
}
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(cookieBasename string) *TokenOptions {
options := TokenOptions{
cookieBasename: cookieBasename,
}
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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: t.cookieBasename + "-auth",
Value: stoken,
Expires: expiresAt,
Path: "/",
Domain: t.CookieDomain,
Secure: t.CookieSecure,
}
}
return stoken, cookie, nil
}
func (t TokenOptions) ParseToken(tokenString string, claims Claims) error {
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (any, error) {
return t.Secret, nil
})
if err != nil {
return fmt.Errorf("invalid token: %w", err)
}
if !token.Valid {
return fmt.Errorf("invalid token")
}
return nil
}