forked from baron/baron-sso
feat: implement enhanced user schema management with validation and admin_only fields
This commit is contained in:
@@ -6,7 +6,10 @@ import (
|
||||
"baron-sso-backend/internal/service"
|
||||
"baron-sso-backend/internal/utils"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -302,6 +305,18 @@ func (h *UserHandler) CreateUser(c *fiber.Ctx) error {
|
||||
Attributes: attributes,
|
||||
}
|
||||
|
||||
// [Validation] Based on Tenant Schema
|
||||
if req.CompanyCode != "" && h.TenantService != nil {
|
||||
tenant, err := h.TenantService.GetTenantBySlug(c.Context(), req.CompanyCode)
|
||||
if err == nil && tenant != nil {
|
||||
if schema, ok := tenant.Config["userSchema"].([]interface{}); ok {
|
||||
if err := h.validateMetadata(req.Metadata, schema, true); err != nil {
|
||||
return errorJSON(c, fiber.StatusBadRequest, "metadata validation failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
identityID, err := h.OryProvider.CreateUser(brokerUser, password)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "already exists") {
|
||||
@@ -408,6 +423,24 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error {
|
||||
}
|
||||
}
|
||||
|
||||
// [Validation] Based on Tenant Schema
|
||||
schemaCompCode := extractTraitString(identity.Traits, "companyCode")
|
||||
if req.CompanyCode != nil {
|
||||
schemaCompCode = *req.CompanyCode
|
||||
}
|
||||
|
||||
if schemaCompCode != "" && h.TenantService != nil {
|
||||
tenant, err := h.TenantService.GetTenantBySlug(c.Context(), schemaCompCode)
|
||||
if err == nil && tenant != nil {
|
||||
if schema, ok := tenant.Config["userSchema"].([]interface{}); ok {
|
||||
isAdmin := requester != nil && (requester.Role == domain.RoleSuperAdmin || requester.Role == domain.RoleTenantAdmin)
|
||||
if err := h.validateMetadataWithAuth(req.Metadata, schema, isAdmin, false); err != nil {
|
||||
return errorJSON(c, fiber.StatusBadRequest, "metadata validation failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
traits := identity.Traits
|
||||
if traits == nil {
|
||||
traits = map[string]interface{}{}
|
||||
@@ -730,3 +763,68 @@ func normalizePhoneNumber(phone string) string {
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
func (h *UserHandler) validateMetadata(metadata map[string]any, schema []interface{}, checkRequired bool) error {
|
||||
return h.validateMetadataWithAuth(metadata, schema, true, checkRequired)
|
||||
}
|
||||
|
||||
func (h *UserHandler) validateMetadataWithAuth(metadata map[string]any, schema []interface{}, isAdmin bool, checkRequired bool) error {
|
||||
schemaMap := make(map[string]map[string]interface{})
|
||||
for _, s := range schema {
|
||||
if m, ok := s.(map[string]interface{}); ok {
|
||||
if key, ok := m["key"].(string); ok {
|
||||
schemaMap[key] = m
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Check required fields
|
||||
if checkRequired {
|
||||
for key, config := range schemaMap {
|
||||
required, _ := config["required"].(bool)
|
||||
val, exists := metadata[key]
|
||||
if required && (!exists || val == nil || val == "") {
|
||||
return errors.New("field " + key + " is required")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Check each field in metadata
|
||||
for key, val := range metadata {
|
||||
config, exists := schemaMap[key]
|
||||
if !exists {
|
||||
continue // Ignore fields not in schema or allow? Let's allow for now
|
||||
}
|
||||
|
||||
// Admin Only check
|
||||
adminOnly, _ := config["adminOnly"].(bool)
|
||||
if adminOnly && !isAdmin {
|
||||
return errors.New("field " + key + " is admin only")
|
||||
}
|
||||
|
||||
// Regex validation
|
||||
if regexStr, ok := config["validation"].(string); ok && regexStr != "" {
|
||||
strVal := ""
|
||||
switch v := val.(type) {
|
||||
case string:
|
||||
strVal = v
|
||||
case float64:
|
||||
strVal = fmt.Sprintf("%v", v)
|
||||
case int:
|
||||
strVal = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
if strVal != "" {
|
||||
matched, err := regexp.MatchString(regexStr, strVal)
|
||||
if err != nil {
|
||||
return errors.New("invalid regex pattern for field " + key)
|
||||
}
|
||||
if !matched {
|
||||
return errors.New("field " + key + " does not match validation pattern")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user