forked from baron/baron-sso
feat(user): support fixed UUID registration and enhance bulk import results
- Added support for fixed UUIDs during bulk registration (Search-first + ExternalID mapping) - Implemented idempotency and visibility restoration for soft-deleted users - Enhanced bulk upload UI to show 'New/Updated/Unchanged' status and modified fields - Added logic to reclaim identifiers (login_id) from colliding records - Added frontend E2E and backend unit tests for UUID integrity and conflict handling - Fixed i18n, formatting, and mock tests to satisfy code-check - Applied 'go fix' for 'omitzero' tags and general Go standards
This commit is contained in:
@@ -98,15 +98,15 @@ var hanmacInitialRomanization = []string{
|
||||
|
||||
func SplitEmailDomain(email string) (string, string, error) {
|
||||
normalized := strings.ToLower(strings.TrimSpace(email))
|
||||
at := strings.Index(normalized, "@")
|
||||
if at < 0 {
|
||||
before, after, ok := strings.Cut(normalized, "@")
|
||||
if !ok {
|
||||
return "", "", errors.New("email must contain @")
|
||||
}
|
||||
if strings.Count(normalized, "@") != 1 {
|
||||
return "", "", errors.New("email must contain one @")
|
||||
}
|
||||
localPart := strings.TrimSpace(normalized[:at])
|
||||
domainPart := strings.TrimSpace(normalized[at+1:])
|
||||
localPart := strings.TrimSpace(before)
|
||||
domainPart := strings.TrimSpace(after)
|
||||
if domainPart == "" || !strings.Contains(domainPart, ".") {
|
||||
return "", "", errors.New("email domain is invalid")
|
||||
}
|
||||
|
||||
@@ -19,21 +19,21 @@ const (
|
||||
)
|
||||
|
||||
type HydraClient struct {
|
||||
ClientID string `json:"client_id"`
|
||||
ClientName string `json:"client_name,omitempty"`
|
||||
ClientSecret string `json:"client_secret,omitempty"` // Added
|
||||
ClientURI string `json:"client_uri,omitempty"`
|
||||
RedirectURIs []string `json:"redirect_uris,omitempty"`
|
||||
GrantTypes []string `json:"grant_types,omitempty"`
|
||||
ResponseTypes []string `json:"response_types,omitempty"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
|
||||
SkipConsent *bool `json:"skip_consent,omitempty"`
|
||||
JWKSUri string `json:"jwks_uri,omitempty"`
|
||||
JWKS interface{} `json:"jwks,omitempty"`
|
||||
BackChannelLogoutURI string `json:"backchannel_logout_uri,omitempty"`
|
||||
BackChannelLogoutSessionRequired *bool `json:"backchannel_logout_session_required,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientName string `json:"client_name,omitempty"`
|
||||
ClientSecret string `json:"client_secret,omitempty"` // Added
|
||||
ClientURI string `json:"client_uri,omitempty"`
|
||||
RedirectURIs []string `json:"redirect_uris,omitempty"`
|
||||
GrantTypes []string `json:"grant_types,omitempty"`
|
||||
ResponseTypes []string `json:"response_types,omitempty"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
|
||||
SkipConsent *bool `json:"skip_consent,omitempty"`
|
||||
JWKSUri string `json:"jwks_uri,omitempty"`
|
||||
JWKS any `json:"jwks,omitempty"`
|
||||
BackChannelLogoutURI string `json:"backchannel_logout_uri,omitempty"`
|
||||
BackChannelLogoutSessionRequired *bool `json:"backchannel_logout_session_required,omitempty"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
func (c *HydraClient) SupportsHeadlessLogin() bool {
|
||||
@@ -65,7 +65,7 @@ func (c *HydraClient) HeadlessJWKSURI() string {
|
||||
return strings.TrimSpace(c.JWKSUri)
|
||||
}
|
||||
|
||||
func (c *HydraClient) HeadlessJWKS() interface{} {
|
||||
func (c *HydraClient) HeadlessJWKS() any {
|
||||
if c.Metadata != nil {
|
||||
if value, ok := c.Metadata[MetadataHeadlessJWKS]; ok && value != nil {
|
||||
return value
|
||||
@@ -140,6 +140,6 @@ type HydraConsentSession struct {
|
||||
AuthenticatedAt *time.Time `json:"authenticated_at,omitempty"`
|
||||
RequestedAt *time.Time `json:"requested_at,omitempty"`
|
||||
HandledAt *time.Time `json:"handled_at,omitempty"`
|
||||
Client HydraClient `json:"client,omitempty"`
|
||||
Client HydraClient `json:"client"`
|
||||
ConsentRequest *HydraConsentRequest `json:"consent_request,omitempty"`
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ type BrokerUser struct {
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
// Attributes stores custom user attributes.
|
||||
// The "required_keys" tag specifies which keys MUST be present in the IDP's schema support.
|
||||
Attributes map[string]interface{} `json:"attributes" required_keys:"grade,department"`
|
||||
Attributes map[string]any `json:"attributes" required_keys:"grade,department"`
|
||||
}
|
||||
|
||||
// IDPMetadata represents the schema capabilities of an Identity Provider.
|
||||
|
||||
@@ -2,6 +2,7 @@ package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -208,10 +209,8 @@ func ValidateLoginID(loginID string, emails []string, phone string) error {
|
||||
|
||||
reserved := []string{"admin", "system", "root", "master", "superuser", "guest", "operator"}
|
||||
lowerID := strings.ToLower(loginID)
|
||||
for _, r := range reserved {
|
||||
if lowerID == r {
|
||||
return fmt.Errorf("reserved ID cannot be used")
|
||||
}
|
||||
if slices.Contains(reserved, lowerID) {
|
||||
return fmt.Errorf("reserved ID cannot be used")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user