feat(mtproto): per-client ad-tags, management-API auth, and record secret sync

Catch the panel up to the mtg-multi README (v1.14.0):

- Each client can now carry its own 32-hex advertising tag overriding the
  inbound-level one. The tag lives on the client (settings JSON is the
  source of truth, clients.ad_tag is the UI projection), is rendered into
  the fork's [secret-ad-tags] section for active secrets only (mtg rejects
  a config whose override names an unknown secret), is pushed per entry
  through PUT /secrets, and is part of the reload fingerprint so a tag
  edit hot-applies without dropping connections.
- The loopback management API can replace the whole secret set, so every
  mtg process now gets a random per-process api-token; the manager sends
  it as a bearer token on PUT /secrets and GET /stats and reuses it across
  config rewrites, because mtg reads the token only at startup.
- Malformed tags are rejected at every save path and additionally dropped
  in InstanceFromInbound: one bad tag would otherwise fail the whole
  generated config and take every client of the inbound down with it.
- SyncInbound never copied a re-keyed mtproto secret into the canonical
  clients table, so the clients page and subscription links kept serving
  the old secret, which mtg then rejects. It is now guarded-copied like
  the other credentials.
This commit is contained in:
MHSanaei
2026-07-07 12:00:43 +02:00
parent 659f0f404c
commit 43500a5470
33 changed files with 361 additions and 54 deletions
+25 -6
View File
@@ -119,6 +119,7 @@ interface FormState {
wgPreSharedKey: string;
wgAllowedIPs: string;
secret: string;
adTag: string;
}
function emptyForm(): FormState {
@@ -148,6 +149,7 @@ function emptyForm(): FormState {
wgPreSharedKey: '',
wgAllowedIPs: '',
secret: '',
adTag: '',
};
}
@@ -253,6 +255,7 @@ export default function ClientFormModal({
wgPreSharedKey: client.preSharedKey || '',
wgAllowedIPs: client.allowedIPs || '',
secret: client.secret || '',
adTag: client.adTag || '',
};
if (et < 0) {
next.delayedStart = true;
@@ -538,7 +541,13 @@ export default function ClientFormModal({
}
if (showMtproto) {
const adTag = form.adTag.trim();
if (adTag !== '' && !/^[0-9a-fA-F]{32}$/.test(adTag)) {
messageApi.error(t('pages.inbounds.form.mtgAdTagInvalid'));
return;
}
clientPayload.secret = form.secret;
clientPayload.adTag = adTag;
}
const externalLinks: ExternalLinkInput[] = form.externalLinks
@@ -862,12 +871,22 @@ export default function ClientFormModal({
</>
)}
{showMtproto && (
<Form.Item label={t('pages.clients.mtprotoSecret')} extra={t('pages.clients.mtprotoSecretHint')}>
<Space.Compact style={{ display: 'flex' }}>
<Input value={form.secret} style={{ flex: 1 }} onChange={(e) => update('secret', e.target.value)} />
<Button aria-label={t('regenerate')} icon={<ReloadOutlined />} onClick={regenerateMtprotoSecret} />
</Space.Compact>
</Form.Item>
<>
<Form.Item label={t('pages.clients.mtprotoSecret')} extra={t('pages.clients.mtprotoSecretHint')}>
<Space.Compact style={{ display: 'flex' }}>
<Input value={form.secret} style={{ flex: 1 }} onChange={(e) => update('secret', e.target.value)} />
<Button aria-label={t('regenerate')} icon={<ReloadOutlined />} onClick={regenerateMtprotoSecret} />
</Space.Compact>
</Form.Item>
<Form.Item label={t('pages.clients.mtprotoAdTag')} extra={t('pages.clients.mtprotoAdTagHint')}>
<Input
value={form.adTag}
allowClear
placeholder="0123456789abcdef0123456789abcdef"
onChange={(e) => update('adTag', e.target.value)}
/>
</Form.Item>
</>
)}
</>
),