Fix linking existing VPN clients (WireGuard/service protocols and API errors).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
orohi
2026-07-26 00:31:57 +03:00
co-authored by Cursor
parent 69886f130c
commit e612a7bc34
8 changed files with 109 additions and 46 deletions
+73 -34
View File
@@ -956,10 +956,17 @@ def _manager_call(manager, method, protocol, *args, **kwargs):
"""Unified call: WireGuard manager methods don't take protocol_type argument."""
fn = getattr(manager, method)
if isinstance(manager, WireGuardManager):
# WireGuard signatures omit protocol and often omit port.
if method in ('get_client_config', 'add_client') and len(args) >= 2:
return fn(args[0], args[1])
return fn(*args, **kwargs)
return fn(protocol, *args, **kwargs)
# Protocols that own VPN clients (can list/link connections)
CLIENT_VPN_BASES = {'awg', 'awg2', 'awg_legacy', 'xray', 'telemt', 'wireguard'}
def generate_vpn_link(config_text):
b64 = base64.b64encode(config_text.strip().encode('utf-8')).decode('utf-8')
return f"vpn://{b64}"
@@ -3616,22 +3623,27 @@ async def api_add_user_connection(request: Request, user_id: str, req: AddUserCo
return JSONResponse({'error': 'User not found'}, status_code=404)
if req.server_id >= len(data['servers']):
return JSONResponse({'error': 'Server not found'}, status_code=404)
if protocol_base(req.protocol) not in CLIENT_VPN_BASES:
return JSONResponse(
{'error': f'Protocol "{req.protocol}" does not support user connections'},
status_code=400,
)
server = data['servers'][req.server_id]
proto_info = server.get('protocols', {}).get(req.protocol, {})
port = proto_info.get('port', '55424')
ssh = get_ssh(server)
await asyncio.to_thread(ssh.connect)
manager = get_protocol_manager(ssh, req.protocol)
if req.client_id:
# Use existing client
target_client_id = req.client_id
# Retrieve config for existing client
config = await asyncio.to_thread(manager.get_client_config, req.protocol, req.client_id, server['host'], port)
result = {'client_id': target_client_id, 'config': config}
else:
# Create new client
if protocol_base(req.protocol) == 'telemt':
try:
manager = get_protocol_manager(ssh, req.protocol)
if req.client_id:
# Link existing client
config = await asyncio.to_thread(
_manager_call, manager, 'get_client_config',
req.protocol, req.client_id, server['host'], port,
)
result = {'client_id': req.client_id, 'config': config}
elif protocol_base(req.protocol) == 'telemt':
result = await asyncio.to_thread(
manager.add_client, req.protocol, req.name, server['host'], port,
telemt_quota=req.telemt_quota,
@@ -3639,12 +3651,17 @@ async def api_add_user_connection(request: Request, user_id: str, req: AddUserCo
telemt_expiry=req.telemt_expiry,
secret=req.telemt_secret,
user_ad_tag=req.telemt_ad_tag,
max_tcp_conns=req.telemt_max_conns
max_tcp_conns=req.telemt_max_conns,
)
elif protocol_base(req.protocol) == 'wireguard':
result = await asyncio.to_thread(manager.add_client, req.name, server['host'])
else:
result = await asyncio.to_thread(manager.add_client, req.protocol, req.name, server['host'], port)
await asyncio.to_thread(ssh.disconnect)
result = await asyncio.to_thread(
_manager_call, manager, 'add_client',
req.protocol, req.name, server['host'], port,
)
finally:
await asyncio.to_thread(ssh.disconnect)
if result.get('client_id'):
conn = {
@@ -3656,9 +3673,10 @@ async def api_add_user_connection(request: Request, user_id: str, req: AddUserCo
'name': req.name,
'created_at': datetime.now().isoformat(),
}
data = load_data()
data['user_connections'].append(conn)
save_data(data)
async with DATA_LOCK:
data = load_data()
data['user_connections'].append(conn)
save_data(data)
resp = {'status': 'success'}
if result.get('config'):
@@ -3667,7 +3685,7 @@ async def api_add_user_connection(request: Request, user_id: str, req: AddUserCo
return resp
except Exception as e:
logger.exception("Error adding user connection")
return JSONResponse({'error': str(e)}, status_code=500)
return JSONResponse({'error': str(e) or e.__class__.__name__}, status_code=500)
@app.get('/api/users/{user_id}/connections', tags=["Users"])
@@ -4092,31 +4110,52 @@ async def api_get_server_clients(request: Request, server_id: int, protocol: str
if not _check_admin(request):
return JSONResponse({'error': 'Forbidden'}, status_code=403)
try:
if not protocol or protocol_base(protocol) not in CLIENT_VPN_BASES:
return JSONResponse(
{'error': f'Protocol "{protocol}" does not support client listing'},
status_code=400,
)
data = load_data()
if server_id >= len(data['servers']):
return JSONResponse({'error': 'Server not found'}, status_code=404)
server = data['servers'][server_id]
ssh = get_ssh(server)
ssh.connect()
manager = get_protocol_manager(ssh, protocol)
clients = manager.get_clients(protocol)
ssh.disconnect()
await asyncio.to_thread(ssh.connect)
try:
manager = get_protocol_manager(ssh, protocol)
if not hasattr(manager, 'get_clients'):
return JSONResponse(
{'error': f'Manager for "{protocol}" has no get_clients'},
status_code=400,
)
clients = await asyncio.to_thread(_manager_call, manager, 'get_clients', protocol)
finally:
await asyncio.to_thread(ssh.disconnect)
# Filter: only show clients that are not assigned to anyone in the panel
assigned_ids = {c['client_id'] for c in data.get('user_connections', []) if c['server_id'] == server_id and c['protocol'] == protocol}
assigned_ids = {
c['client_id']
for c in data.get('user_connections', [])
if c.get('server_id') == server_id and c.get('protocol') == protocol
}
filtered = []
for c in clients:
if c['clientId'] not in assigned_ids:
filtered.append({
'id': c['clientId'],
'name': c.get('userData', {}).get('clientName', 'Unnamed')
})
for c in clients or []:
cid = c.get('clientId') or c.get('client_id') or c.get('id')
if not cid or cid in assigned_ids:
continue
filtered.append({
'id': cid,
'name': (c.get('userData') or {}).get('clientName')
or c.get('name')
or c.get('email')
or 'Unnamed',
})
return {'clients': filtered}
except Exception as e:
logger.exception("Error getting server clients")
return JSONResponse({'error': str(e)}, status_code=500)
return JSONResponse({'error': str(e) or e.__class__.__name__}, status_code=500)
@app.get('/api/settings/tokens', tags=["API Tokens"])