91 lines
2.5 KiB
Go
91 lines
2.5 KiB
Go
package ficha
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// RevocationStore is the interface ficha uses to track revoked tokens.
|
|
// Implementations are responsible for storage; ficha provides only the
|
|
// in-memory reference implementation below.
|
|
//
|
|
// Implementations must be safe for concurrent use.
|
|
type RevocationStore interface {
|
|
// IsRevoked reports whether tokenID has been revoked.
|
|
IsRevoked(ctx context.Context, tokenID string) (bool, error)
|
|
|
|
// Revoke marks tokenID as revoked. The until parameter is the
|
|
// token's natural expiry — implementations may discard the entry
|
|
// after that time, since expired tokens fail validation anyway.
|
|
// A zero until indicates the token never expires; the entry must
|
|
// be retained indefinitely.
|
|
Revoke(ctx context.Context, tokenID string, until time.Time) error
|
|
}
|
|
|
|
// MemoryRevocationStore is an in-memory RevocationStore suitable for
|
|
// tests, single-process deployments, or as a reference implementation.
|
|
// Not suitable for production multi-server use — entries are not shared.
|
|
type MemoryRevocationStore struct {
|
|
mu sync.RWMutex
|
|
revoked map[string]time.Time
|
|
now func() time.Time
|
|
}
|
|
|
|
// NewMemoryRevocationStore returns an empty in-memory store.
|
|
func NewMemoryRevocationStore() *MemoryRevocationStore {
|
|
return &MemoryRevocationStore{
|
|
revoked: make(map[string]time.Time),
|
|
now: time.Now,
|
|
}
|
|
}
|
|
|
|
func (m *MemoryRevocationStore) IsRevoked(_ context.Context, tokenID string) (bool, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
until, ok := m.revoked[tokenID]
|
|
if !ok {
|
|
return false, nil
|
|
}
|
|
if !until.IsZero() && !m.now().Before(until) {
|
|
// Past expiry — would fail validation regardless. Treat as not revoked.
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (m *MemoryRevocationStore) Revoke(_ context.Context, tokenID string, until time.Time) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.revoked[tokenID] = until
|
|
return nil
|
|
}
|
|
|
|
// Cleanup removes expired entries. Call periodically to bound memory use.
|
|
// Returns the number of entries removed.
|
|
func (m *MemoryRevocationStore) Cleanup() int {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
now := m.now()
|
|
removed := 0
|
|
for id, until := range m.revoked {
|
|
if until.IsZero() {
|
|
continue
|
|
}
|
|
if !now.Before(until) {
|
|
delete(m.revoked, id)
|
|
removed++
|
|
}
|
|
}
|
|
return removed
|
|
}
|
|
|
|
// Len returns the current number of tracked entries (including any not
|
|
// yet cleaned up). Mainly useful for tests and metrics.
|
|
func (m *MemoryRevocationStore) Len() int {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
return len(m.revoked)
|
|
}
|