diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 47cffb6..f04a0d0 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -3,15 +3,17 @@ package main import ( - "flag" // standard library for parsing command-line flags - "fmt" // formatted I/O, used here for printing output - "log" // logging package - "net/http" // HTTP server and client - "os" // OS-level operations like reading command-line args - "strings" // string manipulation utilities + "crypto/rand" // cryptographically secure random number generator + "flag" // standard library for parsing command-line flags + "fmt" // formatted I/O, used here for printing output + "log" // logging package + "net/http" // HTTP server and client + "os" // OS-level operations like reading command-line args + "strings" // string manipulation utilities "github.com/gin-gonic/gin" // web framework "github.com/imc-vibe/backend/internal/api" // HTTP API routing and handlers + "github.com/imc-vibe/backend/internal/auth" // password hashing "github.com/imc-vibe/backend/internal/config" // configuration loading "github.com/imc-vibe/backend/internal/db" // database connection and operations ) @@ -33,6 +35,10 @@ func main() { // Common ports: 80 (HTTP), 443 (HTTPS), 8080 (HTTP alternate). port := flag.String("port", "", "Port to listen on (default: 8080)") + // --reset-admin-password: Generate a random password for the admin user and exit. + // Useful for initial setup or recovering from a lost password. + resetAdminPassword := flag.Bool("reset-admin-password", false, "Reset admin password to a random value and exit") + // Customize the help output to show double-hyphen style (Unix convention). // By default, Go's flag package uses single hyphens. flag.Usage = func() { @@ -75,6 +81,23 @@ func main() { log.Printf("Warning: Could not initialize schema: %v", err) // Non-fatal = continue } + // Handle --reset-admin-password flag. + // When this flag is set, we generate a random password, update the database, and exit. + if *resetAdminPassword { + password := generateRandomPassword(16) + hash, err := auth.HashPassword(password) + if err != nil { + log.Fatalf("Failed to hash password: %v", err) + } + if err := database.UpsertAdminUser("admin", hash); err != nil { + log.Fatalf("Failed to reset admin password: %v", err) + } + fmt.Printf("Admin password reset successfully.\n") + fmt.Printf("Username: admin\n") + fmt.Printf("Password: %s\n", password) + return + } + // Start the web server. // From here on, we set up the HTTP router and handlers. @@ -162,6 +185,19 @@ func serveGinStaticFile(c *gin.Context, fs http.FileSystem, path string) { http.ServeContent(c.Writer, c.Request, fi.Name(), fi.ModTime(), file) } +// generateRandomPassword creates a cryptographically secure random password. +func generateRandomPassword(length int) string { + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*" + result := make([]byte, length) + if _, err := rand.Read(result); err != nil { + log.Fatalf("Failed to generate random password: %v", err) + } + for i := range result { + result[i] = charset[int(result[i])%len(charset)] + } + return string(result) +} + // mimeType returns the MIME type for a file based on its extension. // MIME types tell the browser what kind of file it is. // This is important for browsers to correctly interpret files. diff --git a/backend/internal/db/imc_users.go b/backend/internal/db/imc_users.go index 1c703f6..a02c837 100644 --- a/backend/internal/db/imc_users.go +++ b/backend/internal/db/imc_users.go @@ -68,6 +68,17 @@ func (d *DB) DeleteImcUser(id uint) error { return d.Delete(&ImcUser{}, id).Error // Delete by primary key } +// UpsertAdminUser creates the admin user if it doesn't exist, or updates the password if it does. +func (d *DB) UpsertAdminUser(username, passwordHash string) error { + var user ImcUser + err := d.Where("username = ? AND role = ?", username, "admin").First(&user).Error + if err == nil { + return d.Model(&ImcUser{}).Where("id = ?", user.ID).Update("password_hash", passwordHash).Error + } + _, err = d.CreateImcUser(username, passwordHash, "admin") + return err +} + // ============================================================================= // Domain Access Control // These functions manage which domains users can access.