forked from baron/baron-sso
221 lines
5.2 KiB
Go
221 lines
5.2 KiB
Go
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 <ProjectID>
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
|
|
|
|