178 lines
7.0 KiB
Go
178 lines
7.0 KiB
Go
package models
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// Session represents an authenticated user session in the system.
|
|
// A session is created when a user logs in and tracks their authentication state.
|
|
//
|
|
// What is a session?
|
|
// - Created during login
|
|
// - Associated with a refresh token
|
|
// - Tracks device and location information
|
|
// - Can be individually revoked
|
|
// - Has an expiration date
|
|
//
|
|
// Why track sessions?
|
|
// 1. Security: See all active logins
|
|
// 2. Control: Revoke specific sessions ("logout from my phone")
|
|
// 3. Audit: Track when/where users logged in
|
|
// 4. Device management: Show users their active devices
|
|
// 5. Token validation: Verify refresh tokens haven't been revoked
|
|
//
|
|
// Session lifecycle:
|
|
// 1. Created: When user logs in
|
|
// 2. Active: Used to refresh access tokens
|
|
// 3. Last used updated: Every time refresh token is used
|
|
// 4. Expired: When expires_at passes
|
|
// 5. Revoked: When user logs out or admin revokes
|
|
// 6. Deleted: Cleanup job removes old expired/revoked sessions
|
|
//
|
|
// Security model:
|
|
// - Refresh token hash stored (not plaintext)
|
|
// - Session can be revoked (logout)
|
|
// - All sessions can be revoked (password change)
|
|
// - Tracks device/location for anomaly detection
|
|
type Session struct {
|
|
// ID is the unique identifier for this session
|
|
// Type: UUID v4 (128-bit random identifier)
|
|
// Generated: Automatically by database on insert
|
|
// Used for: Looking up sessions, revoking specific sessions
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
|
|
// UserID identifies which user this session belongs to
|
|
// Type: UUID v4 (foreign key to users table)
|
|
// Used for: Finding all sessions for a user, revoking all user sessions
|
|
// Relationship: Many sessions can belong to one user
|
|
UserID uuid.UUID `json:"user_id" db:"user_id"`
|
|
|
|
// RefreshTokenHash is the hashed version of the refresh token
|
|
// Type: String (base64-encoded SHA-256 hash)
|
|
// Security: NEVER expose this in API responses (hence json tag comment)
|
|
// Why hashed: If database breached, tokens can't be used
|
|
// Hashing: SHA-256 (deterministic, allows lookup)
|
|
// Storage: Should be excluded from JSON in production/staging
|
|
RefreshTokenHash string `json:"refresh_token_hash" db:"refresh_token_hash"` // Never expose in JSON in (prod, staging)
|
|
|
|
// UserAgent contains the browser/application identifier
|
|
// Type: Optional string (can be null)
|
|
// Example: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/91.0.4472.124"
|
|
// Used for: Displaying device information to user
|
|
// Privacy: Contains OS and browser info (PII consideration)
|
|
UserAgent *string `json:"user_agent" db:"user_agent"`
|
|
|
|
// IPAddress is the IP address from which the session was created
|
|
// Type: Optional string (can be null, IPv4 or IPv6)
|
|
// Example: "192.168.1.1" or "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
|
|
// Used for: Location display, anomaly detection
|
|
// Privacy: PII under GDPR, may need anonymization after time
|
|
// Note: Could be proxy IP if behind load balancer
|
|
IPAddress *string `json:"ip_address" db:"ip_address"`
|
|
|
|
// DeviceName is an optional user-friendly name for the device
|
|
// Type: Optional string (can be null)
|
|
// Example: "John's iPhone", "Work Laptop", "Home PC"
|
|
// Used for: User convenience (easier to identify devices)
|
|
// Set by: Client can optionally provide this
|
|
DeviceName *string `json:"device_name" db:"device_name"`
|
|
|
|
// DeviceType categorizes the type of device
|
|
// Type: String (enum-like values)
|
|
// Possible values: "mobile", "desktop", "web", "unknown"
|
|
// Detection: Based on user agent string parsing
|
|
// Used for: Filtering sessions, displaying appropriate icons
|
|
DeviceType string `json:"device_type" db:"device_type"`
|
|
|
|
// ExpiresAt is when this session expires
|
|
// Type: Timestamp (typically 7 days from creation)
|
|
// After expiry: Session cannot be used to get new access tokens
|
|
// Cleanup: Expired sessions eventually deleted by cleanup job
|
|
// Database check: Queries filter out expired sessions
|
|
ExpiresAt time.Time `json:"expires_at" db:"expires_at"`
|
|
|
|
// IsRevoked indicates if session has been explicitly invalidated
|
|
// Type: Boolean (default false)
|
|
// Set to true when: User logs out, password changed, admin action
|
|
// Effect: Refresh token can no longer be used
|
|
// Database: Indexed for faster queries
|
|
IsRevoked bool `json:"is_revoked" db:"is_revoked"`
|
|
|
|
// RevokedAt is when the session was revoked
|
|
// Type: Optional timestamp (null if not revoked)
|
|
// Set when: IsRevoked changed to true
|
|
// Used for: Audit trail, analytics
|
|
RevokedAt *time.Time `json:"revoked_at" db:"revoked_at"`
|
|
|
|
// RevokedReason explains why the session was revoked
|
|
// Type: Optional string (null if not revoked)
|
|
// Common values: "user_logout", "password_change", "admin_action", "security_breach"
|
|
// Used for: Audit trail, understanding logout patterns, security investigations
|
|
RevokedReason *string `json:"revoked_reason" db:"revoked_reason"`
|
|
|
|
// CreatedAt is when the session was created (login time)
|
|
// Type: Timestamp (automatically set by database)
|
|
// Used for: Displaying "logged in" time, sorting sessions
|
|
// Database: Set by DEFAULT NOW() in schema
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
|
|
// LastUsedAt is when the refresh token was last used
|
|
// Type: Timestamp (updated on each token refresh)
|
|
// Used for: Showing session activity, identifying stale sessions
|
|
// Updated: Every ~15 minutes when access token is refreshed
|
|
// Cleanup: Can remove sessions not used in X days
|
|
LastUsedAt time.Time `json:"last_used_at" db:"last_used_at"`
|
|
}
|
|
|
|
// CreateSessionInput contains the data needed to create a new session.
|
|
// This is a DTO (Data Transfer Object) used to pass data to the repository.
|
|
//
|
|
// Why separate input struct?
|
|
// - Separates what client provides from what database generates
|
|
// - Clear interface for session creation
|
|
// - Database generates ID and timestamps
|
|
// - Type safety (can't accidentally set ID or timestamps)
|
|
//
|
|
// When used:
|
|
// - During login (user authentication)
|
|
// - When creating refresh token
|
|
type CreateSessionInput struct {
|
|
// UserID identifies which user this session belongs to
|
|
// Required: Must be valid user ID
|
|
UserID uuid.UUID
|
|
|
|
// RefreshToken is the plaintext token to be hashed
|
|
// Security: Will be hashed before storage (SHA-256)
|
|
// Never stored plaintext in database
|
|
// Generated: Random 32-byte value, base64 encoded
|
|
RefreshToken string
|
|
|
|
// UserAgent is optional browser/app information
|
|
// Extracted from: HTTP User-Agent header
|
|
// Can be nil: If header not provided
|
|
UserAgent *string
|
|
|
|
// IPAddress is optional IP address of request
|
|
// Extracted from: X-Forwarded-For or RemoteAddr
|
|
// Can be nil: If not available
|
|
IPAddress *string
|
|
|
|
// DeviceName is optional user-friendly device name
|
|
// Provided by: Client (optional)
|
|
// Can be nil: Most often not provided
|
|
DeviceName *string
|
|
|
|
// DeviceType categorizes the device
|
|
// Detected from: User agent string
|
|
// Values: "mobile", "desktop", "web", "unknown"
|
|
// Required: Always set (uses "unknown" if can't detect)
|
|
DeviceType string
|
|
|
|
// ExpiresAt is when the session should expire
|
|
// Calculated: Now + RefreshExpiry (typically 7 days)
|
|
// Required: Must be set
|
|
ExpiresAt time.Time
|
|
}
|