Documentation
¶
Overview ¶
Package usersession issues, validates and ends user sessions. A session is a signed token plus a Redis record under its session id. Every token also carries the user's session epoch from when it was issued; revoking replaces the epoch, which invalidates every token issued before. Password changes and resets revoke, so a stolen session does not outlive them. A sign-in reads the epoch before it checks the credential and gets its session only if the epoch is still the same, so a revocation racing the sign-in does not leave a valid session behind either.
Index ¶
- Constants
- Variables
- func AcquireEpoch(ctx context.Context, client Store, userID int64) (string, error)
- func Check(claims map[string]any, current string) error
- func End(ctx context.Context, client Deleter, sessionID string) error
- func Issue(ctx context.Context, client Store, secret string, lifetime int64, grant Grant) (string, error)
- func Key(userID int64) string
- func RevocationTime(epoch string) (time.Time, bool)
- func Revoke(ctx context.Context, client Store, userID int64) error
- func RevokedSince(ctx context.Context, client Store, userID int64, since time.Time) (bool, error)
- func Rotate(ctx context.Context, client Store, userID int64) (string, error)
- func SessionKey(sessionID string) string
- type Claims
- type Deleter
- type Grant
- type Store
- type ValidationStore
Constants ¶
const ( UserIDClaim = "UserId" SessionIDClaim = "SessionId" LoginTypeClaim = "LoginType" // EpochClaim carries the user's session epoch. EpochClaim = "SessionEpoch" )
The claims of a session token.
Variables ¶
var ( ErrUnavailable = errors.New("user session store unavailable") // ErrInvalidToken reports a token whose signature or lifetime does not // verify. ErrInvalidToken = errors.New("session token is invalid or expired") // ErrNotSession reports a verified token that is not a session: other // tokens, order event tickets for one, are signed with the same secret. ErrNotSession = errors.New("token is not a session") // ErrEnded reports a session that was ended or has expired. ErrEnded = errors.New("session ended") // ErrRevoked reports a session issued before the user's latest // revocation. ErrRevoked = errors.New("session revoked") // ErrEpochMoved reports a sign-in whose credential check a revocation // overtook: the epoch read before the check is no longer the current // one, so no session is issued. ErrEpochMoved = errors.New("user sessions were revoked during sign-in") // ErrDeviceSession reports device claims that are malformed or missing // from a device session; the client has to sign in again. ErrDeviceSession = errors.New("device session must be renewed") // ErrDeviceRevoked reports a device session whose device was revoked. ErrDeviceRevoked = errors.New("device session revoked") )
Functions ¶
func AcquireEpoch ¶
AcquireEpoch returns the epoch a new session of the user carries, creating it on first use. The key never expires: an evicted epoch fails every token that carries one closed.
func Check ¶
Check reports whether a token's claims are still valid against the user's current epoch (empty when none is stored). A token issued before epochs existed carries none and stays valid until the user is first revoked.
func Issue ¶
func Issue(ctx context.Context, client Store, secret string, lifetime int64, grant Grant) (string, error)
Issue signs a session token for grant that lives for lifetime seconds and records the session.
func Key ¶
Key is the Redis key holding the user's session epoch; validation reads it together with the session itself.
func RevocationTime ¶
RevocationTime returns when a revocation epoch was created and whether the epoch came from a revocation at all; the zero time reports a revocation whose time cannot be read.
func RevokedSince ¶
RevokedSince reports whether the user's sessions were revoked at or after since. A revocation epoch carries the time it was created (a UUID v7, to the millisecond), so a capability handed out before the revocation, such as a guest order's checkout token, can be refused without a second record. A revocation in the same millisecond as since, or whose time cannot be read, counts as one after since.
func Rotate ¶
Rotate invalidates every session issued to the user so far and returns the epoch the sessions issued from now on carry. A flow that signs the user in right after revoking, such as a password reset, pins its session to that epoch (Grant.Epoch), so a revocation racing it cannot leave the session valid.
func SessionKey ¶
SessionKey is the Redis key recording the session sessionID; it holds the user id until the session ends.
Types ¶
type Claims ¶
type Claims struct {
UserID int64
SessionID string
LoginType string
// DeviceID is the device the session is bound to; 0 for a session bound
// to no device. The caller still checks that the device is enabled and
// belongs to the user.
DeviceID int64
}
Claims are the claims of a live session.
type Grant ¶
type Grant struct {
UserID int64
// LoginType records how the user signed in; "device" marks a session of
// the device transport.
LoginType string
// DeviceID binds the session to a device, so revoking the device ends it;
// 0 issues a session bound to no device.
DeviceID int64
// Epoch is the user's epoch the sign-in read (AcquireEpoch) before it
// checked the credential. A revocation that lands between the check and
// the session would otherwise be carried by the new token, so Issue
// refuses with ErrEpochMoved once the epoch differs. Empty skips the
// comparison.
Epoch string
}
Grant describes a session to issue.
type Store ¶
type Store interface {
Get(ctx context.Context, key string) *redis.StringCmd
Set(ctx context.Context, key string, value any, expiration time.Duration) *redis.StatusCmd
SetNX(ctx context.Context, key string, value any, expiration time.Duration) *redis.BoolCmd
}
Store is the Redis surface issuing and revoking sessions uses; *redis.Client satisfies it.