package handler import ( "baron-sso-backend/internal/domain" "bytes" "encoding/json" "fmt" "io" "net/http" "os" "github.com/gofiber/fiber/v2" ) type AuthHandler struct { ProjectID string } func NewAuthHandler() *AuthHandler { pid := os.Getenv("DESCOPE_PROJECT_ID") if pid == "" { // Fallback for dev if not set pid = "P37DsGepBT6uDWb5TYYpb5RxUPuq" } return &AuthHandler{ProjectID: pid} } // getBaseURL extracts the region code from Project ID if present (e.g., P37... -> api.37ds.descope.com) // Default is api.descope.com func (h *AuthHandler) getBaseURL() string { if len(h.ProjectID) >= 32 { // Heuristic: Descope project IDs usually start with 'P' // If it's a region-specific project, the URL changes. // For P37DsGepBT6uDWb5TYYpb5RxUPuq, the region is likely '37ds'. // Actually, the safest bet is to use the standard API or check the logic. // The error log showed 'api.37ds.descope.com'. // Let's implement dynamic extraction or just use the standard one which redirects? // No, standard is safer if region is unsure, but let's try to match the error URL. // Region code is usually the first 4 chars after P? No. // Let's rely on standard logic: https://api.descope.com usually works and routes. // BUT the user specifically saw api.37ds.descope.com. // Let's try the generic endpoint first. return "https://api.descope.com" } return "https://api.descope.com" } // InitEnchantedLink proxies the sign-up/in request func (h *AuthHandler) InitEnchantedLink(c *fiber.Ctx) error { var req domain.EnchantedLinkInitRequest if err := c.BodyParser(&req); err != nil { fmt.Printf("[DEBUG] BodyParser failed: %v\n", err) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"}) } fmt.Printf("[DEBUG] InitEnchantedLink - Received LoginID: '%s', URI: '%s'\n", req.LoginID, req.URI) // Prepare Descope Request // Note: We are using the public API endpoint which expects Bearer // Determine endpoint type (email vs sms) // Default to Enchanted Link Email apiPath := "enchantedlink/signup-in/email" if req.Method == "sms" { apiPath = "magiclink/signup-in/sms" } else if len(req.LoginID) > 0 && req.LoginID[0] == '+' { // Auto-detect if starts with + apiPath = "magiclink/signup-in/sms" } url := fmt.Sprintf("%s/v1/auth/%s", h.getBaseURL(), apiPath) payload := map[string]string{ "loginId": req.LoginID, // "redirectUrl": req.URI, // Let Descope use default from console configuration } body, _ := json.Marshal(payload) r, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) if err != nil { return c.Status(fiber.StatusInternalServerError).SendString(err.Error()) } r.Header.Set("Content-Type", "application/json") r.Header.Set("Authorization", "Bearer "+h.ProjectID) client := &http.Client{} resp, err := client.Do(r) if err != nil { return c.Status(fiber.StatusBadGateway).SendString(err.Error()) } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) if resp.StatusCode >= 400 { return c.Status(resp.StatusCode).Send(respBody) } return c.Send(respBody) } // PollEnchantedLink proxies the polling request func (h *AuthHandler) PollEnchantedLink(c *fiber.Ctx) error { var req domain.EnchantedLinkPollRequest if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"}) } url := fmt.Sprintf("%s/v1/auth/enchantedlink/pending-session", h.getBaseURL()) payload := map[string]string{ "pendingRef": req.PendingRef, } body, _ := json.Marshal(payload) r, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) if err != nil { return c.Status(fiber.StatusInternalServerError).SendString(err.Error()) } r.Header.Set("Content-Type", "application/json") r.Header.Set("Authorization", "Bearer "+h.ProjectID) client := &http.Client{} resp, err := client.Do(r) if err != nil { return c.Status(fiber.StatusBadGateway).SendString(err.Error()) } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) if resp.StatusCode >= 400 { return c.Status(resp.StatusCode).Send(respBody) } return c.Send(respBody) } // VerifyMagicLink verifies the token (t) from the email link func (h *AuthHandler) VerifyMagicLink(c *fiber.Ctx) error { var req domain.MagicLinkVerifyRequest if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"}) } // Use Magic Link Verify API url := fmt.Sprintf("%s/v1/auth/magiclink/verify", h.getBaseURL()) payload := map[string]string{ "token": req.Token, } body, _ := json.Marshal(payload) r, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) if err != nil { return c.Status(fiber.StatusInternalServerError).SendString(err.Error()) } r.Header.Set("Content-Type", "application/json") r.Header.Set("Authorization", "Bearer "+h.ProjectID) client := &http.Client{} resp, err := client.Do(r) if err != nil { return c.Status(fiber.StatusBadGateway).SendString(err.Error()) } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) if resp.StatusCode >= 400 { return c.Status(resp.StatusCode).Send(respBody) } return c.Send(respBody) }