Browse Source

refactor!: migrate from express to h3

pull/880/head
cany748 1 year ago
parent
commit
e8a160b14f
  1. 217
      src/lib/Server.js
  2. 10
      src/lib/ServerError.js
  3. 115
      src/package-lock.json
  4. 1
      src/package.json
  5. 3
      src/www/index.html

217
src/lib/Server.js

@ -1,15 +1,27 @@
'use strict';
const path = require('path');
const bcrypt = require('bcryptjs');
const crypto = require('node:crypto');
const { createServer } = require('node:http');
const { stat, readFile } = require('node:fs/promises');
const { join } = require('node:path');
const express = require('express');
const expressSession = require('express-session');
const debug = require('debug')('Server');
const Util = require('./Util');
const ServerError = require('./ServerError');
const {
createApp,
createError,
createRouter,
defineEventHandler,
fromNodeMiddleware,
getRouterParam,
toNodeListener,
readBody,
setHeader,
serveStatic,
} = require('h3');
const WireGuard = require('../services/WireGuard');
const {
@ -23,33 +35,39 @@ const {
module.exports = class Server {
constructor() {
// Express
this.app = express()
.disable('etag')
.use('/', express.static(path.join(__dirname, '..', 'www')))
.use(express.json())
.use(expressSession({
secret: crypto.randomBytes(256).toString('hex'),
resave: true,
saveUninitialized: true,
cookie: {
httpOnly: true,
},
}))
const app = createApp();
this.app = app;
app.use(fromNodeMiddleware(expressSession({
secret: crypto.randomBytes(256).toString('hex'),
resave: true,
saveUninitialized: true,
cookie: {
httpOnly: true,
},
})));
const router = createRouter();
app.use(router);
router
.get('/api/release', defineEventHandler((event) => {
setHeader(event, 'Content-Type', 'application/json');
.get('/api/release', (Util.promisify(async () => {
return RELEASE;
})))
}))
.get('/api/lang', (Util.promisify(async () => {
return LANG;
})))
.get('/api/lang', defineEventHandler((event) => {
setHeader(event, 'Content-Type', 'application/json');
return `"${LANG}"`;
}))
// Authentication
.get('/api/session', Util.promisify(async (req) => {
.get('/api/session', defineEventHandler((event) => {
const requiresPassword = !!process.env.PASSWORD;
const authenticated = requiresPassword
? !!(req.session && req.session.authenticated)
? !!(event.node.req.session && event.node.req.session.authenticated)
: true;
return {
@ -57,28 +75,35 @@ module.exports = class Server {
authenticated,
};
}))
.post('/api/session', Util.promisify(async (req) => {
const {
password,
} = req.body;
.post('/api/session', defineEventHandler(async (event) => {
const { password } = await readBody(event);
if (typeof password !== 'string') {
throw new ServerError('Missing: Password', 401);
throw createError({
status: 401,
message: 'Missing: Password',
});
}
if (password !== PASSWORD) {
throw new ServerError('Incorrect Password', 401);
throw createError({
status: 401,
message: 'Incorrect Password',
});
}
req.session.authenticated = true;
req.session.save();
event.node.req.session.authenticated = true;
event.node.req.session.save();
debug(`New Session: ${req.session.id}`);
}))
debug(`New Session: ${event.node.req.session.id}`);
return { succcess: true };
}));
// WireGuard
.use((req, res, next) => {
if (!PASSWORD) {
app.use(
fromNodeMiddleware((req, res, next) => {
if (!PASSWORD || !req.url.startsWith('/api/')) {
return next();
}
@ -86,7 +111,7 @@ module.exports = class Server {
return next();
}
if (req.path.startsWith('/api/') && req.headers['authorization']) {
if (req.url.startsWith('/api/') && req.headers['authorization']) {
if (bcrypt.compareSync(req.headers['authorization'], bcrypt.hashSync(PASSWORD, 10))) {
return next();
}
@ -98,25 +123,32 @@ module.exports = class Server {
return res.status(401).json({
error: 'Not Logged In',
});
})
.delete('/api/session', Util.promisify(async (req) => {
const sessionId = req.session.id;
}),
);
req.session.destroy();
const router2 = createRouter();
app.use(router2);
router2
.delete('/api/session', defineEventHandler((event) => {
const sessionId = event.node.req.session.id;
event.node.req.session.destroy();
debug(`Deleted Session: ${sessionId}`);
return { success: true };
}))
.get('/api/wireguard/client', Util.promisify(async (req) => {
.get('/api/wireguard/client', defineEventHandler(() => {
return WireGuard.getClients();
}))
.get('/api/wireguard/client/:clientId/qrcode.svg', Util.promisify(async (req, res) => {
const { clientId } = req.params;
.get('/api/wireguard/client/:clientId/qrcode.svg', defineEventHandler(async (event) => {
const clientId = getRouterParam(event, 'clientId');
const svg = await WireGuard.getClientQRCodeSVG({ clientId });
res.header('Content-Type', 'image/svg+xml');
res.send(svg);
setHeader(event, 'Content-Type', 'image/svg+xml');
return svg;
}))
.get('/api/wireguard/client/:clientId/configuration', Util.promisify(async (req, res) => {
const { clientId } = req.params;
.get('/api/wireguard/client/:clientId/configuration', defineEventHandler(async (event) => {
const clientId = getRouterParam(event, 'clientId');
const client = await WireGuard.getClient({ clientId });
const config = await WireGuard.getClientConfiguration({ clientId });
const configName = client.name
@ -124,52 +156,79 @@ module.exports = class Server {
.replace(/(-{2,}|-$)/g, '-')
.replace(/-$/, '')
.substring(0, 32);
res.header('Content-Disposition', `attachment; filename="${configName || clientId}.conf"`);
res.header('Content-Type', 'text/plain');
res.send(config);
setHeader(event, 'Content-Disposition', `attachment; filename="${configName || clientId}.conf"`);
setHeader(event, 'Content-Type', 'text/plain');
return config;
}))
.post('/api/wireguard/client', Util.promisify(async (req) => {
const { name } = req.body;
return WireGuard.createClient({ name });
.post('/api/wireguard/client', defineEventHandler(async (event) => {
const { name } = await readBody(event);
await WireGuard.createClient({ name });
return { success: true };
}))
.delete('/api/wireguard/client/:clientId', Util.promisify(async (req) => {
const { clientId } = req.params;
return WireGuard.deleteClient({ clientId });
.delete('/api/wireguard/client/:clientId', defineEventHandler(async (event) => {
const clientId = getRouterParam(event, 'clientId');
await WireGuard.deleteClient({ clientId });
return { success: true };
}))
.post('/api/wireguard/client/:clientId/enable', Util.promisify(async (req, res) => {
const { clientId } = req.params;
.post('/api/wireguard/client/:clientId/enable', defineEventHandler(async (event) => {
const clientId = getRouterParam(event, 'clientId');
if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') {
res.end(403);
throw createError({ status: 403 });
}
return WireGuard.enableClient({ clientId });
await WireGuard.enableClient({ clientId });
return { success: true };
}))
.post('/api/wireguard/client/:clientId/disable', Util.promisify(async (req, res) => {
const { clientId } = req.params;
.post('/api/wireguard/client/:clientId/disable', defineEventHandler(async (event) => {
const clientId = getRouterParam(event, 'clientId');
if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') {
res.end(403);
throw createError({ status: 403 });
}
return WireGuard.disableClient({ clientId });
await WireGuard.disableClient({ clientId });
return { success: true };
}))
.put('/api/wireguard/client/:clientId/name', Util.promisify(async (req, res) => {
const { clientId } = req.params;
.put('/api/wireguard/client/:clientId/name', defineEventHandler(async (event) => {
const clientId = getRouterParam(event, 'clientId');
if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') {
res.end(403);
throw createError({ status: 403 });
}
const { name } = req.body;
return WireGuard.updateClientName({ clientId, name });
const { name } = await readBody(event);
await WireGuard.updateClientName({ clientId, name });
return { success: true };
}))
.put('/api/wireguard/client/:clientId/address', Util.promisify(async (req, res) => {
const { clientId } = req.params;
.put('/api/wireguard/client/:clientId/address', defineEventHandler(async (event) => {
const clientId = getRouterParam(event, 'clientId');
if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') {
res.end(403);
throw createError({ status: 403 });
}
const { address } = req.body;
return WireGuard.updateClientAddress({ clientId, address });
}))
const { address } = await readBody(event);
await WireGuard.updateClientAddress({ clientId, address });
return { success: true };
}));
// Static assets
const publicDir = 'www';
app.use(
defineEventHandler((event) => {
return serveStatic(event, {
getContents: (id) => readFile(join(publicDir, id)),
getMeta: async (id) => {
const stats = await stat(join(publicDir, id)).catch(() => {});
if (!stats || !stats.isFile()) {
return;
}
return {
size: stats.size,
mtime: stats.mtimeMs,
};
},
});
}),
);
.listen(PORT, WEBUI_HOST, () => {
debug(`Listening on http://${WEBUI_HOST}:${PORT}`);
});
createServer(toNodeListener(app)).listen(PORT, WEBUI_HOST);
debug(`Listening on http://${WEBUI_HOST}:${PORT}`);
}
};

10
src/lib/ServerError.js

@ -1,10 +0,0 @@
'use strict';
module.exports = class ServerError extends Error {
constructor(message, statusCode = 500) {
super(message);
this.statusCode = statusCode;
}
};

115
src/package-lock.json

@ -13,6 +13,7 @@
"debug": "^4.3.4",
"express": "^4.18.2",
"express-session": "^1.18.0",
"h3": "^1.11.1",
"qrcode": "^1.5.3",
"uuid": "^9.0.1"
},
@ -1200,6 +1201,14 @@
"integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==",
"dev": true
},
"node_modules/consola": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz",
"integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==",
"engines": {
"node": "^14.18.0 || >=16.10.0"
}
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@ -1227,6 +1236,11 @@
"node": ">= 0.6"
}
},
"node_modules/cookie-es": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.0.0.tgz",
"integrity": "sha512-mWYvfOLrfEc996hlKcdABeIiPHUPC6DM2QYZdGGOvhOTbA3tjm2eBwqlJpoFdjC89NI4Qt6h0Pu06Mp+1Pj5OQ=="
},
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@ -1246,6 +1260,19 @@
"node": ">= 8"
}
},
"node_modules/crossws": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/crossws/-/crossws-0.2.4.tgz",
"integrity": "sha512-DAxroI2uSOgUKLz00NX6A8U/8EE3SZHmIND+10jkVSaypvyt57J5JEOxAQOL6lQxyzi/wZbTIwssU1uy69h5Vg==",
"peerDependencies": {
"uWebSockets.js": "*"
},
"peerDependenciesMeta": {
"uWebSockets.js": {
"optional": true
}
}
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@ -1322,6 +1349,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/defu": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@ -1330,6 +1362,11 @@
"node": ">= 0.8"
}
},
"node_modules/destr": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz",
"integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ=="
},
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
@ -2623,6 +2660,23 @@
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true
},
"node_modules/h3": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/h3/-/h3-1.11.1.tgz",
"integrity": "sha512-AbaH6IDnZN6nmbnJOH72y3c5Wwh9P97soSVdGSBbcDACRdkC0FEWf25pzx4f/NuOCK6quHmW18yF2Wx+G4Zi1A==",
"dependencies": {
"cookie-es": "^1.0.0",
"crossws": "^0.2.2",
"defu": "^6.1.4",
"destr": "^2.0.3",
"iron-webcrypto": "^1.0.0",
"ohash": "^1.1.3",
"radix3": "^1.1.0",
"ufo": "^1.4.0",
"uncrypto": "^0.1.3",
"unenv": "^1.9.0"
}
},
"node_modules/has-bigints": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
@ -2801,6 +2855,14 @@
"node": ">= 0.10"
}
},
"node_modules/iron-webcrypto": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.0.0.tgz",
"integrity": "sha512-anOK1Mktt8U1Xi7fCM3RELTuYbnFikQY5VtrDj7kPgpejV7d43tWKhzgioO0zpkazLEL/j/iayRqnJhrGfqUsg==",
"funding": {
"url": "https://github.com/sponsors/brc-dd"
}
},
"node_modules/is-array-buffer": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
@ -3372,6 +3434,11 @@
"node": ">= 0.6"
}
},
"node_modules/node-fetch-native": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.2.tgz",
"integrity": "sha512-69mtXOFZ6hSkYiXAVB5SqaRvrbITC/NPyqv7yuu/qw0nmgPyYbIMYYNIDhNtwPrzk0ptrimrLz/hhjvm4w5Z+w=="
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@ -3495,6 +3562,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/ohash": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.3.tgz",
"integrity": "sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw=="
},
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@ -3668,6 +3740,11 @@
"node": ">=8"
}
},
"node_modules/pathe": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@ -3963,6 +4040,11 @@
}
]
},
"node_modules/radix3": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.0.tgz",
"integrity": "sha512-pNsHDxbGORSvuSScqNJ+3Km6QAVqk8CfsCBIEoDgpqLrkD2f3QM4I7d1ozJJ172OmIcoUcerZaNWqtLkRXTV3A=="
},
"node_modules/ramda": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.2.tgz",
@ -4888,6 +4970,11 @@
"node": ">=4.2.0"
}
},
"node_modules/ufo": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.4.0.tgz",
"integrity": "sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ=="
},
"node_modules/uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
@ -4914,6 +5001,34 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/uncrypto": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz",
"integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="
},
"node_modules/unenv": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/unenv/-/unenv-1.9.0.tgz",
"integrity": "sha512-QKnFNznRxmbOF1hDgzpqrlIf6NC5sbZ2OJ+5Wl3OX8uM+LUJXbj4TXvLJCtwbPTmbMHCLIz6JLKNinNsMShK9g==",
"dependencies": {
"consola": "^3.2.3",
"defu": "^6.1.3",
"mime": "^3.0.0",
"node-fetch-native": "^1.6.1",
"pathe": "^1.1.1"
}
},
"node_modules/unenv/node_modules/mime": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",

1
src/package.json

@ -17,6 +17,7 @@
"debug": "^4.3.4",
"express": "^4.18.2",
"express-session": "^1.18.0",
"h3": "^1.11.1",
"qrcode": "^1.5.3",
"uuid": "^9.0.1"
},

3
src/www/index.html

@ -3,6 +3,7 @@
<head>
<title>WireGuard</title>
<meta charset="utf-8"/>
<link href="./css/app.css" rel="stylesheet">
<link rel="manifest" href="./manifest.json">
<link rel="icon" type="image/png" href="./img/favicon.png">
@ -490,7 +491,7 @@
</div>
<p v-cloak class="text-center m-10 text-gray-300 dark:text-neutral-600 text-xs"> <a class="hover:underline" target="_blank"
<p v-cloak class="text-center m-10 text-gray-300 dark:text-neutral-600 text-xs"> <a class="hover:underline" target="_blank"
href="https://github.com/wg-easy/wg-easy">WireGuard Easy</a> © 2021-2024 by <a class="hover:underline" target="_blank"
href="https://emilenijssen.nl/?ref=wg-easy">Emile Nijssen</a> is licensed under <a class="hover:underline" target="_blank"
href="http://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a> · <a class="hover:underline"

Loading…
Cancel
Save