Browse Source

Vlessenc (#3426)

* mlkem768

* VlessEnc
Sanaei 1 day ago
parent
commit
b008ff4ad2

+ 7 - 0
database/model/model.go

@@ -106,3 +106,10 @@ type Client struct {
 	CreatedAt  int64  `json:"created_at,omitempty"`
 	CreatedAt  int64  `json:"created_at,omitempty"`
 	UpdatedAt  int64  `json:"updated_at,omitempty"`
 	UpdatedAt  int64  `json:"updated_at,omitempty"`
 }
 }
+
+type VLESSSettings struct {
+	Clients    []Client `json:"clients"`
+	Decryption string   `json:"decryption"`
+	Encryption string   `json:"encryption"`
+	Fallbacks  []any    `json:"fallbacks"`
+}

+ 2 - 3
sub/subController.go

@@ -53,7 +53,6 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) {
 	gJson := g.Group(a.subJsonPath)
 	gJson := g.Group(a.subJsonPath)
 
 
 	gLink.GET(":subid", a.subs)
 	gLink.GET(":subid", a.subs)
-
 	gJson.GET(":subid", a.subJsons)
 	gJson.GET(":subid", a.subJsons)
 }
 }
 
 
@@ -85,7 +84,7 @@ func (a *SUBController) subs(c *gin.Context) {
 		// Add headers
 		// Add headers
 		c.Writer.Header().Set("Subscription-Userinfo", header)
 		c.Writer.Header().Set("Subscription-Userinfo", header)
 		c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
 		c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
-		c.Writer.Header().Set("Profile-Title", "base64:" + base64.StdEncoding.EncodeToString([]byte(a.subTitle)))
+		c.Writer.Header().Set("Profile-Title", "base64:"+base64.StdEncoding.EncodeToString([]byte(a.subTitle)))
 
 
 		if a.subEncrypt {
 		if a.subEncrypt {
 			c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
 			c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
@@ -119,7 +118,7 @@ func (a *SUBController) subJsons(c *gin.Context) {
 		// Add headers
 		// Add headers
 		c.Writer.Header().Set("Subscription-Userinfo", header)
 		c.Writer.Header().Set("Subscription-Userinfo", header)
 		c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
 		c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
-		c.Writer.Header().Set("Profile-Title", "base64:" + base64.StdEncoding.EncodeToString([]byte(a.subTitle)))
+		c.Writer.Header().Set("Profile-Title", "base64:"+base64.StdEncoding.EncodeToString([]byte(a.subTitle)))
 
 
 		c.String(200, jsonSub)
 		c.String(200, jsonSub)
 	}
 	}

+ 10 - 4
sub/subJsonService.go

@@ -184,8 +184,14 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
 		var newOutbounds []json_util.RawMessage
 		var newOutbounds []json_util.RawMessage
 
 
 		switch inbound.Protocol {
 		switch inbound.Protocol {
-		case "vmess", "vless":
-			newOutbounds = append(newOutbounds, s.genVnext(inbound, streamSettings, client))
+		case "vmess":
+			newOutbounds = append(newOutbounds, s.genVnext(inbound, streamSettings, client, ""))
+		case "vless":
+			var vlessSettings model.VLESSSettings
+			_ = json.Unmarshal([]byte(inbound.Settings), &vlessSettings)
+
+			newOutbounds = append(newOutbounds,
+				s.genVnext(inbound, streamSettings, client, vlessSettings.Encryption))
 		case "trojan", "shadowsocks":
 		case "trojan", "shadowsocks":
 			newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client))
 			newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client))
 		}
 		}
@@ -284,7 +290,7 @@ func (s *SubJsonService) realityData(rData map[string]any) map[string]any {
 	return rltyData
 	return rltyData
 }
 }
 
 
-func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client) json_util.RawMessage {
+func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client, encryption string) json_util.RawMessage {
 	outbound := Outbound{}
 	outbound := Outbound{}
 	usersData := make([]UserVnext, 1)
 	usersData := make([]UserVnext, 1)
 
 
@@ -295,7 +301,7 @@ func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_ut
 	}
 	}
 	if inbound.Protocol == model.VLESS {
 	if inbound.Protocol == model.VLESS {
 		usersData[0].Flow = client.Flow
 		usersData[0].Flow = client.Flow
-		usersData[0].Encryption = "none"
+		usersData[0].Encryption = encryption
 	}
 	}
 
 
 	vnextData := make([]VnextSetting, 1)
 	vnextData := make([]VnextSetting, 1)

+ 6 - 0
sub/subService.go

@@ -313,6 +313,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
 	if inbound.Protocol != model.VLESS {
 	if inbound.Protocol != model.VLESS {
 		return ""
 		return ""
 	}
 	}
+	var vlessSettings model.VLESSSettings
+	_ = json.Unmarshal([]byte(inbound.Settings), &vlessSettings)
+
 	var stream map[string]any
 	var stream map[string]any
 	json.Unmarshal([]byte(inbound.StreamSettings), &stream)
 	json.Unmarshal([]byte(inbound.StreamSettings), &stream)
 	clients, _ := s.inboundService.GetClients(inbound)
 	clients, _ := s.inboundService.GetClients(inbound)
@@ -327,6 +330,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
 	port := inbound.Port
 	port := inbound.Port
 	streamNetwork := stream["network"].(string)
 	streamNetwork := stream["network"].(string)
 	params := make(map[string]string)
 	params := make(map[string]string)
+	if vlessSettings.Encryption != "" {
+		params["encryption"] = vlessSettings.Encryption
+	}
 	params["type"] = streamNetwork
 	params["type"] = streamNetwork
 
 
 	switch streamNetwork {
 	switch streamNetwork {

+ 35 - 10
web/assets/js/model/inbound.js

@@ -1301,6 +1301,7 @@ class Inbound extends XrayCommonClass {
         const security = forceTls == 'same' ? this.stream.security : forceTls;
         const security = forceTls == 'same' ? this.stream.security : forceTls;
         const params = new Map();
         const params = new Map();
         params.set("type", this.stream.network);
         params.set("type", this.stream.network);
+        params.set("encryption", this.settings.encryption);
         switch (type) {
         switch (type) {
             case "tcp":
             case "tcp":
                 const tcp = this.stream.tcp;
                 const tcp = this.stream.tcp;
@@ -1859,13 +1860,16 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
     constructor(
     constructor(
         protocol,
         protocol,
         vlesses = [new Inbound.VLESSSettings.VLESS()],
         vlesses = [new Inbound.VLESSSettings.VLESS()],
-        decryption = 'none',
-        fallbacks = []
+        decryption = "none",
+        encryption = "",
+        fallbacks = [],
     ) {
     ) {
         super(protocol);
         super(protocol);
         this.vlesses = vlesses;
         this.vlesses = vlesses;
         this.decryption = decryption;
         this.decryption = decryption;
+        this.encryption = encryption;
         this.fallbacks = fallbacks;
         this.fallbacks = fallbacks;
+        this.selectedAuth = "X25519, not Post-Quantum";
     }
     }
 
 
     addFallback() {
     addFallback() {
@@ -1876,22 +1880,43 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
         this.fallbacks.splice(index, 1);
         this.fallbacks.splice(index, 1);
     }
     }
 
 
-    // decryption should be set to static value
     static fromJson(json = {}) {
     static fromJson(json = {}) {
-        return new Inbound.VLESSSettings(
+        const obj = new Inbound.VLESSSettings(
             Protocols.VLESS,
             Protocols.VLESS,
-            json.clients.map(client => Inbound.VLESSSettings.VLESS.fromJson(client)),
-            json.decryption || 'none',
-            Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks),);
+            (json.clients || []).map(client => Inbound.VLESSSettings.VLESS.fromJson(client)),
+            json.decryption,
+            json.encryption,
+            Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks || [])
+        );
+        obj.selectedAuth = json.selectedAuth || "X25519, not Post-Quantum";
+        return obj;
     }
     }
 
 
+
     toJson() {
     toJson() {
-        return {
+        const json = {
             clients: Inbound.VLESSSettings.toJsonArray(this.vlesses),
             clients: Inbound.VLESSSettings.toJsonArray(this.vlesses),
-            decryption: this.decryption,
-            fallbacks: Inbound.VLESSSettings.toJsonArray(this.fallbacks),
         };
         };
+
+        if (this.decryption) {
+            json.decryption = this.decryption;
+        }
+
+        if (this.encryption) {
+            json.encryption = this.encryption;
+        }
+
+        if (this.fallbacks && this.fallbacks.length > 0) {
+            json.fallbacks = Inbound.VLESSSettings.toJsonArray(this.fallbacks);
+        }
+        if (this.selectedAuth) {
+            json.selectedAuth = this.selectedAuth;
+        }
+
+        return json;
     }
     }
+
+
 };
 };
 
 
 Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
 Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {

+ 4 - 4
web/assets/js/model/outbound.js

@@ -813,7 +813,7 @@ class Outbound extends CommonClass {
         var settings;
         var settings;
         switch (protocol) {
         switch (protocol) {
             case Protocols.VLESS:
             case Protocols.VLESS:
-                settings = new Outbound.VLESSSettings(address, port, userData, url.searchParams.get('flow') ?? '');
+                settings = new Outbound.VLESSSettings(address, port, userData, url.searchParams.get('flow') ?? '', url.searchParams.get('encryption') ?? 'none');
                 break;
                 break;
             case Protocols.Trojan:
             case Protocols.Trojan:
                 settings = new Outbound.TrojanSettings(address, port, userData);
                 settings = new Outbound.TrojanSettings(address, port, userData);
@@ -1046,13 +1046,13 @@ Outbound.VmessSettings = class extends CommonClass {
     }
     }
 };
 };
 Outbound.VLESSSettings = class extends CommonClass {
 Outbound.VLESSSettings = class extends CommonClass {
-    constructor(address, port, id, flow, encryption = 'none') {
+    constructor(address, port, id, flow, encryption) {
         super();
         super();
         this.address = address;
         this.address = address;
         this.port = port;
         this.port = port;
         this.id = id;
         this.id = id;
         this.flow = flow;
         this.flow = flow;
-        this.encryption = encryption
+        this.encryption = encryption;
     }
     }
 
 
     static fromJson(json = {}) {
     static fromJson(json = {}) {
@@ -1071,7 +1071,7 @@ Outbound.VLESSSettings = class extends CommonClass {
             vnext: [{
             vnext: [{
                 address: this.address,
                 address: this.address,
                 port: this.port,
                 port: this.port,
-                users: [{ id: this.id, flow: this.flow, encryption: 'none', }],
+                users: [{ id: this.id, flow: this.flow, encryption: this.encryption }],
             }],
             }],
         };
         };
     }
     }

+ 10 - 0
web/controller/server.go

@@ -55,6 +55,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
 	g.POST("/getNewX25519Cert", a.getNewX25519Cert)
 	g.POST("/getNewX25519Cert", a.getNewX25519Cert)
 	g.POST("/getNewmldsa65", a.getNewmldsa65)
 	g.POST("/getNewmldsa65", a.getNewmldsa65)
 	g.POST("/getNewEchCert", a.getNewEchCert)
 	g.POST("/getNewEchCert", a.getNewEchCert)
+	g.POST("/getNewVlessEnc", a.getNewVlessEnc)
 }
 }
 
 
 func (a *ServerController) refreshStatus() {
 func (a *ServerController) refreshStatus() {
@@ -266,3 +267,12 @@ func (a *ServerController) getNewEchCert(c *gin.Context) {
 	}
 	}
 	jsonObj(c, cert, nil)
 	jsonObj(c, cert, nil)
 }
 }
+
+func (a *ServerController) getNewVlessEnc(c *gin.Context) {
+	out, err := a.serverService.GetNewVlessEnc()
+	if err != nil {
+		jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.getNewVlessEncError"), err)
+		return
+	}
+	jsonObj(c, out, nil)
+}

+ 5 - 0
web/html/form/outbound.html

@@ -226,6 +226,11 @@
         </template>
         </template>
 
 
         <!-- vless settings -->
         <!-- vless settings -->
+        <template v-if="outbound.protocol === Protocols.VLESS">
+          <a-form-item label='encryption'>
+            <a-input v-model.trim="outbound.settings.encryption"></a-input>
+          </a-form-item>
+        </template>
         <template v-if="outbound.canEnableTlsFlow()">
         <template v-if="outbound.canEnableTlsFlow()">
           <a-form-item label='Flow'>
           <a-form-item label='Flow'>
             <a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme">
             <a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme">

+ 23 - 1
web/html/form/protocol/vless.html

@@ -18,7 +18,29 @@
     </table>
     </table>
   </a-collapse-panel>
   </a-collapse-panel>
 </a-collapse>
 </a-collapse>
-<template v-if="inbound.isTcp">
+<template v-if="!inbound.stream.isTLS || !inbound.stream.isReality">
+  <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
+    <a-form-item label="Authentication">
+      <a-select v-model="inbound.settings.selectedAuth" @change="getNewVlessEnc" :dropdown-class-name="themeSwitcher.currentTheme">
+        <a-select-option value="X25519, not Post-Quantum">X25519 (not Post-Quantum)</a-select-option>
+        <a-select-option value="ML-KEM-768, Post-Quantum">ML-KEM-768 (Post-Quantum)</a-select-option>
+      </a-select>
+    </a-form-item>
+    <a-form-item label="decryption">
+      <a-input v-model.trim="inbound.settings.decryption"></a-input>
+    </a-form-item>
+    <a-form-item label="encryption">
+      <a-input v-model="inbound.settings.encryption" disabled></a-input>
+    </a-form-item>
+    <a-form-item label=" ">
+      <a-space>
+        <a-button type="primary" icon="import" @click="getNewVlessEnc">Get New keys</a-button>
+        <a-button danger @click="clearKeys">Clear</a-button>
+      </a-space>
+    </a-form-item>
+  </a-form>
+</template>
+<template v-if="inbound.isTcp && !inbound.settings.encryption">
   <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
   <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
     <a-form-item label="Fallbacks">
     <a-form-item label="Fallbacks">
       <a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button>
       <a-button icon="plus" type="primary" size="small" @click="inbound.settings.addFallback()"></a-button>

+ 4 - 4
web/html/form/tls_settings.html

@@ -5,13 +5,13 @@
   <a-form-item label='{{ i18n "security" }}'>
   <a-form-item label='{{ i18n "security" }}'>
     <a-radio-group v-model="inbound.stream.security" button-style="solid">
     <a-radio-group v-model="inbound.stream.security" button-style="solid">
       <a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
       <a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
-      <a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button>
-      <a-radio-button value="tls">TLS</a-radio-button>
+      <a-radio-button v-if="inbound.canEnableReality() && !inbound.settings.encryption" value="reality">Reality</a-radio-button>
+      <a-radio-button v-if="!inbound.settings.encryption" value="tls">TLS</a-radio-button>
     </a-radio-group>
     </a-radio-group>
   </a-form-item>
   </a-form-item>
 
 
   <!-- tls settings -->
   <!-- tls settings -->
-  <template v-if="inbound.stream.isTls">
+  <template v-if="inbound.stream.isTls && !inbound.settings.encryption">
     <a-form-item label="SNI" placeholder="Server Name Indication">
     <a-form-item label="SNI" placeholder="Server Name Indication">
       <a-input v-model.trim="inbound.stream.tls.sni"></a-input>
       <a-input v-model.trim="inbound.stream.tls.sni"></a-input>
     </a-form-item>
     </a-form-item>
@@ -121,7 +121,7 @@
   </template>
   </template>
 
 
   <!-- reality settings -->
   <!-- reality settings -->
-  <template v-if="inbound.stream.isReality">
+  <template v-if="inbound.stream.isReality && !inbound.settings.encryption">
     {{template "form/realitySettings"}}
     {{template "form/realitySettings"}}
   </template>
   </template>
 </a-form>
 </a-form>

+ 3 - 3
web/html/inbounds.html

@@ -706,7 +706,7 @@
     }, {
     }, {
         title: '{{ i18n "pages.inbounds.enable" }}',
         title: '{{ i18n "pages.inbounds.enable" }}',
         align: 'center',
         align: 'center',
-        width: 30,
+        width: 35,
         scopedSlots: { customRender: 'enable' },
         scopedSlots: { customRender: 'enable' },
     }, {
     }, {
         title: '{{ i18n "pages.inbounds.remark" }}',
         title: '{{ i18n "pages.inbounds.remark" }}',
@@ -770,8 +770,8 @@
 
 
     const innerColumns = [
     const innerColumns = [
         { title: '{{ i18n "pages.inbounds.operate" }}', width: 65, scopedSlots: { customRender: 'actions' } },
         { title: '{{ i18n "pages.inbounds.operate" }}', width: 65, scopedSlots: { customRender: 'actions' } },
-        { title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
-        { title: '{{ i18n "online" }}', width: 30, scopedSlots: { customRender: 'online' } },
+        { title: '{{ i18n "pages.inbounds.enable" }}', width: 35, scopedSlots: { customRender: 'enable' } },
+        { title: '{{ i18n "online" }}', width: 32, scopedSlots: { customRender: 'online' } },
         { title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
         { title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
         { title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } },
         { title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } },
         { title: '{{ i18n "pages.inbounds.allTimeTraffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'allTime' } },
         { title: '{{ i18n "pages.inbounds.allTimeTraffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'allTime' } },

+ 6 - 0
web/html/modals/inbound_info_modal.html

@@ -101,6 +101,12 @@
       {{ i18n "security" }}
       {{ i18n "security" }}
       <a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag>
       <a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag>
       <br />
       <br />
+      <td>Authentication</td>
+        <a-tag :color="inbound.settings.selectedAuth ? 'green' : 'red'">[[ inbound.settings.selectedAuth ? inbound.settings.selectedAuth : '' ]]</a-tag>
+      <br />
+      {{ i18n "encryption" }}
+        <a-tag :color="inbound.settings.encryption ? 'green' : 'red'">[[ inbound.settings.encryption ? inbound.settings.encryption : '' ]]</a-tag>
+      <br />
       <template v-if="inbound.stream.security != 'none'">
       <template v-if="inbound.stream.security != 'none'">
         {{ i18n "domainName" }}
         {{ i18n "domainName" }}
         <a-tag v-if="inbound.serverName" :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
         <a-tag v-if="inbound.serverName" :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>

+ 36 - 12
web/html/modals/inbound_modal.html

@@ -1,9 +1,7 @@
 {{define "modals/inboundModal"}}
 {{define "modals/inboundModal"}}
-<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title"
-        :dialog-style="{ top: '20px' }" @ok="inModal.ok"
-        :confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
-        :class="themeSwitcher.currentTheme"
-        :ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
+<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" :dialog-style="{ top: '20px' }"
+    @ok="inModal.ok" :confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
+    :class="themeSwitcher.currentTheme" :ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
     {{template "form/inbound"}}
     {{template "form/inbound"}}
 </a-modal>
 </a-modal>
 <script>
 <script>
@@ -20,7 +18,7 @@
         ok() {
         ok() {
             ObjectUtil.execute(inModal.confirm, inModal.inbound, inModal.dbInbound);
             ObjectUtil.execute(inModal.confirm, inModal.inbound, inModal.dbInbound);
         },
         },
-        show({ title = '', okText = '{{ i18n "sure" }}', inbound = null, dbInbound = null, confirm = (inbound, dbInbound) => {}, isEdit = false }) {
+        show({ title = '', okText = '{{ i18n "sure" }}', inbound = null, dbInbound = null, confirm = (inbound, dbInbound) => { }, isEdit = false }) {
             this.title = title;
             this.title = title;
             this.okText = okText;
             this.okText = okText;
             if (inbound) {
             if (inbound) {
@@ -41,7 +39,7 @@
             inModal.visible = false;
             inModal.visible = false;
             inModal.loading(false);
             inModal.loading(false);
         },
         },
-        loading(loading=true) {
+        loading(loading = true) {
             inModal.confirmLoading = loading;
             inModal.confirmLoading = loading;
         },
         },
     };
     };
@@ -105,9 +103,9 @@
             },
             },
             SSMethodChange() {
             SSMethodChange() {
                 this.inModal.inbound.settings.password = RandomUtil.randomShadowsocksPassword(this.inModal.inbound.settings.method)
                 this.inModal.inbound.settings.password = RandomUtil.randomShadowsocksPassword(this.inModal.inbound.settings.method)
-                
+
                 if (this.inModal.inbound.isSSMultiUser) {
                 if (this.inModal.inbound.isSSMultiUser) {
-                    if (this.inModal.inbound.settings.shadowsockses.length ==0){
+                    if (this.inModal.inbound.settings.shadowsockses.length == 0) {
                         this.inModal.inbound.settings.shadowsockses = [new Inbound.ShadowsocksSettings.Shadowsocks()];
                         this.inModal.inbound.settings.shadowsockses = [new Inbound.ShadowsocksSettings.Shadowsocks()];
                     }
                     }
                     if (!this.inModal.inbound.isSS2022) {
                     if (!this.inModal.inbound.isSS2022) {
@@ -123,7 +121,7 @@
                         client.password = RandomUtil.randomShadowsocksPassword(this.inModal.inbound.settings.method)
                         client.password = RandomUtil.randomShadowsocksPassword(this.inModal.inbound.settings.method)
                     })
                     })
                 } else {
                 } else {
-                    if (this.inModal.inbound.settings.shadowsockses.length > 0){
+                    if (this.inModal.inbound.settings.shadowsockses.length > 0) {
                         this.inModal.inbound.settings.shadowsockses = [];
                         this.inModal.inbound.settings.shadowsockses = [];
                     }
                     }
                 }
                 }
@@ -154,7 +152,7 @@
             },
             },
             async getNewEchCert() {
             async getNewEchCert() {
                 inModal.loading(true);
                 inModal.loading(true);
-                const msg = await HttpUtil.post('/server/getNewEchCert', {sni: inModal.inbound.stream.tls.sni});
+                const msg = await HttpUtil.post('/server/getNewEchCert', { sni: inModal.inbound.stream.tls.sni });
                 inModal.loading(false);
                 inModal.loading(false);
                 if (!msg.success) {
                 if (!msg.success) {
                     return;
                     return;
@@ -162,8 +160,34 @@
                 inModal.inbound.stream.tls.echServerKeys = msg.obj.echServerKeys;
                 inModal.inbound.stream.tls.echServerKeys = msg.obj.echServerKeys;
                 inModal.inbound.stream.tls.settings.echConfigList = msg.obj.echConfigList;
                 inModal.inbound.stream.tls.settings.echConfigList = msg.obj.echConfigList;
             },
             },
+            async getNewVlessEnc() {
+                inModal.loading(true);
+                const msg = await HttpUtil.post('/server/getNewVlessEnc');
+                inModal.loading(false);
+
+                if (!msg.success) {
+                    return;
+                }
+
+                const auths = msg.obj.auths || [];
+                const selected = inModal.inbound.settings.selectedAuth;
+                const block = auths.find(a => a.label === selected);
+
+                if (!block) {
+                    console.error("No auth block for", selected);
+                    return;
+                }
+
+                inModal.inbound.settings.decryption = block.decryption;
+                inModal.inbound.settings.encryption = block.encryption;
+            },
+            clearKeys() {
+                this.inbound.settings.decryption = 'none';
+                this.inbound.settings.encryption = '';
+            }
+
         },
         },
     });
     });
 
 
 </script>
 </script>
-{{end}}
+{{end}}

+ 50 - 0
web/service/server.go

@@ -871,3 +871,53 @@ func (s *ServerService) GetNewEchCert(sni string) (interface{}, error) {
 		"echConfigList": configList,
 		"echConfigList": configList,
 	}, nil
 	}, nil
 }
 }
+
+type AuthBlock struct {
+	Label      string `json:"label"`
+	Decryption string `json:"decryption"`
+	Encryption string `json:"encryption"`
+}
+
+func (s *ServerService) GetNewVlessEnc() (any, error) {
+	cmd := exec.Command(xray.GetBinaryPath(), "vlessenc")
+	var out bytes.Buffer
+	cmd.Stdout = &out
+	if err := cmd.Run(); err != nil {
+		return nil, err
+	}
+
+	lines := strings.Split(out.String(), "\n")
+
+	var blocks []AuthBlock
+	var current *AuthBlock
+
+	for _, line := range lines {
+		line = strings.TrimSpace(line)
+		if strings.HasPrefix(line, "Authentication:") {
+			if current != nil {
+				blocks = append(blocks, *current)
+			}
+			current = &AuthBlock{Label: strings.TrimSpace(strings.TrimPrefix(line, "Authentication:"))}
+		} else if strings.HasPrefix(line, `"decryption"`) || strings.HasPrefix(line, `"encryption"`) {
+			parts := strings.SplitN(line, ":", 2)
+			if len(parts) == 2 && current != nil {
+				key := strings.Trim(parts[0], `" `)
+				val := strings.Trim(parts[1], `" `)
+				switch key {
+				case "decryption":
+					current.Decryption = val
+				case "encryption":
+					current.Encryption = val
+				}
+			}
+		}
+	}
+
+	if current != nil {
+		blocks = append(blocks, *current)
+	}
+
+	return map[string]any{
+		"auths": blocks,
+	}, nil
+}

+ 1 - 0
web/translation/translate.ar_EG.toml

@@ -267,6 +267,7 @@
 "trafficGetError" = "خطأ في الحصول على حركات المرور"
 "trafficGetError" = "خطأ في الحصول على حركات المرور"
 "getNewX25519CertError" = "حدث خطأ أثناء الحصول على شهادة X25519."
 "getNewX25519CertError" = "حدث خطأ أثناء الحصول على شهادة X25519."
 "getNewmldsa65Error" = "حدث خطاء في الحصول على mldsa65."
 "getNewmldsa65Error" = "حدث خطاء في الحصول على mldsa65."
+"getNewVlessEncError" = "حدث خطأ أثناء الحصول على VlessEnc."
 
 
 [pages.inbounds.stream.general]
 [pages.inbounds.stream.general]
 "request" = "طلب"
 "request" = "طلب"

+ 1 - 0
web/translation/translate.en_US.toml

@@ -267,6 +267,7 @@
 "trafficGetError" = "Error getting traffics."
 "trafficGetError" = "Error getting traffics."
 "getNewX25519CertError" = "Error while obtaining the X25519 certificate."
 "getNewX25519CertError" = "Error while obtaining the X25519 certificate."
 "getNewmldsa65Error" = "Error while obtaining mldsa65."
 "getNewmldsa65Error" = "Error while obtaining mldsa65."
+"getNewVlessEncError" = "Error while obtaining VlessEnc."
 
 
 [pages.inbounds.stream.general]
 [pages.inbounds.stream.general]
 "request" = "Request"
 "request" = "Request"

+ 1 - 0
web/translation/translate.es_ES.toml

@@ -267,6 +267,7 @@
 "trafficGetError" = "Error al obtener los tráficos"
 "trafficGetError" = "Error al obtener los tráficos"
 "getNewX25519CertError" = "Error al obtener el certificado X25519."
 "getNewX25519CertError" = "Error al obtener el certificado X25519."
 "getNewmldsa65Error" = "Error al obtener el certificado mldsa65."
 "getNewmldsa65Error" = "Error al obtener el certificado mldsa65."
+"getNewVlessEncError" = "Error al obtener el certificado VlessEnc."
 
 
 [pages.inbounds.stream.general]
 [pages.inbounds.stream.general]
 "request" = "Pedido"
 "request" = "Pedido"

+ 1 - 0
web/translation/translate.fa_IR.toml

@@ -267,6 +267,7 @@
 "trafficGetError" = "خطا در دریافت ترافیک‌ها"
 "trafficGetError" = "خطا در دریافت ترافیک‌ها"
 "getNewX25519CertError" = "خطا در دریافت گواهی X25519."
 "getNewX25519CertError" = "خطا در دریافت گواهی X25519."
 "getNewmldsa65Error" = "خطا در دریافت گواهی mldsa65."
 "getNewmldsa65Error" = "خطا در دریافت گواهی mldsa65."
+"getNewVlessEncError" = "خطا در دریافت گواهی VlessEnc."
 
 
 [pages.inbounds.stream.general]
 [pages.inbounds.stream.general]
 "request" = "درخواست"
 "request" = "درخواست"

+ 1 - 0
web/translation/translate.id_ID.toml

@@ -267,6 +267,7 @@
 "trafficGetError" = "Gagal mendapatkan data lalu lintas"
 "trafficGetError" = "Gagal mendapatkan data lalu lintas"
 "getNewX25519CertError" = "Terjadi kesalahan saat mendapatkan sertifikat X25519."
 "getNewX25519CertError" = "Terjadi kesalahan saat mendapatkan sertifikat X25519."
 "getNewmldsa65Error" = "Terjadi kesalahan saat mendapatkan sertifikat mldsa65."
 "getNewmldsa65Error" = "Terjadi kesalahan saat mendapatkan sertifikat mldsa65."
+"getNewVlessEncError" = "Terjadi kesalahan saat mendapatkan sertifikat VlessEnc."
 
 
 [pages.inbounds.stream.general]
 [pages.inbounds.stream.general]
 "request" = "Permintaan"
 "request" = "Permintaan"

+ 1 - 0
web/translation/translate.ja_JP.toml

@@ -267,6 +267,7 @@
 "trafficGetError" = "トラフィックの取得中にエラーが発生しました"
 "trafficGetError" = "トラフィックの取得中にエラーが発生しました"
 "getNewX25519CertError" = "X25519証明書の取得中にエラーが発生しました。"
 "getNewX25519CertError" = "X25519証明書の取得中にエラーが発生しました。"
 "getNewmldsa65Error" = "mldsa65証明書の取得中にエラーが発生しました。"
 "getNewmldsa65Error" = "mldsa65証明書の取得中にエラーが発生しました。"
+"getNewVlessEncError" = "VlessEnc証明書の取得中にエラーが発生しました。"
 
 
 [pages.inbounds.stream.general]
 [pages.inbounds.stream.general]
 "request" = "リクエスト"
 "request" = "リクエスト"

+ 1 - 0
web/translation/translate.pt_BR.toml

@@ -267,6 +267,7 @@
 "trafficGetError" = "Erro ao obter tráfegos"
 "trafficGetError" = "Erro ao obter tráfegos"
 "getNewX25519CertError" = "Erro ao obter o certificado X25519."
 "getNewX25519CertError" = "Erro ao obter o certificado X25519."
 "getNewmldsa65Error" = "Erro ao obter o certificado mldsa65."
 "getNewmldsa65Error" = "Erro ao obter o certificado mldsa65."
+"getNewVlessEncError" = "Erro ao obter o certificado VlessEnc."
 
 
 [pages.inbounds.stream.general]
 [pages.inbounds.stream.general]
 "request" = "Requisição"
 "request" = "Requisição"

+ 1 - 0
web/translation/translate.ru_RU.toml

@@ -267,6 +267,7 @@
 "trafficGetError" = "Ошибка получения данных о трафике"
 "trafficGetError" = "Ошибка получения данных о трафике"
 "getNewX25519CertError" = "Ошибка при получении сертификата X25519."
 "getNewX25519CertError" = "Ошибка при получении сертификата X25519."
 "getNewmldsa65Error" = "Ошибка при получении сертификата mldsa65."
 "getNewmldsa65Error" = "Ошибка при получении сертификата mldsa65."
+"getNewVlessEncError" = "Ошибка при получении сертификата VlessEnc."
 
 
 [pages.inbounds.stream.general]
 [pages.inbounds.stream.general]
 "request" = "Запрос"
 "request" = "Запрос"

+ 1 - 0
web/translation/translate.tr_TR.toml

@@ -267,6 +267,7 @@
 "trafficGetError" = "Trafik bilgisi alınırken hata oluştu"
 "trafficGetError" = "Trafik bilgisi alınırken hata oluştu"
 "getNewX25519CertError" = "X25519 sertifikası alınırken hata oluştu."
 "getNewX25519CertError" = "X25519 sertifikası alınırken hata oluştu."
 "getNewmldsa65Error" = "mldsa65 sertifikası alınırken hata oluştu."
 "getNewmldsa65Error" = "mldsa65 sertifikası alınırken hata oluştu."
+"getNewVlessEncError" = "VlessEnc sertifikası alınırken hata oluştu."
 
 
 [pages.inbounds.stream.general]
 [pages.inbounds.stream.general]
 "request" = "İstek"
 "request" = "İstek"

+ 1 - 0
web/translation/translate.uk_UA.toml

@@ -267,6 +267,7 @@
 "trafficGetError" = "Помилка отримання даних про трафік"
 "trafficGetError" = "Помилка отримання даних про трафік"
 "getNewX25519CertError" = "Помилка при отриманні сертифіката X25519."
 "getNewX25519CertError" = "Помилка при отриманні сертифіката X25519."
 "getNewmldsa65Error" = "Помилка при отриманні сертифіката mldsa65."
 "getNewmldsa65Error" = "Помилка при отриманні сертифіката mldsa65."
+"getNewVlessEncError" = "Помилка при отриманні сертифіката VlessEnc."
 
 
 [pages.inbounds.stream.general]
 [pages.inbounds.stream.general]
 "request" = "Запит"
 "request" = "Запит"

+ 2 - 1
web/translation/translate.vi_VN.toml

@@ -266,7 +266,8 @@
 "resetInboundClientTrafficSuccess" = "Đã đặt lại lưu lượng"
 "resetInboundClientTrafficSuccess" = "Đã đặt lại lưu lượng"
 "trafficGetError" = "Lỗi khi lấy thông tin lưu lượng"
 "trafficGetError" = "Lỗi khi lấy thông tin lưu lượng"
 "getNewX25519CertError" = "Lỗi khi lấy chứng chỉ X25519."
 "getNewX25519CertError" = "Lỗi khi lấy chứng chỉ X25519."
-"getNewmldsa65Error" = "Lỗi khi lấy chúng tôi mldsa65."
+"getNewmldsa65Error" = "Lỗi khi lấy chứng chỉ mldsa65."
+"getNewVlessEncError" = "Lỗi khi lấy chứng chỉ VlessEnc."
 
 
 [pages.inbounds.stream.general]
 [pages.inbounds.stream.general]
 "request" = "Lời yêu cầu"
 "request" = "Lời yêu cầu"

+ 1 - 0
web/translation/translate.zh_CN.toml

@@ -267,6 +267,7 @@
 "trafficGetError" = "获取流量数据时出错"
 "trafficGetError" = "获取流量数据时出错"
 "getNewX25519CertError" = "获取X25519证书时出错。"
 "getNewX25519CertError" = "获取X25519证书时出错。"
 "getNewmldsa65Error" = "获取mldsa65证书时出错。"
 "getNewmldsa65Error" = "获取mldsa65证书时出错。"
+"getNewVlessEncError" = "获取VlessEnc证书时出错。"
 
 
 [pages.inbounds.stream.general]
 [pages.inbounds.stream.general]
 "request" = "请求"
 "request" = "请求"

+ 1 - 0
web/translation/translate.zh_TW.toml

@@ -267,6 +267,7 @@
 "trafficGetError" = "取得流量資料時發生錯誤"
 "trafficGetError" = "取得流量資料時發生錯誤"
 "getNewX25519CertError" = "取得X25519憑證時發生錯誤。"
 "getNewX25519CertError" = "取得X25519憑證時發生錯誤。"
 "getNewmldsa65Error" = "取得mldsa65憑證時發生錯誤。"
 "getNewmldsa65Error" = "取得mldsa65憑證時發生錯誤。"
+"getNewVlessEncError" = "取得VlessEnc憑證時發生錯誤。"
 
 
 [pages.inbounds.stream.general]
 [pages.inbounds.stream.general]
 "request" = "請求"
 "request" = "請求"