227 lines
6.4 KiB
Go
227 lines
6.4 KiB
Go
package main
|
|
|
|
import (
|
|
"authentication/db"
|
|
"authentication/docs"
|
|
"authentication/helper"
|
|
"authentication/models"
|
|
"authentication/redisclient"
|
|
"database/sql"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
|
|
"authentication/routes"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/getsentry/sentry-go"
|
|
"github.com/gorilla/mux"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
"github.com/rs/cors"
|
|
)
|
|
|
|
// @swagger: "2.0"
|
|
// @title UESS Authentication Microservice
|
|
// @version 1.0
|
|
|
|
// @description This is the API for Authentication Microservice for UESS. It doesn't support OAS 3.0 and is only for documentation purposes. The library used doesn't support @server annotation.
|
|
// @contact.name Darrel Israel
|
|
// @contact.email d.israel.psa@gmail.com
|
|
|
|
// @BasePath /
|
|
|
|
// @securityDefinitions.apikey BearerToken
|
|
// @in header
|
|
// @name Authorization
|
|
|
|
var (
|
|
dbOpenConnections = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "db_open_connections",
|
|
Help: "Number of open database connections",
|
|
})
|
|
dbInUseConnections = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "db_in_use_connections",
|
|
Help: "Number of in-use database connections",
|
|
})
|
|
dbIdleConnections = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "db_idle_connections",
|
|
Help: "Number of idle database connections",
|
|
})
|
|
)
|
|
|
|
var (
|
|
httpRequestsTotal = prometheus.NewCounterVec(
|
|
prometheus.CounterOpts{
|
|
Name: "http_requests_total",
|
|
Help: "Total number of HTTP requests",
|
|
},
|
|
[]string{"path", "method"},
|
|
)
|
|
httpRequestDuration = prometheus.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Name: "http_request_duration_seconds",
|
|
Help: "Duration of HTTP requests in seconds",
|
|
Buckets: prometheus.DefBuckets,
|
|
},
|
|
[]string{"path", "method"},
|
|
)
|
|
httpRequestSize = prometheus.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Name: "http_request_size_bytes",
|
|
Help: "Size of HTTP requests in bytes",
|
|
Buckets: prometheus.ExponentialBuckets(100, 10, 8),
|
|
},
|
|
[]string{"path", "method"},
|
|
)
|
|
httpResponseSize = prometheus.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Name: "http_response_size_bytes",
|
|
Help: "Size of HTTP responses in bytes",
|
|
Buckets: prometheus.ExponentialBuckets(100, 10, 8),
|
|
},
|
|
[]string{"path", "method"},
|
|
)
|
|
)
|
|
|
|
func init() {
|
|
prometheus.MustRegister(httpRequestsTotal)
|
|
prometheus.MustRegister(httpRequestDuration)
|
|
prometheus.MustRegister(httpRequestSize)
|
|
prometheus.MustRegister(httpResponseSize)
|
|
prometheus.MustRegister(dbOpenConnections)
|
|
prometheus.MustRegister(dbInUseConnections)
|
|
prometheus.MustRegister(dbIdleConnections)
|
|
}
|
|
|
|
func loggingMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
start := time.Now()
|
|
|
|
if r.URL.Path != metricsPath {
|
|
helper.LogInfo(fmt.Sprintf("INFO: Started %s %s", r.Method, r.URL.Path))
|
|
}
|
|
|
|
httpRequestsTotal.WithLabelValues(r.URL.Path, r.Method).Inc()
|
|
|
|
requestSize := float64(r.ContentLength)
|
|
if requestSize < 0 {
|
|
requestSize = 0
|
|
}
|
|
httpRequestSize.WithLabelValues(r.URL.Path, r.Method).Observe(requestSize)
|
|
|
|
rw := &models.ResponseWriter{ResponseWriter: w}
|
|
next.ServeHTTP(rw, r)
|
|
|
|
duration := time.Since(start).Seconds()
|
|
httpRequestDuration.WithLabelValues(r.URL.Path, r.Method).Observe(duration)
|
|
httpResponseSize.WithLabelValues(r.URL.Path, r.Method).Observe(float64(rw.Size))
|
|
|
|
// Log completion for non-metrics endpoints
|
|
if r.URL.Path != metricsPath {
|
|
helper.LogInfo(fmt.Sprintf("INFO: Completed %s %s in %.3f seconds", r.Method, r.URL.Path, duration))
|
|
}
|
|
})
|
|
}
|
|
|
|
func collectDBMetrics(database *sql.DB) {
|
|
for {
|
|
stats := database.Stats()
|
|
dbOpenConnections.Set(float64(stats.OpenConnections))
|
|
dbInUseConnections.Set(float64(stats.InUse))
|
|
dbIdleConnections.Set(float64(stats.Idle))
|
|
time.Sleep(10 * time.Second) // Adjust the interval as needed
|
|
}
|
|
}
|
|
|
|
func allowOnlyGrafana(next http.Handler, allowedIP string) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
remoteIP, _, err := net.SplitHostPort(r.RemoteAddr)
|
|
if err != nil {
|
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
|
return
|
|
}
|
|
if remoteIP == allowedIP {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
|
})
|
|
}
|
|
|
|
func main() {
|
|
// Initialize Sentry
|
|
goEnv := os.Getenv("GO_ENV")
|
|
if goEnv == "" {
|
|
log.Fatal("GO_ENV is not set in main. Please set the GO_ENV environment variable.")
|
|
}
|
|
|
|
DSN := os.Getenv("DSN")
|
|
if DSN == "" {
|
|
log.Fatal("Sentry DSN is not set. Please set the DSN environment variable.")
|
|
}
|
|
|
|
err := sentry.Init(sentry.ClientOptions{
|
|
Dsn: os.Getenv("DSN"),
|
|
TracesSampleRate: 1.0,
|
|
Environment: goEnv,
|
|
})
|
|
if err != nil {
|
|
log.Fatalf("sentry.Init: %s", err)
|
|
}
|
|
defer sentry.Flush(2 * time.Second)
|
|
|
|
docs.SwaggerInfo.Host = "localhost:8080"
|
|
docs.SwaggerInfo.Schemes = []string{"http"}
|
|
|
|
helper.LogInfo("INFO: Initializing database connection...")
|
|
var database *sql.DB
|
|
for {
|
|
database, err = db.InitDB()
|
|
if err == nil {
|
|
break
|
|
}
|
|
helper.LogError(fmt.Errorf("ERROR: error initializing database: %v", err), "database initialization error")
|
|
time.Sleep(2 * time.Second)
|
|
}
|
|
|
|
go collectDBMetrics(database)
|
|
router := mux.NewRouter()
|
|
routes.SetupRoutes(router, database)
|
|
helper.LogInfo("INFO: Database initialized successfully.")
|
|
|
|
allowedIP := os.Getenv("ALLOWED_IP")
|
|
helper.LogInfo("INFO: Setting up routes...")
|
|
router.Handle(metricsPath, allowOnlyGrafana(promhttp.Handler(), allowedIP))
|
|
router.Use(loggingMiddleware)
|
|
|
|
c := cors.New(cors.Options{
|
|
AllowedOrigins: []string{"http://localhost:4173", "http://localhost:5173"}, // Your frontend URL
|
|
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"},
|
|
AllowedHeaders: []string{"*"}, // Allow all headers temporarily
|
|
AllowCredentials: true, // Critical for withCredentials requests
|
|
MaxAge: 86400, // Cache preflight results
|
|
})
|
|
|
|
handler := c.Handler(router)
|
|
|
|
redisclient.Init()
|
|
|
|
helper.LogInfo("INFO: Connected to Redis successfully!")
|
|
|
|
helper.LogInfo("WARNING: Ensure Redis is secured to prevent unauthorized access. Use a strong password and bind Redis to localhost or a secure network.")
|
|
|
|
helper.LogInfo("INFO: Authentication Microservice is running on http://localhost:8080")
|
|
server := &http.Server{
|
|
Addr: ":8080",
|
|
Handler: handler,
|
|
ReadTimeout: 15 * time.Second,
|
|
WriteTimeout: 300 * time.Second,
|
|
IdleTimeout: 60 * time.Second,
|
|
}
|
|
log.Fatal(server.ListenAndServe())
|
|
}
|
|
|