diff --git a/frontend/src/schemas/client.ts b/frontend/src/schemas/client.ts index e5c327ed..52624b9c 100644 --- a/frontend/src/schemas/client.ts +++ b/frontend/src/schemas/client.ts @@ -119,7 +119,7 @@ export const GroupSummarySchema = z.object({ export const GroupSummaryListSchema = z.array(GroupSummarySchema).nullable().transform((v) => v ?? []); -export function emailHasForbiddenChars(value: string): boolean { +export function hasForbiddenClientChars(value: string): boolean { if (value.includes('/') || value.includes('\\') || value.includes(' ')) return true; for (let i = 0; i < value.length; i++) { const code = value.charCodeAt(i); @@ -133,8 +133,8 @@ export const ClientFormSchema = z.object({ .string() .trim() .min(1, 'pages.clients.email') - .refine((v) => !emailHasForbiddenChars(v), 'pages.clients.emailInvalidChars'), - subId: z.string(), + .refine((v) => !hasForbiddenClientChars(v), 'pages.clients.emailInvalidChars'), + subId: z.string().refine((v) => !hasForbiddenClientChars(v), 'pages.clients.subIdInvalidChars'), uuid: z.string(), password: z.string(), auth: z.string(), diff --git a/web/service/client.go b/web/service/client.go index b164d63e..a706a376 100644 --- a/web/service/client.go +++ b/web/service/client.go @@ -408,12 +408,26 @@ type ClientCreatePayload struct { InboundIds []int `json:"inboundIds"` } -func validateClientEmail(email string) error { - for _, r := range email { +func hasForbiddenClientChar(s string) bool { + for _, r := range s { if r == '/' || r == '\\' || r == ' ' || r < 0x20 || r == 0x7f { - return common.NewError("client email contains an invalid character:", email) + return true } } + return false +} + +func validateClientEmail(email string) error { + if hasForbiddenClientChar(email) { + return common.NewError("client email contains an invalid character:", email) + } + return nil +} + +func validateClientSubID(subID string) error { + if hasForbiddenClientChar(subID) { + return common.NewError("client subId contains an invalid character:", subID) + } return nil } @@ -428,6 +442,9 @@ func (s *ClientService) Create(inboundSvc *InboundService, payload *ClientCreate if err := validateClientEmail(client.Email); err != nil { return false, err } + if err := validateClientSubID(client.SubID); err != nil { + return false, err + } if len(payload.InboundIds) == 0 { return false, common.NewError("at least one inbound is required") } @@ -596,6 +613,9 @@ func (s *ClientService) Update(inboundSvc *InboundService, id int, updated model if err := validateClientEmail(updated.Email); err != nil { return false, err } + if err := validateClientSubID(updated.SubID); err != nil { + return false, err + } if updated.SubID == "" { updated.SubID = existing.SubID } diff --git a/web/service/client_email_validation_test.go b/web/service/client_email_validation_test.go index 974404dd..4e01b207 100644 --- a/web/service/client_email_validation_test.go +++ b/web/service/client_email_validation_test.go @@ -30,3 +30,28 @@ func TestValidateClientEmail(t *testing.T) { } } } + +func TestValidateClientSubID(t *testing.T) { + valid := []string{ + "", + "abc123", + "sub-id_value", + } + for _, subID := range valid { + if err := validateClientSubID(subID); err != nil { + t.Errorf("validateClientSubID(%q) = %v, want nil", subID, err) + } + } + + invalid := []string{ + "a/b", + "with space", + "back\\slash", + "new\nline", + } + for _, subID := range invalid { + if err := validateClientSubID(subID); err == nil { + t.Errorf("validateClientSubID(%q) = nil, want error", subID) + } + } +} diff --git a/web/translation/ar-EG.json b/web/translation/ar-EG.json index aa1594d9..3ca3f842 100644 --- a/web/translation/ar-EG.json +++ b/web/translation/ar-EG.json @@ -647,6 +647,7 @@ "online": "متصل", "email": "البريد", "emailInvalidChars": "لا يمكن أن يحتوي البريد الإلكتروني على مسافات أو '/' أو '\\' أو أحرف تحكم", + "subIdInvalidChars": "لا يمكن أن يحتوي معرّف الاشتراك على مسافات أو '/' أو '\\' أو أحرف تحكم", "group": "المجموعة", "groupDesc": "تسمية منطقية لتجميع العملاء (مثل فريق، عميل، منطقة). يمكن تصفيتها من شريط الأدوات.", "groupPlaceholder": "مثلاً customer-a", diff --git a/web/translation/en-US.json b/web/translation/en-US.json index 7f2a1c6b..4e299ea5 100644 --- a/web/translation/en-US.json +++ b/web/translation/en-US.json @@ -647,6 +647,7 @@ "online": "Online", "email": "Email", "emailInvalidChars": "Email cannot contain spaces, '/', '\\', or control characters", + "subIdInvalidChars": "Subscription ID cannot contain spaces, '/', '\\', or control characters", "group": "Group", "groupDesc": "Logical label used to bucket related clients (e.g. team, customer, region). Filterable from the toolbar.", "groupPlaceholder": "e.g. customer-a", diff --git a/web/translation/es-ES.json b/web/translation/es-ES.json index 141a30be..88bd86f8 100644 --- a/web/translation/es-ES.json +++ b/web/translation/es-ES.json @@ -647,6 +647,7 @@ "online": "En línea", "email": "Email", "emailInvalidChars": "El correo no puede contener espacios, '/', '\\' ni caracteres de control", + "subIdInvalidChars": "El ID de suscripción no puede contener espacios, '/', '\\' ni caracteres de control", "group": "Grupo", "groupDesc": "Etiqueta lógica para agrupar clientes relacionados (p. ej. equipo, cliente, región). Filtrable desde la barra de herramientas.", "groupPlaceholder": "p. ej. customer-a", diff --git a/web/translation/fa-IR.json b/web/translation/fa-IR.json index a42d010d..f7477982 100644 --- a/web/translation/fa-IR.json +++ b/web/translation/fa-IR.json @@ -647,6 +647,7 @@ "online": "آنلاین", "email": "ایمیل", "emailInvalidChars": "ایمیل نمی‌تواند شامل فاصله، '/'، '\\' یا کاراکترهای کنترلی باشد", + "subIdInvalidChars": "شناسه‌ی اشتراک نمی‌تواند شامل فاصله، '/'، '\\' یا کاراکترهای کنترلی باشد", "group": "گروه", "groupDesc": "برچسبی منطقی برای دسته‌بندی کاربران مرتبط (مثل تیم، مشتری، منطقه). از نوار ابزار قابل فیلتر است.", "groupPlaceholder": "مثلاً customer-a", diff --git a/web/translation/id-ID.json b/web/translation/id-ID.json index 83547827..eebd74f6 100644 --- a/web/translation/id-ID.json +++ b/web/translation/id-ID.json @@ -647,6 +647,7 @@ "online": "Online", "email": "Email", "emailInvalidChars": "Email tidak boleh mengandung spasi, '/', '\\', atau karakter kontrol", + "subIdInvalidChars": "ID langganan tidak boleh mengandung spasi, '/', '\\', atau karakter kontrol", "group": "Grup", "groupDesc": "Label logis untuk mengelompokkan klien terkait (mis. tim, pelanggan, wilayah). Dapat difilter dari toolbar.", "groupPlaceholder": "mis. customer-a", diff --git a/web/translation/ja-JP.json b/web/translation/ja-JP.json index efe33679..541c6640 100644 --- a/web/translation/ja-JP.json +++ b/web/translation/ja-JP.json @@ -647,6 +647,7 @@ "online": "オンライン", "email": "メール", "emailInvalidChars": "メールアドレスにスペース、'/'、'\\'、または制御文字を含めることはできません", + "subIdInvalidChars": "サブスクリプションIDにスペース、'/'、'\\'、または制御文字を含めることはできません", "group": "グループ", "groupDesc": "関連クライアントをまとめる論理ラベル(チーム、顧客、地域など)。ツールバーからフィルタ可能。", "groupPlaceholder": "例: customer-a", diff --git a/web/translation/pt-BR.json b/web/translation/pt-BR.json index 6f1a997b..799d60d6 100644 --- a/web/translation/pt-BR.json +++ b/web/translation/pt-BR.json @@ -647,6 +647,7 @@ "online": "Online", "email": "Email", "emailInvalidChars": "O e-mail não pode conter espaços, '/', '\\' ou caracteres de controle", + "subIdInvalidChars": "O ID de assinatura não pode conter espaços, '/', '\\' ou caracteres de controle", "group": "Grupo", "groupDesc": "Rótulo lógico para agrupar clientes relacionados (ex.: equipe, cliente, região). Filtrável pela barra de ferramentas.", "groupPlaceholder": "ex.: customer-a", diff --git a/web/translation/ru-RU.json b/web/translation/ru-RU.json index 996982a8..27aff996 100644 --- a/web/translation/ru-RU.json +++ b/web/translation/ru-RU.json @@ -647,6 +647,7 @@ "online": "В сети", "email": "Email", "emailInvalidChars": "Email не может содержать пробелы, '/', '\\' или управляющие символы", + "subIdInvalidChars": "ID подписки не может содержать пробелы, '/', '\\' или управляющие символы", "group": "Группа", "groupDesc": "Логическая метка для группировки связанных клиентов (например, команда, клиент, регион). Фильтруется из панели инструментов.", "groupPlaceholder": "например, customer-a", diff --git a/web/translation/tr-TR.json b/web/translation/tr-TR.json index da212a7d..4e6c23a5 100644 --- a/web/translation/tr-TR.json +++ b/web/translation/tr-TR.json @@ -647,6 +647,7 @@ "online": "Çevrimiçi", "email": "Email", "emailInvalidChars": "E-posta boşluk, '/', '\\' veya kontrol karakterleri içeremez", + "subIdInvalidChars": "Abonelik kimliği boşluk, '/', '\\' veya kontrol karakterleri içeremez", "group": "Grup", "groupDesc": "İlgili istemcileri gruplamak için mantıksal etiket (ekip, müşteri, bölge). Araç çubuğundan filtrelenebilir.", "groupPlaceholder": "örn. customer-a", diff --git a/web/translation/uk-UA.json b/web/translation/uk-UA.json index 654a7d0b..0852b478 100644 --- a/web/translation/uk-UA.json +++ b/web/translation/uk-UA.json @@ -647,6 +647,7 @@ "online": "У мережі", "email": "Email", "emailInvalidChars": "Email не може містити пробіли, '/', '\\' або керуючі символи", + "subIdInvalidChars": "ID підписки не може містити пробіли, '/', '\\' або керуючі символи", "group": "Група", "groupDesc": "Логічна мітка для групування пов'язаних клієнтів (напр. команда, клієнт, регіон). Фільтрується з панелі інструментів.", "groupPlaceholder": "напр. customer-a", diff --git a/web/translation/vi-VN.json b/web/translation/vi-VN.json index f240e46b..e0926290 100644 --- a/web/translation/vi-VN.json +++ b/web/translation/vi-VN.json @@ -647,6 +647,7 @@ "online": "Trực tuyến", "email": "Email", "emailInvalidChars": "Email không được chứa khoảng trắng, '/', '\\' hoặc ký tự điều khiển", + "subIdInvalidChars": "ID đăng ký không được chứa khoảng trắng, '/', '\\' hoặc ký tự điều khiển", "group": "Nhóm", "groupDesc": "Nhãn logic để gom các client liên quan (nhóm, khách hàng, khu vực). Có thể lọc từ thanh công cụ.", "groupPlaceholder": "ví dụ customer-a", diff --git a/web/translation/zh-CN.json b/web/translation/zh-CN.json index 9831e805..9c603ba8 100644 --- a/web/translation/zh-CN.json +++ b/web/translation/zh-CN.json @@ -647,6 +647,7 @@ "online": "在线", "email": "邮箱", "emailInvalidChars": "邮箱不能包含空格、'/'、'\\' 或控制字符", + "subIdInvalidChars": "订阅ID不能包含空格、'/'、'\\' 或控制字符", "group": "分组", "groupDesc": "用于对相关客户端进行分桶的逻辑标签(如团队、客户、地区)。可从工具栏筛选。", "groupPlaceholder": "如 customer-a", diff --git a/web/translation/zh-TW.json b/web/translation/zh-TW.json index d6e10e5a..e9e0a2e8 100644 --- a/web/translation/zh-TW.json +++ b/web/translation/zh-TW.json @@ -647,6 +647,7 @@ "online": "上線", "email": "電子郵件", "emailInvalidChars": "電子郵件不能包含空格、'/'、'\\' 或控制字元", + "subIdInvalidChars": "訂閱ID不能包含空格、'/'、'\\' 或控制字元", "group": "群組", "groupDesc": "用於將相關客戶端歸類的邏輯標籤(如團隊、客戶、地區)。可從工具列篩選。", "groupPlaceholder": "如 customer-a",