Files
LocalAI/core/http/auth/models.go
Ettore Di Giacinto 59108fbe32 feat: add distributed mode (#9124)
* feat: add distributed mode (experimental)

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix data races, mutexes, transactions

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactorings

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fixups

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix events and tool stream in agent chat

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* use ginkgo

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactoring and consolidation

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactoring and consolidation

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactoring and consolidation

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactoring and consolidation

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactoring and consolidation

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactoring and consolidation

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactoring and consolidation

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactoring and consolidation

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix(cron): compute correctly time boundaries avoiding re-triggering

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* enhancements, refactorings

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* do not flood of healthy checks

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* do not list obvious backends as text backends

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* tests fixups

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* refactoring and consolidation

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* Drop redundant healthcheck

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* enhancements, refactorings

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

---------

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-03-30 00:47:27 +02:00

150 lines
4.5 KiB
Go

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"`
}