1
0
forked from baron/baron-sso

Resolve merge conflicts with main

This commit is contained in:
2026-01-29 16:45:40 +09:00
69 changed files with 6049 additions and 1379 deletions

View File

@@ -158,6 +158,28 @@ func main() {
slog.Warn("Failed to connect to Redis. Auth features may fail.", "error", err)
}
// Oathkeeper 상태를 주기적으로 확인해 다운을 감지합니다.
var oathkeeperProbe *HTTPProbe
if strings.ToLower(getEnv("OATHKEEPER_HEALTH_ENABLED", "true")) != "false" {
intervalSec, err := strconv.Atoi(getEnv("OATHKEEPER_HEALTH_INTERVAL_SECONDS", "10"))
if err != nil || intervalSec <= 0 {
intervalSec = 10
}
timeoutSec, err := strconv.Atoi(getEnv("OATHKEEPER_HEALTH_TIMEOUT_SECONDS", "2"))
if err != nil || timeoutSec <= 0 {
timeoutSec = 2
}
oathkeeperProbe = NewHTTPProbe(
"oathkeeper",
getEnv("OATHKEEPER_HEALTH_URL", "http://oathkeeper:4456/health/ready"),
time.Duration(intervalSec)*time.Second,
time.Duration(timeoutSec)*time.Second,
)
oathkeeperProbe.Start()
} else {
slog.Info("Oathkeeper probe disabled")
}
// 2. Initialize Handlers
auditHandler := handler.NewAuditHandler(auditRepo)
authHandler := handler.NewAuthHandler(redisService, idpProvider)
@@ -249,10 +271,14 @@ func main() {
app.Use(recover.New(recover.Config{
EnableStackTrace: true,
}))
allowedOrigins := getEnv("CORS_ALLOWED_ORIGINS", "http://localhost:5000")
allowCredentials := allowedOrigins != "*"
app.Use(cors.New(cors.Config{
AllowOrigins: "*", // Adjust in production
AllowHeaders: "Origin, Content-Type, Accept, Authorization",
AllowMethods: "GET, POST, HEAD, PUT, DELETE, PATCH, OPTIONS",
AllowOrigins: allowedOrigins,
AllowHeaders: "Origin, Content-Type, Accept, Authorization",
AllowMethods: "GET, POST, HEAD, PUT, DELETE, PATCH, OPTIONS",
AllowCredentials: allowCredentials,
}))
// Ensure COOKIE_SECRET is exactly 32 bytes for AES-256
@@ -271,24 +297,30 @@ func main() {
Key: cookieSecret,
}))
app.Get("/docs", func(c *fiber.Ctx) error {
return c.SendFile("./docs/swagger-ui/index.html")
})
app.Get("/docs/", func(c *fiber.Ctx) error {
return c.SendFile("./docs/swagger-ui/index.html")
})
app.Static("/docs", "./docs/swagger-ui")
app.Get("/redoc", func(c *fiber.Ctx) error {
return c.SendFile("./docs/redoc/index.html")
})
app.Get("/redoc/", func(c *fiber.Ctx) error {
return c.SendFile("./docs/redoc/index.html")
})
app.Static("/redoc", "./docs/redoc")
app.Get("/openapi.yaml", func(c *fiber.Ctx) error {
c.Type("yaml")
return c.SendFile("./docs/openapi.yaml")
})
// [Security] Disable Swagger/ReDoc in Production
if appEnv != "production" {
app.Get("/docs", func(c *fiber.Ctx) error {
return c.SendFile("./docs/swagger-ui/index.html")
})
app.Get("/docs/", func(c *fiber.Ctx) error {
return c.SendFile("./docs/swagger-ui/index.html")
})
app.Static("/docs", "./docs/swagger-ui")
app.Get("/redoc", func(c *fiber.Ctx) error {
return c.SendFile("./docs/redoc/index.html")
})
app.Get("/redoc/", func(c *fiber.Ctx) error {
return c.SendFile("./docs/redoc/index.html")
})
app.Static("/redoc", "./docs/redoc")
app.Get("/openapi.yaml", func(c *fiber.Ctx) error {
c.Type("yaml")
return c.SendFile("./docs/openapi.yaml")
})
slog.Info("📚 API Docs enabled", "swagger", "/docs", "redoc", "/redoc")
} else {
slog.Info("🔒 API Docs disabled in production")
}
// Routes
app.Get("/", func(c *fiber.Ctx) error {
@@ -325,6 +357,32 @@ func main() {
status = "degraded"
}
// Check Oathkeeper
if oathkeeperProbe != nil {
snapshot := oathkeeperProbe.Snapshot()
switch snapshot.Status {
case "ok":
checks["oathkeeper"] = "ok"
case "":
checks["oathkeeper"] = "unknown"
if status != "error" {
status = "degraded"
}
default:
if snapshot.Error == "" {
checks["oathkeeper"] = "error"
} else {
checks["oathkeeper"] = "error: " + snapshot.Error
}
status = "error"
}
} else {
checks["oathkeeper"] = "disabled"
if status != "error" {
status = "degraded"
}
}
if status == "error" {
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
"status": status,
@@ -340,12 +398,19 @@ func main() {
// API Group
api := app.Group("/api/v1")
api.Use(middleware.RequireAudit(middleware.AuditRequiredConfig{
workerCount, _ := strconv.Atoi(getEnv("AUDIT_WORKER_COUNT", "5"))
queueSize, _ := strconv.Atoi(getEnv("AUDIT_QUEUE_SIZE", "2000"))
api.Use(middleware.AuditMiddleware(middleware.AuditConfig{
Repo: auditRepo,
ExcludePaths: map[string]struct{}{
"/api/v1/audit": {},
"/api/v1/client-log": {},
},
BodyDump: true,
WorkerCount: workerCount,
QueueSize: queueSize,
}))
api.Post("/audit", auditHandler.CreateLog)
api.Get("/audit", auditHandler.ListLogs)
@@ -355,6 +420,8 @@ func main() {
auth.Post("/enchanted-link/init", authHandler.InitEnchantedLink)
auth.Post("/enchanted-link/poll", authHandler.PollEnchantedLink)
auth.Post("/magic-link/verify", authHandler.VerifyMagicLink)
auth.Post("/login/code/verify", authHandler.VerifyLoginCode)
auth.Post("/login/code/verify-short", authHandler.VerifyLoginShortCode)
auth.Post("/password/login", authHandler.PasswordLogin)
auth.Post("/password/reset/initiate", authHandler.InitiatePasswordReset)
// [Changed] Use Interstitial Page for GET to prevent Scanner consumption
@@ -388,6 +455,7 @@ func main() {
admin := api.Group("/admin")
admin.Use(middleware.ApiKeyAuth(middleware.ApiKeyAuthConfig{DB: db})) // API Key 인증 추가
admin.Get("/check", adminHandler.CheckAuth)
admin.Get("/stats", adminHandler.GetSystemStats)
admin.Get("/tenants", tenantHandler.ListTenants)
admin.Post("/tenants", tenantHandler.CreateTenant)
admin.Get("/tenants/:id", tenantHandler.GetTenant)
@@ -423,6 +491,9 @@ func main() {
// Webhook for Descope Generic Email Gateway (Fake Email Strategy)
auth.Post("/webhooks/descope-email", authHandler.HandleDescopeEmailRelay)
// Webhook for Kratos courier (HTTP delivery)
auth.Post("/webhooks/kratos-courier", authHandler.HandleKratosCourierRelay)
// Client Logging Route (Standardized & Flattened)
api.Post("/client-log", func(c *fiber.Ctx) error {
type LogReq struct {