forked from baron/baron-sso
backend: 회원가입 시 도메인 대조를 통한 가족사(AFFILIATE) 강제 로직을 전면 제거하고 기본 개인(Personal) 가입으로 통합 (#1183)
This commit is contained in:
@@ -712,69 +712,17 @@ func (h *AuthHandler) Signup(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 소속이 비어 있는 일반 가입자는 PERSONAL tenant를 자동 생성해 대표소속을 보장합니다.
|
// 소속이 비어 있는 일반 가입자는 PERSONAL tenant를 자동 생성해 대표소속을 보장합니다.
|
||||||
tenantSlug := strings.TrimSpace(req.TenantSlug)
|
// 모든 온라인 가입자는 기본적으로 개인(Personal) 테넌트 소속으로 가입합니다.
|
||||||
|
// 기업/가족사 소속 연동은 별도 문의를 통해 처리되므로 온라인 가입 흐름에서는 제외합니다.
|
||||||
|
req.AffiliationType = "GENERAL"
|
||||||
|
slog.Info("[Signup] Forcing AffiliationType to GENERAL (Default personal tenant signup policy)", "email", req.Email)
|
||||||
|
|
||||||
var tenantID *string
|
var tenantID *string
|
||||||
|
tenant, err := createPersonalTenantForUser(c.Context(), h.TenantService, req.Email)
|
||||||
parts := strings.Split(req.Email, "@")
|
if err != nil {
|
||||||
if len(parts) != 2 {
|
return errorJSON(c, fiber.StatusServiceUnavailable, "failed to create personal tenant")
|
||||||
return errorJSON(c, fiber.StatusBadRequest, "Invalid email format")
|
|
||||||
}
|
|
||||||
domainName := parts[1]
|
|
||||||
|
|
||||||
// Check if this domain belongs to a predefined family affiliate
|
|
||||||
isInternal, _ := h.isAffiliateTenant(c.Context(), domainName)
|
|
||||||
|
|
||||||
// [Strict Policy] Force AffiliationType based on predefined family slugs (User cannot choose)
|
|
||||||
if isInternal {
|
|
||||||
req.AffiliationType = "AFFILIATE"
|
|
||||||
slog.Info("[Signup] Forcing AffiliationType to AFFILIATE", "email", req.Email)
|
|
||||||
} else {
|
|
||||||
req.AffiliationType = "GENERAL"
|
|
||||||
slog.Info("[Signup] Forcing AffiliationType to GENERAL", "email", req.Email)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tenantSlug != "" {
|
|
||||||
// [Security] Cross-check: If domain is NOT internal, they cannot provide a tenantSlug
|
|
||||||
if !isInternal {
|
|
||||||
slog.Warn("[Signup] Security violation: non-internal email providing tenantSlug", "email", req.Email)
|
|
||||||
return errorJSON(c, fiber.StatusForbidden, "Only affiliate members can join an organization.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !affiliateSlugs[strings.ToLower(tenantSlug)] {
|
|
||||||
return errorJSON(c, fiber.StatusForbidden, "The selected organization is not a valid family affiliate.")
|
|
||||||
}
|
|
||||||
|
|
||||||
tenant, err := h.TenantService.GetTenantBySlug(c.Context(), tenantSlug)
|
|
||||||
if err == nil && tenant != nil {
|
|
||||||
if tenant.Status == domain.TenantStatusActive {
|
|
||||||
slog.Info("[Signup] Assigning tenant by manual slug", "email", req.Email, "tenant", tenant.Slug)
|
|
||||||
tenantSlug = tenant.Slug
|
|
||||||
tenantID = &tenant.ID
|
|
||||||
} else {
|
|
||||||
return errorJSON(c, fiber.StatusForbidden, "The specified organization is not active.")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
slog.Warn("[Signup] Attempted to join non-existent organization", "slug", tenantSlug, "email", req.Email)
|
|
||||||
return errorJSON(c, fiber.StatusNotFound, "The specified organization code was not found.")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If it's a family affiliate domain, they MUST select one of the family companies
|
|
||||||
if isInternal {
|
|
||||||
return errorJSON(c, fiber.StatusBadRequest, "Please select your organization.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tenantID == nil && req.AffiliationType == "AFFILIATE" {
|
|
||||||
return errorJSON(c, fiber.StatusBadRequest, "We couldn't verify your organization affiliation. Please check your choice.")
|
|
||||||
}
|
|
||||||
if tenantID == nil && req.AffiliationType == "GENERAL" {
|
|
||||||
tenant, err := createPersonalTenantForUser(c.Context(), h.TenantService, req.Email)
|
|
||||||
if err != nil {
|
|
||||||
return errorJSON(c, fiber.StatusServiceUnavailable, "failed to create personal tenant")
|
|
||||||
}
|
|
||||||
tenantSlug = tenant.Slug
|
|
||||||
tenantID = &tenant.ID
|
|
||||||
}
|
}
|
||||||
|
tenantID = &tenant.ID
|
||||||
|
|
||||||
// Normalize Phone (E.164 형태로 보관)
|
// Normalize Phone (E.164 형태로 보관)
|
||||||
normalizedPhone := domain.NormalizePhoneNumber(req.Phone)
|
normalizedPhone := domain.NormalizePhoneNumber(req.Phone)
|
||||||
|
|||||||
@@ -116,22 +116,18 @@ func TestSignup_TenantSlugValidation(t *testing.T) {
|
|||||||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Active Tenant Slug", func(t *testing.T) {
|
t.Run("Success creates Personal Tenant", func(t *testing.T) {
|
||||||
reqBody := domain.SignupRequest{
|
reqBody := domain.SignupRequest{
|
||||||
Email: "user@hanmaceng.co.kr",
|
Email: "user@hanmaceng.co.kr",
|
||||||
Password: "StrongPass123!",
|
Password: "StrongPass123!",
|
||||||
Name: "Test User",
|
Name: "Test User",
|
||||||
Phone: "010-1234-5678",
|
Phone: "010-1234-5678",
|
||||||
TermsAccepted: true,
|
TermsAccepted: true,
|
||||||
TenantSlug: "hanmac",
|
|
||||||
}
|
}
|
||||||
body, _ := json.Marshal(reqBody)
|
body, _ := json.Marshal(reqBody)
|
||||||
|
|
||||||
validTenant := &domain.Tenant{ID: "t1", Slug: "hanmac", Status: domain.TenantStatusActive}
|
validTenant := &domain.Tenant{ID: "personal-t1", Slug: "personal-slug", Status: domain.TenantStatusActive}
|
||||||
mockTenantSvc.On("GetTenantByDomain", mock.Anything, "hanmaceng.co.kr").Return(&domain.Tenant{Slug: "hanmac"}, nil).Once()
|
mockTenantSvc.On("RegisterTenant", mock.Anything, "Personal - user@hanmaceng.co.kr", mock.Anything, domain.TenantTypePersonal, "Automatically provisioned personal tenant", []string(nil), (*string)(nil), "").Return(validTenant, nil).Once()
|
||||||
mockTenantSvc.On("ProvisionTenantByDomain", mock.Anything, "hanmaceng.co.kr").Return(validTenant, nil).Maybe()
|
|
||||||
mockTenantSvc.On("GetTenantBySlug", mock.Anything, "hanmac").Return(validTenant, nil).Once()
|
|
||||||
mockTenantSvc.On("GetTenant", mock.Anything, "t1").Return(validTenant, nil).Once()
|
|
||||||
mockIdp.On("CreateUser", mock.Anything, mock.Anything).Return("user-id", nil).Once()
|
mockIdp.On("CreateUser", mock.Anything, mock.Anything).Return("user-id", nil).Once()
|
||||||
mockRedis.On("Delete", mock.Anything).Return(nil)
|
mockRedis.On("Delete", mock.Anything).Return(nil)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user