package main import ( "baron-sso-backend/internal/handler" "baron-sso-backend/internal/logger" "baron-sso-backend/internal/repository" "baron-sso-backend/internal/service" "fmt" "log/slog" "os" "strconv" "strings" "time" "github.com/bwmarrin/snowflake" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/encryptcookie" "github.com/gofiber/fiber/v2/middleware/recover" "github.com/gofiber/fiber/v2/middleware/requestid" ) func getEnv(key, fallback string) string { if value, ok := os.LookupEnv(key); ok { return value } return fallback } func main() { // 0. Initialize Logger logger.Init(logger.Config{ ServiceName: "baron-sso", Environment: getEnv("GO_ENV", "dev"), }) // Initialize Snowflake Node (Node 2 for Baron) node, err := snowflake.NewNode(2) if err != nil { slog.Error("Failed to initialize snowflake node", "error", err) os.Exit(1) } // 1. Log Config on Startup fmt.Println("============================================================") fmt.Println(` |\__/,| (\ _.|o o |_ ) ) -(((---(((-------- `) fmt.Println("🚀 Baron SSO Backend Starting...") slog.Info("Service starting", "service", "baron-sso", "app_env", getEnv("APP_ENV", "dev"), "db_port", getEnv("DB_PORT", "5532"), "backend_port", getEnv("BACKEND_PORT", "3000"), "frontend_port", getEnv("FRONTEND_PORT", "5000"), "frontend_url", getEnv("FRONTEND_URL", "http://ssologin.hmac.kr"), "redis_addr", getEnv("REDIS_ADDR", "redis:6379"), ) // 2. Initialize DB Connections chHost := getEnv("CLICKHOUSE_HOST", "localhost") chPort, _ := strconv.Atoi(getEnv("CLICKHOUSE_PORT_NATIVE", "9000")) chUser := getEnv("CLICKHOUSE_USER", "default") chPass := getEnv("CLICKHOUSE_PASSWORD", "") chDB := getEnv("CLICKHOUSE_DB", "default") auditRepo, err := repository.NewClickHouseRepository(chHost, chPort, chUser, chPass, chDB) if err != nil { slog.Warn("Failed to connect to ClickHouse. Audit logs will fail.", "error", err) } redisService, err := service.NewRedisService() if err != nil { slog.Warn("Failed to connect to Redis. Auth features may fail.", "error", err) } // 2. Initialize Handlers auditHandler := handler.NewAuditHandler(auditRepo) authHandler := handler.NewAuthHandler(redisService) adminHandler := handler.NewAdminHandler() // 3. Initialize Fiber app := fiber.New(fiber.Config{ AppName: "Baron SSO Backend", DisableStartupMessage: true, // Clean logs }) // Middleware app.Use(requestid.New(requestid.Config{ Generator: func() string { return node.Generate().String() }, })) // [Standardized] HTTP Request Logger Middleware using slog app.Use(func(c *fiber.Ctx) error { start := time.Now() // Handle request err := c.Next() // Log after request latency := time.Since(start) status := c.Response().StatusCode() path := c.Path() // Skip logging for all successful requests (status < 400) if status < 400 { return err } msg := "http_request" if err != nil { msg = "http_request_error" } slog.Info(msg, "status", status, "method", c.Method(), "path", path, "latency", latency.String(), "ip", c.IP(), "req_id", c.GetRespHeader(fiber.HeaderXRequestID), ) return err }) app.Use(recover.New()) app.Use(cors.New(cors.Config{ AllowOrigins: "*", // Adjust in production AllowHeaders: "Origin, Content-Type, Accept, Authorization", AllowMethods: "GET, POST, HEAD, PUT, DELETE, PATCH, OPTIONS", })) app.Use(encryptcookie.New(encryptcookie.Config{ Key: getEnv("COOKIE_SECRET", "secret-key-must-be-32-bytes-long!"), })) // Routes app.Get("/", func(c *fiber.Ctx) error { return c.SendString("Baron SSO Audit Backend Online") }) app.Get("/health", func(c *fiber.Ctx) error { status := "ok" checks := make(map[string]string) // Check ClickHouse if auditRepo != nil { if err := auditRepo.Ping(c.Context()); err != nil { checks["clickhouse"] = "error: " + err.Error() status = "error" } else { checks["clickhouse"] = "ok" } } else { checks["clickhouse"] = "not_initialized" status = "degraded" } // Check Redis if redisService != nil { if err := redisService.Ping(c.Context()); err != nil { checks["redis"] = "error: " + err.Error() status = "error" } else { checks["redis"] = "ok" } } else { checks["redis"] = "not_initialized" status = "degraded" } if status == "error" { return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{ "status": status, "checks": checks, }) } return c.JSON(fiber.Map{ "status": status, "checks": checks, }) }) // API Group api := app.Group("/api/v1") api.Post("/audit", auditHandler.CreateLog) // Auth Proxy Routes auth := api.Group("/auth") auth.Post("/enchanted-link/init", authHandler.InitEnchantedLink) auth.Post("/enchanted-link/poll", authHandler.PollEnchantedLink) auth.Post("/magic-link/verify", authHandler.VerifyMagicLink) auth.Post("/sms", authHandler.SendSms) auth.Post("/verify-sms", authHandler.VerifySms) auth.Post("/qr/init", authHandler.InitQRLogin) auth.Post("/qr/poll", authHandler.PollQRLogin) auth.Post("/qr/approve", authHandler.ScanQRLogin) // Admin Routes admin := api.Group("/admin") admin.Post("/users", adminHandler.CreateUser) admin.Get("/check", adminHandler.CheckAuth) admin.Get("/users", adminHandler.ListUsers) admin.Patch("/users/:loginId", adminHandler.UpdateUser) admin.Delete("/users/:loginId", adminHandler.DeleteUser) admin.Patch("/users/:loginId/status", adminHandler.UpdateUserStatus) // Webhook for Descope Generic SMS Gateway auth.Post("/webhooks/descope-sms", authHandler.HandleDescopeSmsRelay) // Webhook for Descope Generic Email Gateway (Fake Email Strategy) auth.Post("/webhooks/descope-email", authHandler.HandleDescopeEmailRelay) // Client Logging Route (Standardized & Flattened) api.Post("/client-log", func(c *fiber.Ctx) error { type LogReq struct { Level string `json:"level"` Message string `json:"message"` Data map[string]interface{} `json:"data,omitempty"` } var req LogReq if err := c.BodyParser(&req); err != nil { return c.SendStatus(fiber.StatusBadRequest) } // Prepare attributes for flattening attrs := []any{ slog.String("source", "client"), } for k, v := range req.Data { // Skip svc if it's already set by the global logger to avoid confusion, // or keep it as client_svc if k == "svc" { attrs = append(attrs, slog.Any("client_svc", v)) } else { attrs = append(attrs, slog.Any(k, v)) } } // Map and log with correct level var level slog.Level switch req.Level { case "SEVERE", "ERROR": level = slog.LevelError case "WARNING", "WARN": level = slog.LevelWarn default: level = slog.LevelInfo } // Filter out noisy client navigation logs if level == slog.LevelInfo { msg := strings.ToLower(req.Message) if strings.Contains(msg, "navigating to") || strings.Contains(msg, "going to") || strings.Contains(msg, "redirecting to") || strings.Contains(msg, "full paths for routes") { return c.SendStatus(fiber.StatusOK) } } slog.Log(c.Context(), level, req.Message, attrs...) return c.SendStatus(fiber.StatusOK) }) // Start Server port := getEnv("BACKEND_PORT", "3000") slog.Info("Server listening", "port", port) fmt.Println("============================================================") if err := app.Listen(":" + port); err != nil { slog.Error("Server failed to start", "error", err) os.Exit(1) } }