aurganize-backend/backend/internal/models/sessions.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
}