heimdall

package module
v1.0.2 Latest Latest
Warning

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

Go to latest
Published: Jan 14, 2026 License: MIT Imports: 10 Imported by: 0

README

Heimdall

Session management SDK for Go. Enforce single-session policies, detect suspicious logins, let users manage their devices.

h, _ := heimdall.New(heimdall.Config{})

// On login
device, location, _ := h.ExtractRequestInfo(r)
result, _ := h.RegisterSession(userID, sessionID, device, location, 3) // max 3 sessions

if result.LimitExceeded {
    // Too many sessions - show user their active devices
}
if result.IsNewLocation {
    // Login from new city - send security alert
}

// On logout
h.InvalidateSession(sessionID)

// In auth middleware
if invalidated, _ := h.IsSessionInvalidated(sessionID); invalidated {
    // Reject request
}

// Show user their sessions
sessions, _ := h.ListSessions(userID)

Install

go get github.com/aadithya-v/heimdall

What it does

Feature Description
Concurrent session limit Reject new logins when user has N active sessions
New location detection Flag logins from unusual locations (uses IP geolocation)
Session listing Let users see and revoke their active sessions
Audit trail Sessions soft-deleted, kept for compliance

API

New(Config) (*Heimdall, error)
ExtractRequestInfo(*http.Request) (DeviceInfo, LocationInfo, error)
RegisterSession(userID, sessionID string, device, location, limit int) (*RegisterResult, error)
InvalidateSession(sessionID string) error
IsSessionInvalidated(sessionID string) (bool, error)
ListSessions(userID string) ([]*Session, error)
Close() error

Pluggable Storage

Bring your own storage backend. Implement the interface, plug it in.

Backend SessionStore InvalidationCache
SQLite (default) store.NewSQLite(path) store.NewSQLiteInvalidationCache(path)
MySQL store.NewMySQL(dsn)
Redis store.NewRedisSimple(addr, pass, db)
In-Memory store.NewMemorySessionStore() store.NewMemoryCache()
Custom Implement store.SessionStore Implement store.InvalidationCache
// Zero-config (SQLite)
h, _ := heimdall.New(heimdall.Config{})

// Production (MySQL + Redis)
h, _ := heimdall.New(heimdall.Config{
    SessionStore:      store.NewMySQL("user:pass@tcp(localhost:3306)/db"),
    InvalidationCache: store.NewRedisSimple("localhost:6379", "", 0),
})

// Custom backend
h, _ := heimdall.New(heimdall.Config{
    SessionStore:      myPostgresStore,      // implements store.SessionStore
    InvalidationCache: myMemcachedCache,     // implements store.InvalidationCache
})

Interfaces:

type SessionStore interface {
    Save(session *Session) error
    Delete(sessionID string) error
    GetActiveByUser(userID string) ([]*Session, error)
    Close() error
}

type InvalidationCache interface {
    Set(sessionID string, ttl time.Duration) error
    Exists(sessionID string) (bool, error)
    Close() error
}

Config

heimdall.Config{
    SessionTTL:             24 * time.Hour,  // How long sessions live
    NewLocationThresholdKM: 100,             // Distance to trigger alert
    GeoIPDatabasePath:      "GeoLite2.mmdb", // Optional: MaxMind DB for location
    DatabasePath:           "heimdall.db",   // SQLite path
}

GeoIP (optional)

For city/country detection from IP:

  1. Get free database from MaxMind GeoLite2
  2. Set GeoIPDatabasePath in config

Without it, LocationInfo only contains the IP address.

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrSessionNotFound is returned when a session does not exist.
	ErrSessionNotFound = errors.New("heimdall: session not found")

	// ErrSessionLimitExceeded is returned when the concurrent session limit is exceeded.
	ErrSessionLimitExceeded = errors.New("heimdall: concurrent session limit exceeded")

	// ErrSessionInvalidated is returned when attempting to use an invalidated session.
	ErrSessionInvalidated = errors.New("heimdall: session has been invalidated")

	// ErrGeoIPDatabaseNotConfigured is returned when GeoIP lookup is attempted
	// without configuring the GeoIP database path.
	ErrGeoIPDatabaseNotConfigured = errors.New("heimdall: GeoIP database path not configured")

	// ErrGeoIPLookupFailed is returned when IP geolocation lookup fails.
	ErrGeoIPLookupFailed = errors.New("heimdall: GeoIP lookup failed")

	// ErrInvalidIP is returned when an invalid IP address is provided.
	ErrInvalidIP = errors.New("heimdall: invalid IP address")
)

Functions

func HaversineDistance

func HaversineDistance(lat1, lng1, lat2, lng2 float64) float64

HaversineDistance calculates the distance in kilometers between two geographic coordinates using the Haversine formula.

func IsNewLocation

func IsNewLocation(prev, curr LocationInfo, thresholdKM float64) bool

IsNewLocation returns true if the distance between two locations exceeds the given threshold in kilometers.

func IsPrivateIP

func IsPrivateIP(ip string) bool

IsPrivateIP returns true if the IP is in a private/reserved range.

Types

type Config

type Config struct {
	// SessionTTL is how long sessions remain active.
	// Default: 24 hours.
	SessionTTL time.Duration

	// InvalidationTTL is how long to remember invalidated sessions.
	// This should be at least as long as SessionTTL to prevent
	// invalidated sessions from being reused.
	// Default: 24 hours (Same as SessionTTL).
	InvalidationTTL time.Duration

	// GeoIPDatabasePath is the path to MaxMind GeoLite2-City.mmdb file.
	// Required for IP-based location detection.
	// Download from: https://dev.maxmind.com/geoip/geolite2-free-geolocation-data
	GeoIPDatabasePath string

	// NewLocationThresholdKM is the distance threshold in kilometers
	// for triggering a "new location" alert.
	// Default: 100 km.
	NewLocationThresholdKM float64

	// SessionStore is the storage backend for sessions.
	// Default: SQLite store (creates heimdall.db in current directory).
	SessionStore store.SessionStore

	// InvalidationCache is the cache for invalidated session IDs.
	// Default: in-memory cache.
	InvalidationCache store.InvalidationCache

	// DatabasePath is the path for the default SQLite database.
	// Only used if SessionStore is nil.
	// Default: "heimdall.db".
	DatabasePath string
}

Config contains configuration options for Heimdall.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns a Config with sensible defaults.

type DeviceInfo

type DeviceInfo struct {
	IP         string `json:"ip"`
	UserAgent  string `json:"user_agent"`
	Browser    string `json:"browser"`
	OS         string `json:"os"`
	DeviceType string `json:"device_type"` // mobile, desktop, tablet
}

DeviceInfo contains device information extracted from the HTTP request.

func ExtractDeviceInfo

func ExtractDeviceInfo(r *http.Request) DeviceInfo

ExtractDeviceInfo extracts device information from an HTTP request.

type GeoIPReader

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

GeoIPReader provides IP geolocation using MaxMind GeoLite2 database.

func NewGeoIPReader

func NewGeoIPReader(dbPath string) (*GeoIPReader, error)

NewGeoIPReader opens a MaxMind GeoLite2-City database.

func (*GeoIPReader) Close

func (r *GeoIPReader) Close() error

Close closes the GeoIP database.

func (*GeoIPReader) Lookup

func (r *GeoIPReader) Lookup(ip string) (*LocationInfo, error)

Lookup returns location information for an IP address.

func (*GeoIPReader) LookupWithFallback

func (r *GeoIPReader) LookupWithFallback(ip string) LocationInfo

LookupWithFallback attempts IP geolocation, returning a partial result with just the IP if lookup fails.

type Heimdall

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

Heimdall is the main SDK interface for session management.

func New

func New(cfg Config) (*Heimdall, error)

New creates a new Heimdall instance with the given configuration. If SessionStore or InvalidationCache are not provided, defaults are used: - SessionStore: SQLite (creates heimdall.db) - InvalidationCache: SQLite (uses sessions table's invalidated_at column)

func (*Heimdall) Close

func (h *Heimdall) Close() error

Close releases all resources held by Heimdall. Should be called when the application shuts down.

func (*Heimdall) ExtractRequestInfo

func (h *Heimdall) ExtractRequestInfo(r *http.Request) (DeviceInfo, LocationInfo, error)

ExtractRequestInfo extracts device and location information from an HTTP request. If GeoIP is not configured, location will contain only the IP address.

func (*Heimdall) InvalidateSession

func (h *Heimdall) InvalidateSession(sessionID string) error

InvalidateSession marks a session as invalidated. The session ID is stored in the invalidation cache with the configured TTL. The session is also deleted from the session store.

func (*Heimdall) IsSessionInvalidated

func (h *Heimdall) IsSessionInvalidated(sessionID string) (bool, error)

IsSessionInvalidated checks if a session has been invalidated. Returns true if the session ID was explicitly invalidated and the invalidation TTL has not expired.

func (*Heimdall) ListSessions

func (h *Heimdall) ListSessions(userID string) ([]*Session, error)

ListSessions returns all active (non-expired) sessions for a user. Sessions are ordered by creation time, newest first.

func (*Heimdall) RegisterSession

func (h *Heimdall) RegisterSession(
	userID, sessionID string,
	device DeviceInfo,
	location LocationInfo,
	concurrentLimit int,
) (*RegisterResult, error)

RegisterSession registers a new session for the user.

concurrentLimit 0 means no limit. Otherwise, if the number of active sessions equals or exceeds concurrentLimit, the new session is NOT saved and LimitExceeded is set to true. The caller should then prompt the user to invalidate an existing session.

If the user is logging in from a new location (distance > NewLocationThresholdKM), IsNewLocation is set to true and PreviousLocation contains the last known location.

type LocationInfo

type LocationInfo struct {
	IP        string  `json:"ip"`
	City      string  `json:"city"`
	Country   string  `json:"country"`
	Latitude  float64 `json:"latitude"`
	Longitude float64 `json:"longitude"`
}

LocationInfo contains geographic location extracted from IP address.

type RegisterResult

type RegisterResult struct {
	// Session is the newly created session. Nil if LimitExceeded is true.
	Session *Session `json:"session,omitempty"`

	// IsNewLocation is true if the user is logging in from an unusual location.
	IsNewLocation bool `json:"is_new_location"`

	// PreviousLocation is the last known location for comparison.
	// Only set if IsNewLocation is true.
	PreviousLocation *LocationInfo `json:"previous_location,omitempty"`

	// ActiveSessions contains all active sessions for this user.
	ActiveSessions []*Session `json:"active_sessions"`

	// LimitExceeded is true if the concurrent session limit was exceeded.
	// When true, the new session was NOT saved.
	LimitExceeded bool `json:"limit_exceeded"`
}

RegisterResult is returned from RegisterSession with session info and alerts.

type Session

type Session struct {
	SessionID  string       `json:"session_id"`
	UserID     string       `json:"user_id"`
	Device     DeviceInfo   `json:"device"`
	Location   LocationInfo `json:"location"`
	CreatedAt  time.Time    `json:"created_at"`
	TTLSeconds int64        `json:"ttl_seconds"`
}

Session represents an active user session.

func (*Session) ExpiresAt

func (s *Session) ExpiresAt() time.Time

ExpiresAt returns the time when this session expires.

func (*Session) IsExpired

func (s *Session) IsExpired() bool

IsExpired returns true if the session has expired based on its TTL.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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