1
0
forked from baron/baron-sso

네이버 계정 정합성 맞춤

This commit is contained in:
2026-06-15 19:54:09 +09:00
parent 8e9d015443
commit 4d468cd39f
97 changed files with 5837 additions and 2031 deletions

View File

@@ -4,7 +4,9 @@ import (
"baron-sso-backend/internal/domain"
"context"
"fmt"
"log/slog"
"slices"
"strconv"
"strings"
)
@@ -28,7 +30,13 @@ type hanmacEmailEvaluation struct {
LocalPart string
}
func (h *UserHandler) evaluateHanmacImportEmail(ctx context.Context, item bulkUserItem, scope *hanmacEmailScope, usedLocalParts map[string]bool) hanmacEmailEvaluation {
type hanmacLocalPartOwner struct {
UserID string
Email string
Name string
}
func (h *UserHandler) evaluateHanmacImportEmail(ctx context.Context, item bulkUserItem, scope *hanmacEmailScope, usedLocalParts map[string]hanmacLocalPartOwner) hanmacEmailEvaluation {
originalEmail := strings.TrimSpace(item.Email)
name := strings.TrimSpace(item.Name)
evaluation := hanmacEmailEvaluation{
@@ -68,9 +76,9 @@ func (h *UserHandler) evaluateHanmacImportEmail(ctx context.Context, item bulkUs
}
evaluation.LocalPart = localPart
if usedLocalParts[localPart] {
if owner, exists := usedLocalParts[localPart]; exists {
evaluation.Status = "blockingError"
evaluation.Message = "한맥가족 내에서 이미 사용 중인 이메일 ID입니다."
evaluation.Message = formatHanmacLocalPartConflictMessage(localPart, owner)
evaluation.Blocking = true
return evaluation
}
@@ -88,6 +96,14 @@ func (h *UserHandler) evaluateHanmacImportEmail(ctx context.Context, item bulkUs
}
func (h *UserHandler) ensureHanmacCreateEmailAllowed(ctx context.Context, email string, tenantSlug string, tenantID string) error {
return h.ensureHanmacEmailAllowedWithLog(ctx, email, tenantSlug, tenantID, "", "hanmac create email local-part conflict")
}
func (h *UserHandler) ensureHanmacEmailAllowed(ctx context.Context, email string, tenantSlug string, tenantID string, currentUserID string) error {
return h.ensureHanmacEmailAllowedWithLog(ctx, email, tenantSlug, tenantID, currentUserID, "hanmac email local-part conflict")
}
func (h *UserHandler) ensureHanmacEmailAllowedWithLog(ctx context.Context, email string, tenantSlug string, tenantID string, currentUserID string, logMessage string) error {
scope, err := h.resolveHanmacEmailScope(ctx)
if err != nil || scope == nil || !scope.ContainsTenant(tenantID, tenantSlug) {
return nil
@@ -102,8 +118,22 @@ func (h *UserHandler) ensureHanmacCreateEmailAllowed(ctx context.Context, email
if err != nil {
return err
}
if usedLocalParts[localPart] {
return fmt.Errorf("한맥가족 내에서 이미 사용 중인 이메일 ID입니다.")
if owner, exists := usedLocalParts[localPart]; exists {
ownerUserID := strings.TrimSpace(owner.UserID)
if currentUserID != "" && ownerUserID != "" && ownerUserID == strings.TrimSpace(currentUserID) {
return nil
}
slog.Warn(
logMessage,
"requestedEmail", email,
"localPart", localPart,
"ownerUserID", owner.UserID,
"ownerEmail", owner.Email,
"ownerName", owner.Name,
"tenantID", tenantID,
"tenantSlug", tenantSlug,
)
return fmt.Errorf("%s", formatHanmacLocalPartConflictMessage(localPart, owner))
}
return nil
}
@@ -149,8 +179,8 @@ func (h *UserHandler) resolveHanmacEmailScope(ctx context.Context) (*hanmacEmail
return scope, nil
}
func (h *UserHandler) loadHanmacLocalParts(ctx context.Context, scope *hanmacEmailScope) (map[string]bool, error) {
used := make(map[string]bool)
func (h *UserHandler) loadHanmacLocalParts(ctx context.Context, scope *hanmacEmailScope) (map[string]hanmacLocalPartOwner, error) {
used := make(map[string]hanmacLocalPartOwner)
if h.UserRepo == nil || scope == nil {
return used, nil
}
@@ -160,7 +190,7 @@ func (h *UserHandler) loadHanmacLocalParts(ctx context.Context, scope *hanmacEma
if err != nil {
return nil, err
}
addUserEmailLocalParts(used, users)
addUserEmailLocalPartOwners(used, users)
}
if len(scope.SlugList) > 0 {
@@ -168,7 +198,7 @@ func (h *UserHandler) loadHanmacLocalParts(ctx context.Context, scope *hanmacEma
if err != nil {
return nil, err
}
addUserEmailLocalParts(used, users)
addUserEmailLocalPartOwners(used, users)
}
return used, nil
@@ -210,31 +240,79 @@ func isTenantDescendantOf(tenant domain.Tenant, rootID string, tenantByID map[st
return false
}
func addUserEmailLocalParts(target map[string]bool, users []domain.User) {
func addUserEmailLocalPartOwners(target map[string]hanmacLocalPartOwner, users []domain.User) {
for _, user := range users {
localPart, err := domain.ExtractNormalizedEmailLocalPart(user.Email)
if err == nil && localPart != "" {
target[localPart] = true
if err != nil || localPart == "" {
continue
}
if _, exists := target[localPart]; exists {
continue
}
target[localPart] = hanmacLocalPartOwner{
UserID: strings.TrimSpace(user.ID),
Email: strings.TrimSpace(user.Email),
Name: strings.TrimSpace(user.Name),
}
}
}
func nextAvailableHanmacLocalPart(base string, usedLocalParts map[string]bool) string {
func formatHanmacLocalPartConflictMessage(localPart string, owner hanmacLocalPartOwner) string {
message := fmt.Sprintf("한맥가족 내에서 이미 사용 중인 이메일 ID입니다. local-part=%s", strings.TrimSpace(localPart))
if owner.Email != "" {
message += ", 사용 계정=" + owner.Email
}
if owner.Name != "" {
message += ", 사용자=" + owner.Name
}
if owner.UserID != "" {
message += ", 사용자 ID=" + owner.UserID
}
return message
}
func nextAvailableHanmacLocalPart(base string, usedLocalParts map[string]hanmacLocalPartOwner) string {
base = strings.ToLower(strings.TrimSpace(base))
if base == "" {
return ""
}
if !usedLocalParts[base] {
if _, exists := usedLocalParts[base]; !exists {
return base
}
for index := 1; ; index++ {
candidate := fmt.Sprintf("%s%d", base, index)
if !usedLocalParts[candidate] {
stem, nextIndex := splitTrailingNumericSuffix(base)
if stem == "" {
stem = base
}
for index := nextIndex; ; index++ {
candidate := fmt.Sprintf("%s%d", stem, index)
if _, exists := usedLocalParts[candidate]; !exists {
return candidate
}
}
}
func splitTrailingNumericSuffix(value string) (string, int) {
value = strings.ToLower(strings.TrimSpace(value))
if value == "" {
return "", 1
}
index := len(value)
for index > 0 && value[index-1] >= '0' && value[index-1] <= '9' {
index--
}
if index == len(value) {
return value, 1
}
stem := value[:index]
suffix := value[index:]
number, err := strconv.Atoi(suffix)
if err != nil {
return value, 1
}
return stem, number + 1
}
func appendUniqueString(values []string, value string) []string {
if slices.Contains(values, value) {
return values