Răsfoiți Sursa

feat: ldap skip tls verify (#5637)

* feat(ldap): add InsecureSkipVerify field and tlsConfig helper

Extract the inline TLS config at both LDAPS dial sites (FetchVlessFlags,
AuthenticateUser) into a tlsConfig(cfg) helper, and add a new
Config.InsecureSkipVerify bool that flows through to
tls.Config.InsecureSkipVerify. This unblocks enterprise environments
(e.g. Microsoft AD CS with internal CAs) where the server certificate
chain cannot be imported into the system trust store.

Behavior is identical when InsecureSkipVerify is false (the default) -
pure refactor + plumbing. The helper is unit-testable without a live
server, which is why it is extracted.

Closes https://github.com/MHSanaei/3x-ui/issues/5538

* feat(settings): add LdapInsecureSkipVerify setting

Plumb the new LDAP skip-TLS-verify toggle through the settings stack:
- AllSetting struct field (json/form tag: ldapInsecureSkipVerify)
- defaultValueMap default ("false")
- GetLdapInsecureSkipVerify() getter
- ldap_sync_job wiring into ldaputil.Config (FetchVlessFlags path)
- panel/user.go wiring into ldaputil.Config (AuthenticateUser path;
  the original issue's file list missed this)

Persistence is handled by UpdateAllSetting's reflect loop, matching
the existing pattern used by ldapUseTLS (no explicit setter).

Closes https://github.com/MHSanaei/3x-ui/issues/5538

* feat(ui): add Skip TLS verification switch in LDAP settings

Wire the new ldapInsecureSkipVerify setting into the hand-written
frontend model and Zod schema, and render it as a new Switch in
GeneralTab right under "Use TLS (LDAPS)". The switch is disabled
when TLS is off (the setting is meaningless without LDAPS) and shows
an insecure-warning description to make the security implication
visible to operators.

Also adds a Vitest round-trip test pinning schema acceptance and
model default-to-false behavior.

Closes https://github.com/MHSanaei/3x-ui/issues/5538

* chore(i18n): add Skip TLS verification strings to all locales

Add pages.settings.ldap.skipTlsVerify and skipTlsVerifyDesc to all 13
backend-served translation files, matching the existing repo
convention of keeping LDAP keys present in every locale (en-US, fa-IR,
ru-RU, zh-CN, zh-TW, pt-BR, ar-EG, uk-UA, id-ID, tr-TR, vi-VN, ja-JP,
es-ES). No translation-parity test exists in CI, but every other
LDAP key is replicated across all files, so this keeps the
invariant intact.

Closes https://github.com/MHSanaei/3x-ui/issues/5538

* chore(codegen): regenerate frontend artifacts

Regenerate frontend/src/generated/{zod,types,schemas,examples}.ts
and frontend/public/openapi.json via `npm run gen` to reflect the
new ldapInsecureSkipVerify field. The codegen CI job runs
`git diff --exit-code` on these files; failing to commit them would
break the build.

Closes https://github.com/MHSanaei/3x-ui/issues/5538
Nikan Zeyaei 11 ore în urmă
părinte
comite
60c54827aa

+ 8 - 0
frontend/public/openapi.json

@@ -84,6 +84,9 @@
           "ldapInboundTags": {
             "type": "string"
           },
+          "ldapInsecureSkipVerify": {
+            "type": "boolean"
+          },
           "ldapInvertFlag": {
             "type": "boolean"
           },
@@ -429,6 +432,7 @@
           "ldapFlagField",
           "ldapHost",
           "ldapInboundTags",
+          "ldapInsecureSkipVerify",
           "ldapInvertFlag",
           "ldapPassword",
           "ldapPort",
@@ -589,6 +593,9 @@
           "ldapInboundTags": {
             "type": "string"
           },
+          "ldapInsecureSkipVerify": {
+            "type": "boolean"
+          },
           "ldapInvertFlag": {
             "type": "boolean"
           },
@@ -941,6 +948,7 @@
           "ldapFlagField",
           "ldapHost",
           "ldapInboundTags",
+          "ldapInsecureSkipVerify",
           "ldapInvertFlag",
           "ldapPassword",
           "ldapPort",

+ 2 - 0
frontend/src/generated/examples.ts

@@ -16,6 +16,7 @@ export const EXAMPLES: Record<string, unknown> = {
     "ldapFlagField": "",
     "ldapHost": "",
     "ldapInboundTags": "",
+    "ldapInsecureSkipVerify": false,
     "ldapInvertFlag": false,
     "ldapPassword": "",
     "ldapPort": 0,
@@ -118,6 +119,7 @@ export const EXAMPLES: Record<string, unknown> = {
     "ldapFlagField": "",
     "ldapHost": "",
     "ldapInboundTags": "",
+    "ldapInsecureSkipVerify": false,
     "ldapInvertFlag": false,
     "ldapPassword": "",
     "ldapPort": 0,

+ 8 - 0
frontend/src/generated/schemas.ts

@@ -58,6 +58,9 @@ export const SCHEMAS: Record<string, unknown> = {
       "ldapInboundTags": {
         "type": "string"
       },
+      "ldapInsecureSkipVerify": {
+        "type": "boolean"
+      },
       "ldapInvertFlag": {
         "type": "boolean"
       },
@@ -403,6 +406,7 @@ export const SCHEMAS: Record<string, unknown> = {
       "ldapFlagField",
       "ldapHost",
       "ldapInboundTags",
+      "ldapInsecureSkipVerify",
       "ldapInvertFlag",
       "ldapPassword",
       "ldapPort",
@@ -563,6 +567,9 @@ export const SCHEMAS: Record<string, unknown> = {
       "ldapInboundTags": {
         "type": "string"
       },
+      "ldapInsecureSkipVerify": {
+        "type": "boolean"
+      },
       "ldapInvertFlag": {
         "type": "boolean"
       },
@@ -915,6 +922,7 @@ export const SCHEMAS: Record<string, unknown> = {
       "ldapFlagField",
       "ldapHost",
       "ldapInboundTags",
+      "ldapInsecureSkipVerify",
       "ldapInvertFlag",
       "ldapPassword",
       "ldapPort",

+ 2 - 0
frontend/src/generated/types.ts

@@ -22,6 +22,7 @@ export interface AllSetting {
   ldapFlagField: string;
   ldapHost: string;
   ldapInboundTags: string;
+  ldapInsecureSkipVerify: boolean;
   ldapInvertFlag: boolean;
   ldapPassword: string;
   ldapPort: number;
@@ -125,6 +126,7 @@ export interface AllSettingView {
   ldapFlagField: string;
   ldapHost: string;
   ldapInboundTags: string;
+  ldapInsecureSkipVerify: boolean;
   ldapInvertFlag: boolean;
   ldapPassword: string;
   ldapPort: number;

+ 2 - 0
frontend/src/generated/zod.ts

@@ -34,6 +34,7 @@ export const AllSettingSchema = z.object({
   ldapFlagField: z.string(),
   ldapHost: z.string(),
   ldapInboundTags: z.string(),
+  ldapInsecureSkipVerify: z.boolean(),
   ldapInvertFlag: z.boolean(),
   ldapPassword: z.string(),
   ldapPort: z.number().int().min(0).max(65535),
@@ -138,6 +139,7 @@ export const AllSettingViewSchema = z.object({
   ldapFlagField: z.string(),
   ldapHost: z.string(),
   ldapInboundTags: z.string(),
+  ldapInsecureSkipVerify: z.boolean(),
   ldapInvertFlag: z.boolean(),
   ldapPassword: z.string(),
   ldapPort: z.number().int().min(0).max(65535),

+ 1 - 0
frontend/src/models/setting.ts

@@ -68,6 +68,7 @@ export class AllSetting {
   ldapHost = '';
   ldapPort = 389;
   ldapUseTLS = false;
+  ldapInsecureSkipVerify = false;
   ldapBindDN = '';
   ldapPassword = '';
   ldapBaseDN = '';

+ 11 - 0
frontend/src/pages/settings/GeneralTab.tsx

@@ -312,6 +312,17 @@ export default function GeneralTab({ allSetting, updateSetting }: GeneralTabProp
             <SettingListItem paddings="small" title={t('pages.settings.ldap.useTls')}>
               <Switch checked={allSetting.ldapUseTLS} onChange={(v) => updateSetting({ ldapUseTLS: v })} />
             </SettingListItem>
+            <SettingListItem
+              paddings="small"
+              title={t('pages.settings.ldap.skipTlsVerify')}
+              description={t('pages.settings.ldap.skipTlsVerifyDesc')}
+            >
+              <Switch
+                checked={allSetting.ldapInsecureSkipVerify}
+                disabled={!allSetting.ldapUseTLS}
+                onChange={(v) => updateSetting({ ldapInsecureSkipVerify: v })}
+              />
+            </SettingListItem>
             <SettingListItem paddings="small" title={t('pages.settings.ldap.bindDn')}>
               <Input value={allSetting.ldapBindDN} onChange={(e) => updateSetting({ ldapBindDN: e.target.value })} />
             </SettingListItem>

+ 1 - 0
frontend/src/schemas/setting.ts

@@ -69,6 +69,7 @@ export const AllSettingSchema = z.object({
   ldapHost: z.string().optional(),
   ldapPort: port.optional(),
   ldapUseTLS: z.boolean().optional(),
+  ldapInsecureSkipVerify: z.boolean().optional(),
   ldapBindDN: z.string().optional(),
   ldapPassword: z.string().optional(),
   ldapBaseDN: z.string().optional(),

+ 20 - 0
frontend/src/test/setting-ldap-skip-verify.test.ts

@@ -0,0 +1,20 @@
+import { describe, it, expect } from 'vitest';
+import { AllSettingSchema } from '@/schemas/setting';
+import { AllSetting } from '@/models/setting';
+
+describe('ldapInsecureSkipVerify', () => {
+  it('parses through the Zod schema', () => {
+    const r = AllSettingSchema.safeParse({ ldapInsecureSkipVerify: true });
+    expect(r.success).toBe(true);
+    expect(r.success && r.data.ldapInsecureSkipVerify).toBe(true);
+  });
+
+  it('rejects non-boolean values', () => {
+    expect(AllSettingSchema.safeParse({ ldapInsecureSkipVerify: 'yes' }).success).toBe(false);
+  });
+
+  it('defaults to false on the model and clones from payload', () => {
+    expect(new AllSetting().ldapInsecureSkipVerify).toBe(false);
+    expect(new AllSetting({ ldapInsecureSkipVerify: true }).ldapInsecureSkipVerify).toBe(true);
+  });
+});

+ 18 - 17
internal/util/ldap/ldap.go

@@ -9,17 +9,22 @@ import (
 )
 
 type Config struct {
-	Host       string
-	Port       int
-	UseTLS     bool
-	BindDN     string
-	Password   string
-	BaseDN     string
-	UserFilter string
-	UserAttr   string
-	FlagField  string
-	TruthyVals []string
-	Invert     bool
+	Host               string
+	Port               int
+	UseTLS             bool
+	InsecureSkipVerify bool
+	BindDN             string
+	Password           string
+	BaseDN             string
+	UserFilter         string
+	UserAttr           string
+	FlagField          string
+	TruthyVals         []string
+	Invert             bool
+}
+
+func tlsConfig(cfg Config) *tls.Config {
+	return &tls.Config{InsecureSkipVerify: cfg.InsecureSkipVerify}
 }
 
 // FetchVlessFlags returns map[email]enabled
@@ -35,9 +40,7 @@ func FetchVlessFlags(cfg Config) (map[string]bool, error) {
 
 	var opts []ldap.DialOpt
 	if cfg.UseTLS {
-		opts = append(opts, ldap.DialWithTLSConfig(&tls.Config{
-			InsecureSkipVerify: false,
-		}))
+		opts = append(opts, ldap.DialWithTLSConfig(tlsConfig(cfg)))
 	}
 
 	conn, err := ldap.DialURL(ldapURL, opts...)
@@ -105,9 +108,7 @@ func AuthenticateUser(cfg Config, username, password string) (bool, error) {
 
 	var opts []ldap.DialOpt
 	if cfg.UseTLS {
-		opts = append(opts, ldap.DialWithTLSConfig(&tls.Config{
-			InsecureSkipVerify: false,
-		}))
+		opts = append(opts, ldap.DialWithTLSConfig(tlsConfig(cfg)))
 	}
 
 	conn, err := ldap.DialURL(ldapURL, opts...)

+ 22 - 0
internal/util/ldap/ldap_test.go

@@ -0,0 +1,22 @@
+package ldaputil
+
+import "testing"
+
+func TestTLSConfig_InsecureSkipVerifyPropagates(t *testing.T) {
+	cases := []struct {
+		name string
+		skip bool
+		want bool
+	}{
+		{"default verifies", false, false},
+		{"skip flows through", true, true},
+	}
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			got := tlsConfig(Config{InsecureSkipVerify: c.skip})
+			if got.InsecureSkipVerify != c.want {
+				t.Fatalf("InsecureSkipVerify = %v, want %v", got.InsecureSkipVerify, c.want)
+			}
+		})
+	}
+}

+ 12 - 11
internal/web/entity/entity.go

@@ -105,17 +105,18 @@ type AllSetting struct {
 	SubHideSettings             bool   `json:"subHideSettings" form:"subHideSettings"`   // Hide server settings in happ subscription (Only for Happ)
 
 	// LDAP settings
-	LdapEnable     bool   `json:"ldapEnable" form:"ldapEnable"`
-	LdapHost       string `json:"ldapHost" form:"ldapHost"`
-	LdapPort       int    `json:"ldapPort" form:"ldapPort" validate:"gte=0,lte=65535"`
-	LdapUseTLS     bool   `json:"ldapUseTLS" form:"ldapUseTLS"`
-	LdapBindDN     string `json:"ldapBindDN" form:"ldapBindDN"`
-	LdapPassword   string `json:"ldapPassword" form:"ldapPassword"`
-	LdapBaseDN     string `json:"ldapBaseDN" form:"ldapBaseDN"`
-	LdapUserFilter string `json:"ldapUserFilter" form:"ldapUserFilter"`
-	LdapUserAttr   string `json:"ldapUserAttr" form:"ldapUserAttr"` // e.g., mail or uid
-	LdapVlessField string `json:"ldapVlessField" form:"ldapVlessField"`
-	LdapSyncCron   string `json:"ldapSyncCron" form:"ldapSyncCron"`
+	LdapEnable             bool   `json:"ldapEnable" form:"ldapEnable"`
+	LdapHost               string `json:"ldapHost" form:"ldapHost"`
+	LdapPort               int    `json:"ldapPort" form:"ldapPort" validate:"gte=0,lte=65535"`
+	LdapUseTLS             bool   `json:"ldapUseTLS" form:"ldapUseTLS"`
+	LdapInsecureSkipVerify bool   `json:"ldapInsecureSkipVerify" form:"ldapInsecureSkipVerify"`
+	LdapBindDN             string `json:"ldapBindDN" form:"ldapBindDN"`
+	LdapPassword           string `json:"ldapPassword" form:"ldapPassword"`
+	LdapBaseDN             string `json:"ldapBaseDN" form:"ldapBaseDN"`
+	LdapUserFilter         string `json:"ldapUserFilter" form:"ldapUserFilter"`
+	LdapUserAttr           string `json:"ldapUserAttr" form:"ldapUserAttr"` // e.g., mail or uid
+	LdapVlessField         string `json:"ldapVlessField" form:"ldapVlessField"`
+	LdapSyncCron           string `json:"ldapSyncCron" form:"ldapSyncCron"`
 	// Generic flag configuration
 	LdapFlagField         string `json:"ldapFlagField" form:"ldapFlagField"`
 	LdapTruthyValues      string `json:"ldapTruthyValues" form:"ldapTruthyValues"`

+ 12 - 11
internal/web/job/ldap_sync_job.go

@@ -69,17 +69,18 @@ func (j *LdapSyncJob) Run() {
 
 	// --- LDAP fetch ---
 	cfg := ldaputil.Config{
-		Host:       mustGetString(j.settingService.GetLdapHost),
-		Port:       mustGetInt(j.settingService.GetLdapPort),
-		UseTLS:     mustGetBool(j.settingService.GetLdapUseTLS),
-		BindDN:     mustGetString(j.settingService.GetLdapBindDN),
-		Password:   mustGetString(j.settingService.GetLdapPassword),
-		BaseDN:     mustGetString(j.settingService.GetLdapBaseDN),
-		UserFilter: mustGetString(j.settingService.GetLdapUserFilter),
-		UserAttr:   mustGetString(j.settingService.GetLdapUserAttr),
-		FlagField:  mustGetStringOr(j.settingService.GetLdapFlagField, mustGetString(j.settingService.GetLdapVlessField)),
-		TruthyVals: splitCsv(mustGetString(j.settingService.GetLdapTruthyValues)),
-		Invert:     mustGetBool(j.settingService.GetLdapInvertFlag),
+		Host:               mustGetString(j.settingService.GetLdapHost),
+		Port:               mustGetInt(j.settingService.GetLdapPort),
+		UseTLS:             mustGetBool(j.settingService.GetLdapUseTLS),
+		InsecureSkipVerify: mustGetBool(j.settingService.GetLdapInsecureSkipVerify),
+		BindDN:             mustGetString(j.settingService.GetLdapBindDN),
+		Password:           mustGetString(j.settingService.GetLdapPassword),
+		BaseDN:             mustGetString(j.settingService.GetLdapBaseDN),
+		UserFilter:         mustGetString(j.settingService.GetLdapUserFilter),
+		UserAttr:           mustGetString(j.settingService.GetLdapUserAttr),
+		FlagField:          mustGetStringOr(j.settingService.GetLdapFlagField, mustGetString(j.settingService.GetLdapVlessField)),
+		TruthyVals:         splitCsv(mustGetString(j.settingService.GetLdapTruthyValues)),
+		Invert:             mustGetBool(j.settingService.GetLdapInvertFlag),
 	}
 
 	flags, err := ldaputil.FetchVlessFlags(cfg)

+ 10 - 8
internal/web/service/panel/user.go

@@ -60,6 +60,7 @@ func (s *UserService) CheckUser(username string, password string, twoFactorCode
 		host, _ := s.settingService.GetLdapHost()
 		port, _ := s.settingService.GetLdapPort()
 		useTLS, _ := s.settingService.GetLdapUseTLS()
+		skipVerify, _ := s.settingService.GetLdapInsecureSkipVerify()
 		bindDN, _ := s.settingService.GetLdapBindDN()
 		ldapPass, _ := s.settingService.GetLdapPassword()
 		baseDN, _ := s.settingService.GetLdapBaseDN()
@@ -67,14 +68,15 @@ func (s *UserService) CheckUser(username string, password string, twoFactorCode
 		userAttr, _ := s.settingService.GetLdapUserAttr()
 
 		cfg := ldaputil.Config{
-			Host:       host,
-			Port:       port,
-			UseTLS:     useTLS,
-			BindDN:     bindDN,
-			Password:   ldapPass,
-			BaseDN:     baseDN,
-			UserFilter: userFilter,
-			UserAttr:   userAttr,
+			Host:               host,
+			Port:               port,
+			UseTLS:             useTLS,
+			InsecureSkipVerify: skipVerify,
+			BindDN:             bindDN,
+			Password:           ldapPass,
+			BaseDN:             baseDN,
+			UserFilter:         userFilter,
+			UserAttr:           userAttr,
 		}
 		ok, err := ldaputil.AuthenticateUser(cfg, username, password)
 		if err != nil || !ok {

+ 25 - 20
internal/web/service/setting.go

@@ -114,26 +114,27 @@ var defaultValueMap = map[string]string{
 	"devChannelEnable":            "false",
 
 	// LDAP defaults
-	"ldapEnable":            "false",
-	"ldapHost":              "",
-	"ldapPort":              "389",
-	"ldapUseTLS":            "false",
-	"ldapBindDN":            "",
-	"ldapPassword":          "",
-	"ldapBaseDN":            "",
-	"ldapUserFilter":        "(objectClass=person)",
-	"ldapUserAttr":          "mail",
-	"ldapVlessField":        "vless_enabled",
-	"ldapSyncCron":          "@every 1m",
-	"ldapFlagField":         "",
-	"ldapTruthyValues":      "true,1,yes,on",
-	"ldapInvertFlag":        "false",
-	"ldapInboundTags":       "",
-	"ldapAutoCreate":        "false",
-	"ldapAutoDelete":        "false",
-	"ldapDefaultTotalGB":    "0",
-	"ldapDefaultExpiryDays": "0",
-	"ldapDefaultLimitIP":    "0",
+	"ldapEnable":             "false",
+	"ldapHost":               "",
+	"ldapPort":               "389",
+	"ldapUseTLS":             "false",
+	"ldapInsecureSkipVerify": "false",
+	"ldapBindDN":             "",
+	"ldapPassword":           "",
+	"ldapBaseDN":             "",
+	"ldapUserFilter":         "(objectClass=person)",
+	"ldapUserAttr":           "mail",
+	"ldapVlessField":         "vless_enabled",
+	"ldapSyncCron":           "@every 1m",
+	"ldapFlagField":          "",
+	"ldapTruthyValues":       "true,1,yes,on",
+	"ldapInvertFlag":         "false",
+	"ldapInboundTags":        "",
+	"ldapAutoCreate":         "false",
+	"ldapAutoDelete":         "false",
+	"ldapDefaultTotalGB":     "0",
+	"ldapDefaultExpiryDays":  "0",
+	"ldapDefaultLimitIP":     "0",
 
 	// Event bus — per-subscriber event filtering (empty = all disabled)
 	"tgEnabledEvents":   "login.attempt,cpu.high",
@@ -922,6 +923,10 @@ func (s *SettingService) GetLdapUseTLS() (bool, error) {
 	return s.getBool("ldapUseTLS")
 }
 
+func (s *SettingService) GetLdapInsecureSkipVerify() (bool, error) {
+	return s.getBool("ldapInsecureSkipVerify")
+}
+
 func (s *SettingService) GetLdapBindDN() (string, error) {
 	return s.getString("ldapBindDN")
 }

+ 2 - 0
internal/web/translation/ar-EG.json

@@ -1220,6 +1220,8 @@
         "host": "مضيف LDAP",
         "port": "منفذ LDAP",
         "useTls": "استخدام TLS (LDAPS)",
+        "skipTlsVerify": "تخطي التحقق من شهادة TLS",
+        "skipTlsVerifyDesc": "غير آمن — يعطل التحقق من شهادة الخادم. استخدم فقط مع CA الداخلية/غير الموثوقة.",
         "bindDn": "Bind DN",
         "passwordConfigured": "مهيأة؛ اترك فارغاً للاحتفاظ بكلمة المرور الحالية.",
         "passwordUnconfigured": "غير مهيأة.",

+ 2 - 0
internal/web/translation/en-US.json

@@ -1341,6 +1341,8 @@
         "host": "LDAP host",
         "port": "LDAP port",
         "useTls": "Use TLS (LDAPS)",
+        "skipTlsVerify": "Skip TLS certificate verification",
+        "skipTlsVerifyDesc": "Insecure — disables server certificate validation. Use only with internal/untrusted CAs.",
         "bindDn": "Bind DN",
         "passwordConfigured": "Configured; leave blank to keep current password.",
         "passwordUnconfigured": "Not configured.",

+ 2 - 0
internal/web/translation/es-ES.json

@@ -1220,6 +1220,8 @@
         "host": "Host LDAP",
         "port": "Puerto LDAP",
         "useTls": "Usar TLS (LDAPS)",
+        "skipTlsVerify": "Omitir verificación de certificado TLS",
+        "skipTlsVerifyDesc": "Inseguro — desactiva la validación del certificado del servidor. Usar solo con CA internos/no confiables.",
         "bindDn": "Bind DN",
         "passwordConfigured": "Configurada; deja en blanco para mantener la contraseña actual.",
         "passwordUnconfigured": "No configurada.",

+ 2 - 0
internal/web/translation/fa-IR.json

@@ -1222,6 +1222,8 @@
         "host": "میزبان LDAP",
         "port": "پورت LDAP",
         "useTls": "استفاده از TLS (LDAPS)",
+        "skipTlsVerify": "رد کردن تأیید گواهی TLS",
+        "skipTlsVerifyDesc": "ناامن — اعتبارسنجی گواهی سرور را غیرفعال می‌کند. فقط برای CAهای داخلی/غیرمعتبر استفاده کنید.",
         "bindDn": "Bind DN",
         "passwordConfigured": "تنظیم‌شده؛ برای حفظ رمز فعلی خالی بگذارید.",
         "passwordUnconfigured": "تنظیم نشده.",

+ 2 - 0
internal/web/translation/id-ID.json

@@ -1220,6 +1220,8 @@
         "host": "LDAP host",
         "port": "Port LDAP",
         "useTls": "Gunakan TLS (LDAPS)",
+        "skipTlsVerify": "Lewati verifikasi sertifikat TLS",
+        "skipTlsVerifyDesc": "Tidak aman — menonaktifkan validasi sertifikat server. Gunakan hanya dengan CA internal/tidak terpercaya.",
         "bindDn": "Bind DN",
         "passwordConfigured": "Terkonfigurasi; biarkan kosong untuk mempertahankan kata sandi saat ini.",
         "passwordUnconfigured": "Tidak terkonfigurasi.",

+ 2 - 0
internal/web/translation/ja-JP.json

@@ -1220,6 +1220,8 @@
         "host": "LDAP host",
         "port": "LDAP ポート",
         "useTls": "TLS (LDAPS) を使用",
+        "skipTlsVerify": "TLS 証明書の検証をスキップ",
+        "skipTlsVerifyDesc": "安全ではありません — サーバー証明書の検証を無効化します。内部/信頼できない CA でのみ使用してください。",
         "bindDn": "Bind DN",
         "passwordConfigured": "設定済み;現在のパスワードを保持するには空のままにします。",
         "passwordUnconfigured": "未設定。",

+ 2 - 0
internal/web/translation/pt-BR.json

@@ -1220,6 +1220,8 @@
         "host": "Host LDAP",
         "port": "Porta LDAP",
         "useTls": "Usar TLS (LDAPS)",
+        "skipTlsVerify": "Pular verificação de certificado TLS",
+        "skipTlsVerifyDesc": "Inseguro — desativa a validação do certificado do servidor. Use apenas com CAs internos/não confiáveis.",
         "bindDn": "Bind DN",
         "passwordConfigured": "Configurada; deixe em branco para manter a senha atual.",
         "passwordUnconfigured": "Não configurada.",

+ 2 - 0
internal/web/translation/ru-RU.json

@@ -1220,6 +1220,8 @@
         "host": "LDAP-хост",
         "port": "Порт LDAP",
         "useTls": "Использовать TLS (LDAPS)",
+        "skipTlsVerify": "Пропустить проверку сертификата TLS",
+        "skipTlsVerifyDesc": "Небезопасно — отключает проверку сертификата сервера. Используйте только с внутренними/недоверенными CA.",
         "bindDn": "Bind DN",
         "passwordConfigured": "Настроено; оставьте пустым, чтобы сохранить текущий пароль.",
         "passwordUnconfigured": "Не настроено.",

+ 2 - 0
internal/web/translation/tr-TR.json

@@ -1220,6 +1220,8 @@
         "host": "LDAP host",
         "port": "LDAP port",
         "useTls": "TLS kullan (LDAPS)",
+        "skipTlsVerify": "TLS sertifika doğrulamasını atla",
+        "skipTlsVerifyDesc": "Güvenli değil — sunucu sertifika doğrulamasını devre dışı bırakır. Yalnızca dahili/güvenilmeyen CA'larla kullanın.",
         "bindDn": "Bind DN",
         "passwordConfigured": "Yapılandırıldı; mevcut parolayı korumak için boş bırakın.",
         "passwordUnconfigured": "Yapılandırılmadı.",

+ 2 - 0
internal/web/translation/uk-UA.json

@@ -1220,6 +1220,8 @@
         "host": "LDAP-хост",
         "port": "Порт LDAP",
         "useTls": "Використовувати TLS (LDAPS)",
+        "skipTlsVerify": "Пропустити перевірку сертифіката TLS",
+        "skipTlsVerifyDesc": "Небезпечно — вимикає перевірку сертифіката сервера. Використовуйте лише з внутрішніми/ненадійними CA.",
         "bindDn": "Bind DN",
         "passwordConfigured": "Налаштовано; залиште порожнім для збереження поточного паролю.",
         "passwordUnconfigured": "Не налаштовано.",

+ 2 - 0
internal/web/translation/vi-VN.json

@@ -1220,6 +1220,8 @@
         "host": "LDAP host",
         "port": "Cổng LDAP",
         "useTls": "Dùng TLS (LDAPS)",
+        "skipTlsVerify": "Bỏ qua xác minh chứng chỉ TLS",
+        "skipTlsVerifyDesc": "Không an toàn — tắt xác thực chứng chỉ máy chủ. Chỉ dùng với CA nội bộ/không đáng tin.",
         "bindDn": "Bind DN",
         "passwordConfigured": "Đã cấu hình; để trống để giữ mật khẩu hiện tại.",
         "passwordUnconfigured": "Chưa cấu hình.",

+ 2 - 0
internal/web/translation/zh-CN.json

@@ -1220,6 +1220,8 @@
         "host": "LDAP host",
         "port": "LDAP 端口",
         "useTls": "使用 TLS (LDAPS)",
+        "skipTlsVerify": "跳过 TLS 证书验证",
+        "skipTlsVerifyDesc": "不安全 — 禁用服务器证书验证。仅用于内部/不受信任的 CA。",
         "bindDn": "Bind DN",
         "passwordConfigured": "已配置;留空以保留当前密码。",
         "passwordUnconfigured": "未配置。",

+ 2 - 0
internal/web/translation/zh-TW.json

@@ -1220,6 +1220,8 @@
         "host": "LDAP host",
         "port": "LDAP 連接埠",
         "useTls": "使用 TLS (LDAPS)",
+        "skipTlsVerify": "略過 TLS 憑證驗證",
+        "skipTlsVerifyDesc": "不安全 — 停用伺服器憑證驗證。僅用於內部/不受信任的 CA。",
         "bindDn": "Bind DN",
         "passwordConfigured": "已設定;留空以保留目前密碼。",
         "passwordUnconfigured": "未設定。",