1
0
forked from baron/baron-sso

qr 로그인

This commit is contained in:
2026-01-16 17:42:59 +09:00
parent 50385d510b
commit b65ecc1b24
8 changed files with 446 additions and 14 deletions

View File

@@ -132,7 +132,7 @@ func (h *AuthHandler) InitEnchantedLink(c *fiber.Ctx) error {
loginID := strings.ReplaceAll(req.LoginID, "-", "")
loginID = strings.ReplaceAll(loginID, " ", "")
// Generate secure tokens
token := GenerateSecureToken(3)
pendingRef := GenerateSecureToken(3)
@@ -150,7 +150,7 @@ func (h *AuthHandler) InitEnchantedLink(c *fiber.Ctx) error {
}
link := fmt.Sprintf("%s/verify/%s", frontendURL, token)
content := fmt.Sprintf("[Baron SSO] 로그인 링크: %s", link)
log.Printf("[Enchanted] Sending SMS to %s via Naver Cloud", loginID)
if err := h.SmsService.SendSms(loginID, content); err != nil {
@@ -230,8 +230,8 @@ func (h *AuthHandler) VerifyMagicLink(c *fiber.Ctx) error {
if strings.HasPrefix(searchPhone, "010") {
searchPhone = "+82" + searchPhone[1:]
} else if strings.HasPrefix(searchPhone, "82") {
searchPhone = "+" + searchPhone
}
searchPhone = "+" + searchPhone
}
}
log.Printf("[Verify] Searching for user with phone: %s", searchPhone)
@@ -239,10 +239,10 @@ func (h *AuthHandler) VerifyMagicLink(c *fiber.Ctx) error {
Phones: []string{searchPhone},
Limit: 1,
}
var targetLoginID string
users, _, errSearch := h.DescopeClient.Management.User().SearchAll(context.Background(), searchOptions)
if errSearch == nil && len(users) > 0 {
if len(users[0].LoginIDs) > 0 {
targetLoginID = users[0].LoginIDs[0]
@@ -264,7 +264,7 @@ func (h *AuthHandler) VerifyMagicLink(c *fiber.Ctx) error {
if err != nil {
if strings.Contains(err.Error(), "User not found") || strings.Contains(err.Error(), "E062108") {
log.Printf("[Verify] User %s not found. Creating...", targetLoginID)
// Create User with Explicit Phone Attribute
userObj := &descope.UserRequest{}
if strings.Contains(targetLoginID, "@") {
@@ -278,7 +278,7 @@ func (h *AuthHandler) VerifyMagicLink(c *fiber.Ctx) error {
log.Printf("[Verify] Failed to create user: %v", errCreate)
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)
if err != nil {
log.Printf("[Verify] Failed to generate token after creation: %v", err)
@@ -304,12 +304,93 @@ func (h *AuthHandler) VerifyMagicLink(c *fiber.Ctx) error {
"jwt": sessionToken,
})
h.RedisService.Set(prefixSession+pendingRef, string(sessionData), defaultExpiration)
return c.JSON(fiber.Map{
"token": sessionToken,
"message": "Login successful",
})
}
// InitQRLogin - Step 1: Web 패널에서 QR 로그인 세션을 생성합니다.
func (h *AuthHandler) InitQRLogin(c *fiber.Ctx) error {
pendingRef := GenerateSecureToken(16)
// QR 코드 페이로드를 실제 접속 가능한 URL로 변경합니다.
frontendURL := os.Getenv("FRONTEND_URL")
if frontendURL == "" {
frontendURL = "https://ssologin.hmac.kr"
}
qrPayload := fmt.Sprintf("%s/approve?ref=%s", frontendURL, pendingRef)
log.Printf("[QR] Init: PendingRef=%s, URL=%s", pendingRef, qrPayload)
// Redis에 초기 상태 저장 (5분 만료)
h.RedisService.Set(prefixSession+pendingRef, fmt.Sprintf(`{"status":"%s"}`, statusPending), 5*time.Minute)
return c.JSON(fiber.Map{
"qrCode": qrPayload, // 프론트엔드에서 이 텍스트로 QR을 생성하거나, 이미지를 반환
"pendingRef": pendingRef,
"expiresIn": 300,
})
}
// PollQRLogin - Step 2: 웹에서 승인 여부를 폴링합니다.
func (h *AuthHandler) PollQRLogin(c *fiber.Ctx) error {
var req struct {
PendingRef string `json:"pendingRef"`
}
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid body"})
}
val, err := h.RedisService.Get(prefixSession + req.PendingRef)
if err != nil || val == "" {
return c.JSON(fiber.Map{"status": "expired"})
}
var data map[string]string
json.Unmarshal([]byte(val), &data)
if data["status"] == statusSuccess {
return c.JSON(fiber.Map{
"status": "ok",
"sessionJwt": data["jwt"],
})
}
return c.JSON(fiber.Map{"status": statusPending})
}
// ScanQRLogin - Step 3: 모바일 앱에서 QR 스캔 후 승인할 때 호출합니다.
// (이미 로그인된 세션이 필요함)
func (h *AuthHandler) ScanQRLogin(c *fiber.Ctx) error {
var req struct {
PendingRef string `json:"pendingRef"`
Token string `json:"token"` // 모바일 사용자의 세션 토큰 (검증용)
}
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid body"})
}
log.Printf("[QR] Scan & Approve: PendingRef=%s", req.PendingRef)
// 1. Redis에서 세션 확인
val, err := h.RedisService.Get(prefixSession + req.PendingRef)
if err != nil || val == "" {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Session expired or not found"})
}
// 2. 모바일 유저의 토큰으로 새 세션 토큰(웹용)을 발행하거나 그대로 전달
sessionData, _ := json.Marshal(map[string]string{
"status": statusSuccess,
"jwt": req.Token,
})
h.RedisService.Set(prefixSession+req.PendingRef, string(sessionData), 5*time.Minute)
return c.JSON(fiber.Map{"message": "QR Login Approved"})
}
// ProxyToDescope (Placeholder)
func (h *AuthHandler) ProxyToDescope(c *fiber.Ctx, path string, payload interface{}) error {
return c.Status(501).SendString("Descope Proxy Disabled")