mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-17 15:47:14 +00:00
feat(clients): show short HWID fingerprint in admin device list (#6464)
* feat(clients): show short HWID fingerprint in admin device list Expose a 12-char prefix of the stored hwid_hash in the admin HWID list API and UI so admins can distinguish devices without querying the database. Full hashes and raw HWIDs remain unexposed. Fixes #6359 * ci: retrigger release matrix after 386 dependency download flake --------- Co-authored-by: mrchatam <mrchatam@users.noreply.github.com> Co-authored-by: mrchatam <287639636+mrchatam@users.noreply.github.com>
This commit is contained in:
@@ -227,8 +227,8 @@ _openapi:
|
|||||||
title: Reset the recorded IP list for a client.
|
title: Reset the recorded IP list for a client.
|
||||||
url: '#reset-the-recorded-ip-list-for-a-client'
|
url: '#reset-the-recorded-ip-list-for-a-client'
|
||||||
- depth: 2
|
- depth: 2
|
||||||
title: List registered HWID devices for a client. Hashes are not exposed.
|
title: List registered HWID devices for a client with a short fingerprint. Full hashes are not exposed.
|
||||||
url: '#list-registered-hwid-devices-for-a-client-hashes-are-not-exposed'
|
url: '#list-registered-hwid-devices-for-a-client-with-a-short-fingerprint-full-hashes-are-not-exposed'
|
||||||
- depth: 2
|
- depth: 2
|
||||||
title: Clear all registered HWID devices for a client so new devices can
|
title: Clear all registered HWID devices for a client so new devices can
|
||||||
register again.
|
register again.
|
||||||
@@ -473,8 +473,8 @@ _openapi:
|
|||||||
id: list-source-ips-that-have-connected-with-the-given-clients-credentials-returns-an-array-of-ip-timestamp-strings
|
id: list-source-ips-that-have-connected-with-the-given-clients-credentials-returns-an-array-of-ip-timestamp-strings
|
||||||
- content: Reset the recorded IP list for a client.
|
- content: Reset the recorded IP list for a client.
|
||||||
id: reset-the-recorded-ip-list-for-a-client
|
id: reset-the-recorded-ip-list-for-a-client
|
||||||
- content: List registered HWID devices for a client. Hashes are not exposed.
|
- content: List registered HWID devices for a client with a short fingerprint. Full hashes are not exposed.
|
||||||
id: list-registered-hwid-devices-for-a-client-hashes-are-not-exposed
|
id: list-registered-hwid-devices-for-a-client-with-a-short-fingerprint-full-hashes-are-not-exposed
|
||||||
- content: Clear all registered HWID devices for a client so new devices can
|
- content: Clear all registered HWID devices for a client so new devices can
|
||||||
register again.
|
register again.
|
||||||
id: clear-all-registered-hwid-devices-for-a-client-so-new-devices-can-register-again
|
id: clear-all-registered-hwid-devices-for-a-client-so-new-devices-can-register-again
|
||||||
|
|||||||
@@ -9604,7 +9604,7 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"Clients"
|
"Clients"
|
||||||
],
|
],
|
||||||
"summary": "List registered HWID devices for a client. Hashes are not exposed.",
|
"summary": "List registered HWID devices for a client with a short fingerprint. Full hashes are not exposed.",
|
||||||
"operationId": "post_panel_api_clients_hwids_email",
|
"operationId": "post_panel_api_clients_hwids_email",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -9644,7 +9644,8 @@
|
|||||||
"userAgent": "Happ/1.0",
|
"userAgent": "Happ/1.0",
|
||||||
"deviceOs": "android",
|
"deviceOs": "android",
|
||||||
"osVersion": "15",
|
"osVersion": "15",
|
||||||
"deviceModel": "Pixel 9"
|
"deviceModel": "Pixel 9",
|
||||||
|
"fingerprint": "6ad17c93e821"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9604,7 +9604,7 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"Clients"
|
"Clients"
|
||||||
],
|
],
|
||||||
"summary": "List registered HWID devices for a client. Hashes are not exposed.",
|
"summary": "List registered HWID devices for a client with a short fingerprint. Full hashes are not exposed.",
|
||||||
"operationId": "post_panel_api_clients_hwids_email",
|
"operationId": "post_panel_api_clients_hwids_email",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -9644,7 +9644,8 @@
|
|||||||
"userAgent": "Happ/1.0",
|
"userAgent": "Happ/1.0",
|
||||||
"deviceOs": "android",
|
"deviceOs": "android",
|
||||||
"osVersion": "15",
|
"osVersion": "15",
|
||||||
"deviceModel": "Pixel 9"
|
"deviceModel": "Pixel 9",
|
||||||
|
"fingerprint": "6ad17c93e821"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,14 @@ export default function ClientHwidListModal({
|
|||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{entry.fingerprint && (
|
||||||
|
<>
|
||||||
|
<br />
|
||||||
|
<Typography.Text type="secondary" code>
|
||||||
|
{t('pages.clients.hwidFingerprint')}: {entry.fingerprint}
|
||||||
|
</Typography.Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title={t('pages.clients.deleteHwidConfirm')}
|
title={t('pages.clients.deleteHwidConfirm')}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export type ClientHwidInfo = {
|
|||||||
deviceOs: string;
|
deviceOs: string;
|
||||||
osVersion: string;
|
osVersion: string;
|
||||||
deviceModel: string;
|
deviceModel: string;
|
||||||
|
fingerprint: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// normalizeClientHwids accepts the API payload and returns typed entries,
|
// normalizeClientHwids accepts the API payload and returns typed entries,
|
||||||
|
|||||||
@@ -1483,10 +1483,11 @@ export const sections: readonly Section[] = [
|
|||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/panel/api/clients/hwids/:email',
|
path: '/panel/api/clients/hwids/:email',
|
||||||
summary: 'List registered HWID devices for a client. Hashes are not exposed.',
|
summary:
|
||||||
|
'List registered HWID devices for a client with a short fingerprint. Full hashes are not exposed.',
|
||||||
params: [{ name: 'email', in: 'path', type: 'string', desc: 'Client email.' }],
|
params: [{ name: 'email', in: 'path', type: 'string', desc: 'Client email.' }],
|
||||||
response:
|
response:
|
||||||
'{\n "success": true,\n "obj": [\n {\n "id": 1,\n "firstSeen": 1735000000000,\n "lastSeen": 1735100000000,\n "userAgent": "Happ/1.0",\n "deviceOs": "android",\n "osVersion": "15",\n "deviceModel": "Pixel 9"\n }\n ]\n}',
|
'{\n "success": true,\n "obj": [\n {\n "id": 1,\n "firstSeen": 1735000000000,\n "lastSeen": 1735100000000,\n "userAgent": "Happ/1.0",\n "deviceOs": "android",\n "osVersion": "15",\n "deviceModel": "Pixel 9",\n "fingerprint": "6ad17c93e821"\n }\n ]\n}',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
|
|||||||
@@ -41,7 +41,10 @@ type HwidSlotStatus struct {
|
|||||||
Full bool `json:"full" example:"false"`
|
Full bool `json:"full" example:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const minHwidLength = 6
|
const (
|
||||||
|
minHwidLength = 6
|
||||||
|
hwidFingerprintLength = 12
|
||||||
|
)
|
||||||
|
|
||||||
type ClientHwidInfo struct {
|
type ClientHwidInfo struct {
|
||||||
Id int `json:"id"`
|
Id int `json:"id"`
|
||||||
@@ -51,6 +54,7 @@ type ClientHwidInfo struct {
|
|||||||
DeviceOS string `json:"deviceOs"`
|
DeviceOS string `json:"deviceOs"`
|
||||||
OsVersion string `json:"osVersion"`
|
OsVersion string `json:"osVersion"`
|
||||||
DeviceModel string `json:"deviceModel"`
|
DeviceModel string `json:"deviceModel"`
|
||||||
|
Fingerprint string `json:"fingerprint"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func hashHwid(raw string) string {
|
func hashHwid(raw string) string {
|
||||||
@@ -58,6 +62,13 @@ func hashHwid(raw string) string {
|
|||||||
return hex.EncodeToString(sum[:])
|
return hex.EncodeToString(sum[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func shortHwidFingerprint(hash string) string {
|
||||||
|
if len(hash) <= hwidFingerprintLength {
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
return hash[:hwidFingerprintLength]
|
||||||
|
}
|
||||||
|
|
||||||
func trimHwidMeta(s string) string {
|
func trimHwidMeta(s string) string {
|
||||||
s = strings.TrimSpace(s)
|
s = strings.TrimSpace(s)
|
||||||
r := []rune(s)
|
r := []rune(s)
|
||||||
@@ -232,6 +243,7 @@ func (s *ClientService) ListClientHwids(email string) ([]ClientHwidInfo, error)
|
|||||||
DeviceOS: r.DeviceOS,
|
DeviceOS: r.DeviceOS,
|
||||||
OsVersion: r.OsVersion,
|
OsVersion: r.OsVersion,
|
||||||
DeviceModel: r.DeviceModel,
|
DeviceModel: r.DeviceModel,
|
||||||
|
Fingerprint: shortHwidFingerprint(r.HwidHash),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
|
|||||||
@@ -121,8 +121,15 @@ func TestClientHwidGateRegistersAndBlocks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
foundUpdated := false
|
foundUpdated := false
|
||||||
for _, row := range list {
|
for _, row := range list {
|
||||||
|
if len(row.Fingerprint) != hwidFingerprintLength {
|
||||||
|
t.Fatalf("fingerprint length = %d, want %d: %q", len(row.Fingerprint), hwidFingerprintLength, row.Fingerprint)
|
||||||
|
}
|
||||||
if row.DeviceModel == "updated-model" && row.UserAgent == "Karing/2.0" && row.DeviceOS == "ios" && row.OsVersion == "18" {
|
if row.DeviceModel == "updated-model" && row.UserAgent == "Karing/2.0" && row.DeviceOS == "ios" && row.OsVersion == "18" {
|
||||||
foundUpdated = true
|
foundUpdated = true
|
||||||
|
want := hashHwid(firstRaw)[:hwidFingerprintLength]
|
||||||
|
if row.Fingerprint != want {
|
||||||
|
t.Fatalf("fingerprint = %q, want %q", row.Fingerprint, want)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !foundUpdated {
|
if !foundUpdated {
|
||||||
|
|||||||
@@ -762,6 +762,7 @@
|
|||||||
"noHwids": "لا توجد أجهزة HWID بعد",
|
"noHwids": "لا توجد أجهزة HWID بعد",
|
||||||
"firstSeen": "أول ظهور",
|
"firstSeen": "أول ظهور",
|
||||||
"lastSeen": "آخر ظهور",
|
"lastSeen": "آخر ظهور",
|
||||||
|
"hwidFingerprint": "بصمة HWID",
|
||||||
"deleteHwid": "إزالة الجهاز",
|
"deleteHwid": "إزالة الجهاز",
|
||||||
"deleteHwidConfirm": "إزالة هذا الجهاز؟ سيحتاج إلى إعادة التسجيل عند جلب الاشتراك التالي.",
|
"deleteHwidConfirm": "إزالة هذا الجهاز؟ سيحتاج إلى إعادة التسجيل عند جلب الاشتراك التالي.",
|
||||||
"hwidDeleted": "تمت إزالة الجهاز.",
|
"hwidDeleted": "تمت إزالة الجهاز.",
|
||||||
|
|||||||
@@ -762,6 +762,7 @@
|
|||||||
"noHwids": "No HWID devices yet",
|
"noHwids": "No HWID devices yet",
|
||||||
"firstSeen": "First seen",
|
"firstSeen": "First seen",
|
||||||
"lastSeen": "Last seen",
|
"lastSeen": "Last seen",
|
||||||
|
"hwidFingerprint": "HWID fingerprint",
|
||||||
"deleteHwid": "Remove device",
|
"deleteHwid": "Remove device",
|
||||||
"deleteHwidConfirm": "Remove this device? It will need to re-register on its next subscription fetch.",
|
"deleteHwidConfirm": "Remove this device? It will need to re-register on its next subscription fetch.",
|
||||||
"hwidDeleted": "Device removed.",
|
"hwidDeleted": "Device removed.",
|
||||||
|
|||||||
@@ -762,6 +762,7 @@
|
|||||||
"noHwids": "Aún no hay dispositivos HWID",
|
"noHwids": "Aún no hay dispositivos HWID",
|
||||||
"firstSeen": "Visto por primera vez",
|
"firstSeen": "Visto por primera vez",
|
||||||
"lastSeen": "Visto por última vez",
|
"lastSeen": "Visto por última vez",
|
||||||
|
"hwidFingerprint": "Huella HWID",
|
||||||
"deleteHwid": "Eliminar dispositivo",
|
"deleteHwid": "Eliminar dispositivo",
|
||||||
"deleteHwidConfirm": "¿Eliminar este dispositivo? Deberá volver a registrarse en la próxima obtención de la suscripción.",
|
"deleteHwidConfirm": "¿Eliminar este dispositivo? Deberá volver a registrarse en la próxima obtención de la suscripción.",
|
||||||
"hwidDeleted": "Dispositivo eliminado.",
|
"hwidDeleted": "Dispositivo eliminado.",
|
||||||
|
|||||||
@@ -762,6 +762,7 @@
|
|||||||
"noHwids": "هنوز دستگاه HWID ثبت نشده است",
|
"noHwids": "هنوز دستگاه HWID ثبت نشده است",
|
||||||
"firstSeen": "اولین مشاهده",
|
"firstSeen": "اولین مشاهده",
|
||||||
"lastSeen": "آخرین مشاهده",
|
"lastSeen": "آخرین مشاهده",
|
||||||
|
"hwidFingerprint": "اثر انگشت HWID",
|
||||||
"deleteHwid": "حذف دستگاه",
|
"deleteHwid": "حذف دستگاه",
|
||||||
"deleteHwidConfirm": "این دستگاه حذف شود؟ در دریافت بعدی اشتراک باید دوباره ثبتنام شود.",
|
"deleteHwidConfirm": "این دستگاه حذف شود؟ در دریافت بعدی اشتراک باید دوباره ثبتنام شود.",
|
||||||
"hwidDeleted": "دستگاه حذف شد.",
|
"hwidDeleted": "دستگاه حذف شد.",
|
||||||
|
|||||||
@@ -762,6 +762,7 @@
|
|||||||
"noHwids": "Belum ada perangkat HWID",
|
"noHwids": "Belum ada perangkat HWID",
|
||||||
"firstSeen": "Pertama terlihat",
|
"firstSeen": "Pertama terlihat",
|
||||||
"lastSeen": "Terakhir terlihat",
|
"lastSeen": "Terakhir terlihat",
|
||||||
|
"hwidFingerprint": "Sidik jari HWID",
|
||||||
"deleteHwid": "Hapus perangkat",
|
"deleteHwid": "Hapus perangkat",
|
||||||
"deleteHwidConfirm": "Hapus perangkat ini? Perangkat perlu mendaftar ulang pada pengambilan langganan berikutnya.",
|
"deleteHwidConfirm": "Hapus perangkat ini? Perangkat perlu mendaftar ulang pada pengambilan langganan berikutnya.",
|
||||||
"hwidDeleted": "Perangkat dihapus.",
|
"hwidDeleted": "Perangkat dihapus.",
|
||||||
|
|||||||
@@ -762,6 +762,7 @@
|
|||||||
"noHwids": "HWID デバイスはまだありません",
|
"noHwids": "HWID デバイスはまだありません",
|
||||||
"firstSeen": "初回確認",
|
"firstSeen": "初回確認",
|
||||||
"lastSeen": "最終確認",
|
"lastSeen": "最終確認",
|
||||||
|
"hwidFingerprint": "HWID フィンガープリント",
|
||||||
"deleteHwid": "デバイスを削除",
|
"deleteHwid": "デバイスを削除",
|
||||||
"deleteHwidConfirm": "このデバイスを削除しますか?次回のサブスクリプション取得時に再登録が必要になります。",
|
"deleteHwidConfirm": "このデバイスを削除しますか?次回のサブスクリプション取得時に再登録が必要になります。",
|
||||||
"hwidDeleted": "デバイスを削除しました。",
|
"hwidDeleted": "デバイスを削除しました。",
|
||||||
|
|||||||
@@ -762,6 +762,7 @@
|
|||||||
"noHwids": "Ainda não há dispositivos HWID",
|
"noHwids": "Ainda não há dispositivos HWID",
|
||||||
"firstSeen": "Visto primeiro",
|
"firstSeen": "Visto primeiro",
|
||||||
"lastSeen": "Visto por último",
|
"lastSeen": "Visto por último",
|
||||||
|
"hwidFingerprint": "Impressão digital HWID",
|
||||||
"deleteHwid": "Remover dispositivo",
|
"deleteHwid": "Remover dispositivo",
|
||||||
"deleteHwidConfirm": "Remover este dispositivo? Ele precisará se registrar novamente na próxima busca da assinatura.",
|
"deleteHwidConfirm": "Remover este dispositivo? Ele precisará se registrar novamente na próxima busca da assinatura.",
|
||||||
"hwidDeleted": "Dispositivo removido.",
|
"hwidDeleted": "Dispositivo removido.",
|
||||||
|
|||||||
@@ -762,6 +762,7 @@
|
|||||||
"noHwids": "Устройств HWID пока нет",
|
"noHwids": "Устройств HWID пока нет",
|
||||||
"firstSeen": "Первое появление",
|
"firstSeen": "Первое появление",
|
||||||
"lastSeen": "Последнее появление",
|
"lastSeen": "Последнее появление",
|
||||||
|
"hwidFingerprint": "Отпечаток HWID",
|
||||||
"deleteHwid": "Удалить устройство",
|
"deleteHwid": "Удалить устройство",
|
||||||
"deleteHwidConfirm": "Удалить это устройство? При следующем запросе подписки оно зарегистрируется заново.",
|
"deleteHwidConfirm": "Удалить это устройство? При следующем запросе подписки оно зарегистрируется заново.",
|
||||||
"hwidDeleted": "Устройство удалено.",
|
"hwidDeleted": "Устройство удалено.",
|
||||||
|
|||||||
@@ -762,6 +762,7 @@
|
|||||||
"noHwids": "Henüz HWID cihazı yok",
|
"noHwids": "Henüz HWID cihazı yok",
|
||||||
"firstSeen": "İlk görülme",
|
"firstSeen": "İlk görülme",
|
||||||
"lastSeen": "Son görülme",
|
"lastSeen": "Son görülme",
|
||||||
|
"hwidFingerprint": "HWID parmak izi",
|
||||||
"deleteHwid": "Cihazı kaldır",
|
"deleteHwid": "Cihazı kaldır",
|
||||||
"deleteHwidConfirm": "Bu cihaz kaldırılsın mı? Bir sonraki abonelik alımında yeniden kaydolması gerekecek.",
|
"deleteHwidConfirm": "Bu cihaz kaldırılsın mı? Bir sonraki abonelik alımında yeniden kaydolması gerekecek.",
|
||||||
"hwidDeleted": "Cihaz kaldırıldı.",
|
"hwidDeleted": "Cihaz kaldırıldı.",
|
||||||
|
|||||||
@@ -762,6 +762,7 @@
|
|||||||
"noHwids": "Пристроїв HWID ще немає",
|
"noHwids": "Пристроїв HWID ще немає",
|
||||||
"firstSeen": "Перша поява",
|
"firstSeen": "Перша поява",
|
||||||
"lastSeen": "Остання поява",
|
"lastSeen": "Остання поява",
|
||||||
|
"hwidFingerprint": "Відбиток HWID",
|
||||||
"deleteHwid": "Видалити пристрій",
|
"deleteHwid": "Видалити пристрій",
|
||||||
"deleteHwidConfirm": "Видалити цей пристрій? Йому потрібно буде зареєструватися знову під час наступного отримання підписки.",
|
"deleteHwidConfirm": "Видалити цей пристрій? Йому потрібно буде зареєструватися знову під час наступного отримання підписки.",
|
||||||
"hwidDeleted": "Пристрій видалено.",
|
"hwidDeleted": "Пристрій видалено.",
|
||||||
|
|||||||
@@ -762,6 +762,7 @@
|
|||||||
"noHwids": "Chưa có thiết bị HWID",
|
"noHwids": "Chưa có thiết bị HWID",
|
||||||
"firstSeen": "Lần đầu thấy",
|
"firstSeen": "Lần đầu thấy",
|
||||||
"lastSeen": "Lần cuối thấy",
|
"lastSeen": "Lần cuối thấy",
|
||||||
|
"hwidFingerprint": "Dấu vân tay HWID",
|
||||||
"deleteHwid": "Xóa thiết bị",
|
"deleteHwid": "Xóa thiết bị",
|
||||||
"deleteHwidConfirm": "Xóa thiết bị này? Thiết bị sẽ cần đăng ký lại vào lần lấy gói đăng ký tiếp theo.",
|
"deleteHwidConfirm": "Xóa thiết bị này? Thiết bị sẽ cần đăng ký lại vào lần lấy gói đăng ký tiếp theo.",
|
||||||
"hwidDeleted": "Đã xóa thiết bị.",
|
"hwidDeleted": "Đã xóa thiết bị.",
|
||||||
|
|||||||
@@ -762,6 +762,7 @@
|
|||||||
"noHwids": "暂无 HWID 设备",
|
"noHwids": "暂无 HWID 设备",
|
||||||
"firstSeen": "首次出现",
|
"firstSeen": "首次出现",
|
||||||
"lastSeen": "最后出现",
|
"lastSeen": "最后出现",
|
||||||
|
"hwidFingerprint": "HWID 指纹",
|
||||||
"deleteHwid": "移除设备",
|
"deleteHwid": "移除设备",
|
||||||
"deleteHwidConfirm": "移除此设备?下次获取订阅时它将需要重新注册。",
|
"deleteHwidConfirm": "移除此设备?下次获取订阅时它将需要重新注册。",
|
||||||
"hwidDeleted": "设备已移除。",
|
"hwidDeleted": "设备已移除。",
|
||||||
|
|||||||
@@ -762,6 +762,7 @@
|
|||||||
"noHwids": "尚無 HWID 裝置",
|
"noHwids": "尚無 HWID 裝置",
|
||||||
"firstSeen": "首次出現",
|
"firstSeen": "首次出現",
|
||||||
"lastSeen": "最後出現",
|
"lastSeen": "最後出現",
|
||||||
|
"hwidFingerprint": "HWID 指紋",
|
||||||
"deleteHwid": "移除裝置",
|
"deleteHwid": "移除裝置",
|
||||||
"deleteHwidConfirm": "移除此裝置?下次取得訂閱時它將需要重新註冊。",
|
"deleteHwidConfirm": "移除此裝置?下次取得訂閱時它將需要重新註冊。",
|
||||||
"hwidDeleted": "裝置已移除。",
|
"hwidDeleted": "裝置已移除。",
|
||||||
|
|||||||
Reference in New Issue
Block a user