Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 131 additions & 10 deletions app/api/client/download/route.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,126 @@
import { NextResponse } from 'next/server';
import { query } from '@/lib/db';
import {
generateClientConfig,
InboundConfig,
ServerInfo,
UserCredentials
} from '@/lib/config-generators';
import { getPkiService } from '@/lib/pki-service';

Check failure on line 9 in app/api/client/download/route.ts

View workflow job for this annotation

GitHub Actions / build-and-test

Module '"@/lib/pki-service"' has no exported member 'getPkiService'.

export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const username = searchParams.get('username');
const inboundId = searchParams.get('inbound');
const protocol = searchParams.get('protocol');

if (!username) {
return NextResponse.json({ error: 'Username is required' }, { status: 400 });
}

export async function GET() {
try {
const configContent = "vless://example-config-content";
const filename = "config.conf";

return new Response(configContent, {
headers: {
'Content-Type': 'application/octet-stream',
'Content-Disposition': `attachment; filename="${filename}"`,
},
});
// Get user
const users = await query(
'SELECT * FROM vpn_users WHERE username = ? LIMIT 1',
[username]
);

if (!users || users.length === 0) {
return NextResponse.json({ error: 'User not found' }, { status: 404 });
}

const user = users[0];

// Get inbound config
let inbound: InboundConfig | null = null;

if (inboundId) {
const inbounds = await query(
'SELECT * FROM vpn_inbounds WHERE id = ? LIMIT 1',
[parseInt(inboundId)]
);
inbound = inbounds[0] || null;
} else if (protocol) {
const inbounds = await query(
'SELECT * FROM vpn_inbounds WHERE protocol = ? AND status = ? LIMIT 1',
[protocol, 'active']
);
inbound = inbounds[0] || null;
}

if (!inbound) {
return NextResponse.json({ error: 'Inbound not found' }, { status: 404 });
}

// Get server info
const servers = await query(
'SELECT * FROM vpn_servers WHERE is_active = 1 ORDER BY load_score ASC LIMIT 1'
);

const server: ServerInfo = servers[0] || {
ip_address: '127.0.0.1',
domain: null,
ports: [443]
};

const userCreds: UserCredentials = {
username: user.username,
password: user.password_hash ? undefined : user.password,
uuid: user.xray_uuid,
wg_pubkey: user.wg_pubkey,
wg_ip: user.wg_ip
};

// Generate config based on protocol
let config: any;

if (inbound.protocol === 'openvpn') {
// Get PKI certs for OpenVPN
const pkiService = getPkiService();
const pki = await pkiService.generateClientCertificate(user.username);
config = generateClientConfig(inbound.protocol, server, inbound, userCreds, pki);
} else if (inbound.protocol === 'wireguard') {
// For WireGuard, we need to generate a client private key
// In production, this would be stored per-user
const clientPrivateKey = user.wg_privkey || generateWireGuardPrivateKey();
config = generateClientConfig(inbound.protocol, server, inbound, userCreds, undefined, clientPrivateKey);
} else {
config = generateClientConfig(inbound.protocol, server, inbound, userCreds);
}

// Return based on config type
if (config.type === 'file' && config.content) {
return new Response(config.content, {
headers: {
'Content-Type': config.mimeType || 'application/octet-stream',
'Content-Disposition': `attachment; filename="${config.filename}"`,
},
});
}

// For URL-based configs (Xray protocols)
if (config.type === 'url' && config.url) {
return NextResponse.json({
success: true,
protocol: inbound.protocol,
url: config.url,
qrData: config.qrData,
config: config.config
});
}

// For instruction-based configs (Cisco, L2TP)
if (config.type === 'instructions') {
return NextResponse.json({
success: true,
protocol: inbound.protocol,
...config
});
}

return NextResponse.json({ error: 'Unable to generate config' }, { status: 500 });
} catch (error: any) {
console.error('Download error:', error);
return NextResponse.json({
error: {
code: 'DOWNLOAD_FAILED',
Expand All @@ -21,3 +130,15 @@
}, { status: 500 });
}
}

// Helper function to generate WireGuard private key
function generateWireGuardPrivateKey(): string {
// In production, use proper crypto
// This is a placeholder that generates a base64-like string
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
let key = '';
for (let i = 0; i < 43; i++) {
key += chars.charAt(Math.floor(Math.random() * chars.length));
}
return key + '=';
}
162 changes: 157 additions & 5 deletions app/api/inbounds/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { NextResponse } from 'next/server';
import db from '@/lib/db';

// All supported protocols
const SUPPORTED_PROTOCOLS = [
'openvpn', 'wireguard', 'cisco', 'l2tp',
'vless', 'vmess', 'trojan', 'shadowsocks'
];

export async function GET() {
try {
const [inbounds] = await db.execute('SELECT * FROM vpn_inbounds ORDER BY created_at DESC');
Expand All @@ -13,18 +19,164 @@ export async function GET() {

export async function POST(req: Request) {
try {
const { name, protocol, port, remark } = await req.json();
const body = await req.json();
const {
name,
protocol,
port,
remark,
// OpenVPN fields
ovpn_protocol,
ovpn_cipher,
ovpn_auth,
ovpn_dev,
// WireGuard fields
wg_private_key,
wg_public_key,
wg_address,
wg_dns,
wg_mtu,
// Cisco fields
cisco_auth_method,
cisco_max_clients,
cisco_dpd,
// L2TP fields
l2tp_psk,
l2tp_dns,
l2tp_local_ip,
l2tp_remote_ip_range,
// Xray fields
xray_uuid,
xray_flow,
xray_network,
xray_security,
xray_sni,
xray_fingerprint,
xray_public_key,
xray_short_id,
xray_path,
xray_service_name,
xray_encryption,
} = body;

// Validate required fields
if (!name || !protocol || !port) {
return NextResponse.json({ error: 'Name, protocol, and port are required' }, { status: 400 });
}

const [result] = await db.execute(
'INSERT INTO vpn_inbounds (name, protocol, port, remark) VALUES (?, ?, ?, ?)',
[name, protocol, parseInt(port, 10), remark || '']
// Validate protocol
if (!SUPPORTED_PROTOCOLS.includes(protocol)) {
return NextResponse.json({
error: `Invalid protocol. Supported: ${SUPPORTED_PROTOCOLS.join(', ')}`
}, { status: 400 });
}

// Check for port conflicts on same protocol
const [existingPorts] = await db.execute(
'SELECT * FROM vpn_inbounds WHERE port = ? AND protocol = ?',
[parseInt(port, 10), protocol]
);

if (Array.isArray(existingPorts) && existingPorts.length > 0) {
return NextResponse.json({
error: `Port ${port} is already in use for ${protocol} protocol`
}, { status: 400 });
}

// Build SQL based on protocol type
const columns = ['name', 'protocol', 'port', 'remark', 'status'];
const values: any[] = [name, protocol, parseInt(port, 10), remark || '', 'active'];
const placeholders = ['?', '?', '?', '?', '?'];

// Add protocol-specific fields
switch (protocol) {
case 'openvpn':
columns.push('ovpn_protocol', 'ovpn_cipher', 'ovpn_auth', 'ovpn_dev');
values.push(
ovpn_protocol || 'udp',
ovpn_cipher || 'AES-256-GCM',
ovpn_auth || 'SHA256',
ovpn_dev || 'tun'
);
placeholders.push('?', '?', '?', '?');
break;

case 'wireguard':
columns.push('wg_private_key', 'wg_public_key', 'wg_address', 'wg_dns', 'wg_mtu');
values.push(
wg_private_key || '',
wg_public_key || '',
wg_address || '10.0.0.1/24',
wg_dns || '1.1.1.1',
wg_mtu || 1420
);
placeholders.push('?', '?', '?', '?', '?');
break;

case 'cisco':
columns.push('cisco_auth_method', 'cisco_max_clients', 'cisco_dpd');
values.push(
cisco_auth_method || 'password',
cisco_max_clients || 100,
cisco_dpd || 90
);
placeholders.push('?', '?', '?');
break;

case 'l2tp':
columns.push('l2tp_psk', 'l2tp_dns', 'l2tp_local_ip', 'l2tp_remote_ip_range');
values.push(
l2tp_psk || '',
l2tp_dns || '8.8.8.8',
l2tp_local_ip || '10.10.10.1',
l2tp_remote_ip_range || '10.10.10.2-10.10.10.254'
);
placeholders.push('?', '?', '?', '?');
break;

case 'vless':
case 'vmess':
case 'trojan':
columns.push(
'xray_protocol', 'xray_uuid', 'xray_flow', 'xray_network',
'xray_security', 'xray_sni', 'xray_fingerprint',
'xray_public_key', 'xray_short_id', 'xray_path', 'xray_service_name'
);
values.push(
protocol,
xray_uuid || '',
xray_flow || '',
xray_network || 'tcp',
xray_security || 'reality',
xray_sni || 'www.google.com',
xray_fingerprint || 'chrome',
xray_public_key || '',
xray_short_id || '',
xray_path || '/ws',
xray_service_name || 'grpc'
);
placeholders.push('?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?');
break;

case 'shadowsocks':
columns.push('xray_protocol', 'xray_encryption', 'xray_network');
values.push(
'shadowsocks',
xray_encryption || 'chacha20-ietf-poly1305',
xray_network || 'tcp'
);
placeholders.push('?', '?', '?');
break;
}

const sql = `INSERT INTO vpn_inbounds (${columns.join(', ')}) VALUES (${placeholders.join(', ')})`;
const [result] = await db.execute(sql, values);

return NextResponse.json({ success: true, id: (result as any).insertId });
return NextResponse.json({
success: true,
id: (result as any).insertId,
message: `${protocol.toUpperCase()} inbound created successfully`
});
} catch (error: any) {
console.error('Error creating inbound:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
Expand Down
Loading
Loading