mirror of
https://github.com/mudler/LocalAI.git
synced 2026-03-31 13:15:51 -04:00
* 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>
150 lines
4.5 KiB
Go
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"`
|
|
}
|