forked from baron/baron-sso
admin page del
This commit is contained in:
@@ -251,12 +251,7 @@ func main() {
|
|||||||
|
|
||||||
// Admin Routes
|
// Admin Routes
|
||||||
admin := api.Group("/admin")
|
admin := api.Group("/admin")
|
||||||
admin.Post("/users", adminHandler.CreateUser)
|
|
||||||
admin.Get("/check", adminHandler.CheckAuth)
|
admin.Get("/check", adminHandler.CheckAuth)
|
||||||
admin.Get("/users", adminHandler.ListUsers)
|
|
||||||
admin.Patch("/users/:loginId", adminHandler.UpdateUser)
|
|
||||||
admin.Delete("/users/:loginId", adminHandler.DeleteUser)
|
|
||||||
admin.Patch("/users/:loginId/status", adminHandler.UpdateUserStatus)
|
|
||||||
|
|
||||||
// Webhook for Descope Generic SMS Gateway
|
// Webhook for Descope Generic SMS Gateway
|
||||||
auth.Post("/webhooks/descope-sms", authHandler.HandleDescopeSmsRelay)
|
auth.Post("/webhooks/descope-sms", authHandler.HandleDescopeSmsRelay)
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/descope/go-sdk/descope"
|
|
||||||
"github.com/descope/go-sdk/descope/client"
|
"github.com/descope/go-sdk/descope/client"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
@@ -40,10 +36,6 @@ func NewAdminHandler() *AdminHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func boolPtr(b bool) *bool {
|
|
||||||
return &b
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkAuth Helper
|
// checkAuth Helper
|
||||||
func (h *AdminHandler) checkAuth(c *fiber.Ctx) error {
|
func (h *AdminHandler) checkAuth(c *fiber.Ctx) error {
|
||||||
adminPass := os.Getenv("ADMIN_PASSWORD")
|
adminPass := os.Getenv("ADMIN_PASSWORD")
|
||||||
@@ -58,234 +50,9 @@ func (h *AdminHandler) checkAuth(c *fiber.Ctx) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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) CheckAuth(c *fiber.Ctx) error {
|
func (h *AdminHandler) CheckAuth(c *fiber.Ctx) error {
|
||||||
if err := h.checkAuth(c); err != nil {
|
if err := h.checkAuth(c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "ok"})
|
return c.Status(fiber.StatusOK).JSON(fiber.Map{"status": "ok"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListUsers - GET /api/v1/admin/users
|
|
||||||
func (h *AdminHandler) ListUsers(c *fiber.Ctx) error {
|
|
||||||
if err := h.checkAuth(c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
text := c.Query("text")
|
|
||||||
// Limit is not directly supported in SearchAll options as a simple int in all SDK versions,
|
|
||||||
// but let's check the options struct.
|
|
||||||
// Based on previous inspection: SearchAll takes UserSearchOptions.
|
|
||||||
|
|
||||||
var users []*descope.UserResponse
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if text != "" {
|
|
||||||
options := &descope.UserSearchOptions{Text: text, Limit: 50}
|
|
||||||
users, _, err = h.DescopeClient.Management.User().SearchAll(context.Background(), options)
|
|
||||||
} else {
|
|
||||||
// Nil options means default search (usually returns all or default page)
|
|
||||||
users, _, err = h.DescopeClient.Management.User().SearchAll(context.Background(), nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("[Admin] ListUsers failed", "error", err)
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(fiber.Map{"users": users})
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteUser - DELETE /api/v1/admin/users/:loginId
|
|
||||||
func (h *AdminHandler) DeleteUser(c *fiber.Ctx) error {
|
|
||||||
if err := h.checkAuth(c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
loginID := c.Params("loginId")
|
|
||||||
// Decode if necessary (Fiber usually decodes params, but let's be safe if it's double encoded)
|
|
||||||
if decoded, err := url.QueryUnescape(loginID); err == nil {
|
|
||||||
loginID = decoded
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("[Admin] Deleting user", "loginID", loginID)
|
|
||||||
if err := h.DescopeClient.Management.User().Delete(context.Background(), loginID); err != nil {
|
|
||||||
slog.Error("[Admin] DeleteUser failed", "error", err)
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(fiber.Map{"message": "User deleted successfully"})
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateUserStatus - PATCH /api/v1/admin/users/:loginId/status
|
|
||||||
func (h *AdminHandler) UpdateUserStatus(c *fiber.Ctx) error {
|
|
||||||
if err := h.checkAuth(c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
loginID := c.Params("loginId")
|
|
||||||
if decoded, err := url.QueryUnescape(loginID); err == nil {
|
|
||||||
loginID = decoded
|
|
||||||
}
|
|
||||||
|
|
||||||
var req struct {
|
|
||||||
Status string `json:"status"` // "enabled" or "disabled"
|
|
||||||
}
|
|
||||||
if err := c.BodyParser(&req); err != nil {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid body"})
|
|
||||||
}
|
|
||||||
|
|
||||||
var user *descope.UserResponse
|
|
||||||
var err error
|
|
||||||
|
|
||||||
slog.Info("[Admin] Updating status", "loginID", loginID, "status", req.Status)
|
|
||||||
|
|
||||||
if req.Status == "enabled" || req.Status == "active" {
|
|
||||||
user, err = h.DescopeClient.Management.User().Activate(context.Background(), loginID)
|
|
||||||
} else {
|
|
||||||
user, err = h.DescopeClient.Management.User().Deactivate(context.Background(), loginID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("[Admin] Status update failed", "error", err)
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(fiber.Map{
|
|
||||||
"message": "Status updated",
|
|
||||||
"user": user,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateUser - PATCH /api/v1/admin/users/:loginId
|
|
||||||
func (h *AdminHandler) UpdateUser(c *fiber.Ctx) error {
|
|
||||||
if err := h.checkAuth(c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
loginID := c.Params("loginId")
|
|
||||||
if decoded, err := url.QueryUnescape(loginID); err == nil {
|
|
||||||
loginID = decoded
|
|
||||||
}
|
|
||||||
|
|
||||||
var req struct {
|
|
||||||
Email *string `json:"email"`
|
|
||||||
Phone *string `json:"phone"`
|
|
||||||
DisplayName *string `json:"displayName"`
|
|
||||||
}
|
|
||||||
if err := c.BodyParser(&req); err != nil {
|
|
||||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid body"})
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Update Display Name
|
|
||||||
if req.DisplayName != nil {
|
|
||||||
_, err = h.DescopeClient.Management.User().UpdateDisplayName(ctx, loginID, *req.DisplayName)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(500).JSON(fiber.Map{"error": "Failed to update name: " + err.Error()})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update Email
|
|
||||||
if req.Email != nil {
|
|
||||||
_, err = h.DescopeClient.Management.User().UpdateEmail(ctx, loginID, *req.Email, true, false) // verified=true, addToLoginIDs=false
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(500).JSON(fiber.Map{"error": "Failed to update email: " + err.Error()})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update Phone
|
|
||||||
if req.Phone != nil {
|
|
||||||
phone := *req.Phone
|
|
||||||
// Normalize
|
|
||||||
if strings.HasPrefix(phone, "010") {
|
|
||||||
phone = "+82" + phone[1:]
|
|
||||||
}
|
|
||||||
_, err = h.DescopeClient.Management.User().UpdatePhone(ctx, loginID, phone, true, false) // verified=true, addToLoginIDs=false
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(500).JSON(fiber.Map{"error": "Failed to update phone: " + err.Error()})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(fiber.Map{"message": "User updated successfully"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *AdminHandler) CreateUser(c *fiber.Ctx) error {
|
|
||||||
if err := h.checkAuth(c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("[Admin] Creating user", "loginID", req.LoginID, "email", req.Email, "phone", normalizedPhone)
|
|
||||||
|
|
||||||
res, err := h.DescopeClient.Management.User().Create(context.Background(), req.LoginID, userObj)
|
|
||||||
if err != nil {
|
|
||||||
slog.Error("[Admin] Failed to create user", "error", err)
|
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(fiber.Map{
|
|
||||||
"message": "User created successfully",
|
|
||||||
"user": res,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user