pkg/cache/cache.go
Rama master · réplica de lectura del repositorio original.
package cache
import (
"context"
"errors"
"time"
)
// ErrGetNotFound is returned when a key is absent. A miss is an error here rather than a zero
// value, so a caller cannot mistake "nothing cached" for "cached nothing".
var ErrGetNotFound = errors.New("failed to find the value on the cache")
// Get reads a key and decodes it into T, saving the caller a variable to pass by address. It
// returns ErrGetNotFound when the key is absent.
func Get[T any](ctx context.Context, cache Cache, key string) (*T, error) {
var t *T
if err := cache.Get(ctx, key, &t); err != nil {
return nil, err
}
if t == nil {
return nil, ErrGetNotFound
}
return t, nil
}
// Cache is the key/value store the services cache in. Implementations must be safe for concurrent
// use; NewNullCache is the no-op one, which is what an instance without Redis runs.
type Cache interface {
Get(ctx context.Context, key string, value any) error
Set(ctx context.Context, key string, value any, ttl time.Duration) error
Delete(ctx context.Context, key string) error
// SetNX atomically sets key to value with the given ttl only if the key does
// not already exist, and reports whether it was set. It is the building block
// for single-use reservations, such as a one-time token that must be redeemed
// exactly once under concurrent requests, which Get+Set cannot do race-free.
SetNX(ctx context.Context, key string, value any, ttl time.Duration) (bool, error)
// CompareAndDelete atomically deletes key only while it still holds value, and reports whether
// it did. It releases a reservation taken with SetNX under a value unique to its owner, so an
// owner whose reservation expired cannot delete the one another caller took after it.
CompareAndDelete(ctx context.Context, key, value string) (bool, error)
// HasAccountLockout reports whether the source is currently blocked from attempting to
// log in to a user with the specified userID. It returns the absolute Unix timestamp
// in seconds representing the end of the lockout while the cache's clock is before it, or 0
// once that second is reached or no lockout was found; the attempt number and an error
// if any.
HasAccountLockout(ctx context.Context, source, userID string) (lockout int64, attempt int, err error)
// StoreLoginAttempt stores a login attempt from source to the user with the specified userID.
// If the attempt number equals or exceeds 3, it sets a lockout for future login attempts.
//
// The lockout duration is calculated based on the number of attempts made, increasing exponentially
// by a factor of 4 after the third attempt. Attempts must last for half of the double lockout duration.
//
// This means that a user who was locked out for 4 minutes must have the attempts stored for 10
// minutes (or 6 minutes after the timeout). Any wrong attempt within this time will increase the
// lockout once again. After this, the attempts will be reset, and new wrong attempts will start the
// attempt counter from 0.
//
// The following equations are used to calculate both lockout and attempt duration, with 'x' representing
// the lockout duration and 'y' the attempt duration:
//
// F(x) = min(B * 4^(a - 3), M)
// F(y) = max(min(x * 2.5, M), 2 * B)
//
// Where:
//
// x is the lockout duration.
// y is the attempt duration.
// a is the attempt number.
// B is the first lockout's duration, specified by the "SHELLHUB_ACCOUNT_LOCKOUT_BASE" environment variable.
// M is the maximum duration value, specified by the "SHELLHUB_MAXIMUM_ACCOUNT_LOCKOUT" environment variable.
//
// Examples, in minutes, for B = 1 minute, M = 32768 (15 days) and a = n:
//
// n = 3 | 4 | 5 | 8 | 11
// __________________________________
// F(x) = 1 | 4 | 16 | 1024 | 32768
// F(y) = 2.5 | 10 | 40 | 2560 | 32768
//
// It returns the absolute Unix timestamp in seconds representing the end of the lockout, or 0 if no
// lockout was found; the attempt number and an error if any.
StoreLoginAttempt(ctx context.Context, source, userID string) (lockout int64, attempt int, err error)
// ResetLoginAttempts resets the login attempts and associated lockout from the source to
// the user with the specified userID.
ResetLoginAttempts(ctx context.Context, source, userID string) error
}