diff --git a/backend/cmd/kubevoyage/main.go b/backend/cmd/kubevoyage/main.go index 3ee8a75..2e03739 100644 --- a/backend/cmd/kubevoyage/main.go +++ b/backend/cmd/kubevoyage/main.go @@ -1,8 +1,8 @@ package main import ( + "github.com/B-Urb/KubeVoyage/internal/app" "github.com/B-Urb/KubeVoyage/internal/handlers" - "github.com/B-Urb/KubeVoyage/internal/models" "github.com/rs/cors" "gorm.io/gorm" "log" @@ -14,25 +14,22 @@ import ( var db *gorm.DB func main() { - app, err := NewApp() + app, err := application.NewApp() // Assuming NewApp is in the same package if err != nil { - log.Fatalf("Failed to initialize app: %v", err) + // Handle error } + handler := handlers.NewHandler(app.DB) app.Migrate() - handler := setupServer(app) + mux := setupServer(handler) log.Println("Starting server on :8080") - log.Fatal(http.ListenAndServe(":8080", handler)) + log.Fatal(http.ListenAndServe(":8080", mux)) } -func setupServer(app *App) http.Handler { +func setupServer(handle *handlers.Handler) http.Handler { mux := http.NewServeMux() - // Migrate the schema - db.AutoMigrate(&models.User{}, &models.Site{}, &models.UserSite{}) - //generateTestData() - handler := cors.Default().Handler(mux) // Serve static files @@ -62,19 +59,18 @@ func setupServer(app *App) http.Handler { handlers.HandleRequests(w, r, db) }) mux.HandleFunc("/api/register", func(w http.ResponseWriter, r *http.Request) { - handlers.HandleRegister(w, r, db) + handle.HandleRegister(w, r) }) mux.HandleFunc("/api/login", func(w http.ResponseWriter, r *http.Request) { - handlers.HandleLogin(w, r, db) + handle.HandleLogin(w, r) }) mux.HandleFunc("/api/authenticate", func(w http.ResponseWriter, r *http.Request) { - handlers.HandleAuthenticate(w, r, db) + handle.HandleAuthenticate(w, r) }) mux.HandleFunc("/api/request", func(w http.ResponseWriter, r *http.Request) { handlers.HandleRequestSite(w, r, db) }) - handler := cors.Default().Handler(mux) return handler } diff --git a/backend/go.mod b/backend/go.mod index cdcdecf..f7460c4 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -2,6 +2,7 @@ module github.com/B-Urb/KubeVoyage go 1.21 + require ( github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/rs/cors v1.10.0 diff --git a/backend/internal/app/app.go b/backend/internal/app/app.go index b58cc3b..c0b1e07 100644 --- a/backend/internal/app/app.go +++ b/backend/internal/app/app.go @@ -1,50 +1,30 @@ -package main +package application import ( "fmt" + "github.com/B-Urb/KubeVoyage/internal/database" "github.com/B-Urb/KubeVoyage/internal/models" "gorm.io/gorm" - "log" - "os" ) type App struct { - DB *gorm.DB - JWTKey []byte - BaseURL string + DB *gorm.DB } func NewApp() (*App, error) { - db, err := initializeDatabase() + db, err := database.InitializeDatabase() if err != nil { return nil, fmt.Errorf("failed to initialize database: %v", err) } - jwtKey, err := getEnvOrError("JWT_SECRET_KEY") - if err != nil { - log.Fatalf("Error reading JWT_SECRET_KEY: %v", err) - } - - baseURL, err := getEnvOrError("BASE_URL") - if err != nil { - log.Fatalf("Error reading BASE_URL: %v", err) - } - return &App{ - DB: db, - JWTKey: []byte(jwtKey), - BaseURL: baseURL, + DB: db, }, nil } func (app *App) Migrate() { - app.DB.AutoMigrate(&models.User{}, &models.Site{}, &models.UserSite{}) -} - -func getEnvOrError(key string) (string, error) { - value := os.Getenv(key) - if value == "" { - return "", fmt.Errorf("environment variable %s not set", key) + err := app.DB.AutoMigrate(models.User{}, models.Site{}, models.UserSite{}) + if err != nil { + return } - return value, nil } diff --git a/backend/internal/database/database.go b/backend/internal/database/database.go index 6c964de..86ad6cd 100644 --- a/backend/internal/database/database.go +++ b/backend/internal/database/database.go @@ -2,41 +2,41 @@ package database import ( "fmt" + "github.com/B-Urb/KubeVoyage/internal/util" "gorm.io/driver/mysql" "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" - "os" ) func InitializeDatabase() (*gorm.DB, error) { // Read environment variables - dbType, err := getEnvOrError("DB_TYPE") + dbType, err := util.GetEnvOrError("DB_TYPE") if err != nil { return nil, err } - dbHost, err := getEnvOrError("DB_HOST") + dbHost, err := util.GetEnvOrError("DB_HOST") if err != nil { return nil, err } - dbPort, err := getEnvOrError("DB_PORT") + dbPort, err := util.GetEnvOrError("DB_PORT") if err != nil { return nil, err } - dbUser, err := getEnvOrError("DB_USER") + dbUser, err := util.GetEnvOrError("DB_USER") if err != nil { return nil, err } - dbPassword, err := getEnvOrError("DB_PASSWORD") + dbPassword, err := util.GetEnvOrError("DB_PASSWORD") if err != nil { return nil, err } - dbName, err := getEnvOrError("DB_NAME") + dbName, err := util.GetEnvOrError("DB_NAME") if err != nil { return nil, err } @@ -64,11 +64,3 @@ func InitializeDatabase() (*gorm.DB, error) { return db, nil } - -func getEnvOrError(key string) (string, error) { - value := os.Getenv(key) - if value == "" { - return "", fmt.Errorf("Environment variable %s not set", key) - } - return value, nil -} diff --git a/backend/internal/go.mod b/backend/internal/go.mod deleted file mode 100644 index 350fc31..0000000 --- a/backend/internal/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module internal - -go 1.21 diff --git a/backend/internal/handlers/auth.go b/backend/internal/handlers/auth.go index 40a299e..c733dc1 100644 --- a/backend/internal/handlers/auth.go +++ b/backend/internal/handlers/auth.go @@ -8,6 +8,8 @@ import ( "errors" "fmt" "github.com/B-Urb/KubeVoyage/internal/models" + "github.com/B-Urb/KubeVoyage/internal/util" + "github.com/dgrijalva/jwt-go" "golang.org/x/crypto/scrypt" "gorm.io/gorm" "log" @@ -16,9 +18,26 @@ import ( "time" ) -var jwtKey = []byte("your_secret_key") +type Handler struct { + db *gorm.DB + JWTKey []byte + BaseURL string +} + +func NewHandler(db *gorm.DB) *Handler { + jwtKey, err := util.GetEnvOrError("JWT_SECRET_KEY") + if err != nil { + log.Fatalf("Error reading JWT_SECRET_KEY: %v", err) + } -func (app *App) HandleLogin(w http.ResponseWriter, r *http.Request) { + baseURL, err := util.GetEnvOrError("BASE_URL") + if err != nil { + log.Fatalf("Error reading BASE_URL: %v", err) + } + return &Handler{db: db, JWTKey: []byte(jwtKey), BaseURL: baseURL} +} + +func (h *Handler) HandleLogin(w http.ResponseWriter, r *http.Request) { var inputUser models.User var dbUser models.User @@ -30,7 +49,7 @@ func (app *App) HandleLogin(w http.ResponseWriter, r *http.Request) { } // Fetch the user from the database - result := db.Where("email = ?", inputUser.Email).First(&dbUser) + result := h.db.Where("email = ?", inputUser.Email).First(&dbUser) if result.Error != nil { sendJSONError(w, "User not found", http.StatusNotFound) return @@ -54,7 +73,7 @@ func (app *App) HandleLogin(w http.ResponseWriter, r *http.Request) { }) // Sign and get the complete encoded token as a string using the secret - tokenString, err := token.SignedString(jwtKey) + tokenString, err := token.SignedString(h.JWTKey) if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) return @@ -79,10 +98,13 @@ func (app *App) HandleLogin(w http.ResponseWriter, r *http.Request) { } // Here, you'd typically generate a JWT or session token and send it back to the client. // For simplicity, we'll just send a success message. - w.Write([]byte("Login successful")) + _, err = w.Write([]byte("Login successful")) + if err != nil { + return + } } -func (app *App) HandleRegister(w http.ResponseWriter, r *http.Request) { +func (h *Handler) HandleRegister(w http.ResponseWriter, r *http.Request) { var user models.User // Parse the request body @@ -107,7 +129,7 @@ func (app *App) HandleRegister(w http.ResponseWriter, r *http.Request) { } user.Password = base64.StdEncoding.EncodeToString(hash) var existingUser models.User - if err := db.Where("email = ?", user.Email).First(&existingUser).Error; err != nil { + if err := h.db.Where("email = ?", user.Email).First(&existingUser).Error; err != nil { if !errors.Is(err, gorm.ErrRecordNotFound) { sendJSONError(w, "Database error", http.StatusInternalServerError) return @@ -117,7 +139,7 @@ func (app *App) HandleRegister(w http.ResponseWriter, r *http.Request) { return } // Save the user to the database - result := db.Create(&user) + result := h.db.Create(&user) if result.Error != nil { sendJSONError(w, result.Error.Error(), http.StatusInternalServerError) return @@ -125,11 +147,11 @@ func (app *App) HandleRegister(w http.ResponseWriter, r *http.Request) { sendJSONSuccess(w, "", http.StatusCreated) } -func (app *App) HandleAuthenticate(w http.ResponseWriter, r *http.Request) { +func (h *Handler) HandleAuthenticate(w http.ResponseWriter, r *http.Request) { // 1. Extract the user's email from the session or JWT token. - userEmail, err := app.getUserEmailFromToken(r) + userEmail, err := h.getUserEmailFromToken(r) if err != nil { - app.logError(w, "Failed to get user email from token", err, http.StatusUnauthorized) + h.logError(w, "Failed to get user email from token", err, http.StatusUnauthorized) return } @@ -138,14 +160,14 @@ func (app *App) HandleAuthenticate(w http.ResponseWriter, r *http.Request) { if siteURL == "" { siteURL = r.URL.Query().Get("redirect") if siteURL == "" { - app.logError(w, "Redirect URL missing from both header and URL parameter", nil, http.StatusBadRequest) + h.logError(w, "Redirect URL missing from both header and URL parameter", nil, http.StatusBadRequest) return } } // 3. Query the database to check if the user has an "authorized" state for the given site. var userSite models.UserSite - err = app.DB.Joins("JOIN users ON users.id = user_sites.user_id"). + err = h.db.Joins("JOIN users ON users.id = user_sites.user_id"). Joins("JOIN sites ON sites.id = user_sites.site_id"). Where("users.email = ? AND sites.url = ? AND user_sites.state = ?", userEmail, siteURL, "authorized"). First(&userSite).Error @@ -156,13 +178,13 @@ func (app *App) HandleAuthenticate(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/request?redirect="+url.QueryEscape(siteURL), http.StatusSeeOther) return } - app.logError(w, "Database error while checking user authorization", err, http.StatusInternalServerError) + h.logError(w, "Database error while checking user authorization", err, http.StatusInternalServerError) return } http.Redirect(w, r, siteURL, http.StatusSeeOther) } -func (app *App) logError(w http.ResponseWriter, message string, err error, statusCode int) { +func (h *Handler) logError(w http.ResponseWriter, message string, err error, statusCode int) { logMessage := message if err != nil { logMessage = fmt.Sprintf("%s: %v", message, err) @@ -171,7 +193,7 @@ func (app *App) logError(w http.ResponseWriter, message string, err error, statu http.Error(w, message, statusCode) } -func getUserEmailFromToken(r *http.Request) (string, error) { +func (h *Handler) getUserEmailFromToken(r *http.Request) (string, error) { cookie, err := r.Cookie("auth_token") if err != nil { return "", fmt.Errorf("Authentication cookie missing") @@ -181,7 +203,7 @@ func getUserEmailFromToken(r *http.Request) (string, error) { claims := &jwt.MapClaims{} _, err = jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) { - return jwtKey, nil + return h.JWTKey, nil }) if err != nil { diff --git a/backend/internal/test/test.go b/backend/internal/test/test.go new file mode 100644 index 0000000..01de882 --- /dev/null +++ b/backend/internal/test/test.go @@ -0,0 +1,33 @@ +package test + +//func generateTestData() { +// // Insert test data for Users +// users := []models.User{ +// {Email: "user1@example.com", Password: "password1", Role: "admin"}, +// {Email: "user2@example.com", Password: "password2", Role: "user"}, +// {Email: "user3@example.com", Password: "password3", Role: "user"}, +// } +// for _, user := range users { +// db.Create(&user) +// } +// +// // Insert test data for Sites +// sites := []models.Site{ +// {URL: "https://site1.com"}, +// {URL: "https://site2.com"}, +// {URL: "https://site3.com"}, +// } +// for _, site := range sites { +// db.Create(&site) +// } +// +// // Insert test data for UserSite +// userSites := []models.UserSite{ +// {UserID: 1, SiteID: 1, State: "authorized"}, +// {UserID: 2, SiteID: 2, State: "requested"}, +// {UserID: 3, SiteID: 3, State: "authorized"}, +// } +// for _, userSite := range userSites { +// db.Create(&userSite) +// } +//} diff --git a/backend/internal/util/util.go b/backend/internal/util/util.go index acd9a36..be51573 100644 --- a/backend/internal/util/util.go +++ b/backend/internal/util/util.go @@ -1,37 +1,14 @@ -package main +package util import ( - "github.com/B-Urb/KubeVoyage/internal/models" + "fmt" + "os" ) -func generateTestData() { - // Insert test data for Users - users := []models.User{ - {Email: "user1@example.com", Password: "password1", Role: "admin"}, - {Email: "user2@example.com", Password: "password2", Role: "user"}, - {Email: "user3@example.com", Password: "password3", Role: "user"}, - } - for _, user := range users { - db.Create(&user) - } - - // Insert test data for Sites - sites := []models.Site{ - {URL: "https://site1.com"}, - {URL: "https://site2.com"}, - {URL: "https://site3.com"}, - } - for _, site := range sites { - db.Create(&site) - } - - // Insert test data for UserSite - userSites := []models.UserSite{ - {UserID: 1, SiteID: 1, State: "authorized"}, - {UserID: 2, SiteID: 2, State: "requested"}, - {UserID: 3, SiteID: 3, State: "authorized"}, - } - for _, userSite := range userSites { - db.Create(&userSite) +func GetEnvOrError(key string) (string, error) { + value := os.Getenv(key) + if value == "" { + return "", fmt.Errorf("environment variable %s not set", key) } + return value, nil }