Refactor installation and runner scripts for enhanced usability and security
- Updated the installation script to clarify that production wrappers do not include fixed test secrets, improving user guidance for local testing. - Modified the jiggablend-manager and jiggablend-runner scripts to remove fixed test configurations, emphasizing the use of local test credentials via `make init-test`. - Enhanced error handling in the runner script to ensure that an API key is provided, improving security and user feedback. - Added new flags and options for the runner, including sandboxing capabilities and GPU ray tracing control, enhancing flexibility for users. - Improved README documentation to reflect changes in usage and configuration, ensuring users have clear instructions for setup and execution.
This commit is contained in:
+72
-44
@@ -45,6 +45,9 @@ type Auth struct {
|
||||
sessionCache map[string]*Session // In-memory cache for performance
|
||||
cacheMu sync.RWMutex
|
||||
stopCleanup chan struct{}
|
||||
// oauthStates maps state token -> expiry for CSRF protection
|
||||
oauthStates map[string]time.Time
|
||||
oauthMu sync.Mutex
|
||||
}
|
||||
|
||||
// Session represents a user session
|
||||
@@ -63,6 +66,7 @@ func NewAuth(db *database.DB, cfg *config.Config) (*Auth, error) {
|
||||
cfg: cfg,
|
||||
sessionCache: make(map[string]*Session),
|
||||
stopCleanup: make(chan struct{}),
|
||||
oauthStates: make(map[string]time.Time),
|
||||
}
|
||||
|
||||
// Initialize Google OAuth from database config
|
||||
@@ -216,7 +220,9 @@ func (a *Auth) cleanupExpiredSessions() {
|
||||
|
||||
// initializeSettings ensures all admin settings are initialized with defaults if they don't exist
|
||||
func (a *Auth) initializeSettings() error {
|
||||
// Initialize registration_enabled setting (default: true) if it doesn't exist
|
||||
// Default registration to false (safer for internet-facing boots). Admins enable via CLI/UI.
|
||||
// In non-production, still default false — make init-test / CLI create the first admin.
|
||||
defaultReg := "false"
|
||||
var settingCount int
|
||||
err := a.db.With(func(conn *sql.DB) error {
|
||||
return conn.QueryRow("SELECT COUNT(*) FROM settings WHERE key = ?", "registration_enabled").Scan(&settingCount)
|
||||
@@ -227,15 +233,15 @@ func (a *Auth) initializeSettings() error {
|
||||
if settingCount == 0 {
|
||||
err = a.db.With(func(conn *sql.DB) error {
|
||||
_, err := conn.Exec(
|
||||
`INSERT INTO settings (key, value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP)`,
|
||||
"registration_enabled", "true",
|
||||
)
|
||||
`INSERT INTO settings (key, value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP)`,
|
||||
"registration_enabled", defaultReg,
|
||||
)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize registration_enabled setting: %w", err)
|
||||
}
|
||||
log.Printf("Initialized admin setting: registration_enabled = true")
|
||||
log.Printf("Initialized admin setting: registration_enabled = %s", defaultReg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -303,12 +309,44 @@ func (a *Auth) initializeTestUser() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateOAuthState mints a one-time OAuth state token and stores it until used or expired.
|
||||
func (a *Auth) CreateOAuthState() string {
|
||||
state := uuid.New().String()
|
||||
a.oauthMu.Lock()
|
||||
// Opportunistic cleanup of expired states
|
||||
now := time.Now()
|
||||
for k, exp := range a.oauthStates {
|
||||
if now.After(exp) {
|
||||
delete(a.oauthStates, k)
|
||||
}
|
||||
}
|
||||
a.oauthStates[state] = now.Add(10 * time.Minute)
|
||||
a.oauthMu.Unlock()
|
||||
return state
|
||||
}
|
||||
|
||||
// ConsumeOAuthState validates and single-use-consumes an OAuth state token.
|
||||
// Returns false if missing, unknown, or expired.
|
||||
func (a *Auth) ConsumeOAuthState(state string) bool {
|
||||
if state == "" {
|
||||
return false
|
||||
}
|
||||
a.oauthMu.Lock()
|
||||
defer a.oauthMu.Unlock()
|
||||
exp, ok := a.oauthStates[state]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
delete(a.oauthStates, state)
|
||||
return time.Now().Before(exp)
|
||||
}
|
||||
|
||||
// GoogleLoginURL returns the Google OAuth login URL
|
||||
func (a *Auth) GoogleLoginURL() (string, error) {
|
||||
if a.googleConfig == nil {
|
||||
return "", fmt.Errorf("Google OAuth not configured")
|
||||
}
|
||||
state := uuid.New().String()
|
||||
state := a.CreateOAuthState()
|
||||
return a.googleConfig.AuthCodeURL(state), nil
|
||||
}
|
||||
|
||||
@@ -317,7 +355,7 @@ func (a *Auth) DiscordLoginURL() (string, error) {
|
||||
if a.discordConfig == nil {
|
||||
return "", fmt.Errorf("Discord OAuth not configured")
|
||||
}
|
||||
state := uuid.New().String()
|
||||
state := a.CreateOAuthState()
|
||||
return a.discordConfig.AuthCodeURL(state), nil
|
||||
}
|
||||
|
||||
@@ -390,8 +428,8 @@ func (a *Auth) IsRegistrationEnabled() (bool, error) {
|
||||
return conn.QueryRow("SELECT value FROM settings WHERE key = ?", "registration_enabled").Scan(&value)
|
||||
})
|
||||
if err == sql.ErrNoRows {
|
||||
// Default to enabled if setting doesn't exist
|
||||
return true, nil
|
||||
// Default to disabled if setting doesn't exist (safer bootstrap)
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to check registration setting: %w", err)
|
||||
@@ -438,8 +476,9 @@ func (a *Auth) SetRegistrationEnabled(enabled bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// getOrCreateUser gets or creates a user in the database
|
||||
// Automatically links accounts by email across different OAuth providers and local login
|
||||
// getOrCreateUser gets or creates a user in the database.
|
||||
// Identity is (oauth_provider, oauth_id). Email collision with a different provider
|
||||
// is NOT auto-linked (prevents account takeover); the user must sign in with the original method.
|
||||
func (a *Auth) getOrCreateUser(provider, oauthID, email, name string) (*Session, error) {
|
||||
var userID int64
|
||||
var dbEmail, dbName string
|
||||
@@ -449,18 +488,18 @@ func (a *Auth) getOrCreateUser(provider, oauthID, email, name string) (*Session,
|
||||
// First, try to find by provider + oauth_id
|
||||
err := a.db.With(func(conn *sql.DB) error {
|
||||
return conn.QueryRow(
|
||||
"SELECT id, email, name, is_admin, oauth_provider, oauth_id FROM users WHERE oauth_provider = ? AND oauth_id = ?",
|
||||
provider, oauthID,
|
||||
).Scan(&userID, &dbEmail, &dbName, &isAdmin, &dbProvider, &dbOAuthID)
|
||||
"SELECT id, email, name, is_admin, oauth_provider, oauth_id FROM users WHERE oauth_provider = ? AND oauth_id = ?",
|
||||
provider, oauthID,
|
||||
).Scan(&userID, &dbEmail, &dbName, &isAdmin, &dbProvider, &dbOAuthID)
|
||||
})
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
// Not found by provider+oauth_id, check by email for account linking
|
||||
// Not found by provider+oauth_id — check email only to refuse silent takeover
|
||||
err = a.db.With(func(conn *sql.DB) error {
|
||||
return conn.QueryRow(
|
||||
"SELECT id, email, name, is_admin, oauth_provider, oauth_id FROM users WHERE email = ?",
|
||||
email,
|
||||
).Scan(&userID, &dbEmail, &dbName, &isAdmin, &dbProvider, &dbOAuthID)
|
||||
"SELECT id, email, name, is_admin, oauth_provider, oauth_id FROM users WHERE email = ?",
|
||||
email,
|
||||
).Scan(&userID, &dbEmail, &dbName, &isAdmin, &dbProvider, &dbOAuthID)
|
||||
})
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
@@ -487,7 +526,7 @@ func (a *Auth) getOrCreateUser(provider, oauthID, email, name string) (*Session,
|
||||
err = a.db.With(func(conn *sql.DB) error {
|
||||
result, err := conn.Exec(
|
||||
"INSERT INTO users (email, name, oauth_provider, oauth_id, is_admin) VALUES (?, ?, ?, ?, ?)",
|
||||
email, name, provider, oauthID, isAdmin,
|
||||
email, name, provider, oauthID, isAdmin,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -501,19 +540,8 @@ func (a *Auth) getOrCreateUser(provider, oauthID, email, name string) (*Session,
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("failed to query user by email: %w", err)
|
||||
} else {
|
||||
// User exists with same email but different provider - link accounts by updating provider info
|
||||
// This allows the user to log in with any provider that has the same email
|
||||
err = a.db.With(func(conn *sql.DB) error {
|
||||
_, err = conn.Exec(
|
||||
"UPDATE users SET oauth_provider = ?, oauth_id = ?, name = ? WHERE id = ?",
|
||||
provider, oauthID, name, userID,
|
||||
)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to link account: %w", err)
|
||||
}
|
||||
log.Printf("Linked account: user %d (email: %s) now accessible via %s provider", userID, email, provider)
|
||||
// Email already belongs to another identity — do not overwrite oauth_provider/oauth_id
|
||||
return nil, fmt.Errorf("an account with this email already exists; sign in with the original method")
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("failed to query user: %w", err)
|
||||
@@ -522,9 +550,9 @@ func (a *Auth) getOrCreateUser(provider, oauthID, email, name string) (*Session,
|
||||
if dbEmail != email || dbName != name {
|
||||
err = a.db.With(func(conn *sql.DB) error {
|
||||
_, err = conn.Exec(
|
||||
"UPDATE users SET email = ?, name = ? WHERE id = ?",
|
||||
email, name, userID,
|
||||
)
|
||||
"UPDATE users SET email = ?, name = ? WHERE id = ?",
|
||||
email, name, userID,
|
||||
)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
@@ -652,20 +680,20 @@ func (a *Auth) DeleteSession(sessionID string) {
|
||||
}
|
||||
}
|
||||
|
||||
// IsProductionMode returns true if running in production mode
|
||||
// This is a package-level function that checks the environment variable
|
||||
// For config-based checks, use Config.IsProductionMode()
|
||||
// IsProductionMode returns true if running in production mode.
|
||||
// Prefer Config.IsProductionMode() / Auth.IsProductionModeFromConfig() for app logic.
|
||||
// This package-level helper still honors PRODUCTION=true for legacy callers.
|
||||
func IsProductionMode() bool {
|
||||
// Check environment variable first for backwards compatibility
|
||||
if os.Getenv("PRODUCTION") == "true" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return os.Getenv("PRODUCTION") == "true"
|
||||
}
|
||||
|
||||
// IsProductionModeFromConfig returns true if production mode is enabled in config
|
||||
// or via PRODUCTION=true environment variable (OR of both sources).
|
||||
func (a *Auth) IsProductionModeFromConfig() bool {
|
||||
return a.cfg.IsProductionMode()
|
||||
if a.cfg != nil && a.cfg.IsProductionMode() {
|
||||
return true
|
||||
}
|
||||
return IsProductionMode()
|
||||
}
|
||||
|
||||
func (a *Auth) writeUnauthorized(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user