1
0
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:
2026-06-01 15:34:08 +09:00
parent 4a1e89e421
commit 31d107ff2e
85 changed files with 2104 additions and 1149 deletions

View File

@@ -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")
}

View File

@@ -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"`
}

View File

@@ -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.

View File

@@ -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