forked from mirrors/amnezia-wg-easy
wip
This commit is contained in:
parent
eaf3d5c3fb
commit
d7bb645470
25 changed files with 4632 additions and 2 deletions
104
src/lib/Server.js
Normal file
104
src/lib/Server.js
Normal file
|
@ -0,0 +1,104 @@
|
|||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const express = require('express');
|
||||
const expressSession = require('express-session');
|
||||
const debug = require('debug')('Server');
|
||||
|
||||
const Util = require('./Util');
|
||||
const ServerError = require('./ServerError');
|
||||
const WireGuard = require('../services/WireGuard');
|
||||
|
||||
const {
|
||||
PORT,
|
||||
PASSWORD,
|
||||
} = require('../config');
|
||||
|
||||
module.exports = class Server {
|
||||
|
||||
constructor() {
|
||||
// Express
|
||||
this.app = express()
|
||||
.use('/', express.static(path.join(__dirname, '..', 'www')))
|
||||
.use(express.json())
|
||||
.use(expressSession({
|
||||
secret: String(Math.random()),
|
||||
resave: true,
|
||||
saveUninitialized: true,
|
||||
}))
|
||||
|
||||
// Authentication
|
||||
.get('/api/session', Util.promisify(async req => {
|
||||
return {
|
||||
authenticated: !!(req.session && req.session.authenticated),
|
||||
};
|
||||
}))
|
||||
.post('/api/session', Util.promisify(async req => {
|
||||
const {
|
||||
password,
|
||||
} = req.body;
|
||||
|
||||
if (typeof password !== 'string') {
|
||||
throw new ServerError('Missing: Password', 401);
|
||||
}
|
||||
|
||||
if (password !== PASSWORD) {
|
||||
throw new ServerError('Incorrect Password', 401);
|
||||
}
|
||||
|
||||
req.session.authenticated = true;
|
||||
req.session.save();
|
||||
|
||||
debug(`New Session: ${req.session.id})`);
|
||||
}))
|
||||
|
||||
// WireGuard
|
||||
.use(Util.requireSession)
|
||||
.delete('/api/session', Util.promisify(async req => {
|
||||
const sessionId = req.session.id;
|
||||
|
||||
req.session.destroy();
|
||||
|
||||
debug(`Deleted Session: ${sessionId}`);
|
||||
}))
|
||||
.get('/api/wireguard/client', Util.promisify(async req => {
|
||||
return WireGuard.getClients();
|
||||
}))
|
||||
.get('/api/wireguard/client/:clientId/qrcode.svg', Util.promisify(async (req, res) => {
|
||||
const { clientId } = req.params;
|
||||
const svg = await WireGuard.getClientQRCodeSVG({ clientId });
|
||||
res.header('Content-Type', 'image/svg+xml');
|
||||
res.send(svg);
|
||||
}))
|
||||
.get('/api/wireguard/client/:clientId/configuration', Util.promisify(async (req, res) => {
|
||||
const { clientId } = req.params;
|
||||
const client = await WireGuard.getClient({ clientId });
|
||||
const config = await WireGuard.getClientConfiguration({ clientId });
|
||||
res.header('Content-Disposition', `attachment; filename="${client.name}.conf"`);
|
||||
res.header('Content-Type', 'text/plain');
|
||||
res.send(config);
|
||||
}))
|
||||
.post('/api/wireguard/client', Util.promisify(async req => {
|
||||
const { name } = req.body;
|
||||
return WireGuard.createClient({ name });
|
||||
}))
|
||||
.delete('/api/wireguard/client/:clientId', Util.promisify(async req => {
|
||||
const { clientId } = req.params;
|
||||
return WireGuard.deleteClient({ clientId });
|
||||
}))
|
||||
.post('/api/wireguard/client/:clientId/enable', Util.promisify(async req => {
|
||||
const { clientId } = req.params;
|
||||
return WireGuard.enableClient({ clientId });
|
||||
}))
|
||||
.post('/api/wireguard/client/:clientId/disable', Util.promisify(async req => {
|
||||
const { clientId } = req.params;
|
||||
return WireGuard.disableClient({ clientId });
|
||||
}))
|
||||
|
||||
.listen(PORT, () => {
|
||||
debug(`Listening on http://0.0.0.0:${PORT}`);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
10
src/lib/ServerError.js
Normal file
10
src/lib/ServerError.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = class ServerError extends Error {
|
||||
|
||||
constructor(message, statusCode = 500) {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
};
|
46
src/lib/Util.js
Normal file
46
src/lib/Util.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = class Util {
|
||||
|
||||
static requireSession(req, res, next) {
|
||||
if (req.session && req.session.authenticated) {
|
||||
return next();
|
||||
}
|
||||
|
||||
return res.status(401).json({
|
||||
error: 'Not Logged In',
|
||||
});
|
||||
}
|
||||
|
||||
static promisify(fn) {
|
||||
return function(req, res) {
|
||||
Promise.resolve().then(async () => fn(req, res))
|
||||
.then(result => {
|
||||
if (res.headersSent) return;
|
||||
|
||||
if (typeof result === 'undefined') {
|
||||
return res
|
||||
.status(204)
|
||||
.end();
|
||||
}
|
||||
|
||||
return res
|
||||
.status(200)
|
||||
.json(result);
|
||||
})
|
||||
.catch(error => {
|
||||
if (typeof error === 'string') {
|
||||
error = new Error(error);
|
||||
}
|
||||
|
||||
return res
|
||||
.status(error.statusCode || 500)
|
||||
.json({
|
||||
error: error.message || error.toString(),
|
||||
stack: error.stack,
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
};
|
183
src/lib/WireGuard.js
Normal file
183
src/lib/WireGuard.js
Normal file
|
@ -0,0 +1,183 @@
|
|||
'use strict';
|
||||
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
|
||||
const QRCode = require('qrcode');
|
||||
|
||||
const ServerError = require('./ServerError');
|
||||
|
||||
const {
|
||||
WG_PATH,
|
||||
WG_HOST,
|
||||
WG_PORT,
|
||||
WG_DEFAULT_ADDRESS,
|
||||
} = require('../config');
|
||||
|
||||
module.exports = class WireGuard {
|
||||
|
||||
constructor() {
|
||||
Promise.resolve().then(async () => {
|
||||
try {
|
||||
const config = await fs.readFile(path.join(WG_PATH, 'wg0.json'), 'utf8');
|
||||
this.config = JSON.parse(config);
|
||||
} catch (err) {
|
||||
this.config = {
|
||||
// TODO: Generate new config
|
||||
server: {
|
||||
address: WG_DEFAULT_ADDRESS,
|
||||
},
|
||||
clients: {},
|
||||
};
|
||||
// TODO: Save JSON config
|
||||
}
|
||||
|
||||
console.log('this.config', this.config);
|
||||
|
||||
await this.saveConfig();
|
||||
}).catch(err => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
|
||||
// eslint-disable-next-line no-process-exit
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
async saveConfig() {
|
||||
let result = `
|
||||
# Note: Do not edit this file directly.
|
||||
# Your changes will be overwritten!
|
||||
|
||||
# Server
|
||||
[Interface]
|
||||
PrivateKey = ${this.config.server.privateKey}
|
||||
Address = ${this.config.server.address}
|
||||
ListenPort = ${this.config.server.port}
|
||||
DNS = ${this.config.server.dns}`;
|
||||
|
||||
for (const [clientId, client] of Object.entries(this.config.clients)) {
|
||||
if (!client.enabled) continue;
|
||||
|
||||
result += `
|
||||
|
||||
# Client: ${client.name} (${clientId})
|
||||
[Peer]
|
||||
PublicKey = ${client.publicKey}
|
||||
PresharedKey = ${client.preSharedKey}
|
||||
AllowedIPs = ${client.allowedIPs}`;
|
||||
}
|
||||
|
||||
await fs.writeFile(path.join(WG_PATH, 'wg0.json'), JSON.stringify(this.config, false, 2));
|
||||
await fs.writeFile(path.join(WG_PATH, 'wg0.conf'), result);
|
||||
}
|
||||
|
||||
async getClients() {
|
||||
return Object.entries(this.config.clients).map(([clientId, client]) => ({
|
||||
id: clientId,
|
||||
name: client.name,
|
||||
enabled: client.enabled,
|
||||
publicKey: client.publicKey,
|
||||
createdAt: client.createdAt,
|
||||
updatedAt: client.updatedAt,
|
||||
allowedIPs: client.allowedIPs,
|
||||
|
||||
// TODO:
|
||||
latestHandshake: new Date(),
|
||||
transferRx: 0,
|
||||
transferTx: 0,
|
||||
}));
|
||||
// const { stdout } = await Util.exec('sudo cat /etc/wireguard/configs/clients.txt');
|
||||
// return stdout
|
||||
// .trim()
|
||||
// .split('\n')
|
||||
// .filter(line => {
|
||||
// return line.length > 0;
|
||||
// })
|
||||
// .map(line => {
|
||||
// const [ name, publicKey, createdAt ] = line.split(' ');
|
||||
// return {
|
||||
// name,
|
||||
// publicKey,
|
||||
// createdAt: new Date(Number(createdAt + '000')),
|
||||
// };
|
||||
// });
|
||||
}
|
||||
|
||||
async getClient({ clientId }) {
|
||||
const client = this.config.clients[clientId];
|
||||
if (!client) {
|
||||
throw new ServerError(`Client Not Found: ${clientId}`, 404);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
async getClientConfiguration({ clientId }) {
|
||||
const client = await this.getClient({ clientId });
|
||||
|
||||
return `
|
||||
[Interface]
|
||||
PrivateKey = ${client.privateKey}
|
||||
Address = ${client.address}
|
||||
DNS = ${this.config.server.dns}
|
||||
|
||||
[Peer]
|
||||
PublicKey = ${client.publicKey}
|
||||
PresharedKey = ${client.preSharedKey}
|
||||
AllowedIPs = ${client.allowedIPs}
|
||||
Endpoint = ${WG_HOST}:${WG_PORT}`;
|
||||
}
|
||||
|
||||
async getClientQRCodeSVG({ clientId }) {
|
||||
const config = await this.getClientConfiguration({ clientId });
|
||||
return QRCode.toString(config, {
|
||||
type: 'svg',
|
||||
width: 512,
|
||||
});
|
||||
}
|
||||
|
||||
async createClient({ name }) {
|
||||
if (!name) {
|
||||
throw new Error('Missing: Name');
|
||||
}
|
||||
|
||||
// try {
|
||||
// await this.getClient({ name });
|
||||
// throw new Error(`Duplicate Client: ${name}`);
|
||||
// } catch( err ) {
|
||||
// if( err.message.startsWith('Duplicate Client') ) {
|
||||
// throw err;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // TODO: This is unsafe
|
||||
// await this.ssh.exec(`pivpn add -n ${name}`);
|
||||
|
||||
// return this.getClient({ name });
|
||||
|
||||
await this.saveConfig();
|
||||
}
|
||||
|
||||
async deleteClient({ clientId }) {
|
||||
if (this.config.clients[clientId]) {
|
||||
delete this.config.clients[clientId];
|
||||
await this.saveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
async enableClient({ clientId }) {
|
||||
const client = await this.getClient({ clientId });
|
||||
client.enabled = true;
|
||||
|
||||
await this.saveConfig();
|
||||
}
|
||||
|
||||
async disableClient({ clientId }) {
|
||||
const client = await this.getClient({ clientId });
|
||||
client.enabled = false;
|
||||
|
||||
await this.saveConfig();
|
||||
}
|
||||
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue