forked from baron/baron-sso
토큰 8자리
This commit is contained in:
@@ -4,6 +4,8 @@ import (
|
|||||||
"baron-sso-backend/internal/domain"
|
"baron-sso-backend/internal/domain"
|
||||||
"baron-sso-backend/internal/service"
|
"baron-sso-backend/internal/service"
|
||||||
"context"
|
"context"
|
||||||
|
crand "crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@@ -24,6 +26,15 @@ type AuthHandler struct {
|
|||||||
DescopeClient *client.DescopeClient
|
DescopeClient *client.DescopeClient
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to generate secure random strings
|
||||||
|
func generateSecureToken(length int) string {
|
||||||
|
b := make([]byte, length)
|
||||||
|
if _, err := crand.Read(b); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(b)
|
||||||
|
}
|
||||||
|
|
||||||
func NewAuthHandler() *AuthHandler {
|
func NewAuthHandler() *AuthHandler {
|
||||||
redisService, err := service.NewRedisService()
|
redisService, err := service.NewRedisService()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -108,10 +119,9 @@ func (h *AuthHandler) InitEnchantedLink(c *fiber.Ctx) error {
|
|||||||
loginID := strings.ReplaceAll(req.LoginID, "-", "")
|
loginID := strings.ReplaceAll(req.LoginID, "-", "")
|
||||||
loginID = strings.ReplaceAll(loginID, " ", "")
|
loginID = strings.ReplaceAll(loginID, " ", "")
|
||||||
|
|
||||||
// Generate tokens
|
// Generate secure tokens
|
||||||
rand.Seed(time.Now().UnixNano())
|
token := generateSecureToken(4)
|
||||||
token := fmt.Sprintf("tk_%d%d", time.Now().Unix(), rand.Intn(100000))
|
pendingRef := generateSecureToken(4)
|
||||||
pendingRef := fmt.Sprintf("ref_%d%d", time.Now().Unix(), rand.Intn(100000))
|
|
||||||
|
|
||||||
// Store in Redis
|
// Store in Redis
|
||||||
h.RedisService.Set("enchanted_session:"+pendingRef, `{"status":"pending"}`, 5*time.Minute)
|
h.RedisService.Set("enchanted_session:"+pendingRef, `{"status":"pending"}`, 5*time.Minute)
|
||||||
@@ -177,7 +187,7 @@ func (h *AuthHandler) VerifyMagicLink(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tokenData map[string]string
|
var tokenData map[string]string
|
||||||
json.Unmarshal([]byte(val), &tokenData)
|
json.Unmarshal([]byte(val), &data := tokenData)
|
||||||
pendingRef := tokenData["pendingRef"]
|
pendingRef := tokenData["pendingRef"]
|
||||||
loginID := tokenData["loginId"]
|
loginID := tokenData["loginId"]
|
||||||
|
|
||||||
@@ -186,10 +196,9 @@ func (h *AuthHandler) VerifyMagicLink(c *fiber.Ctx) error {
|
|||||||
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"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use GenerateEmbeddedLink to get a session JWT directly for the user.
|
// Use GenerateEmbeddedLink to get a temporary token directly for the user.
|
||||||
// This generates a JWT that mimics a successful login.
|
// This generates a token that will be exchanged for a real session.
|
||||||
// In the Go SDK, GenerateEmbeddedLink usually returns the token string directly.
|
embeddedToken, err := h.DescopeClient.Management.User().GenerateEmbeddedLink(context.Background(), loginID, nil, 0)
|
||||||
jwtToken, err := h.DescopeClient.Management.User().GenerateEmbeddedLink(context.Background(), loginID, nil, 0)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If user does not exist, create it and retry
|
// If user does not exist, create it and retry
|
||||||
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") {
|
||||||
@@ -203,7 +212,6 @@ func (h *AuthHandler) VerifyMagicLink(c *fiber.Ctx) error {
|
|||||||
userObj.Email = loginID
|
userObj.Email = loginID
|
||||||
} else {
|
} else {
|
||||||
// LoginID is likely a phone number
|
// LoginID is likely a phone number
|
||||||
// Convert 010-XXXX-XXXX (sanitized to 010XXXXXXXX) to +8210XXXXXXXX
|
|
||||||
if strings.HasPrefix(loginID, "010") {
|
if strings.HasPrefix(loginID, "010") {
|
||||||
descopeLoginID = "+82" + loginID[1:]
|
descopeLoginID = "+82" + loginID[1:]
|
||||||
}
|
}
|
||||||
@@ -217,8 +225,8 @@ func (h *AuthHandler) VerifyMagicLink(c *fiber.Ctx) error {
|
|||||||
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"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retry generating token with the Descope LoginID
|
// Retry generating embedded token with the Descope LoginID
|
||||||
jwtToken, err = h.DescopeClient.Management.User().GenerateEmbeddedLink(context.Background(), descopeLoginID, nil, 0)
|
embeddedToken, err = h.DescopeClient.Management.User().GenerateEmbeddedLink(context.Background(), descopeLoginID, nil, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to generate Descope Session after creation: %v", err)
|
log.Printf("Failed to generate Descope Session after creation: %v", err)
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to generate token for new user"})
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to generate token for new user"})
|
||||||
@@ -229,24 +237,23 @@ func (h *AuthHandler) VerifyMagicLink(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exchange the Embedded Token for a real Session JWT
|
// Exchange the Embedded Token for a real User Session JWT
|
||||||
// We pass nil for ResponseWriter as we don't need the SDK to set cookies here.
|
authInfo, err := h.DescopeClient.Auth.MagicLink().Verify(context.Background(), embeddedToken, nil)
|
||||||
authInfo, err := h.DescopeClient.Auth.MagicLink().Verify(context.Background(), jwtToken, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to verify embedded token: %v", err)
|
log.Printf("Failed to verify embedded token: %v", 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"})
|
||||||
}
|
}
|
||||||
realJwtToken := authInfo.SessionToken.JWT
|
sessionToken := authInfo.SessionToken.JWT
|
||||||
|
|
||||||
// Update Session
|
// Update Session in Redis for the polling client
|
||||||
sessionData, _ := json.Marshal(map[string]string{
|
sessionData, _ := json.Marshal(map[string]string{
|
||||||
"status": "success",
|
"status": "success",
|
||||||
"jwt": realJwtToken,
|
"jwt": sessionToken,
|
||||||
})
|
})
|
||||||
h.RedisService.Set("enchanted_session:"+pendingRef, string(sessionData), 5*time.Minute)
|
h.RedisService.Set("enchanted_session:"+pendingRef, string(sessionData), 5*time.Minute)
|
||||||
|
|
||||||
return c.JSON(fiber.Map{
|
return c.JSON(fiber.Map{
|
||||||
"token": realJwtToken,
|
"token": sessionToken,
|
||||||
"message": "Login successful",
|
"message": "Login successful",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -330,4 +337,4 @@ func (h *AuthHandler) HandleDescopeEmailRelay(c *fiber.Ctx) error {
|
|||||||
// 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)
|
log.Printf("[Email Webhook] Real email skipped (Not implemented): %s", 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"})
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user