refac: removed 7 direct and 12 indirect go modules for custom code instead
This commit is contained in:
1 parent
cfcf108488
commit
c68e9a7858
73 files changed
+2455
-428
No files matched your search
@@ -0,0 +1,170 @@
|
||||
package authtoken
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"maps"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
SessionLifetime = 24 * time.Hour
|
||||
|
||||
algorithmHS256 = "HS256"
|
||||
tokenType = "JWT"
|
||||
maxTokenBytes = 4096
|
||||
segmentCount = 3
|
||||
segmentSep = "."
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoToken = errors.New("no token found")
|
||||
ErrInvalid = errors.New("token is invalid")
|
||||
ErrExpired = errors.New("token is expired")
|
||||
ErrNotYetValid = errors.New("token is not yet valid")
|
||||
)
|
||||
|
||||
type Claims map[string]any
|
||||
|
||||
type header struct {
|
||||
Algorithm string `json:"alg"`
|
||||
Type string `json:"typ,omitempty"`
|
||||
}
|
||||
|
||||
type Auth struct {
|
||||
secret []byte
|
||||
clock func() time.Time
|
||||
}
|
||||
|
||||
type Option func(*Auth)
|
||||
|
||||
func WithClock(clock func() time.Time) Option {
|
||||
return func(a *Auth) { a.clock = clock }
|
||||
}
|
||||
|
||||
func New(secret []byte, options ...Option) *Auth {
|
||||
auth := &Auth{secret: secret, clock: time.Now}
|
||||
for _, option := range options {
|
||||
option(auth)
|
||||
}
|
||||
return auth
|
||||
}
|
||||
|
||||
func (a *Auth) Encode(claims map[string]any) (string, error) {
|
||||
payload := maps.Clone(claims)
|
||||
if payload == nil {
|
||||
payload = map[string]any{}
|
||||
}
|
||||
now := a.clock()
|
||||
if _, ok := payload["iat"]; !ok {
|
||||
payload["iat"] = now.Unix()
|
||||
}
|
||||
if _, ok := payload["exp"]; !ok {
|
||||
payload["exp"] = now.Add(SessionLifetime).Unix()
|
||||
}
|
||||
|
||||
headerSegment, err := encodeSegment(header{Algorithm: algorithmHS256, Type: tokenType})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
payloadSegment, err := encodeSegment(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
signingInput := headerSegment + segmentSep + payloadSegment
|
||||
return signingInput + segmentSep + a.sign(signingInput), nil
|
||||
}
|
||||
|
||||
func (a *Auth) Decode(token string) (Claims, error) {
|
||||
if token == "" {
|
||||
return nil, ErrNoToken
|
||||
}
|
||||
if len(token) > maxTokenBytes {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
|
||||
segments := strings.Split(token, segmentSep)
|
||||
if len(segments) != segmentCount {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
|
||||
var tokenHeader header
|
||||
if err := decodeSegment(segments[0], &tokenHeader); err != nil || tokenHeader.Algorithm != algorithmHS256 {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
|
||||
signingInput := segments[0] + segmentSep + segments[1]
|
||||
if !hmac.Equal([]byte(a.sign(signingInput)), []byte(segments[2])) {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
|
||||
var claims Claims
|
||||
if err := decodeSegment(segments[1], &claims); err != nil || claims == nil {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
|
||||
if err := a.validateTimes(claims); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func (a *Auth) validateTimes(claims Claims) error {
|
||||
now := a.clock()
|
||||
|
||||
expiry, present, err := numericDate(claims, "exp")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if present && !now.Before(expiry) {
|
||||
return ErrExpired
|
||||
}
|
||||
|
||||
notBefore, present, err := numericDate(claims, "nbf")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if present && now.Before(notBefore) {
|
||||
return ErrNotYetValid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func numericDate(claims Claims, name string) (time.Time, bool, error) {
|
||||
raw, present := claims[name]
|
||||
if !present {
|
||||
return time.Time{}, false, nil
|
||||
}
|
||||
seconds, ok := raw.(float64)
|
||||
if !ok {
|
||||
return time.Time{}, false, ErrInvalid
|
||||
}
|
||||
return time.Unix(int64(seconds), 0), true, nil
|
||||
}
|
||||
|
||||
func (a *Auth) sign(signingInput string) string {
|
||||
mac := hmac.New(sha256.New, a.secret)
|
||||
mac.Write([]byte(signingInput))
|
||||
return base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
|
||||
}
|
||||
|
||||
func encodeSegment(value any) (string, error) {
|
||||
encoded, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(encoded), nil
|
||||
}
|
||||
|
||||
func decodeSegment(segment string, into any) error {
|
||||
decoded, err := base64.RawURLEncoding.DecodeString(segment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(decoded, into)
|
||||
}
|
||||
Reference in new issue
Block a user