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) }