gopgsession

package module
v0.3.14 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 7, 2024 License: MIT Imports: 21 Imported by: 0

README

go-pg-session

go-pg-session is a distributed session management library that uses PostgreSQL's LISTEN/NOTIFY mechanism to handle session updates across multiple nodes. It leverages the pgln library to quickly recover from disconnections, ensuring robust and reliable session management.

⭐️ Star This Project ⭐️

If you find this project helpful, please give it a star on GitHub! Your support is greatly appreciated.

Table of Contents

Background

The go-pg-session library was created with the goal of combining the speed of JWTs with the simplicity and security of a backend session library. Recognizing that sessions are typically slow to change per user and are mainly read rather than written, this library implements an eventually consistent memory caching strategy on each node.

This approach offers a hybrid solution that leverages the benefits of both cookie-based and server-side session management:

  • Security:

    • Minimized Data Exposure: Only the session identifier is stored in the cookie, minimizing exposure to sensitive data.
    • Server-Side Data Integrity: Actual session data is stored and managed server-side, ensuring data integrity and security.
  • Scalability:

    • Lightweight Cookies: The cookie remains lightweight as it only contains the session identifier. This significantly reduces data transfer costs, which are often the highest expense in modern web applications.
  • Performance:

    • Efficient Session Retrieval: Session data can be fetched quickly using the identifier, and caching strategies are employed to optimize performance.
    • In-Memory Read Operations: Unlike Redis-based solutions, read operations are performed directly from memory, making them even faster than Redis reads. This approach provides ultra-low latency for session data access.
  • Flexibility:

    • No Size Limitations: The database can store large and complex session data without the size limitations of cookies.
    • Persistent Storage: Sessions can persist across server restarts and crashes.
  • Simplified Infrastructure:

    • No Additional Caching Servers: Unlike solutions that rely on Redis, go-pg-session eliminates the need for additional Redis servers and complex Redis cluster management. This simplifies the infrastructure, reducing operational complexity and costs.
    • Leveraging Existing Database: By using PostgreSQL for both data storage and real-time notifications, the solution minimizes the number of components in the system architecture.

Features

  • Distributed Session Management: Uses PostgreSQL LISTEN/NOTIFY for real-time session updates.
  • Quick Recovery: Utilizes the pgln library to handle disconnections efficiently.
  • Session Caching: In-memory caching of sessions with LRU eviction policy.
  • Session Expiration: Automatically expires sessions based on configured durations.
  • Periodic Cleanup: Periodically cleans up expired sessions.
  • Efficient Last Access Update: Accumulates last access times and updates them in batches at a predefined interval, reducing performance hits during session retrieval.
  • Highly Configurable: Various settings can be customized via a configuration structure.
  • Optimistic Locking:
    • Session-Level Versioning: Supports version-based optimistic locking for entire sessions.
    • Attribute-Level Versioning: Option to check versions of individual session attributes, providing fine-grained control over concurrent modifications.
  • Attribute-Level Expiration: Individual session attributes can have their own expiration times, providing fine-grained control over data lifecycle.
  • Distributed Locks: Provides a mechanism for coordinating access to shared resources across multiple nodes.
  • Flexible Inactivity Handling: Option to include or exclude sessions from inactivity-based expiration.
  • Dynamic Expiration Management: Ability to update session expiration time dynamically.
Attribute-Level Expiration: A Powerful Feature

The attribute-level expiration feature in go-pg-session offers several significant benefits:

  1. Fine-Grained Data Control: Unlike whole-session expiration, attribute expiration allows you to set different lifetimes for different pieces of data within a session. This is particularly useful for managing sensitive or temporary information.

  2. Enhanced Security: Sensitive data can be automatically removed from the session after a short period, reducing the window of vulnerability without affecting the overall session.

  3. Compliance with Data Regulations: For applications that need to comply with data protection regulations (like GDPR), attribute expiration provides a mechanism to ensure that certain types of data are not retained longer than necessary.

  4. Optimized Storage: By allowing frequently changing or temporary data to expire automatically, you can keep your session data lean and relevant, potentially improving performance and reducing storage costs.

  5. Flexible User Experiences: You can implement features that require temporary elevated permissions or time-limited offers without compromising on security or user convenience.

Example use cases:

  • Elevated Security (Step-Up Authentication): After a user performs a sensitive action requiring additional authentication, you can store a temporary "elevated_access" attribute that automatically expires after a short period:

    // Grant elevated access for 15 minutes after step-up authentication
    elevatedExpiry := time.Now().Add(15 * time.Minute)
    session.UpdateAttribute("elevated_access", "true", gopgsession.WithUpdateAttExpiresAt(elevatedExpiry))
    
  • Limited-Time Offers: Store time-sensitive promotional codes or offers that automatically expire:

    // Set a promotional offer that expires in 1 hour
    offerExpiry := time.Now().Add(1 * time.Hour)
    session.UpdateAttribute("promo_code", "FLASH_SALE_20", gopgsession.WithUpdateAttExpiresAt(offerExpiry))
    
  • Abandoned UI Processes: For multi-step processes in your UI, store temporary state that cleans itself up if the user abandons the process:

    // Store temporary form data for a multi-step process, expires in 30 minutes
    formExpiry := time.Now().Add(30 * time.Minute)
    session.UpdateAttribute("temp_form_data", jsonEncodedFormData, gopgsession.WithUpdateAttExpiresAt(formExpiry))
    
  • Temporary Access Tokens: Store short-lived access tokens for external services:

    // Store an API token that expires in 5 minutes
    tokenExpiry := time.Now().Add(5 * time.Minute)
    session.UpdateAttribute("external_api_token", "token123", gopgsession.WithUpdateAttExpiresAt(tokenExpiry))
    

By leveraging attribute-level expiration, you can implement these features securely and efficiently, automatically cleaning up sensitive or temporary data without manual intervention or complex cleanup processes. This feature allows for more nuanced session management, enhancing both security and user experience in your application.

Benchmark Results

The following table compares the performance of go-pg-session (PostgreSQL-based) with a Redis-based session management solution. These benchmarks were run on an ARM64 Darwin system.

Operation Storage Operations/sec Nanoseconds/op
GetSession PostgreSQL + go-pg-session 326,712 3,061
GetSession Redis 38,567 25,929
UpdateSession PostgreSQL + go-pg-session 2,137 467,921
UpdateSession Redis 19,515 51,243
Key Observations:
  1. Read Performance (GetSession):

    • go-pg-session outperforms Redis by a significant margin, processing about 8.5 times more read operations per second.
    • The latency for read operations with go-pg-session is about 8.5 times lower than Redis.
  2. Write Performance (UpdateSession):

    • Redis shows better performance for write operations, processing about 9 times more updates per second.
    • The latency for write operations with Redis is about 9 times lower than go-pg-session.
  3. Overall Performance:

    • go-pg-session excels in read-heavy scenarios, which aligns with typical session management use cases where reads are far more frequent than writes.
    • The PostgreSQL-based solution offers a better balance between read and write performance, making it suitable for a wide range of applications.

These results demonstrate that go-pg-session is particularly well-suited for applications with high read volumes, offering superior performance for session retrieval operations. While Redis shows better performance for write operations, the overall balance and especially the read performance of go-pg-session make it an excellent choice for most session management scenarios.

Remember that performance can vary based on hardware, network conditions, and specific use cases. It's always recommended to benchmark in your specific environment for the most accurate results.

Note: A would be go-pg-session for redis (or go-redis-session) could outperform go-pg-session when writing for sure, but the aim of this library is to use only PostgreSQL without additional services like redis. Moreover, since sessions are mostly read oriented, this is less of an advantage. It is possible that a go-redis-session would be great for developers who use redis already and may be developed if there is a demand.

Installation

To install the package, run:

go get github.com/tzahifadida/go-pg-session

Configuration

Create a configuration using the Config struct. You can use the DefaultConfig function to get a default configuration and modify it as needed.

Config Fields
  • MaxSessions: Maximum number of concurrent sessions allowed per user.
  • MaxAttributeLength: Maximum length of session attributes.
  • SessionExpiration: Duration after which a session expires.
  • InactivityDuration: Duration of inactivity after which a session expires.
  • CleanupInterval: Interval at which expired sessions are cleaned up.
  • CacheSize: Size of the in-memory cache for sessions.
  • TablePrefix: Prefix for the table names used in the database.
  • SchemaName: Name of the schema used in the database.
  • CreateSchemaIfMissing: Flag to create the schema if it is missing.
  • LastAccessUpdateInterval: Interval for updating the last access time of sessions.
  • LastAccessUpdateBatchSize: Batch size for updating last access times.
  • NotifyOnUpdates: Flag to enable/disable notifications on updates.
  • MaxSessionLifetimeInCache: Maximum duration a session can remain in the cache before being refreshed from the database.
cfg := gopgsession.DefaultConfig()
cfg.MaxSessions = 10
cfg.SessionExpiration = 24 * time.Hour // 1 day
cfg.CreateSchemaIfMissing = true
cfg.MaxSessionLifetimeInCache = 1 * time.Hour // Force refresh after 1 hour
cfg.InactivityDuration = 2 * time.Hour // Sessions expire after 2 hours of inactivity
MaxSessionLifetimeInCache

The MaxSessionLifetimeInCache setting allows you to control how long a session can remain in the cache before it's forcefully refreshed from the database. This feature helps ensure that any manual changes made to the session in the database (e.g., forced logouts) are reflected in the cache within a reasonable timeframe.

  • If set to a positive duration, sessions older than this duration will be refreshed from the database on the next access.
  • If set to 0 or a negative value, this feature is disabled, and sessions will remain in the cache until explicitly invalidated or evicted.

By default, this value is set to 48 hours. Adjust this setting based on your application's needs and the frequency of out-of-band session modifications.

Usage Examples

Initialization

Initialize a SessionManager with the configuration and a PostgreSQL database connection.

import (
"database/sql"
_ "github.com/jackc/pgx/v5/stdlib"
"github.com/tzahifadida/go-pg-session"
)

// Open a database connection
db, err := sql.Open("pgx", "postgres://username:password@localhost/dbname?sslmode=disable")
if err != nil {
log.Fatalf("Failed to open database: %v", err)
}
defer db.Close()

// Create a new SessionManager
sessionManager, err := gopgsession.NewSessionManager(ctx, cfg, db)
if err != nil {
log.Fatalf("Failed to initialize session manager: %v", err)
}
defer sessionManager.Shutdown(context.Background())
Creating a Session with Custom Options

Create a session for a user with initial attributes and custom options.

ctx := context.Background()
userID := uuid.New()
attributes := map[string]gopgsession.SessionAttributeValue{
"role":        {Value: "admin"},
"preferences": {Value: map[string]string{"theme": "dark"}},
}

// Create a session that doesn't expire due to inactivity and has a custom expiration time
session, err := sessionManager.CreateSession(ctx, userID, attributes,
gopgsession.WithCreateIncludeInactivity(false),
gopgsession.WithCreateExpiresAt(time.Now().Add(48 * time.Hour)))
if err != nil {
log.Fatalf("Failed to create session: %v", err)
}

log.Printf("Created session with ID: %s", session.ID)
Retrieving a Session

Retrieve a session by its ID. You can use GetSession with optional parameters for more control.

session, err := sessionManager.GetSession(ctx, sessionID)
if err != nil {
log.Fatalf("Failed to retrieve session: %v", err)
}

// With options
session, err := sessionManager.GetSession(ctx, sessionID, gopgsession.WithGetDoNotUpdateSessionLastAccess(), gopgsession.WithGetForceRefresh())
if err != nil {
log.Fatalf("Failed to retrieve session: %v", err)
}

var preferences map[string]string
_, err = session.GetAttributeAndRetainUnmarshaled("preferences", &preferences)
if err != nil {
log.Fatalf("Failed to get preferences: %v", err)
}

log.Printf("User preferences: %v", preferences)
Updating Session Expiration

Update the expiration time of an existing session.

session, err := sessionManager.GetSession(ctx, sessionID)
if err != nil {
log.Fatalf("Failed to retrieve session: %v", err)
}

// Extend the session's expiration time
newExpiresAt := time.Now().Add(24 * time.Hour)
updatedSession, err := sessionManager.UpdateSession(ctx, session,
gopgsession.WithUpdateExpiresAt(newExpiresAt))
if err != nil {
log.Fatalf("Failed to update session expiration: %v", err)
}

log.Printf("Updated session expiration to: %v", updatedSession.ExpiresAt)
Toggling Inactivity Expiration

Change whether a session should expire due to inactivity.

session, err := sessionManager.GetSession(ctx, sessionID)
if err != nil {
log.Fatalf("Failed to retrieve session: %v", err)
}

// Disable inactivity-based expiration for this session
updatedSession, err := sessionManager.UpdateSession(ctx, session,
gopgsession.WithUpdateIncludeInactivity(false))
if err != nil {
log.Fatalf("Failed to update session inactivity setting: %v", err)
}

log.Printf("Updated session inactivity setting: %v", updatedSession.IncludeInactivity)
Updating a Session with Version Check

Update a session while ensuring version consistency at the session level.

session, err := sessionManager.GetSession(ctx, sessionID)
if err != nil {
log.Fatalf("Failed to retrieve session: %v", err)
}

err = session.UpdateAttribute("last_access", time.Now())
if err != nil {
log.Fatalf("Failed to update session attribute: %v", err)
}

updatedSession, err := sessionManager.UpdateSession(ctx, session, gopgsession.WithUpdateCheckVersion())
if err != nil {
log.Fatalf("Failed to update session: %v", err)
}
Updating a Session with Attribute Version Check

Update a session while ensuring version consistency at the attribute level.

session, err := sessionManager.GetSession(ctx, sessionID)
if err != nil {
    log.Fatalf("Failed to retrieve session: %v", err)
}

var preferences map[string]string
_, err = session.GetAttributeAndRetainUnmarshaled("preferences", &preferences)
if err != nil {
    log.Fatalf("Failed to get preferences: %v", err)
}

preferences["theme"] = "light"
err = session.UpdateAttribute("preferences", preferences)
if err != nil {
    log.Fatalf("Failed to update session attribute: %v", err)
}

updatedSession, err := sessionManager.UpdateSession(ctx, session, gopgsession.WithUpdateCheckAttributeVersion())
if err != nil {
    log.Fatalf("Failed to update session: %v", err)
}
Deleting a Session

Delete a session by its ID.

err = sessionManager.DeleteSession(ctx, sessionID)
if err != nil {
    log.Fatalf("Failed to delete session: %v", err)
}

log.Printf("Deleted session with ID: %s", sessionID)
Login and Logout Handlers

Here are examples of login and logout handlers that demonstrate session creation and deletion:

func loginHandler(sm *gopgsession.SessionManager) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // Authenticate user (simplified for example)
        username := r.FormValue("username")
        password := r.FormValue("password")

        userID, err := authenticateUser(username, password)
        if err != nil {
            http.Error(w, "Authentication failed", http.StatusUnauthorized)
            return
        }

        // Create new session
        attributes := map[string]gopgsession.SessionAttributeValue{
            "username":    {Value: username},
            "last_login":  {Value: time.Now().Format(time.RFC3339)},
        }
        session, err := sm.CreateSession(r.Context(), userID, attributes)
        if err != nil {
            http.Error(w, "Failed to create session", http.StatusInternalServerError)
            return
        }

        // Set session cookie
        http.SetCookie(w, &http.Cookie{
            Name:     "session_id",
            Value:    session.ID.String(),
            HttpOnly: true,
            Secure:   true,
            SameSite: http.SameSiteStrictMode,
        })

        w.WriteHeader(http.StatusOK)
        w.Write([]byte("Login successful"))
    }
}

func logoutHandler(sm *gopgsession.SessionManager) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        sessionCookie, err := r.Cookie("session_id")
        if err != nil {
            http.Error(w, "No session found", http.StatusBadRequest)
            return
        }

        sessionID, err := uuid.Parse(sessionCookie.Value)
        if err != nil {
            http.Error(w, "Invalid session ID", http.StatusBadRequest)
            return
        }

        // Delete the session
        err = sm.DeleteSession(r.Context(), sessionID)
        if err != nil {
            http.Error(w, "Failed to delete session", http.StatusInternalServerError)
            return
        }

        // Clear the session cookie
        http.SetCookie(w, &http.Cookie{
            Name:     "session_id",
            Value:    "",
            HttpOnly: true,
            Secure:   true,
            SameSite: http.SameSiteStrictMode,
            MaxAge:   -1,
        })

        w.WriteHeader(http.StatusOK)
        w.Write([]byte("Logout successful"))
    }
}
Concurrent Updates Example

Here's a refined example of handling concurrent updates to a session, useful for scenarios like a shopping cart:

func addToCartHandler(sm *gopgsession.SessionManager) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        sessionID, _ := uuid.Parse(r.Cookie("session_id").Value)
        itemID := r.FormValue("item_id")

        maxRetries := 3
        for attempt := 0; attempt < maxRetries; attempt++ {
            var session *gopgsession.Session
            var err error

            if attempt == 0 {
                session, err = sm.GetSession(r.Context(), sessionID)
            } else {
                session, err = sm.GetSession(r.Context(), sessionID, gopgsession.WithGetForceRefresh())
            }

            if err != nil {
                http.Error(w, "Failed to retrieve session", http.StatusInternalServerError)
                return
            }

            // Business logic: Update cart
            var cartItems []string
            _, err = session.GetAttributeAndRetainUnmarshaled("cart", &cartItems)
            if err != nil && err.Error() != "attribute cart not found" {
                http.Error(w, "Failed to get cart", http.StatusInternalServerError)
                return
            }
            cartItems = append(cartItems, itemID)

            err = session.UpdateAttribute("cart", cartItems)
            if err != nil {
                http.Error(w, "Failed to update cart", http.StatusInternalServerError)
                return
            }

            _, err = sm.UpdateSession(r.Context(), session, gopgsession.WithUpdateCheckAttributeVersion())
            if err != nil {
                if err.Error() == "attribute cart version mismatch" {
                    continue // Retry
                }
                http.Error(w, "Failed to save session", http.StatusInternalServerError)
                return
            }

            // Success
            w.WriteHeader(http.StatusOK)
            w.Write([]byte("Item added to cart"))
            return
        }

        http.Error(w, "Failed to add item to cart after max retries", http.StatusConflict)
    }
}
Signed Cookies for DDOS Mitigation

To reduce overhead when handling requests for non-existent sessions (which could be part of a DDOS attack), you can sign the session ID cookie. This allows you to verify the signature before attempting to retrieve the session from the database or cache. The HMACSHA256SignerPool provides SignAndEncode and VerifyAndDecode methods for this purpose.

Here's an example of how to implement this:

import (
    "github.com/tzahifadida/go-pg-session"
    "github.com/google/uuid"
    "fmt"
)

// Initialize the signer
secret := []byte("your-secret-key")
signerPool := gopgsession.NewHMACSHA256SignerPool(secret, 10)

// When creating a session and setting the cookie
func loginHandler(sm *gopgsession.SessionManager) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // ... (authentication logic)

        session, err := sm.CreateSession(r.Context(), userID, attributes)
        if err != nil {
            http.Error(w, "Failed to create session", http.StatusInternalServerError)
            return
        }

        // Sign and encode the session ID
        signedSessionID, err := signerPool.SignAndEncode(session.ID.String())
        if err != nil {
            http.Error(w, "Failed to sign session", http.StatusInternalServerError)
            return
        }

        // Set the signed session cookie
        http.SetCookie(w, &http.Cookie{
            Name:     "session_id",
            Value:    signedSessionID,
            HttpOnly: true,
            Secure:   true,
            SameSite: http.SameSiteStrictMode,
        })

        w.WriteHeader(http.StatusOK)
        w.Write([]byte("Login successful"))
    }
}

// When verifying and retrieving a session
func getSession(sm *gopgsession.SessionManager, r *http.Request) (*gopgsession.Session, error) {
    cookie, err := r.Cookie("session_id")
    if err != nil {
        return nil, err
    }

    // Verify the signature and decode the session ID
    isValid, sessionIDString, err := signerPool.VerifyAndDecode(cookie.Value)
    if err != nil {
        return nil, fmt.Errorf("failed to verify and decode session data: %w", err)
    }
    if !isValid {
        return nil, fmt.Errorf("invalid session signature")
    }

    // Parse the session ID
    sessionID, err := uuid.Parse(sessionIDString)
    if err != nil {
        return nil, fmt.Errorf("invalid session ID: %w", err)
    }

    // Retrieve the session
    return sm.GetSession(r.Context(), sessionID)
}

This approach leverages the HMACSHA256SignerPool's methods for signing and verifying session information:

  1. SignAndEncode: This method signs the given session ID and encodes it into a single string.
  2. VerifyAndDecode: This method verifies the signature and decodes the original session ID.

By using these methods, you ensure that the session ID is signed, providing an additional layer of security. This approach can significantly reduce the impact of DDOS attacks targeting your session management system by allowing you to quickly reject invalid or tampered requests without querying the database or cache.

Key benefits of this approach:

  1. Efficiency: The signature can be verified without accessing the database, reducing load on your backend for invalid requests.
  2. Security: The signed session ID prevents tampering and forgery attempts.
  3. Simplicity: By focusing on just the session ID, the implementation remains straightforward and easy to understand.

Remember that while this method provides an extra layer of security, it's still important to implement other security best practices, such as using HTTPS, setting appropriate cookie flags, and implementing proper session management on the server side.

Deleting Attributes from All User Sessions

The DeleteAttributeFromAllUserSessions method allows you to remove a specific attribute from all active sessions belonging to a particular user. This is particularly useful in scenarios where you need to update user-wide information that affects all sessions, such as changing user roles, permissions, or any other global user settings.

Here's an example of how to use this method:

func updateUserRoles(sm *gopgsession.SessionManager, userID uuid.UUID) error {
    // Delete the 'roles' attribute from all of the user's sessions
    err := sm.DeleteAttributeFromAllUserSessions(context.Background(), userID, "roles")
    if err != nil {
        return fmt.Errorf("failed to delete roles attribute: %w", err)
    }

    // The 'roles' attribute will be recalculated the next time each session is accessed
    return nil
}
Use Case: Updating User Roles or Permissions

When you change a user's roles, permissions, or any other security-related attributes, it's crucial to ensure that these changes are reflected across all active sessions for that user. Here's how you might use DeleteAttributeFromAllUserSessions in such a scenario:

  1. Update the user's roles in your user management system.
  2. Call DeleteAttributeFromAllUserSessions to remove the 'roles' attribute from all active sessions.
  3. The next time each session is accessed, your application will recalculate and set the 'roles' attribute based on the latest information.
func updateUserRolesAndSessions(sm *gopgsession.SessionManager, userID uuid.UUID, newRoles []string) error {
    // Update roles in the user management system
    err := updateUserRolesInDatabase(userID, newRoles)
    if err != nil {
        return fmt.Errorf("failed to update user roles: %w", err)
    }

    // Delete the 'roles' attribute from all active sessions
    err = sm.DeleteAttributeFromAllUserSessions(context.Background(), userID, "roles")
    if err != nil {
        return fmt.Errorf("failed to delete roles attribute from sessions: %w", err)
    }

    log.Printf("Updated roles for user %s and cleared roles attribute from all sessions", userID)
    return nil
}

// In your session retrieval logic
func getSessionWithRoles(sm *gopgsession.SessionManager, sessionID uuid.UUID) (*gopgsession.Session, error) {
    session, err := sm.GetSession(context.Background(), sessionID)
    if err != nil {
        return nil, fmt.Errorf("failed to get session: %w", err)
    }

    // Check if roles need to be recalculated
    _, exists := session.GetAttribute("roles")
    if !exists {
        // Roles attribute doesn't exist, so recalculate it
        roles, err := getUserRolesFromDatabase(session.UserID)
        if err != nil {
            return nil, fmt.Errorf("failed to get user roles: %w", err)
        }

        err = session.UpdateAttribute("roles", roles)
        if err != nil {
            return nil, fmt.Errorf("failed to update roles attribute: %w", err)
        }

        // Save the updated session
        session, err = sm.UpdateSession(context.Background(), session)
        if err != nil {
            return nil, fmt.Errorf("failed to save updated session: %w", err)
        }
    }

    return session, nil
}

This approach ensures that:

  1. Role changes are immediately effective across all sessions.
  2. The performance impact is minimized, as roles are only recalculated when a session is actually used.
  3. There's no need to track down and update every active session immediately, which could be resource-intensive.

By using DeleteAttributeFromAllUserSessions, you can efficiently handle scenarios where global user attributes need to be updated across all active sessions, ensuring consistency and security in your application.

Choosing Between CheckVersion and CheckAttributeVersion

The go-pg-session library offers two types of version checking for optimistic locking:

  1. Session-Level Version Checking (WithUpdateCheckVersion()):

    • Checks the version of the entire session.
    • Useful when you want to ensure the entire session hasn't been modified since it was retrieved.
  2. Attribute-Level Version Checking (WithUpdateCheckAttributeVersion()):

    • Checks the version of individual attributes being updated.
    • Provides finer-grained control over concurrent modifications.
    • Useful when different parts of your application may be updating different attributes concurrently.

Choose the appropriate version checking method based on your specific use case:

  • Use WithUpdateCheckVersion() when:

    • You need to ensure the entire session state is as expected before making updates.
    • You want to detect any changes to the session, even to attributes you're not currently modifying.
  • Use WithUpdateCheckAttributeVersion() when:

    • You want to allow concurrent updates to different attributes of the same session.
    • You need more granular conflict detection and resolution.
    • You're updating a specific attribute and don't care about changes to other attributes.

Remember, you should choose either WithUpdateCheckVersion() or WithUpdateCheckAttributeVersion(), not both, as WithUpdateCheckVersion() implicitly checks all attribute versions as well.

Distributed Locks

The go-pg-session library provides a distributed locking mechanism that allows you to coordinate access to shared resources across multiple nodes in a distributed system. This feature is particularly useful for scenarios where you need to ensure that only one process or node can access or modify a specific resource at a time.

Key Features
  • Lease-based locking with automatic expiration
  • Heartbeat mechanism to maintain locks
  • Configurable retry and timeout settings
  • Support for extending lock lease time
Use Cases
  • Coordinating access to shared resources in a distributed system
  • Implementing distributed cron jobs or scheduled tasks
  • Ensuring single-writer scenarios in multi-node deployments
  • Preventing race conditions in distributed workflows
Basic Usage

Here's a basic example of how to use the distributed lock:

func performCriticalOperation(sm *gopgsession.SessionManager, sessionID uuid.UUID) error {
    // Create a new distributed lock
    lock := sm.NewDistributedLock(sessionID, "critical-operation", nil)

    // Attempt to acquire the lock
    err := lock.Lock(context.Background())
    if err != nil {
        return fmt.Errorf("failed to acquire lock: %w", err)
    }
    defer lock.Unlock(context.Background())

    // Perform your critical operation here
    // ...

    return nil
}
Advanced Usage
Custom Configuration

You can customize the lock behavior by providing a DistributedLockConfig:

config := &gopgsession.DistributedLockConfig{
    MaxRetries:        5,
    RetryDelay:        100 * time.Millisecond,
    HeartbeatInterval: 5 * time.Second,
    LeaseTime:         30 * time.Second,
}

lock := sm.NewDistributedLock(sessionID, "custom-lock", config)
Extending Lease Time

If your operation takes longer than expected, you can extend the lease time:

func longRunningOperation(sm *gopgsession.SessionManager, sessionID uuid.UUID) error {
    lock := sm.NewDistributedLock(sessionID, "long-operation", nil)

    err := lock.Lock(context.Background())
    if err != nil {
        return fmt.Errorf("failed to acquire lock: %w", err)
    }
    defer lock.Unlock(context.Background())

    // Start the operation
    for {
        // Do some work...

        // Extend the lease if needed
        err := lock.ExtendLease(context.Background(), 30*time.Second)
        if err != nil {
            return fmt.Errorf("failed to extend lease: %w", err)
        }

        // Check if the operation is complete
        if isComplete() {
            break
        }
    }

    return nil
}
Handling Lock Acquisition Failures

In some cases, you might want to handle scenarios where the lock can't be acquired:

func tryOperation(sm *gopgsession.SessionManager, sessionID uuid.UUID) error {
    lock := sm.NewDistributedLock(sessionID, "try-operation", nil)

    err := lock.Lock(context.Background())
    if err != nil {
        if errors.Is(err, gopgsession.ErrLockAlreadyHeld) {
            // Handle the case where the lock is already held
            return fmt.Errorf("operation already in progress")
        }
        return fmt.Errorf("failed to acquire lock: %w", err)
    }
    defer lock.Unlock(context.Background())

    // Perform the operation
    // ...

    return nil
}
Best Practices
  1. Always use deferred unlock: Use defer lock.Unlock(ctx) immediately after acquiring the lock to ensure it's released even if there's a panic.

  2. Use appropriate timeout: Set a context timeout when acquiring locks to prevent indefinite waiting.

  3. Handle errors properly: Check for and handle all potential errors, including lock acquisition failures and unlock errors.

  4. Use unique resource names: Ensure that your resource names (second parameter in NewDistributedLock) are unique and descriptive for the operation you're protecting.

  5. Minimize lock duration: Keep the critical section as short as possible to reduce contention.

  6. Consider using try-lock pattern: In some scenarios, it might be better to fail fast if a lock can't be acquired immediately, rather than waiting.

By using the distributed lock feature of go-pg-session, you can coordinate operations across multiple instances of your application, ensuring data consistency and preventing race conditions in distributed environments.

Performance Considerations

The distributed memory cache in go-pg-session provides excellent read performance, as most session retrievals will be served from memory. Write operations are managed efficiently through batched updates and PostgreSQL's NOTIFY mechanism.

In high-traffic scenarios, consider adjusting the following configuration parameters:

  • CacheSize: Increase this value to cache more sessions in memory, reducing database reads.
  • LastAccessUpdateInterval and LastAccessUpdateBatchSize: Tune these values to optimize the frequency and size of batched last-access updates.
  • CleanupInterval: Adjust this value to balance between timely session cleanup and database load.

Remember to monitor your PostgreSQL server's performance and scale it accordingly as your application grows.

When using version checking (either session-level or attribute-level), keep in mind:

  • It provides protection against concurrent modifications but may increase the complexity of conflict resolution.
  • In high-concurrency scenarios, it may lead to more update conflicts, requiring careful retry logic in your application.
  • The performance impact is generally minimal, but extensive use in extremely high-traffic applications should be monitored.
  • Attribute-level version checking can offer more granular control and potentially reduce conflicts in scenarios where different attributes are often updated independently.

Exported Functions and Configuration

NewSessionManager

Initializes a new SessionManager with the given configuration and PostgreSQL database connection.

func NewSessionManager(ctx context.Context, cfg *Config, db *sql.DB) (*SessionManager, error)
CreateSession

Creates a new session for the specified user with given attributes.

func (sm *SessionManager) CreateSession(ctx context.Context, userID uuid.UUID, attributes map[string]SessionAttributeValue, opts ...CreateSessionOption) (*Session, error)
CreateSession Options
  • WithCreateIncludeInactivity(bool): Sets whether the session should expire due to inactivity.
  • WithCreateExpiresAt(time.Time): Sets a custom expiration time for the session.

Usage example:

session, err := sessionManager.CreateSession(ctx, userID, attributes,
    gopgsession.WithCreateIncludeInactivity(false),
    gopgsession.WithCreateExpiresAt(time.Now().Add(48 * time.Hour)))
GetSession

Retrieves a session by its ID with optional parameters.

func (sm *SessionManager) GetSession(ctx context.Context, sessionID uuid.UUID, options ...SessionOption) (*Session, error)
UpdateSession

Updates the session in the database with any changed attributes. Supports session-level or attribute-level version checking and other options.

func (sm *SessionManager) UpdateSession(ctx context.Context, session *Session, options ...UpdateSessionOption) (*Session, error)
Update Session Options
  • WithUpdateCheckVersion(): Enables version checking for the entire session.
  • WithUpdateCheckAttributeVersion(): Enables version checking for individual attributes.
  • WithUpdateDoNotNotify(): Disables notifications for this update operation.
  • WithUpdateIncludeInactivity(bool): Updates whether the session should expire due to inactivity.
  • WithUpdateExpiresAt(time.Time): Updates the expiration time of the session.

Usage examples:

// Session-level version checking
updatedSession, err := sm.UpdateSession(ctx, session, gopgsession.WithUpdateCheckVersion())

// Attribute-level version checking
updatedSession, err := sm.UpdateSession(ctx, session, gopgsession.WithUpdateCheckAttributeVersion())

// Update inactivity setting and expiration time
updatedSession, err := sm.UpdateSession(ctx, session,
    gopgsession.WithUpdateIncludeInactivity(true),
    gopgsession.WithUpdateExpiresAt(time.Now().Add(24 * time.Hour)))
DeleteSession

Deletes a session by its ID.

func (sm *SessionManager) DeleteSession(ctx context.Context, sessionID uuid.UUID) error
DeleteAllUserSessions

Deletes all sessions for a given user.

func (sm *SessionManager) DeleteAllUserSessions(ctx context.Context, userID uuid.UUID) error
Shutdown

Shuts down the session manager gracefully, ensuring all ongoing operations are completed.

func (sm *SessionManager) Shutdown(ctx context.Context) error
ClearEntireCache

Clears all sessions from the cache on the current node and notifies other nodes to do the same.

func (sm *SessionManager) ClearEntireCache(ctx context.Context) error

This method is particularly useful in scenarios where you need to invalidate all cached sessions across all nodes, such as:

  • After a major data migration
  • In response to a security event
  • When deploying significant changes that affect session data

Usage example:

err := sessionManager.ClearEntireCache(context.Background())
Session Methods
UpdateAttribute

Updates or sets an attribute for the session.

func (s *Session) UpdateAttribute(key string, value interface{}, options ...UpdateAttributeOption) error
Update Attribute Options
  • WithUpdateAttExpiresAt(time.Time): Sets an expiration time for the attribute.

Usage examples:

// Update an attribute without expiration
err := session.UpdateAttribute("theme", "dark")

// Update an attribute with expiration
expiresAt := time.Now().Add(24 * time.Hour)
err := session.UpdateAttribute("temporary_flag", true, gopgsession.WithUpdateAttExpiresAt(expiresAt))
DeleteAttribute

Deletes an attribute from the session.

func (s *Session) DeleteAttribute(key string) error
GetAttributes

Returns all attributes of the session.

func (s *Session) GetAttributes() map[string]SessionAttributeValue
GetAttribute

Retrieves a specific attribute from the session.

func (s *Session) GetAttribute(key string) (SessionAttributeValue, bool)
GetAttributeAndRetainUnmarshaled

Retrieves a specific attribute, unmarshals it if necessary, and retains the unmarshaled value in memory for future use.

func (s *Session) GetAttributeAndRetainUnmarshaled(key string, v interface{}) (SessionAttributeValue, error)

This method is particularly useful for attributes that are frequently accessed and expensive to unmarshal. It provides the following benefits:

  1. Lazy Unmarshaling: Attributes are unmarshaled only when requested, saving processing time for unused attributes.
  2. Caching: Once unmarshaled, the value is retained in memory, improving performance for subsequent accesses.
  3. Type Safety: The method uses Go's type system to unmarshal into the correct type.
  4. Efficiency: For string attributes, it avoids unnecessary unmarshaling.
  5. Thread-Safety: The method is safe to use in concurrent environments.

Usage example:

var preferences struct {
Theme string `json:"theme"`
Language string `json:"language"`
}

attr, err := session.GetAttributeAndRetainUnmarshaled("preferences", &preferences)
if err != nil {
log.Printf("Failed to get preferences: %v", err)
return
}

log.Printf("User preferences: Theme=%s, Language=%s", preferences.Theme, preferences.Language)

// The unmarshaled value is now cached in the session for future use
Distributed Lock Methods
NewDistributedLock

Creates a new distributed lock for a given session and resource.

func (sm *SessionManager) NewDistributedLock(sessionID uuid.UUID, resourceName string, config *DistributedLockConfig) *DistributedLock
Lock

Attempts to acquire the distributed lock.

func (dl *DistributedLock) Lock(ctx context.Context) error
Unlock

Releases the distributed lock.

func (dl *DistributedLock) Unlock(ctx context.Context) error
ExtendLease

Extends the lease time of the lock.

func (dl *DistributedLock) ExtendLease(ctx context.Context, duration time.Duration) error

Contributing

Contributions to go-pg-session are welcome! Here are some ways you can contribute:

  1. Report bugs or request features by opening an issue.
  2. Improve documentation.
  3. Submit pull requests with bug fixes or new features.

Please ensure that your code adheres to the existing style and that all tests pass before submitting a pull request.

License

This project is licensed under the MIT License. See the LICENSE file for details.


Thank you for using go-pg-session! If you have any questions, suggestions, or encounter any issues, please don't hesitate to open an issue on the GitHub repository. Your feedback and contributions are greatly appreciated and help make this library better for everyone.

Documentation

Overview

Package gopgsession provides a distributed session management library using PostgreSQL.

It implements an eventually consistent memory caching strategy on each node, offering a hybrid solution that leverages the benefits of both cookie-based and server-side session management. This package is designed for high-performance, scalable applications that require robust session handling across multiple nodes.

Index

Constants

View Source
const (
	NotificationTypeSessionsRemovalFromCache      = "sessions_removal_from_cache"
	NotificationTypeUserSessionsRemovalFromCache  = "user_sessions_removal_from_cache"
	NotificationTypeClearEntireCache              = "clear_entire_cache"
	NotificationTypeGroupSessionsRemovalFromCache = "group_sessions_removal_from_cache"
)

Variables

View Source
var (
	// ErrLockAlreadyHeld is returned when attempting to acquire a lock that is already held by another node.
	ErrLockAlreadyHeld = errors.New("lock is already held by another node")
	// ErrLockNotHeld is returned when attempting to perform an operation on a lock that is not held by the current node.
	ErrLockNotHeld = errors.New("lock is not held by this node")
	// ErrLockExpired is returned when attempting to perform an operation on a lock that has expired.
	ErrLockExpired = errors.New("lock has expired")
)
View Source
var DefaultDistributedLockConfig = DistributedLockConfig{
	MaxRetries:        2,
	RetryDelay:        1 * time.Millisecond,
	HeartbeatInterval: 10 * time.Second,
	LeaseTime:         60 * time.Second,
}

DefaultDistributedLockConfig provides default configuration values for DistributedLock.

View Source
var ErrAttributeNotFound = errors.New("attribute not found")
View Source
var ErrSessionVersionIsOutdated = errors.New("session version is outdated")

Functions

This section is empty.

Types

type Config

type Config struct {
	MaxSessions               int           `json:"maxSessions"`
	MaxAttributeLength        int           `json:"maxAttributeLength"`
	SessionExpiration         time.Duration `json:"sessionExpiration"`
	InactivityDuration        time.Duration `json:"inactivityDuration"`
	CleanupInterval           time.Duration `json:"cleanupInterval"`
	CacheSize                 int           `json:"cacheSize"`
	TablePrefix               string        `json:"tablePrefix"`
	SchemaName                string        `json:"schemaName"`
	CreateSchemaIfMissing     bool          `json:"createSchemaIfMissing"`
	LastAccessUpdateInterval  time.Duration `json:"lastAccessUpdateInterval"`
	LastAccessUpdateBatchSize int           `json:"lastAccessUpdateBatchSize"`
	NotifyOnUpdates           bool
	NotifyOnFailedUpdates     bool
	CustomPGLN                *pgln.PGListenNotify `json:"-"`
	MaxSessionLifetimeInCache time.Duration        `json:"maxSessionLifetimeInCache"`
	Logger                    Logger
}

Config holds the configuration options for the SessionManager.

func DefaultConfig

func DefaultConfig() *Config

DefaultConfig returns a Config struct with default values.

type CreateSessionOption added in v0.3.5

type CreateSessionOption func(*Session)

CreateSessionOption is a function type that modifies Session during creation

func WithCreateExpiresAt added in v0.3.11

func WithCreateExpiresAt(expiresAt time.Time) CreateSessionOption

WithUpdateAttExpiresAt sets a custom ExpiresAt time for the session during creation

func WithCreateGroupID added in v0.3.11

func WithCreateGroupID(groupID uuid.UUID) CreateSessionOption

WithCreateGroupID sets the GroupID for the session during creation. Can be an account or tenant id. You can also change it before updateSession.

func WithCreateIncludeInactivity added in v0.3.11

func WithCreateIncludeInactivity(include bool) CreateSessionOption

WithIncludeInactivity sets the IncludeInactivity flag for the session during creation

type DistributedLock added in v0.3.0

type DistributedLock struct {
	// contains filtered or unexported fields
}

DistributedLock represents a distributed lock implementation.

func (*DistributedLock) ExtendLease added in v0.3.0

func (dl *DistributedLock) ExtendLease(ctx context.Context, extension time.Duration) error

ExtendLease extends the lease time of the lock.

Parameters:

  • ctx: The context for the operation.
  • extension: The duration by which to extend the lease.

Returns:

  • An error if the lease cannot be extended, nil otherwise.

func (*DistributedLock) Lock added in v0.3.0

func (dl *DistributedLock) Lock(ctx context.Context) error

Lock attempts to acquire the distributed lock.

Parameters:

  • ctx: The context for the operation.

Returns:

  • An error if the lock cannot be acquired, nil otherwise.

func (*DistributedLock) Unlock added in v0.3.0

func (dl *DistributedLock) Unlock(ctx context.Context) error

Unlock releases the distributed lock.

Parameters:

  • ctx: The context for the operation.

Returns:

  • An error if the lock cannot be released, nil otherwise.

type DistributedLockConfig added in v0.3.0

type DistributedLockConfig struct {
	// MaxRetries is the maximum number of attempts to acquire the lock.
	MaxRetries int
	// RetryDelay is the duration to wait between lock acquisition attempts.
	RetryDelay time.Duration
	// HeartbeatInterval is the duration between heartbeats to maintain the lock.
	HeartbeatInterval time.Duration
	// LeaseTime is the duration for which the lock is considered valid.
	LeaseTime time.Duration
}

DistributedLockConfig holds the configuration options for a DistributedLock.

type GetSessionOptions added in v0.2.1

type GetSessionOptions struct {
	DoNotUpdateSessionLastAccess bool
	ForceRefresh                 bool
}

type HMACSHA256SignerPool added in v0.2.1

type HMACSHA256SignerPool struct {
	// contains filtered or unexported fields
}

HMACSHA256SignerPool represents a limited pool of HMAC-SHA256 signers. It provides thread-safe access to a pool of HMAC signers for efficient signing and verification.

func NewHMACSHA256SignerPool added in v0.2.1

func NewHMACSHA256SignerPool(secret []byte, maxPoolSize int) *HMACSHA256SignerPool

NewHMACSHA256SignerPool initializes a new limited pool of HMAC-SHA256 signers.

Parameters:

  • secret: A byte slice containing the secret key used for HMAC operations.
  • maxPoolSize: An integer specifying the maximum number of HMAC signers to keep in the pool.

Returns:

  • A pointer to a new HMACSHA256SignerPool instance.

func (*HMACSHA256SignerPool) Sign added in v0.2.1

func (s *HMACSHA256SignerPool) Sign(message []byte) ([]byte, error)

Sign signs a message using a pooled HMAC.

Parameters:

  • message: A byte slice containing the message to be signed.

Returns:

  • A byte slice containing the HMAC signature.
  • An error if the signing process fails.

func (*HMACSHA256SignerPool) SignAndEncode added in v0.3.3

func (s *HMACSHA256SignerPool) SignAndEncode(message string) (string, error)

SignAndEncode signs a message and encodes it with the signature.

Parameters:

  • message: A string containing the message to be signed.

Returns:

  • A string containing the base64-encoded message and HMAC signature, separated by a delimiter.
  • An error if the signing process fails.

func (*HMACSHA256SignerPool) Verify added in v0.2.1

func (s *HMACSHA256SignerPool) Verify(message, signature []byte) bool

Verify verifies a signed message using a pooled HMAC.

Parameters:

  • message: A byte slice containing the original message that was signed.
  • signature: A byte slice containing the HMAC signature to verify.

Returns:

  • A boolean indicating whether the signature is valid (true) or not (false).

func (*HMACSHA256SignerPool) VerifyAndDecode added in v0.3.3

func (s *HMACSHA256SignerPool) VerifyAndDecode(signedMessage string) (bool, string, error)

VerifyAndDecode verifies a signed and encoded message and returns the original message.

Parameters:

  • signedMessage: A string containing the base64-encoded message and HMAC signature.

Returns:

  • A boolean indicating whether the signature is valid (true) or not (false).
  • A string containing the original message.
  • An error if the verification process fails.

type LockInfo added in v0.3.0

type LockInfo struct {
	NodeID        uuid.UUID
	Resource      string
	ExpiresAt     time.Time
	LastHeartbeat time.Time
}

LockInfo holds information about a lock.

type Logger added in v0.3.6

type Logger interface {
	Warn(msg string, args ...any)
	Error(msg string, args ...any)
}

Logger defines the interface for logging operations.

type Session

type Session struct {
	ID                uuid.UUID  `db:"id"`
	UserID            uuid.UUID  `db:"user_id"`
	GroupID           *uuid.UUID `db:"group_id"`
	LastAccessed      time.Time  `db:"last_accessed"`
	ExpiresAt         time.Time  `db:"expires_at"`
	UpdatedAt         time.Time  `db:"updated_at"`
	CreatedAt         time.Time  `db:"created_at"`
	Version           int        `db:"version"`
	IncludeInactivity bool       `db:"include_inactivity"`
	// contains filtered or unexported fields
}

func (*Session) DeleteAttribute added in v0.2.1

func (s *Session) DeleteAttribute(key string) error

DeleteAttribute removes an attribute from the session.

func (*Session) GetAttribute

func (s *Session) GetAttribute(key string) (SessionAttributeValue, bool)

GetAttribute retrieves a specific attribute from the session.

func (*Session) GetAttributeAndRetainUnmarshaled added in v0.2.6

func (s *Session) GetAttributeAndRetainUnmarshaled(key string, v interface{}) (SessionAttributeValue, error)

GetAttributeAndRetainUnmarshaled retrieves a specific attribute, unmarshals it if necessary, and retains the unmarshaled value in memory for future use.

func (*Session) GetAttributes

func (s *Session) GetAttributes() map[string]SessionAttributeValue

GetAttributes returns all attributes of the session.

func (*Session) Invalidate added in v0.3.7

func (s *Session) Invalidate()

Invalidate marks the session as invalidated

func (*Session) IsFromCache added in v0.2.7

func (s *Session) IsFromCache() bool

IsFromCache returns true if the session was loaded from the cache, and false if it was loaded from the database table.

func (*Session) IsInvalidated added in v0.3.7

func (s *Session) IsInvalidated() bool

IsInvalidated returns true if the session has been invalidated

func (*Session) IsModified added in v0.3.5

func (s *Session) IsModified() bool

IsModified returns true if the session has been modified (attributes changed or deleted).

func (*Session) UpdateAttribute

func (s *Session) UpdateAttribute(key string, value interface{}, options ...UpdateAttributeOption) error

UpdateAttribute sets or updates an attribute for the session.

type SessionAttributeRecord added in v0.2.1

type SessionAttributeRecord struct {
	SessionID uuid.UUID  `db:"session_id"`
	Key       string     `db:"key"`
	Value     string     `db:"value"`
	ExpiresAt *time.Time `db:"expires_at"`
	Version   int        `db:"version"`
}

type SessionAttributeValue added in v0.2.1

type SessionAttributeValue struct {
	Value     any
	Marshaled bool
	ExpiresAt *time.Time
	Version   int
}

type SessionManager

type SessionManager struct {
	Config *Config
	// contains filtered or unexported fields
}

SessionManager manages sessions in a PostgreSQL database with caching capabilities.

func NewSessionManager

func NewSessionManager(ctx context.Context, cfg *Config, db *sql.DB) (*SessionManager, error)

NewSessionManager creates a new SessionManager with the given configuration and connection string.

func (*SessionManager) ClearEntireCache added in v0.3.5

func (sm *SessionManager) ClearEntireCache(ctx context.Context) error

ClearEntireCache clears all sessions from the cache and notifies other nodes to do the same

func (*SessionManager) CreateSession

func (sm *SessionManager) CreateSession(ctx context.Context, userID uuid.UUID, attributes map[string]SessionAttributeValue, opts ...CreateSessionOption) (*Session, error)

CreateSession creates a new session for the given user with the provided attributes and options

func (*SessionManager) DeleteAllSessions added in v0.2.1

func (sm *SessionManager) DeleteAllSessions(ctx context.Context) error

DeleteAllSessions deletes all sessions from the database and cache.

func (*SessionManager) DeleteAllSessionsByGroupID added in v0.3.5

func (sm *SessionManager) DeleteAllSessionsByGroupID(ctx context.Context, groupID uuid.UUID) error

DeleteAllSessionsByGroupID deletes all sessions for a given group ID.

func (*SessionManager) DeleteAllUserSessions

func (sm *SessionManager) DeleteAllUserSessions(ctx context.Context, userID uuid.UUID) error

DeleteAllUserSessions deletes all sessions for a given user.

func (*SessionManager) DeleteAttributeFromAllUserSessions added in v0.3.5

func (sm *SessionManager) DeleteAttributeFromAllUserSessions(ctx context.Context, userID uuid.UUID, attributeKey string) error

DeleteAttributeFromAllUserSessions deletes a specific attribute from all sessions of a given user.

func (*SessionManager) DeleteSession

func (sm *SessionManager) DeleteSession(ctx context.Context, sessionID uuid.UUID) error

DeleteSession deletes a session by its ID.

func (*SessionManager) EncodeSessionIDAndVersion added in v0.2.1

func (sm *SessionManager) EncodeSessionIDAndVersion(sessionID uuid.UUID, version int) string

EncodeSessionIDAndVersion encodes a session ID and version into a single string.

func (*SessionManager) GetSession

func (sm *SessionManager) GetSession(ctx context.Context, sessionID uuid.UUID, options ...SessionOption) (*Session, error)

GetSession retrieves a session by its ID with optional parameters.

func (*SessionManager) GetSessionWithVersion added in v0.2.1

func (sm *SessionManager) GetSessionWithVersion(ctx context.Context, sessionID uuid.UUID, version int, options ...SessionOption) (*Session, error)

GetSessionWithVersion retrieves a session by its ID and version with optional parameters.

func (*SessionManager) NewDistributedLock added in v0.3.0

func (sm *SessionManager) NewDistributedLock(sessionID uuid.UUID, resource string, config *DistributedLockConfig) *DistributedLock

NewDistributedLock creates a new DistributedLock instance.

Parameters:

  • sessionID: The UUID of the session associated with this lock.
  • resource: The name of the resource being locked.
  • config: Optional configuration for the lock. If nil, default configuration is used.

Returns:

  • A pointer to the newly created DistributedLock.

func (*SessionManager) ParseSessionIDAndVersion added in v0.2.1

func (sm *SessionManager) ParseSessionIDAndVersion(encodedData string) (uuid.UUID, int, error)

ParseSessionIDAndVersion parses an encoded session ID and version string.

func (*SessionManager) RemoveAllUserCachedSessionsFromAllNodes added in v0.2.1

func (sm *SessionManager) RemoveAllUserCachedSessionsFromAllNodes(userID uuid.UUID) error

RemoveAllUserCachedSessionsFromAllNodes removes all cached sessions for a given user from all nodes.

func (*SessionManager) Shutdown

func (sm *SessionManager) Shutdown(ctx context.Context) error

Shutdown gracefully shuts down the SessionManager.

func (*SessionManager) UpdateSession

func (sm *SessionManager) UpdateSession(ctx context.Context, session *Session, options ...UpdateSessionOption) (*Session, error)

UpdateSession updates the session in the database with any changes made to its attributes and options

type SessionOption added in v0.2.7

type SessionOption func(*GetSessionOptions)

SessionOption is a function type that modifies GetSessionOptions

func WithGetDoNotUpdateSessionLastAccess added in v0.3.11

func WithGetDoNotUpdateSessionLastAccess() SessionOption

WithGetDoNotUpdateSessionLastAccess sets the DoNotUpdateSessionLastAccess option

func WithGetForceRefresh added in v0.3.11

func WithGetForceRefresh() SessionOption

WithGetForceRefresh sets the ForceRefresh option

type UpdateAttributeOption added in v0.3.4

type UpdateAttributeOption func(*UpdateAttributeOptions)

UpdateAttributeOption is a function type that modifies UpdateAttributeOptions

func WithUpdateAttExpiresAt added in v0.3.11

func WithUpdateAttExpiresAt(expiresAt time.Time) UpdateAttributeOption

WithUpdateAttExpiresAt sets the ExpiresAt option

type UpdateAttributeOptions added in v0.3.4

type UpdateAttributeOptions struct {
	ExpiresAt *time.Time
}

UpdateAttributeOptions holds the options for updating an attribute

type UpdateSessionOption added in v0.2.7

type UpdateSessionOption func(*UpdateSessionOptions)

UpdateSessionOption is a function type that modifies UpdateSessionOptions

func WithUpdateCheckAttributeVersion added in v0.3.11

func WithUpdateCheckAttributeVersion() UpdateSessionOption

WithUpdateCheckAttributeVersion sets the CheckAttributeVersion option

func WithUpdateCheckVersion added in v0.3.11

func WithUpdateCheckVersion() UpdateSessionOption

WithUpdateCheckVersion sets the CheckVersion option to true

func WithUpdateDoNotNotify added in v0.3.11

func WithUpdateDoNotNotify() UpdateSessionOption

WithUpdateDoNotNotify sets the DoNotNotify option

func WithUpdateExpiresAt added in v0.3.11

func WithUpdateExpiresAt(expiresAt time.Time) UpdateSessionOption

WithUpdateExpiresAt sets a new ExpiresAt time for the session

func WithUpdateIncludeInactivity added in v0.3.11

func WithUpdateIncludeInactivity(include bool) UpdateSessionOption

WithUpdateIncludeInactivity sets a new IncludeInactivity flag for the session

type UpdateSessionOptions added in v0.2.7

type UpdateSessionOptions struct {
	CheckVersion          bool
	CheckAttributeVersion bool
	DoNotNotify           bool
	IncludeInactivity     *bool
	ExpiresAt             *time.Time
}

UpdateSessionOptions holds the options for updating a session

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL