fix: stale docs, CORS header gap, hardcoded credentials (items 9-11)
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -18,7 +22,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
authUsers = map[string]string{"drjones": "czapiewski"} // default until users.json loads
|
||||
// authUsers is populated from data/users.json on startup. On the very first
|
||||
// run (no users.json) a random password is generated, saved, and printed to
|
||||
// the console — no hard-coded credentials anywhere in the binary.
|
||||
authUsers = map[string]string{}
|
||||
usersFilePath string
|
||||
usersMu sync.RWMutex
|
||||
|
||||
@@ -50,13 +57,50 @@ func loadUsers(dataDir string) {
|
||||
usersFilePath = filepath.Join(dataDir, "users.json")
|
||||
usersMu.Lock()
|
||||
defer usersMu.Unlock()
|
||||
|
||||
data, err := os.ReadFile(usersFilePath)
|
||||
if err == nil {
|
||||
var loaded map[string]string
|
||||
if json.Unmarshal(data, &loaded) == nil && len(loaded) > 0 {
|
||||
authUsers = loaded
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// First run — no users.json (or empty). Generate a random admin password,
|
||||
// save it, and print it clearly so the operator can log in immediately.
|
||||
pw := generateRandomPassword()
|
||||
authUsers = map[string]string{"admin": pw}
|
||||
if err := os.MkdirAll(dataDir, 0755); err == nil {
|
||||
d, _ := json.MarshalIndent(authUsers, "", " ")
|
||||
if writeErr := os.WriteFile(usersFilePath, d, 0600); writeErr != nil {
|
||||
log.Printf("[Auth] WARNING: could not save users.json: %v", writeErr)
|
||||
}
|
||||
}
|
||||
|
||||
banner := fmt.Sprintf(`
|
||||
╔══════════════════════════════════════════════════╗
|
||||
║ AetherForge — First Run ║
|
||||
║ ║
|
||||
║ Dashboard login ║
|
||||
║ Username : admin ║
|
||||
║ Password : %-34s║
|
||||
║ ║
|
||||
║ Save this — it will not be shown again. ║
|
||||
║ Change it later in Calibrate → Users. ║
|
||||
╚══════════════════════════════════════════════════╝`, pw)
|
||||
log.Print(banner)
|
||||
}
|
||||
|
||||
// generateRandomPassword returns a 20-character hex string suitable for use
|
||||
// as an initial admin password.
|
||||
func generateRandomPassword() string {
|
||||
b := make([]byte, 10)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
// Fallback to a fixed marker so the operator knows something went wrong.
|
||||
return "CHANGE-ME-NOW-12345"
|
||||
}
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
func saveUser(username, password string) error {
|
||||
@@ -141,7 +185,10 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
|
||||
r.Use(cors.Handler(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
|
||||
// X-Fleet-Secret is required by agent-facing endpoints; include it so
|
||||
// browser-based callers (dev tools, custom dashboards) are not blocked
|
||||
// by CORS preflight when sending that header.
|
||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-Fleet-Secret"},
|
||||
AllowCredentials: false,
|
||||
}))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user