From 46eeb18a317d444b662cf52ba24a5b1204dcfb46 Mon Sep 17 00:00:00 2001 From: LordMathis Date: Fri, 1 Nov 2024 17:04:08 +0100 Subject: [PATCH] Setup jwt signing key --- backend/cmd/server/main.go | 48 +++++++++++++------ backend/internal/config/config.go | 2 + backend/internal/db/migrations.go | 9 +++- backend/internal/db/system_settings.go | 65 ++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 backend/internal/db/system_settings.go diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 4effa3d..cc21fb7 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -4,19 +4,19 @@ import ( "log" "net/http" "os" + "time" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "novamd/internal/api" + "novamd/internal/auth" "novamd/internal/config" "novamd/internal/db" "novamd/internal/filesystem" - "novamd/internal/user" ) func main() { - // Load configuration cfg, err := config.Load() if err != nil { @@ -28,38 +28,56 @@ func main() { if err != nil { log.Fatal(err) } - defer func() { - if err := database.Close(); err != nil { - log.Printf("Error closing database: %v", err) + defer database.Close() + + // Get or generate JWT signing key + signingKey := cfg.JWTSigningKey + if signingKey == "" { + signingKey, err = database.EnsureJWTSecret() + if err != nil { + log.Fatal("Failed to ensure JWT secret:", err) } - }() + } // Initialize filesystem fs := filesystem.New(cfg.WorkDir) - // Initialize user service - userService := user.NewUserService(database, fs) - - // Create admin user - if _, err := userService.SetupAdminUser(cfg.AdminEmail, cfg.AdminPassword); err != nil { - log.Fatal(err) + // Initialize JWT service + jwtService, err := auth.NewJWTService(auth.JWTConfig{ + SigningKey: signingKey, + AccessTokenExpiry: 15 * time.Minute, + RefreshTokenExpiry: 7 * 24 * time.Hour, + }) + if err != nil { + log.Fatal("Failed to initialize JWT service:", err) } + // Initialize auth middleware + authMiddleware := auth.NewMiddleware(jwtService) + + // Initialize session service + sessionService := auth.NewSessionService(database.DB, jwtService) + // Set up router r := chi.NewRouter() + + // Middleware r.Use(middleware.Logger) r.Use(middleware.Recoverer) + r.Use(middleware.RequestID) + r.Use(middleware.RealIP) + r.Use(middleware.Timeout(30 * time.Second)) - // API routes + // Set up routes r.Route("/api/v1", func(r chi.Router) { - api.SetupRoutes(r, database, fs) + api.SetupRoutes(r, database, fs, authMiddleware, sessionService) }) // Handle all other routes with static file server r.Get("/*", api.NewStaticHandler(cfg.StaticPath).ServeHTTP) // Start server - port := os.Getenv("NOVAMD_PORT") + port := os.Getenv("PORT") if port == "" { port = "8080" } diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index 02257bf..b81e28e 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -16,6 +16,7 @@ type Config struct { AdminEmail string AdminPassword string EncryptionKey string + JWTSigningKey string } func DefaultConfig() *Config { @@ -69,6 +70,7 @@ func Load() (*Config, error) { config.AdminEmail = os.Getenv("NOVAMD_ADMIN_EMAIL") config.AdminPassword = os.Getenv("NOVAMD_ADMIN_PASSWORD") config.EncryptionKey = os.Getenv("NOVAMD_ENCRYPTION_KEY") + config.JWTSigningKey = os.Getenv("NOVAMD_JWT_SIGNING_KEY") // Validate all settings if err := config.Validate(); err != nil { diff --git a/backend/internal/db/migrations.go b/backend/internal/db/migrations.go index b62192f..ae5e4c9 100644 --- a/backend/internal/db/migrations.go +++ b/backend/internal/db/migrations.go @@ -67,7 +67,14 @@ var migrations = []Migration{ ALTER TABLE workspaces ADD COLUMN created_by INTEGER REFERENCES users(id); ALTER TABLE workspaces ADD COLUMN updated_by INTEGER REFERENCES users(id); ALTER TABLE workspaces ADD COLUMN updated_at TIMESTAMP; - `, + + -- Create system_settings table for application settings + CREATE TABLE IF NOT EXISTS system_settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + );`, }, } diff --git a/backend/internal/db/system_settings.go b/backend/internal/db/system_settings.go new file mode 100644 index 0000000..0c8f75b --- /dev/null +++ b/backend/internal/db/system_settings.go @@ -0,0 +1,65 @@ +package db + +import ( + "crypto/rand" + "encoding/base64" + "fmt" +) + +const ( + JWTSecretKey = "jwt_secret" +) + +// EnsureJWTSecret makes sure a JWT signing secret exists in the database +// If no secret exists, it generates and stores a new one +func (db *DB) EnsureJWTSecret() (string, error) { + // First, try to get existing secret + secret, err := db.GetSystemSetting(JWTSecretKey) + if err == nil { + return secret, nil + } + + // Generate new secret if none exists + newSecret, err := generateRandomSecret(32) // 256 bits + if err != nil { + return "", fmt.Errorf("failed to generate JWT secret: %w", err) + } + + // Store the new secret + err = db.SetSystemSetting(JWTSecretKey, newSecret) + if err != nil { + return "", fmt.Errorf("failed to store JWT secret: %w", err) + } + + return newSecret, nil +} + +// GetSystemSetting retrieves a system setting by key +func (db *DB) GetSystemSetting(key string) (string, error) { + var value string + err := db.QueryRow("SELECT value FROM system_settings WHERE key = ?", key).Scan(&value) + if err != nil { + return "", err + } + return value, nil +} + +// SetSystemSetting stores or updates a system setting +func (db *DB) SetSystemSetting(key, value string) error { + _, err := db.Exec(` + INSERT INTO system_settings (key, value) + VALUES (?, ?) + ON CONFLICT(key) DO UPDATE SET value = ?`, + key, value, value) + return err +} + +// generateRandomSecret generates a cryptographically secure random string +func generateRandomSecret(bytes int) (string, error) { + b := make([]byte, bytes) + _, err := rand.Read(b) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(b), nil +}