1
0
forked from baron/baron-sso

slog 통합

This commit is contained in:
2026-01-21 12:51:00 +09:00
parent 03c8c14aa1
commit c39857c66c
5 changed files with 127 additions and 121 deletions

View File

@@ -1,27 +1,19 @@
package main package main
import ( import (
"baron-sso-backend/internal/handler"
"baron-sso-backend/internal/logger"
"baron-sso-backend/internal/repository"
"baron-sso-backend/internal/service"
"fmt"
"log/slog" "log/slog"
"os" "os"
"strings"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/bwmarrin/snowflake" "github.com/bwmarrin/snowflake"
"github.com/gofiber/fiber/v2"
"baron-sso-backend/internal/handler"
"baron-sso-backend/internal/logger"
"baron-sso-backend/internal/repository"
"baron-sso-backend/internal/service"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/encryptcookie" "github.com/gofiber/fiber/v2/middleware/encryptcookie"
"github.com/gofiber/fiber/v2/middleware/recover" "github.com/gofiber/fiber/v2/middleware/recover"
@@ -41,7 +33,6 @@ func main() {
ServiceName: "baron-sso", ServiceName: "baron-sso",
Environment: getEnv("GO_ENV", "dev"), Environment: getEnv("GO_ENV", "dev"),
}) })
// Initialize Snowflake Node (Node 2 for Baron) // Initialize Snowflake Node (Node 2 for Baron)
node, err := snowflake.NewNode(2) node, err := snowflake.NewNode(2)
if err != nil { if err != nil {
@@ -50,10 +41,22 @@ func main() {
} }
// 1. Log Config on Startup // 1. Log Config on Startup
slog.Info("Starting Baron SSO Backend", 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"), "frontend_url", getEnv("FRONTEND_URL", "http://ssologin.hmac.kr"),
"redis_addr", getEnv("REDIS_ADDR", "redis:6379"), "redis_addr", getEnv("REDIS_ADDR", "redis:6379"),
"descope_id", getEnv("DESCOPE_PROJECT_ID", "not-set"),
) )
// 2. Initialize DB Connections // 2. Initialize DB Connections
@@ -80,7 +83,7 @@ func main() {
// 3. Initialize Fiber // 3. Initialize Fiber
app := fiber.New(fiber.Config{ app := fiber.New(fiber.Config{
AppName: "Baron SSO Backend", AppName: "Baron SSO Backend",
DisableStartupMessage: true, // Clean logs DisableStartupMessage: true, // Clean logs
}) })
@@ -94,10 +97,10 @@ func main() {
// [Standardized] HTTP Request Logger Middleware using slog // [Standardized] HTTP Request Logger Middleware using slog
app.Use(func(c *fiber.Ctx) error { app.Use(func(c *fiber.Ctx) error {
start := time.Now() start := time.Now()
// Handle request // Handle request
err := c.Next() err := c.Next()
// Log after request // Log after request
latency := time.Since(start) latency := time.Since(start)
status := c.Response().StatusCode() status := c.Response().StatusCode()
@@ -107,7 +110,7 @@ func main() {
if status < 400 { if status < 400 {
return err return err
} }
msg := "http_request" msg := "http_request"
if err != nil { if err != nil {
msg = "http_request_error" msg = "http_request_error"
@@ -129,7 +132,7 @@ func main() {
AllowOrigins: "*", // Adjust in production AllowOrigins: "*", // Adjust in production
AllowHeaders: "Origin, Content-Type, Accept, Authorization", AllowHeaders: "Origin, Content-Type, Accept, Authorization",
AllowMethods: "GET, POST, HEAD, PUT, DELETE, PATCH, OPTIONS", AllowMethods: "GET, POST, HEAD, PUT, DELETE, PATCH, OPTIONS",
})) }))
app.Use(encryptcookie.New(encryptcookie.Config{ app.Use(encryptcookie.New(encryptcookie.Config{
Key: getEnv("COOKIE_SECRET", "secret-key-must-be-32-bytes-long!"), Key: getEnv("COOKIE_SECRET", "secret-key-must-be-32-bytes-long!"),
})) }))
@@ -185,7 +188,7 @@ func main() {
// API Group // API Group
api := app.Group("/api/v1") api := app.Group("/api/v1")
api.Post("/audit", auditHandler.CreateLog) api.Post("/audit", auditHandler.CreateLog)
// Auth Proxy Routes // Auth Proxy Routes
auth := api.Group("/auth") auth := api.Group("/auth")
auth.Post("/enchanted-link/init", authHandler.InitEnchantedLink) auth.Post("/enchanted-link/init", authHandler.InitEnchantedLink)
@@ -196,7 +199,7 @@ func main() {
auth.Post("/qr/init", authHandler.InitQRLogin) auth.Post("/qr/init", authHandler.InitQRLogin)
auth.Post("/qr/poll", authHandler.PollQRLogin) auth.Post("/qr/poll", authHandler.PollQRLogin)
auth.Post("/qr/approve", authHandler.ScanQRLogin) auth.Post("/qr/approve", authHandler.ScanQRLogin)
// Admin Routes // Admin Routes
admin := api.Group("/admin") admin := api.Group("/admin")
admin.Post("/users", adminHandler.CreateUser) admin.Post("/users", adminHandler.CreateUser)
@@ -205,10 +208,10 @@ func main() {
admin.Patch("/users/:loginId", adminHandler.UpdateUser) admin.Patch("/users/:loginId", adminHandler.UpdateUser)
admin.Delete("/users/:loginId", adminHandler.DeleteUser) admin.Delete("/users/:loginId", adminHandler.DeleteUser)
admin.Patch("/users/:loginId/status", adminHandler.UpdateUserStatus) admin.Patch("/users/:loginId/status", adminHandler.UpdateUserStatus)
// Webhook for Descope Generic SMS Gateway // Webhook for Descope Generic SMS Gateway
auth.Post("/webhooks/descope-sms", authHandler.HandleDescopeSmsRelay) auth.Post("/webhooks/descope-sms", authHandler.HandleDescopeSmsRelay)
// Webhook for Descope Generic Email Gateway (Fake Email Strategy) // Webhook for Descope Generic Email Gateway (Fake Email Strategy)
auth.Post("/webhooks/descope-email", authHandler.HandleDescopeEmailRelay) auth.Post("/webhooks/descope-email", authHandler.HandleDescopeEmailRelay)
@@ -229,7 +232,7 @@ func main() {
slog.String("source", "client"), slog.String("source", "client"),
} }
for k, v := range req.Data { for k, v := range req.Data {
// Skip svc if it's already set by the global logger to avoid confusion, // Skip svc if it's already set by the global logger to avoid confusion,
// or keep it as client_svc // or keep it as client_svc
if k == "svc" { if k == "svc" {
attrs = append(attrs, slog.Any("client_svc", v)) attrs = append(attrs, slog.Any("client_svc", v))
@@ -252,9 +255,10 @@ func main() {
// Filter out noisy client navigation logs // Filter out noisy client navigation logs
if level == slog.LevelInfo { if level == slog.LevelInfo {
msg := strings.ToLower(req.Message) msg := strings.ToLower(req.Message)
if strings.Contains(msg, "navigating to") || if strings.Contains(msg, "navigating to") ||
strings.Contains(msg, "going to") || strings.Contains(msg, "going to") ||
strings.Contains(msg, "redirecting to") { strings.Contains(msg, "redirecting to") ||
strings.Contains(msg, "full paths for routes") {
return c.SendStatus(fiber.StatusOK) return c.SendStatus(fiber.StatusOK)
} }
} }
@@ -264,8 +268,9 @@ func main() {
}) })
// Start Server // Start Server
port := getEnv("PORT", "3000") port := getEnv("BACKEND_PORT", "3000")
slog.Info("Server listening", "port", port) slog.Info("Server listening", "port", port)
fmt.Println("============================================================")
if err := app.Listen(":" + port); err != nil { if err := app.Listen(":" + port); err != nil {
slog.Error("Server failed to start", "error", err) slog.Error("Server failed to start", "error", err)
os.Exit(1) os.Exit(1)

View File

@@ -2,7 +2,7 @@ package handler
import ( import (
"context" "context"
"log" "log/slog"
"os" "os"
"strings" "strings"
"net/url" "net/url"
@@ -29,10 +29,10 @@ func NewAdminHandler() *AdminHandler {
ManagementKey: managementKey, ManagementKey: managementKey,
}) })
if err != nil { if err != nil {
log.Printf("Warning: Failed to initialize Descope Client for Admin: %v", err) slog.Warn("Failed to initialize Descope Client for Admin", "error", err)
} }
} else { } else {
log.Println("Warning: DESCOPE_PROJECT_ID or DESCOPE_MANAGEMENT_KEY missing. Admin functions will fail.") slog.Warn("DESCOPE_PROJECT_ID or DESCOPE_MANAGEMENT_KEY missing. Admin functions will fail.")
} }
return &AdminHandler{ return &AdminHandler{
@@ -95,7 +95,7 @@ func (h *AdminHandler) ListUsers(c *fiber.Ctx) error {
} }
if err != nil { if err != nil {
log.Printf("[Admin] ListUsers failed: %v", err) slog.Error("[Admin] ListUsers failed", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
} }
@@ -112,9 +112,9 @@ func (h *AdminHandler) DeleteUser(c *fiber.Ctx) error {
loginID = decoded loginID = decoded
} }
log.Printf("[Admin] Deleting user: %s", loginID) slog.Info("[Admin] Deleting user", "loginID", loginID)
if err := h.DescopeClient.Management.User().Delete(context.Background(), loginID); err != nil { if err := h.DescopeClient.Management.User().Delete(context.Background(), loginID); err != nil {
log.Printf("[Admin] DeleteUser failed: %v", err) slog.Error("[Admin] DeleteUser failed", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
} }
@@ -140,7 +140,7 @@ func (h *AdminHandler) UpdateUserStatus(c *fiber.Ctx) error {
var user *descope.UserResponse var user *descope.UserResponse
var err error var err error
log.Printf("[Admin] Updating status for %s to %s", loginID, req.Status) slog.Info("[Admin] Updating status", "loginID", loginID, "status", req.Status)
if req.Status == "enabled" || req.Status == "active" { if req.Status == "enabled" || req.Status == "active" {
user, err = h.DescopeClient.Management.User().Activate(context.Background(), loginID) user, err = h.DescopeClient.Management.User().Activate(context.Background(), loginID)
@@ -149,7 +149,7 @@ func (h *AdminHandler) UpdateUserStatus(c *fiber.Ctx) error {
} }
if err != nil { if err != nil {
log.Printf("[Admin] Status update failed: %v", err) slog.Error("[Admin] Status update failed", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
} }
@@ -266,11 +266,11 @@ func (h *AdminHandler) CreateUser(c *fiber.Ctx) error {
userObj.Tenants = userTenants userObj.Tenants = userTenants
} }
log.Printf("[Admin] Creating user: %s (Email: %s, Phone: %s)", req.LoginID, req.Email, normalizedPhone) slog.Info("[Admin] Creating user", "loginID", req.LoginID, "email", req.Email, "phone", normalizedPhone)
res, err := h.DescopeClient.Management.User().Create(context.Background(), req.LoginID, userObj) res, err := h.DescopeClient.Management.User().Create(context.Background(), req.LoginID, userObj)
if err != nil { if err != nil {
log.Printf("[Admin] Failed to create user: %v", err) slog.Error("[Admin] Failed to create user", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
} }

View File

@@ -8,7 +8,7 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "log/slog"
"math/rand" "math/rand"
"os" "os"
"strings" "strings"
@@ -55,35 +55,34 @@ func NewAuthHandler(redisService *service.RedisService) *AuthHandler {
var descopeClient *client.DescopeClient var descopeClient *client.DescopeClient
var err error var err error
if projectID != "" { if projectID != "" {
descopeClient, err = client.NewWithConfig(&client.Config{ descopeClient, err = client.NewWithConfig(&client.Config{
ProjectID: projectID, ProjectID: projectID,
ManagementKey: managementKey, ManagementKey: managementKey,
}) })
if err != nil { if err != nil {
log.Printf("Warning: Failed to initialize Descope Client: %v", err) slog.Warn("Failed to initialize Descope Client", "error", err)
}
}
return &AuthHandler{
ProjectID: projectID,
SmsService: service.NewSmsService(),
EmailService: service.NewEmailService(),
RedisService: redisService,
DescopeClient: descopeClient,
} }
} }
return &AuthHandler{ // SendSms sends a verification code via SMS. (Restored for completeness)
ProjectID: projectID, func (h *AuthHandler) SendSms(c *fiber.Ctx) error {
SmsService: service.NewSmsService(), var req domain.SmsRequest
EmailService: service.NewEmailService(), if err := c.BodyParser(&req); err != nil {
RedisService: redisService, return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"})
DescopeClient: descopeClient, }
}
} slog.Info("[SMS] Sending code", "phoneNumber", req.PhoneNumber)
sanitizedPhone := strings.ReplaceAll(req.PhoneNumber, "-", "")
// SendSms sends a verification code via SMS. (Restored for completeness)
func (h *AuthHandler) SendSms(c *fiber.Ctx) error {
var req domain.SmsRequest
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"})
}
log.Printf("[SMS] Sending code to: %s", req.PhoneNumber)
sanitizedPhone := strings.ReplaceAll(req.PhoneNumber, "-", "")
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
code := fmt.Sprintf("%06d", rand.Intn(1000000)) code := fmt.Sprintf("%06d", rand.Intn(1000000))
content := fmt.Sprintf("[Baron SSO] 인증번호: %s", code) content := fmt.Sprintf("[Baron SSO] 인증번호: %s", code)
@@ -124,7 +123,7 @@ func (h *AuthHandler) VerifySms(c *fiber.Ctx) error {
func (h *AuthHandler) InitEnchantedLink(c *fiber.Ctx) error { func (h *AuthHandler) InitEnchantedLink(c *fiber.Ctx) error {
var req domain.EnchantedLinkInitRequest var req domain.EnchantedLinkInitRequest
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
log.Printf("[Enchanted] Body parse error: %v", err) slog.Error("[Enchanted] Body parse error", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"}) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"})
} }
@@ -135,7 +134,7 @@ func (h *AuthHandler) InitEnchantedLink(c *fiber.Ctx) error {
token := GenerateSecureToken(3) token := GenerateSecureToken(3)
pendingRef := GenerateSecureToken(3) pendingRef := GenerateSecureToken(3)
log.Printf("[Enchanted] Initiating for %s. Token: %s, PendingRef: %s", loginID, token, pendingRef) slog.Info("[Enchanted] Initiating enchanted link", "loginID", loginID, "token", token, "pendingRef", pendingRef)
// Store in Redis // Store in Redis
h.RedisService.Set(prefixSession+pendingRef, fmt.Sprintf(`{"status":"%s"}`, statusPending), defaultExpiration) h.RedisService.Set(prefixSession+pendingRef, fmt.Sprintf(`{"status":"%s"}`, statusPending), defaultExpiration)
@@ -152,7 +151,7 @@ func (h *AuthHandler) InitEnchantedLink(c *fiber.Ctx) error {
if strings.Contains(loginID, "@") { if strings.Contains(loginID, "@") {
// Send Email // Send Email
if h.EmailService == nil { if h.EmailService == nil {
log.Printf("[Enchanted] Email Service not configured") slog.Error("[Enchanted] Email Service not configured")
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Email service not configured"}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Email service not configured"})
} }
@@ -169,18 +168,18 @@ func (h *AuthHandler) InitEnchantedLink(c *fiber.Ctx) error {
</div> </div>
`, link) `, link)
log.Printf("[Enchanted] Sending Email to %s via AWS SES", loginID) slog.Info("[Enchanted] Sending Email via AWS SES", "loginID", loginID)
if err := h.EmailService.SendEmail(loginID, subject, body); err != nil { if err := h.EmailService.SendEmail(loginID, subject, body); err != nil {
log.Printf("[Enchanted] Email Failed: %v", err) slog.Error("[Enchanted] Email Failed", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to send Email"}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to send Email"})
} }
} else { } else {
// Send SMS // Send SMS
content := fmt.Sprintf("[Baron SSO] 로그인 링크: %s", link) content := fmt.Sprintf("[Baron SSO] 로그인 링크: %s", link)
log.Printf("[Enchanted] Sending SMS to %s via Naver Cloud", loginID) slog.Info("[Enchanted] Sending SMS via Naver Cloud", "loginID", loginID)
if err := h.SmsService.SendSms(loginID, content); err != nil { if err := h.SmsService.SendSms(loginID, content); err != nil {
log.Printf("[Enchanted] SMS Failed: %v", err) slog.Error("[Enchanted] SMS Failed", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to send SMS"}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to send SMS"})
} }
} }
@@ -208,7 +207,7 @@ func (h *AuthHandler) PollEnchantedLink(c *fiber.Ctx) error {
json.Unmarshal([]byte(val), &data) json.Unmarshal([]byte(val), &data)
if data["status"] == statusSuccess { if data["status"] == statusSuccess {
log.Printf("[Poll] Success for ref: %s", req.PendingRef) slog.Info("[Poll] Success", "pendingRef", req.PendingRef)
return c.JSON(fiber.Map{ return c.JSON(fiber.Map{
"sessionJwt": data["jwt"], "sessionJwt": data["jwt"],
"status": "ok", "status": "ok",
@@ -222,16 +221,16 @@ func (h *AuthHandler) PollEnchantedLink(c *fiber.Ctx) error {
func (h *AuthHandler) VerifyMagicLink(c *fiber.Ctx) error { func (h *AuthHandler) VerifyMagicLink(c *fiber.Ctx) error {
var req domain.MagicLinkVerifyRequest var req domain.MagicLinkVerifyRequest
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
log.Printf("[Verify] Body parse error: %v", err) slog.Error("[Verify] Body parse error", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"}) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"})
} }
log.Printf("[Verify] Attempting to verify token: %s", req.Token) slog.Info("[Verify] Attempting to verify token", "token", req.Token)
tokenKey := prefixToken + req.Token tokenKey := prefixToken + req.Token
val, err := h.RedisService.Get(tokenKey) val, err := h.RedisService.Get(tokenKey)
if err != nil || val == "" { if err != nil || val == "" {
log.Printf("[Verify] Token not found or expired in Redis: %s", req.Token) slog.Warn("[Verify] Token not found or expired in Redis", "token", req.Token)
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid or expired token"}) return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid or expired token"})
} }
@@ -240,11 +239,11 @@ func (h *AuthHandler) VerifyMagicLink(c *fiber.Ctx) error {
pendingRef := tokenData["pendingRef"] pendingRef := tokenData["pendingRef"]
loginID := tokenData["loginId"] loginID := tokenData["loginId"]
log.Printf("[Verify] Token valid. LoginID: %s, PendingRef: %s", loginID, pendingRef) slog.Info("[Verify] Token valid", "loginID", loginID, "pendingRef", pendingRef)
// 1. Generate Descope Session Directly (Management SDK) // 1. Generate Descope Session Directly (Management SDK)
if h.DescopeClient == nil { if h.DescopeClient == nil {
log.Printf("[Verify] Descope Client is nil!") slog.Error("[Verify] Descope Client is nil!")
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Descope Client not configured"}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Descope Client not configured"})
} }
@@ -260,7 +259,7 @@ func (h *AuthHandler) VerifyMagicLink(c *fiber.Ctx) error {
} }
} }
log.Printf("[Verify] Searching for user with phone: %s", searchPhone) slog.Info("[Verify] Searching for user", "phone", searchPhone)
searchOptions := &descope.UserSearchOptions{ searchOptions := &descope.UserSearchOptions{
Phones: []string{searchPhone}, Phones: []string{searchPhone},
Limit: 1, Limit: 1,
@@ -272,24 +271,24 @@ func (h *AuthHandler) VerifyMagicLink(c *fiber.Ctx) error {
if errSearch == nil && len(users) > 0 { if errSearch == nil && len(users) > 0 {
if len(users[0].LoginIDs) > 0 { if len(users[0].LoginIDs) > 0 {
targetLoginID = users[0].LoginIDs[0] targetLoginID = users[0].LoginIDs[0]
log.Printf("[Verify] User found! Existing LoginID: %s", targetLoginID) slog.Info("[Verify] User found", "existingLoginID", targetLoginID)
} else { } else {
// Should not happen for a valid user, but fallback to UserID or searchPhone // Should not happen for a valid user, but fallback to UserID or searchPhone
log.Printf("[Verify] User found but no LoginIDs. Using UserID.") slog.Warn("[Verify] User found but no LoginIDs, using UserID")
targetLoginID = users[0].UserID targetLoginID = users[0].UserID
} }
} else { } else {
// Not found, or search error. Fallback to using the phone as LoginID. // Not found, or search error. Fallback to using the phone as LoginID.
// Use the normalized phone number to ensure consistency (+82...) // Use the normalized phone number to ensure consistency (+82...)
targetLoginID = searchPhone targetLoginID = searchPhone
log.Printf("[Verify] User not found by phone. Will use/create: %s", targetLoginID) slog.Info("[Verify] User not found by phone, will use/create", "loginID", targetLoginID)
} }
log.Printf("[Verify] Generating embedded link for %s", targetLoginID) slog.Info("[Verify] Generating embedded link", "loginID", targetLoginID)
embeddedToken, err := h.DescopeClient.Management.User().GenerateEmbeddedLink(context.Background(), targetLoginID, nil, 0) embeddedToken, err := h.DescopeClient.Management.User().GenerateEmbeddedLink(context.Background(), targetLoginID, nil, 0)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "User not found") || strings.Contains(err.Error(), "E062108") { if strings.Contains(err.Error(), "User not found") || strings.Contains(err.Error(), "E062108") {
log.Printf("[Verify] User %s not found. Creating...", targetLoginID) slog.Info("[Verify] User not found, creating...", "loginID", targetLoginID)
// Create User with Explicit Phone Attribute // Create User with Explicit Phone Attribute
userObj := &descope.UserRequest{} userObj := &descope.UserRequest{}
@@ -301,30 +300,30 @@ func (h *AuthHandler) VerifyMagicLink(c *fiber.Ctx) error {
_, errCreate := h.DescopeClient.Management.User().Create(context.Background(), targetLoginID, userObj) _, errCreate := h.DescopeClient.Management.User().Create(context.Background(), targetLoginID, userObj)
if errCreate != nil { if errCreate != nil {
log.Printf("[Verify] Failed to create user: %v", errCreate) slog.Error("[Verify] Failed to create user", "error", errCreate)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to create new user"}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to create new user"})
} }
embeddedToken, err = h.DescopeClient.Management.User().GenerateEmbeddedLink(context.Background(), targetLoginID, nil, 0) embeddedToken, err = h.DescopeClient.Management.User().GenerateEmbeddedLink(context.Background(), targetLoginID, nil, 0)
if err != nil { if err != nil {
log.Printf("[Verify] Failed to generate token after creation: %v", err) slog.Error("[Verify] Failed to generate token after creation", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to generate upstream token"}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to generate upstream token"})
} }
} else { } else {
log.Printf("[Verify] Descope Error: %v", err) slog.Error("[Verify] Descope Error", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to generate upstream token"}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to generate upstream token"})
} }
} }
log.Printf("[Verify] Exchanging embedded token for session JWT") slog.Info("[Verify] Exchanging embedded token for session JWT")
authInfo, err := h.DescopeClient.Auth.MagicLink().Verify(context.Background(), embeddedToken, nil) authInfo, err := h.DescopeClient.Auth.MagicLink().Verify(context.Background(), embeddedToken, nil)
if err != nil { if err != nil {
log.Printf("[Verify] Final verification failed: %v", err) slog.Error("[Verify] Final verification failed", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to verify upstream token"}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to verify upstream token"})
} }
sessionToken := authInfo.SessionToken.JWT sessionToken := authInfo.SessionToken.JWT
log.Printf("[Verify] Success! Updating Redis session: %s", pendingRef) slog.Info("[Verify] Success! Updating Redis session", "pendingRef", pendingRef)
sessionData, _ := json.Marshal(map[string]string{ sessionData, _ := json.Marshal(map[string]string{
"status": statusSuccess, "status": statusSuccess,
"jwt": sessionToken, "jwt": sessionToken,
@@ -348,7 +347,7 @@ func (h *AuthHandler) InitQRLogin(c *fiber.Ctx) error {
} }
qrPayload := fmt.Sprintf("%s/approve?ref=%s", frontendURL, pendingRef) qrPayload := fmt.Sprintf("%s/approve?ref=%s", frontendURL, pendingRef)
log.Printf("[QR] Init: PendingRef=%s, URL=%s", pendingRef, qrPayload) slog.Info("[QR] Init", "pendingRef", pendingRef, "url", qrPayload)
// Redis에 초기 상태 저장 (5분 만료) // Redis에 초기 상태 저장 (5분 만료)
h.RedisService.Set(prefixSession+pendingRef, fmt.Sprintf(`{"status":"%s"}`, statusPending), 5*time.Minute) h.RedisService.Set(prefixSession+pendingRef, fmt.Sprintf(`{"status":"%s"}`, statusPending), 5*time.Minute)
@@ -378,6 +377,7 @@ func (h *AuthHandler) PollQRLogin(c *fiber.Ctx) error {
json.Unmarshal([]byte(val), &data) json.Unmarshal([]byte(val), &data)
if data["status"] == statusSuccess { if data["status"] == statusSuccess {
slog.Info("[QR] Poll Success", "pendingRef", req.PendingRef)
return c.JSON(fiber.Map{ return c.JSON(fiber.Map{
"status": "ok", "status": "ok",
"sessionJwt": data["jwt"], "sessionJwt": data["jwt"],
@@ -395,10 +395,11 @@ func (h *AuthHandler) ScanQRLogin(c *fiber.Ctx) error {
Token string `json:"token"` // 모바일 사용자의 세션 토큰 (검증용) Token string `json:"token"` // 모바일 사용자의 세션 토큰 (검증용)
} }
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
slog.Error("[QR] Scan body parse error", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid body"}) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid body"})
} }
log.Printf("[QR] Scan & Approve: PendingRef=%s", req.PendingRef) slog.Info("[QR] Scan & Approve", "pendingRef", req.PendingRef)
// 1. Redis에서 세션 확인 // 1. Redis에서 세션 확인
val, err := h.RedisService.Get(prefixSession + req.PendingRef) val, err := h.RedisService.Get(prefixSession + req.PendingRef)
@@ -430,16 +431,16 @@ func (h *AuthHandler) HandleDescopeSmsRelay(c *fiber.Ctx) error {
} }
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
log.Printf("[Webhook] Body parsing failed: %v", err) slog.Error("Webhook Body parsing failed", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"}) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"})
} }
if req.Recipient == "" || req.Body == "" { if req.Recipient == "" || req.Body == "" {
log.Printf("[Webhook] Missing recipient or body") slog.Warn("Webhook missing recipient or body")
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Missing recipient or body"}) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Missing recipient or body"})
} }
log.Printf("[Webhook] Received SMS request for %s", req.Recipient) slog.Info("Received SMS request", "recipient", req.Recipient)
phone := req.Recipient phone := req.Recipient
if strings.HasPrefix(phone, "+82") { if strings.HasPrefix(phone, "+82") {
@@ -449,11 +450,11 @@ func (h *AuthHandler) HandleDescopeSmsRelay(c *fiber.Ctx) error {
phone = strings.ReplaceAll(phone, " ", "") phone = strings.ReplaceAll(phone, " ", "")
if err := h.SmsService.SendSms(phone, req.Body); err != nil { if err := h.SmsService.SendSms(phone, req.Body); err != nil {
log.Printf("[Webhook] Failed to forward SMS to Naver: %v", err) slog.Error("Failed to forward SMS to Naver", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to send SMS via Naver"}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to send SMS via Naver"})
} }
log.Printf("[Webhook] Successfully forwarded SMS to %s", phone) slog.Info("Successfully forwarded SMS", "phone", phone)
return c.JSON(fiber.Map{"status": "ok"}) return c.JSON(fiber.Map{"status": "ok"})
} }
@@ -467,11 +468,11 @@ func (h *AuthHandler) HandleDescopeEmailRelay(c *fiber.Ctx) error {
} }
if err := c.BodyParser(&req); err != nil { if err := c.BodyParser(&req); err != nil {
log.Printf("[Email Webhook] Body parsing failed: %v", err) slog.Error("[Email Webhook] Body parsing failed", "error", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"}) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"})
} }
log.Printf("[Email Webhook] Received email request for %s", req.To) slog.Info("[Email Webhook] Received email request", "to", req.To)
// Check if it's a Fake Email for SMS // Check if it's a Fake Email for SMS
if strings.HasSuffix(req.To, "@sms.baron") { if strings.HasSuffix(req.To, "@sms.baron") {
@@ -484,16 +485,16 @@ func (h *AuthHandler) HandleDescopeEmailRelay(c *fiber.Ctx) error {
// Send SMS with the text body (Descope template should be optimized for SMS) // Send SMS with the text body (Descope template should be optimized for SMS)
if err := h.SmsService.SendSms(phone, req.Text); err != nil { if err := h.SmsService.SendSms(phone, req.Text); err != nil {
log.Printf("[Email Webhook] Failed to forward Email-as-SMS: %v", err) slog.Error("[Email Webhook] Failed to forward Email-as-SMS", "error", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to send SMS"}) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to send SMS"})
} }
log.Printf("[Email Webhook] Successfully converted Email to SMS for %s", phone) slog.Info("[Email Webhook] Successfully converted Email to SMS", "phone", phone)
return c.JSON(fiber.Map{"status": "ok"}) return c.JSON(fiber.Map{"status": "ok"})
} }
// Real Email Handling (Not implemented in this Relay) // Real Email Handling (Not implemented in this Relay)
// You would need an SMTP service here if you route ALL emails through this relay. // You would need an SMTP service here if you route ALL emails through this relay.
log.Printf("[Email Webhook] Real email skipped (Not implemented): %s", req.To) slog.Warn("[Email Webhook] Real email skipped (Not implemented)", "to", req.To)
return c.Status(501).JSON(fiber.Map{"error": "Real email sending not implemented"}) return c.Status(501).JSON(fiber.Map{"error": "Real email sending not implemented"})
} }

View File

@@ -3,7 +3,7 @@ package service
import ( import (
"context" "context"
"fmt" "fmt"
"log" "log/slog"
"os" "os"
"baron-sso-backend/internal/domain" "baron-sso-backend/internal/domain"
@@ -26,16 +26,15 @@ func NewEmailService() domain.EmailService {
sender := os.Getenv("AWS_SES_SENDER") sender := os.Getenv("AWS_SES_SENDER")
if region == "" || accessKey == "" || secretKey == "" { if region == "" || accessKey == "" || secretKey == "" {
log.Println("[EmailService] AWS configuration missing, email service will not work") slog.Warn("[EmailService] AWS configuration missing, email service will not work")
return nil return nil
} }
cfg, err := config.LoadDefaultConfig(context.TODO(), cfg, err := config.LoadDefaultConfig(context.TODO(),
config.WithRegion(region), config.WithRegion(region),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")), config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")),
) )
if err != nil { if err != nil {
log.Printf("[EmailService] Failed to load AWS config: %v", err) slog.Error("Failed to load AWS config", "error", err)
return nil return nil
} }
@@ -71,9 +70,9 @@ func (s *SesServiceImpl) SendEmail(to, subject, body string) error {
_, err := s.client.SendEmail(context.TODO(), input) _, err := s.client.SendEmail(context.TODO(), input)
if err != nil { if err != nil {
log.Printf("[EmailService] Failed to send email to %s: %v", to, err) slog.Error("[EmailService] Failed to send email", "to", to, "error", err)
} else { } else {
log.Printf("[EmailService] Email sent successfully to %s", to) slog.Info("[EmailService] Email sent successfully", "to", to)
} }
return err return err
} }

View File

@@ -8,7 +8,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log/slog"
"net/http" "net/http"
"os" "os"
"strconv" "strconv"
@@ -17,6 +17,7 @@ import (
"baron-sso-backend/internal/domain" "baron-sso-backend/internal/domain"
) )
type SmsServiceImpl struct { type SmsServiceImpl struct {
accessKey string accessKey string
secretKey string secretKey string
@@ -28,7 +29,7 @@ func NewSmsService() domain.SmsService {
// Sanitize sender phone number right after reading from env // Sanitize sender phone number right after reading from env
rawSenderPhone := os.Getenv("NAVER_SENDER_PHONE_NUMBER") rawSenderPhone := os.Getenv("NAVER_SENDER_PHONE_NUMBER")
sanitizedSenderPhone := strings.ReplaceAll(rawSenderPhone, "-", "") sanitizedSenderPhone := strings.ReplaceAll(rawSenderPhone, "-", "")
log.Printf("[서비스 초기화] 발신자 번호 처리: 원본='%s', 정제 후='%s'", rawSenderPhone, sanitizedSenderPhone) slog.Info("[서비스 초기화] 발신자 번호 처리", "원본", rawSenderPhone, "정제후", sanitizedSenderPhone)
return &SmsServiceImpl{ return &SmsServiceImpl{
accessKey: os.Getenv("NAVER_CLOUD_ACCESS_KEY"), accessKey: os.Getenv("NAVER_CLOUD_ACCESS_KEY"),
@@ -41,7 +42,7 @@ func NewSmsService() domain.SmsService {
func (s *SmsServiceImpl) SendSms(to, content string) error { func (s *SmsServiceImpl) SendSms(to, content string) error {
timestamp := strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10) timestamp := strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)
apiURL := fmt.Sprintf("https://sens.apigw.ntruss.com/sms/v2/services/%s/messages", s.serviceID) apiURL := fmt.Sprintf("https://sens.apigw.ntruss.com/sms/v2/services/%s/messages", s.serviceID)
log.Printf("Requesting SENS API URL: %s", apiURL) slog.Info("[SmsService] Requesting SENS API URL", "url", apiURL)
// Naver SENS API requires phone number without '+' // Naver SENS API requires phone number without '+'
sanitizedTo := strings.Replace(to, "+", "", 1) sanitizedTo := strings.Replace(to, "+", "", 1)
@@ -92,11 +93,11 @@ func (s *SmsServiceImpl) SendSms(to, content string) error {
} }
if resp.StatusCode >= 300 { if resp.StatusCode >= 300 {
log.Printf("error response from naver cloud sms api: %s", string(respBody)) slog.Error("[SmsService] error response from naver cloud sms api", "body", string(respBody))
return fmt.Errorf("error sending sms: status code %d", resp.StatusCode) return fmt.Errorf("error sending sms: status code %d", resp.StatusCode)
} }
log.Printf("sms sent successfully: %s", string(respBody)) slog.Info("[SmsService] sms sent successfully", "body", string(respBody))
return nil return nil
} }
@@ -112,4 +113,4 @@ func (s *SmsServiceImpl) makeSignature(method, url, timestamp string) (string, e
} }
return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
} }