88 lines
2.2 KiB
Go
88 lines
2.2 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"time"
|
|
|
|
"git.yuntuo.hk/k2safe/go-omni/cache"
|
|
)
|
|
|
|
// Sessions stores server-issued opaque bearer credentials as SHA-256 keys.
|
|
// The caller verifies login credentials before Issue; Store owns persistence.
|
|
type Sessions struct {
|
|
store cache.Store
|
|
prefix string
|
|
ttl time.Duration
|
|
}
|
|
|
|
func NewSessions(store cache.Store, prefix string, ttl time.Duration) (*Sessions, error) {
|
|
if store == nil || len(prefix) > 512 || ttl < time.Second || ttl > 30*24*time.Hour {
|
|
return nil, errors.New("auth: invalid session configuration")
|
|
}
|
|
return &Sessions{store: store, prefix: prefix, ttl: ttl}, nil
|
|
}
|
|
func (s *Sessions) Issue(ctx context.Context, p Principal) (string, error) {
|
|
if p.UserID == "" || len(p.UserID) > 256 || len(p.TenantID) > 256 {
|
|
return "", ErrInvalidToken
|
|
}
|
|
data, err := json.Marshal(p)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(data) > 16384 {
|
|
return "", errors.New("auth: session principal too large")
|
|
}
|
|
secret := make([]byte, 32)
|
|
if _, err := rand.Read(secret); err != nil {
|
|
return "", err
|
|
}
|
|
token := base64.RawURLEncoding.EncodeToString(secret)
|
|
key, _ := s.key(token)
|
|
if err := s.store.Set(ctx, key, data, s.ttl); err != nil {
|
|
return "", err
|
|
}
|
|
return token, nil
|
|
}
|
|
func (s *Sessions) key(token string) (string, error) {
|
|
if len(token) != 43 {
|
|
return "", ErrInvalidToken
|
|
}
|
|
b, err := base64.RawURLEncoding.DecodeString(token)
|
|
if err != nil || len(b) != 32 {
|
|
return "", ErrInvalidToken
|
|
}
|
|
sum := sha256.Sum256(b)
|
|
return s.prefix + hex.EncodeToString(sum[:]), nil
|
|
}
|
|
func (s *Sessions) Authenticate(ctx context.Context, token string) (Principal, error) {
|
|
key, err := s.key(token)
|
|
if err != nil {
|
|
return Principal{}, err
|
|
}
|
|
data, err := s.store.Get(ctx, key)
|
|
if errors.Is(err, cache.ErrMiss) {
|
|
return Principal{}, ErrInvalidToken
|
|
}
|
|
if err != nil {
|
|
return Principal{}, err
|
|
}
|
|
var p Principal
|
|
if len(data) > 16384 || json.Unmarshal(data, &p) != nil || p.UserID == "" {
|
|
return Principal{}, ErrInvalidToken
|
|
}
|
|
return p, nil
|
|
}
|
|
func (s *Sessions) Revoke(ctx context.Context, token string) error {
|
|
key, err := s.key(token)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.store.Delete(ctx, key)
|
|
}
|