forked from baron/baron-sso
feat: 사용자 벌크 CSV 등록 시 보조 이메일 지원 (#917)
- `adminfront` CSV 템플릿 헤더에 `secondary_emails` 추가 및 예시 반영 - `adminfront` CSV 파서(`csvParser.ts`)에서 `secondary_emails` 추출 로직 보강 - `backend` 에서 `BulkCreateUsers`, `UpdateUser` 실행 시 보조 이메일을 포함한 모든 이메일에 대해 식별자 유효성(ValidateLoginID) 검사 수행 - `domain.ValidateLoginID`의 파라미터를 복수 이메일 처리를 위해 `[]string`으로 변경 - Playwright E2E 테스트 `users_bulk_secondary.spec.ts` 신규 작성 및 테스트 패스 확인
This commit is contained in:
@@ -156,7 +156,7 @@ func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
}
|
||||
|
||||
// ValidateLoginID checks if the loginID violates any collision, length, or security rules.
|
||||
func ValidateLoginID(loginID, email, phone string) error {
|
||||
func ValidateLoginID(loginID string, emails []string, phone string) error {
|
||||
loginID = strings.TrimSpace(loginID)
|
||||
if loginID == "" {
|
||||
return nil
|
||||
@@ -170,8 +170,10 @@ func ValidateLoginID(loginID, email, phone string) error {
|
||||
return fmt.Errorf("ID cannot be an email format")
|
||||
}
|
||||
|
||||
if email != "" && strings.EqualFold(loginID, email) {
|
||||
return fmt.Errorf("ID cannot be the same as the email address")
|
||||
for _, email := range emails {
|
||||
if email != "" && strings.EqualFold(loginID, email) {
|
||||
return fmt.Errorf("ID cannot be the same as the email address")
|
||||
}
|
||||
}
|
||||
|
||||
if phone != "" {
|
||||
|
||||
@@ -8,30 +8,31 @@ func TestValidateLoginID(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
loginID string
|
||||
email string
|
||||
emails []string
|
||||
phone string
|
||||
wantErr bool
|
||||
}{
|
||||
{"Empty", "", "test@email.com", "01012345678", false},
|
||||
{"Valid alphanumeric", "user123", "test@email.com", "01012345678", false},
|
||||
{"Too short", "us", "test@email.com", "01012345678", true},
|
||||
{"Too long", "thisisaverylongloginidthatiswayoverthirtycharacters", "test@email.com", "01012345678", true},
|
||||
{"Email format", "user@domain.com", "test@email.com", "01012345678", true},
|
||||
{"Exact email match", "Test@Email.Com", "test@email.com", "01012345678", true},
|
||||
{"Phone number match", "010-1234-5678", "test@email.com", "01012345678", true},
|
||||
{"Phone number match +82", "+821012345678", "test@email.com", "01012345678", true},
|
||||
{"Phone number match digits", "01012345678", "test@email.com", "01012345678", true},
|
||||
{"Phone format (11 digits)", "01098765432", "test@email.com", "01012345678", true},
|
||||
{"Valid pure digits (employee ID)", "20230001", "test@email.com", "01012345678", false},
|
||||
{"Valid pure digits long", "123456789", "test@email.com", "01012345678", false},
|
||||
{"Valid pure digits 10 chars", "1234567890", "test@email.com", "01012345678", false},
|
||||
{"Reserved word admin", "ADMIN", "test@email.com", "01012345678", true},
|
||||
{"Reserved word root", "root", "test@email.com", "01012345678", true},
|
||||
{"Empty", "", []string{"test@email.com"}, "01012345678", false},
|
||||
{"Valid alphanumeric", "user123", []string{"test@email.com"}, "01012345678", false},
|
||||
{"Too short", "us", []string{"test@email.com"}, "01012345678", true},
|
||||
{"Too long", "thisisaverylongloginidthatiswayoverthirtycharacters", []string{"test@email.com"}, "01012345678", true},
|
||||
{"Email format", "user@domain.com", []string{"test@email.com"}, "01012345678", true},
|
||||
{"Exact email match", "Test@Email.Com", []string{"test@email.com"}, "01012345678", true},
|
||||
{"Secondary email match", "sub@test.com", []string{"test@email.com", "sub@test.com"}, "01012345678", true},
|
||||
{"Phone number match", "010-1234-5678", []string{"test@email.com"}, "01012345678", true},
|
||||
{"Phone number match +82", "+821012345678", []string{"test@email.com"}, "01012345678", true},
|
||||
{"Phone number match digits", "01012345678", []string{"test@email.com"}, "01012345678", true},
|
||||
{"Phone format (11 digits)", "01098765432", []string{"test@email.com"}, "01012345678", true},
|
||||
{"Valid pure digits (employee ID)", "20230001", []string{"test@email.com"}, "01012345678", false},
|
||||
{"Valid pure digits long", "123456789", []string{"test@email.com"}, "01012345678", false},
|
||||
{"Valid pure digits 10 chars", "1234567890", []string{"test@email.com"}, "01012345678", false},
|
||||
{"Reserved word admin", "ADMIN", []string{"test@email.com"}, "01012345678", true},
|
||||
{"Reserved word root", "root", []string{"test@email.com"}, "01012345678", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := ValidateLoginID(tt.loginID, tt.email, tt.phone)
|
||||
err := ValidateLoginID(tt.loginID, tt.emails, tt.phone)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ValidateLoginID() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
|
||||
@@ -374,7 +374,7 @@ func (h *AuthHandler) CheckLoginID(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// Basic validation via our ValidateLoginID helper (without email/phone since we just check format & collision with reserved words)
|
||||
if err := domain.ValidateLoginID(req.LoginID, "", ""); err != nil {
|
||||
if err := domain.ValidateLoginID(req.LoginID, []string{}, ""); err != nil {
|
||||
return c.JSON(fiber.Map{"available": false, "message": err.Error()})
|
||||
}
|
||||
|
||||
@@ -801,7 +801,7 @@ func (h *AuthHandler) Signup(c *fiber.Ctx) error {
|
||||
// Validate all collected LoginIDs
|
||||
if collectedIDs, ok := attributes["custom_login_ids"].([]string); ok {
|
||||
for _, lid := range collectedIDs {
|
||||
if err := domain.ValidateLoginID(lid, req.Email, normalizedPhone); err != nil {
|
||||
if err := domain.ValidateLoginID(lid, []string{req.Email}, normalizedPhone); err != nil {
|
||||
return errorJSON(c, fiber.StatusBadRequest, "Invalid LoginID ("+lid+"): "+err.Error())
|
||||
}
|
||||
}
|
||||
@@ -7953,7 +7953,7 @@ func (h *AuthHandler) UpdateMe(c *fiber.Ctx) error {
|
||||
userPhone := extractTraitString(traits, "phone_number")
|
||||
if collectedIDs, ok := traits["custom_login_ids"].([]string); ok {
|
||||
for _, lid := range collectedIDs {
|
||||
if err := domain.ValidateLoginID(lid, userEmail, userPhone); err != nil {
|
||||
if err := domain.ValidateLoginID(lid, []string{userEmail}, userPhone); err != nil {
|
||||
return errorJSON(c, fiber.StatusBadRequest, "Invalid LoginID ("+lid+"): "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -757,7 +757,7 @@ func (h *UserHandler) CreateUser(c *fiber.Ctx) error {
|
||||
// Validate all collected LoginIDs
|
||||
if collectedIDs, ok := attributes["custom_login_ids"].([]string); ok {
|
||||
for _, lid := range collectedIDs {
|
||||
if err := domain.ValidateLoginID(lid, email, normalizePhoneNumber(req.Phone)); err != nil {
|
||||
if err := domain.ValidateLoginID(lid, []string{email}, normalizePhoneNumber(req.Phone)); err != nil {
|
||||
return errorJSON(c, fiber.StatusBadRequest, "Invalid LoginID ("+lid+"): "+err.Error())
|
||||
}
|
||||
}
|
||||
@@ -1224,8 +1224,22 @@ func (h *UserHandler) BulkCreateUsers(c *fiber.Ctx) error {
|
||||
// Validate all collected LoginIDs
|
||||
if collectedIDs, ok := attributes["custom_login_ids"].([]string); ok {
|
||||
valid := true
|
||||
// Collect all emails
|
||||
allEmails := []string{userEmail}
|
||||
if secondaryRaw, exists := item.Metadata["secondary_emails"]; exists {
|
||||
if secondaryEmails, ok := secondaryRaw.([]interface{}); ok {
|
||||
for _, se := range secondaryEmails {
|
||||
if seStr, ok := se.(string); ok {
|
||||
allEmails = append(allEmails, seStr)
|
||||
}
|
||||
}
|
||||
} else if secondaryEmails, ok := secondaryRaw.([]string); ok {
|
||||
allEmails = append(allEmails, secondaryEmails...)
|
||||
}
|
||||
}
|
||||
|
||||
for _, lid := range collectedIDs {
|
||||
if err := domain.ValidateLoginID(lid, userEmail, userPhone); err != nil {
|
||||
if err := domain.ValidateLoginID(lid, allEmails, userPhone); err != nil {
|
||||
results = append(results, bulkUserResult{Email: userEmail, OriginalEmail: emailEvaluation.OriginalEmail, SuggestedEmail: emailEvaluation.SuggestedEmail, Status: emailEvaluation.Status, Warnings: emailEvaluation.Warnings, Success: false, Message: "Invalid LoginID (" + lid + "): " + err.Error()})
|
||||
valid = false
|
||||
break
|
||||
@@ -2036,9 +2050,23 @@ func (h *UserHandler) UpdateUser(c *fiber.Ctx) error {
|
||||
// Validate all collected LoginIDs
|
||||
userEmail := extractTraitString(traits, "email")
|
||||
userPhone := extractTraitString(traits, "phone_number")
|
||||
|
||||
allEmails := []string{userEmail}
|
||||
if secondaryRaw, exists := traits["secondary_emails"]; exists {
|
||||
if secondaryEmails, ok := secondaryRaw.([]interface{}); ok {
|
||||
for _, se := range secondaryEmails {
|
||||
if seStr, ok := se.(string); ok {
|
||||
allEmails = append(allEmails, seStr)
|
||||
}
|
||||
}
|
||||
} else if secondaryEmails, ok := secondaryRaw.([]string); ok {
|
||||
allEmails = append(allEmails, secondaryEmails...)
|
||||
}
|
||||
}
|
||||
|
||||
if collectedIDs, ok := traits["custom_login_ids"].([]string); ok {
|
||||
for _, lid := range collectedIDs {
|
||||
if err := domain.ValidateLoginID(lid, userEmail, userPhone); err != nil {
|
||||
if err := domain.ValidateLoginID(lid, allEmails, userPhone); err != nil {
|
||||
return errorJSON(c, fiber.StatusBadRequest, "Invalid LoginID ("+lid+"): "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user