forked from baron/baron-sso
feat: implement multi-identifier architecture (Issue #496)
- Database: Add user_login_ids table for 1:N identifier mapping and remove legacy login_id column - Kratos: Update identity schema to use custom_login_ids array instead of a single id trait - Backend: Implement syncCustomLoginIDs to collect isLoginId fields across tenant schemas - Backend: Add backtracking logic to auto-assign session tenant based on used login identifier - Backend: Add 409 Conflict exception handling for Create/Update operations - AdminFront: Refactor UserDetailPage to a tabbed grid layout (Info, Tenants, Security) - AdminFront: Show '로그인 ID' badge on tenant schema fields used for authentication - UserFront: Remove legacy optional 'Login ID' input from signup flow - Tests: Add multi-identifier repository tests and update handler tests
This commit is contained in:
@@ -97,10 +97,13 @@ func (d *DescopeProvider) CreateUser(user *domain.BrokerUser, password string) (
|
||||
return "", fmt.Errorf("descope provider: user already exists")
|
||||
}
|
||||
|
||||
descopeUser := &descope.UserRequest{}
|
||||
descopeUser.Email = user.Email
|
||||
descopeUser.Phone = normalizedPhone
|
||||
descopeUser.Name = user.Name
|
||||
descopeUser := &descope.UserRequest{
|
||||
User: descope.User{
|
||||
Email: user.Email,
|
||||
Name: user.Name,
|
||||
Phone: normalizedPhone,
|
||||
},
|
||||
}
|
||||
descopeUser.CustomAttributes = map[string]any{}
|
||||
for k, v := range user.Attributes {
|
||||
descopeUser.CustomAttributes[k] = v
|
||||
|
||||
@@ -43,7 +43,7 @@ func (o *OryProvider) Name() string {
|
||||
func (o *OryProvider) GetMetadata() (*domain.IDPMetadata, error) {
|
||||
return &domain.IDPMetadata{
|
||||
SupportedFields: []string{
|
||||
"id", "login_id", "email", "name", "phone_number",
|
||||
"id", "custom_login_ids", "login_id", "email", "name", "phone_number",
|
||||
"grade", "department", "affiliationType", "companyCode",
|
||||
},
|
||||
}, nil
|
||||
@@ -67,6 +67,21 @@ func (o *OryProvider) CreateUser(user *domain.BrokerUser, password string) (stri
|
||||
return "", fmt.Errorf("ory provider: identity already exists for email=%s", user.Email)
|
||||
}
|
||||
|
||||
// [New] Check all custom login IDs for collisions
|
||||
for _, lid := range user.CustomLoginIDs {
|
||||
if lid == "" {
|
||||
continue
|
||||
}
|
||||
existing, err := o.findIdentityID(lid)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("ory provider: search identity failed for %s: %w", lid, err)
|
||||
}
|
||||
if existing != "" {
|
||||
return "", fmt.Errorf("ory provider: identifier %s already exists", lid)
|
||||
}
|
||||
}
|
||||
|
||||
// [Legacy] check single LoginID
|
||||
if user.LoginID != "" {
|
||||
existingLoginID, err := o.findIdentityID(user.LoginID)
|
||||
if err != nil {
|
||||
@@ -91,13 +106,20 @@ func (o *OryProvider) CreateUser(user *domain.BrokerUser, password string) (stri
|
||||
"email": user.Email,
|
||||
"name": user.Name,
|
||||
}
|
||||
if user.LoginID != "" {
|
||||
traits["id"] = user.LoginID
|
||||
if len(user.CustomLoginIDs) > 0 {
|
||||
traits["custom_login_ids"] = user.CustomLoginIDs
|
||||
} else if user.LoginID != "" {
|
||||
traits["custom_login_ids"] = []string{user.LoginID}
|
||||
}
|
||||
|
||||
if user.PhoneNumber != "" {
|
||||
traits["phone_number"] = user.PhoneNumber
|
||||
}
|
||||
for k, v := range user.Attributes {
|
||||
// [SoT Fix] Don't let attributes overwrite core traits or use old 'id' trait
|
||||
if k == "id" || k == "email" || k == "custom_login_ids" {
|
||||
continue
|
||||
}
|
||||
traits[k] = v
|
||||
}
|
||||
|
||||
|
||||
@@ -243,9 +243,6 @@ func (s *userGroupService) AddMember(ctx context.Context, groupID, userID string
|
||||
localUser.CompanyCode = tenant.Slug
|
||||
localUser.TenantID = &tenant.ID
|
||||
localUser.Department = group.Name
|
||||
if localUser.LoginID == "" {
|
||||
localUser.LoginID = localUser.ID
|
||||
}
|
||||
_ = s.userRepo.Update(ctx, localUser)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user