Fix linking existing VPN clients (WireGuard/service protocols and API errors).
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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"])
|
||||
|
||||
Reference in New Issue
Block a user