forked from baron/baron-sso
admin page add
This commit is contained in:
144
backend/internal/handler/admin_handler.go
Normal file
144
backend/internal/handler/admin_handler.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/descope/go-sdk/descope"
|
||||
"github.com/descope/go-sdk/descope/client"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type AdminHandler struct {
|
||||
DescopeClient *client.DescopeClient
|
||||
}
|
||||
|
||||
func NewAdminHandler() *AdminHandler {
|
||||
projectID := os.Getenv("DESCOPE_PROJECT_ID")
|
||||
managementKey := os.Getenv("DESCOPE_MANAGEMENT_KEY")
|
||||
|
||||
var descopeClient *client.DescopeClient
|
||||
var err error
|
||||
|
||||
if projectID != "" && managementKey != "" {
|
||||
descopeClient, err = client.NewWithConfig(&client.Config{
|
||||
ProjectID: projectID,
|
||||
ManagementKey: managementKey,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Warning: Failed to initialize Descope Client for Admin: %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Println("Warning: DESCOPE_PROJECT_ID or DESCOPE_MANAGEMENT_KEY missing. Admin functions will fail.")
|
||||
}
|
||||
|
||||
return &AdminHandler{
|
||||
DescopeClient: descopeClient,
|
||||
}
|
||||
}
|
||||
|
||||
func boolPtr(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
type CreateUserRequest struct {
|
||||
LoginID string `json:"loginId"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Roles []string `json:"roles"`
|
||||
Tenants map[string][]string `json:"tenants"` // tenantId -> roles
|
||||
}
|
||||
|
||||
func (h *AdminHandler) CreateUser(c *fiber.Ctx) error {
|
||||
// 1. Simple Password Check
|
||||
adminPass := os.Getenv("ADMIN_PASSWORD")
|
||||
if adminPass == "" {
|
||||
adminPass = "admin" // Default fallback
|
||||
}
|
||||
|
||||
reqPass := c.Get("X-Admin-Password")
|
||||
if reqPass != adminPass {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid Admin Password"})
|
||||
}
|
||||
|
||||
if h.DescopeClient == nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Descope Client not configured"})
|
||||
}
|
||||
|
||||
var req CreateUserRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request body"})
|
||||
}
|
||||
|
||||
if req.LoginID == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "LoginID is required"})
|
||||
}
|
||||
|
||||
// Normalize Phone
|
||||
normalizedPhone := req.Phone
|
||||
if normalizedPhone != "" {
|
||||
if strings.HasPrefix(normalizedPhone, "010") {
|
||||
normalizedPhone = "+82" + normalizedPhone[1:]
|
||||
} else if strings.HasPrefix(normalizedPhone, "82") {
|
||||
normalizedPhone = "+" + normalizedPhone
|
||||
}
|
||||
}
|
||||
|
||||
userObj := &descope.UserRequest{
|
||||
User: descope.User{
|
||||
Email: req.Email,
|
||||
Phone: normalizedPhone,
|
||||
Name: req.DisplayName,
|
||||
},
|
||||
VerifiedEmail: boolPtr(req.Email != ""),
|
||||
VerifiedPhone: boolPtr(normalizedPhone != ""),
|
||||
}
|
||||
|
||||
// Add Roles if provided
|
||||
if len(req.Roles) > 0 {
|
||||
userObj.Roles = req.Roles
|
||||
}
|
||||
|
||||
// Add Tenants if provided
|
||||
if len(req.Tenants) > 0 {
|
||||
// Convert map[string][]string to []*descope.AssociatedTenant
|
||||
userTenants := []*descope.AssociatedTenant{}
|
||||
for tenantID, roles := range req.Tenants {
|
||||
userTenants = append(userTenants, &descope.AssociatedTenant{
|
||||
TenantID: tenantID,
|
||||
Roles: roles,
|
||||
})
|
||||
}
|
||||
userObj.Tenants = userTenants
|
||||
}
|
||||
|
||||
log.Printf("[Admin] Creating user: %s (Email: %s, Phone: %s)", req.LoginID, req.Email, normalizedPhone)
|
||||
|
||||
res, err := h.DescopeClient.Management.User().Create(context.Background(), req.LoginID, userObj)
|
||||
if err != nil {
|
||||
log.Printf("[Admin] Failed to create user: %v", err)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"message": "User created successfully",
|
||||
"user": res,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AdminHandler) CheckAuth(c *fiber.Ctx) error {
|
||||
adminPass := os.Getenv("ADMIN_PASSWORD")
|
||||
if adminPass == "" {
|
||||
adminPass = "admin"
|
||||
}
|
||||
|
||||
reqPass := c.Get("X-Admin-Password")
|
||||
if reqPass != adminPass {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid Admin Password"})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "ok"})
|
||||
}
|
||||
@@ -222,30 +222,64 @@ func (h *AuthHandler) VerifyMagicLink(c *fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Descope Client not configured"})
|
||||
}
|
||||
|
||||
log.Printf("[Verify] Generating embedded link for %s", loginID)
|
||||
embeddedToken, err := h.DescopeClient.Management.User().GenerateEmbeddedLink(context.Background(), loginID, nil, 0)
|
||||
// [Fix] Search for existing user by phone to prevent fragmentation
|
||||
// Normalize Phone Number for Search (E.164)
|
||||
searchPhone := loginID
|
||||
if !strings.Contains(searchPhone, "@") {
|
||||
// If it looks like a KR mobile number (010...), format to +8210...
|
||||
if strings.HasPrefix(searchPhone, "010") {
|
||||
searchPhone = "+82" + searchPhone[1:]
|
||||
} else if strings.HasPrefix(searchPhone, "82") {
|
||||
searchPhone = "+" + searchPhone
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[Verify] Searching for user with phone: %s", searchPhone)
|
||||
searchOptions := &descope.UserSearchOptions{
|
||||
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]
|
||||
log.Printf("[Verify] User found! Existing LoginID: %s", targetLoginID)
|
||||
} else {
|
||||
// Should not happen for a valid user, but fallback to UserID or searchPhone
|
||||
log.Printf("[Verify] User found but no LoginIDs. Using UserID.")
|
||||
targetLoginID = users[0].UserID
|
||||
}
|
||||
} else {
|
||||
// Not found, or search error. Fallback to using the phone as LoginID.
|
||||
// Use the normalized phone number to ensure consistency (+82...)
|
||||
targetLoginID = searchPhone
|
||||
log.Printf("[Verify] User not found by phone. Will use/create: %s", targetLoginID)
|
||||
}
|
||||
|
||||
log.Printf("[Verify] Generating embedded link for %s", targetLoginID)
|
||||
embeddedToken, err := h.DescopeClient.Management.User().GenerateEmbeddedLink(context.Background(), targetLoginID, nil, 0)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "User not found") || strings.Contains(err.Error(), "E062108") {
|
||||
log.Printf("[Verify] User %s not found. Creating...", loginID)
|
||||
log.Printf("[Verify] User %s not found. Creating...", targetLoginID)
|
||||
|
||||
descopeLoginID := loginID
|
||||
// Create User with Explicit Phone Attribute
|
||||
userObj := &descope.UserRequest{}
|
||||
if strings.Contains(loginID, "@") {
|
||||
userObj.Email = loginID
|
||||
if strings.Contains(targetLoginID, "@") {
|
||||
userObj.Email = targetLoginID
|
||||
} else {
|
||||
if strings.HasPrefix(loginID, "010") {
|
||||
descopeLoginID = "+82" + loginID[1:]
|
||||
}
|
||||
userObj.Phone = descopeLoginID
|
||||
userObj.Phone = targetLoginID // Must be E.164
|
||||
}
|
||||
|
||||
_, errCreate := h.DescopeClient.Management.User().Create(context.Background(), descopeLoginID, userObj)
|
||||
_, errCreate := h.DescopeClient.Management.User().Create(context.Background(), targetLoginID, userObj)
|
||||
if errCreate != nil {
|
||||
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(), descopeLoginID, nil, 0)
|
||||
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)
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to generate upstream token"})
|
||||
|
||||
Reference in New Issue
Block a user