package auth import ( "database/sql/driver" "encoding/json" "fmt" "time" ) // Auth provider constants. const ( ProviderLocal = "local" ProviderGitHub = "github" ProviderOIDC = "oidc" ProviderAgentWorker = "agent-worker" ) // User represents an authenticated user. type User struct { ID string `gorm:"primaryKey;size:36"` Email string `gorm:"size:255;index"` Name string `gorm:"size:255"` AvatarURL string `gorm:"size:512"` Provider string `gorm:"size:50"` // ProviderLocal, ProviderGitHub, ProviderOIDC Subject string `gorm:"size:255"` // provider-specific user ID PasswordHash string `json:"-"` // bcrypt hash, empty for OAuth-only users Role string `gorm:"size:20;default:user"` Status string `gorm:"size:20;default:active"` // "active", "pending" CreatedAt time.Time UpdatedAt time.Time } // Session represents a user login session. type Session struct { ID string `gorm:"primaryKey;size:64"` // HMAC-SHA256 hash of session token UserID string `gorm:"size:36;index"` ExpiresAt time.Time RotatedAt time.Time CreatedAt time.Time User User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"` } // UserAPIKey represents a user-generated API key for programmatic access. type UserAPIKey struct { ID string `gorm:"primaryKey;size:36"` UserID string `gorm:"size:36;index"` Name string `gorm:"size:255"` // user-provided label KeyHash string `gorm:"size:64;uniqueIndex"` KeyPrefix string `gorm:"size:12"` // first 8 chars of key for display Role string `gorm:"size:20"` CreatedAt time.Time ExpiresAt *time.Time `gorm:"index"` LastUsed *time.Time User User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"` } // PermissionMap is a flexible map of feature -> enabled, stored as JSON text. // Known features: "agents", "skills", "collections", "mcp_jobs". // New features can be added without schema changes. type PermissionMap map[string]bool // Value implements driver.Valuer for GORM JSON serialization. func (p PermissionMap) Value() (driver.Value, error) { if p == nil { return "{}", nil } b, err := json.Marshal(p) if err != nil { return nil, fmt.Errorf("failed to marshal PermissionMap: %w", err) } return string(b), nil } // Scan implements sql.Scanner for GORM JSON deserialization. func (p *PermissionMap) Scan(value any) error { if value == nil { *p = PermissionMap{} return nil } var bytes []byte switch v := value.(type) { case string: bytes = []byte(v) case []byte: bytes = v default: return fmt.Errorf("cannot scan %T into PermissionMap", value) } return json.Unmarshal(bytes, p) } // InviteCode represents an admin-generated invitation for user registration. type InviteCode struct { ID string `gorm:"primaryKey;size:36"` Code string `gorm:"uniqueIndex;not null;size:64"` // HMAC-SHA256 hash of invite code CodePrefix string `gorm:"size:12"` // first 8 chars for admin display CreatedBy string `gorm:"size:36;not null"` UsedBy *string `gorm:"size:36"` UsedAt *time.Time ExpiresAt time.Time `gorm:"not null;index"` CreatedAt time.Time Creator User `gorm:"foreignKey:CreatedBy"` Consumer *User `gorm:"foreignKey:UsedBy"` } // ModelAllowlist controls which models a user can access. // When Enabled is false (default), all models are allowed. type ModelAllowlist struct { Enabled bool `json:"enabled"` Models []string `json:"models,omitempty"` } // Value implements driver.Valuer for GORM JSON serialization. func (m ModelAllowlist) Value() (driver.Value, error) { b, err := json.Marshal(m) if err != nil { return nil, fmt.Errorf("failed to marshal ModelAllowlist: %w", err) } return string(b), nil } // Scan implements sql.Scanner for GORM JSON deserialization. func (m *ModelAllowlist) Scan(value any) error { if value == nil { *m = ModelAllowlist{} return nil } var bytes []byte switch v := value.(type) { case string: bytes = []byte(v) case []byte: bytes = v default: return fmt.Errorf("cannot scan %T into ModelAllowlist", value) } return json.Unmarshal(bytes, m) } // UserPermission stores per-user feature permissions. type UserPermission struct { ID string `gorm:"primaryKey;size:36"` UserID string `gorm:"size:36;uniqueIndex"` Permissions PermissionMap `gorm:"type:text"` AllowedModels ModelAllowlist `gorm:"type:text"` CreatedAt time.Time UpdatedAt time.Time User User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"` }