diff --git a/backend/go.mod b/backend/go.mod index 66ea4ba..aa286fb 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -3,6 +3,7 @@ module github.com/metakgp/naarad/backend go 1.22.4 require ( + github.com/joho/godotenv v1.5.1 github.com/mattn/go-sqlite3 v1.14.22 github.com/rs/cors v1.11.0 golang.org/x/oauth2 v0.21.0 diff --git a/backend/go.sum b/backend/go.sum index 29eeb69..d0d8b4b 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -56,6 +56,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfF github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/backend/main.go b/backend/main.go index 8588570..49235b0 100644 --- a/backend/main.go +++ b/backend/main.go @@ -4,6 +4,7 @@ import ( "database/sql" "encoding/json" "fmt" + "log" "math/rand" "net/http" "os" @@ -11,6 +12,7 @@ import ( "strings" "time" + "github.com/joho/godotenv" _ "github.com/mattn/go-sqlite3" "github.com/rs/cors" ) @@ -38,7 +40,39 @@ var jwtValidateResp struct { } var healthResponse struct { - Healthy bool `json:"healthy"` + Healthy bool `json:"healthy"` +} + +type responseRecorder struct { + http.ResponseWriter + status int + size int +} + +func (r *responseRecorder) WriteHeader(status int) { + r.status = status + r.ResponseWriter.WriteHeader(status) +} + +func LoggerMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + recorder := &responseRecorder{w, http.StatusOK, 0} + next.ServeHTTP(recorder, r) + + logLevel := "INFO" + if recorder.status >= 400 { + logLevel = "ERROR" + } + + log.Printf("%s:\t%s - %q %s %d %s\n", + logLevel, + r.Header.Get("X-Real-IP"), + r.Method, + r.RequestURI, + recorder.status, + http.StatusText(recorder.status), + ) + }) } func PasswordGenerator(passwordLength int) string { @@ -70,8 +104,8 @@ func PasswordGenerator(passwordLength int) string { func register(res http.ResponseWriter, req *http.Request) { cookie, err := req.Cookie("heimdall") if err != nil { - fmt.Println("Cookie Error (Heimdall token not found): ", err.Error()) - http.Error(res, "No Heimdall session token received", http.StatusUnauthorized) + fmt.Println("[ERROR] ~ Retrieving Heimdall Token: ", err.Error()) + http.Error(res, "[ERROR] ~ Retrieving Heimdall Token", http.StatusUnauthorized) return } tokenString := cookie.Value @@ -82,14 +116,14 @@ func register(res http.ResponseWriter, req *http.Request) { client := &http.Client{} resp, err := client.Do(reqEmail) if err != nil { - fmt.Println("heimdall/validate-jwt Error: ", err.Error()) - http.Error(res, "Failed to validate Heimdall session", http.StatusInternalServerError) + fmt.Println("[ERROR] ~ Validating Heimdall Token ~", tokenString, ": ", err.Error()) + http.Error(res, "[ERROR] ~ Validating Heimdall Token", http.StatusInternalServerError) return } defer resp.Body.Close() if err := json.NewDecoder(resp.Body).Decode(&jwtValidateResp); err != nil { - fmt.Println("Heimdall Response | Email Decoder Error: ", err.Error()) - http.Error(res, "Failed to retrieve user email", http.StatusInternalServerError) + fmt.Println("[ERROR] ~ Parsing Heimdall Token ~", tokenString, ": ", err.Error()) + http.Error(res, "[ERROR] ~ Parsing Heimdall Token", http.StatusInternalServerError) return } @@ -102,59 +136,59 @@ func register(res http.ResponseWriter, req *http.Request) { signupData := fmt.Sprintf(`{"username": "%s", "password": "%s"}`, username, password) reqNtfy, _ := http.NewRequest("POST", ntfyServerAddr+"/v1/account", strings.NewReader(signupData)) for name, values := range req.Header { - reqNtfy.Header[name] = values - } + reqNtfy.Header[name] = values + } resp, err = client.Do(reqNtfy) if err != nil { - fmt.Println("NTFY User Registration API Error: ", err.Error()) - http.Error(res, "Failed to request user registration for Naarad", http.StatusInternalServerError) + fmt.Println("[ERROR] ~ Requesting User Registration ~", username, ": ", err.Error()) + http.Error(res, "[ERROR] ~ Requesting User Registration", http.StatusInternalServerError) return } defer resp.Body.Close() if resp.StatusCode == 409 { - fmt.Println("User already registered") - http.Error(res, "User already registered", resp.StatusCode) + fmt.Println("[INFO] ~ User Already Registered: ", username) + http.Error(res, "[INFO] ~ User Already Registered", resp.StatusCode) return } else if resp.StatusCode != 200 { - fmt.Println("User registeration failed") - http.Error(res, "Failed to register user", resp.StatusCode) + fmt.Println("[ERROR] ~ Registering User: ", username) + http.Error(res, "[ERROR] ~ Registering User", resp.StatusCode) return } // Get the userid from sqlite db rowD := db.QueryRow(`SELECT id FROM user WHERE user=?`, username) if err = rowD.Scan(&userId); err != nil { - fmt.Println("Database Error | Get User ID: ", err.Error()) - http.Error(res, "Internal Server Error (DB: Fetch UserID)", http.StatusInternalServerError) + fmt.Println("[ERROR] ~ Fetching UserId ~", username, ": ", err.Error()) + http.Error(res, "Internal Server Error", http.StatusInternalServerError) return } // Provide read-only access for kgp-* channels to the user queryGenAccess := fmt.Sprintf(`INSERT INTO user_access VALUES("%s", "kgp-%%", 1, 0, "")`, userId) if _, err = db.Exec(queryGenAccess); err != nil { - fmt.Println("Granting Access Error (kgp-*): ", err.Error()) - http.Error(res, "Internal Server Error (DB: Access Grant)", http.StatusInternalServerError) + fmt.Println("[ERROR] ~ Granting Access to (kgp-*) ~", username, ": ", err.Error()) + http.Error(res, "Internal Server Error", http.StatusInternalServerError) return } // Provide read-only access for kgp-* channels to the user queryGenAccess = fmt.Sprintf(`INSERT INTO user_access VALUES("%s", "st_%%", 1, 0, "")`, userId) if _, err = db.Exec(queryGenAccess); err != nil { - fmt.Println("Granting Access Error (st_*): ", err.Error()) - http.Error(res, "Internal Server Error (DB: Access Grant)", http.StatusInternalServerError) + fmt.Println("[ERROR] ~ Granting Access to (st_*) ~", username, ": ", err.Error()) + http.Error(res, "Internal Server Error", http.StatusInternalServerError) return } // Sending user credentials over mail emailBody := fmt.Sprintf("Here are the credentials to sign in into Naarad.\n\nUsername: %s\nPassword: %s", username, password) if sent, err := sendMail(userEmail, "Naarad Login Credentials | Metakgp", emailBody); err != nil || !sent { - fmt.Println("Sending Credentials Error: ", err.Error()) - http.Error(res, "Failed to send user credentials", http.StatusInternalServerError) + fmt.Println("[ERROR] ~ Sending Credentials ~", username, ": ", err.Error()) + http.Error(res, "[ERROR] ~ Sending Credentials", http.StatusInternalServerError) return } http.Header.Add(res.Header(), "content-type", "application/json") - resStruct.Msg = "User created successfully!" + resStruct.Msg = "[INFO] ~ User " + "(" + username + ")" + "Created Successfully!" if err = json.NewEncoder(res).Encode(&resStruct); err != nil { http.Error(res, err.Error(), http.StatusInternalServerError) @@ -163,7 +197,7 @@ func register(res http.ResponseWriter, req *http.Request) { } func healthCheck(res http.ResponseWriter, req *http.Request) { - res.Header().Set("Content-Type", "application/json") + res.Header().Set("Content-Type", "application/json") // Check database connection err := db.Ping() @@ -179,10 +213,11 @@ func healthCheck(res http.ResponseWriter, req *http.Request) { res.WriteHeader(http.StatusOK) } - json.NewEncoder(res).Encode(healthResponse) + json.NewEncoder(res).Encode(healthResponse) } func main() { + godotenv.Load() initMailer() passwordSize, err := strconv.Atoi(os.Getenv("PASSWORD_SIZE")) @@ -208,15 +243,21 @@ func main() { panic(err) } - http.HandleFunc("GET /health", healthCheck) - http.HandleFunc("GET /register", register) + mux := http.NewServeMux() + mux.HandleFunc("GET /health", healthCheck) + mux.HandleFunc("GET /register", register) + c := cors.New(cors.Options{ AllowedOrigins: []string{"https://naarad.metakgp.org", "https://naarad-signup.metakgp.org", "http://localhost:3000"}, AllowCredentials: true, }) - fmt.Println("Naarad Backend Server running on port : 5173") - if err = http.ListenAndServe(":5173", c.Handler(http.DefaultServeMux)); err != nil { + + handler := c.Handler(mux) + loggedHandler := LoggerMiddleware(handler) + if err := http.ListenAndServe(":5173", loggedHandler); err != nil { fmt.Printf("error starting server: %s\n", err) panic(err) + } else { + fmt.Println("Naarad Backend Server running on port : 5173") } }