refactor!: migrate from express to h3
This commit is contained in:
parent
f963e7a4d1
commit
e8a160b14f
|
@ -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');
|
||||
|
||||
return RELEASE;
|
||||
}))
|
||||
|
||||
.get('/api/release', (Util.promisify(async () => {
|
||||
return RELEASE;
|
||||
})))
|
||||
.get('/api/lang', defineEventHandler((event) => {
|
||||
setHeader(event, 'Content-Type', 'application/json');
|
||||
|
||||
.get('/api/lang', (Util.promisify(async () => {
|
||||
return LANG;
|
||||
})))
|
||||
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 };
|
||||
}));
|
||||
|
||||
.listen(PORT, WEBUI_HOST, () => {
|
||||
debug(`Listening on http://${WEBUI_HOST}:${PORT}`);
|
||||
});
|
||||
// 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,
|
||||
};
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
createServer(toNodeListener(app)).listen(PORT, WEBUI_HOST);
|
||||
debug(`Listening on http://${WEBUI_HOST}:${PORT}`);
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = class ServerError extends Error {
|
||||
|
||||
constructor(message, statusCode = 500) {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
};
|
|
@ -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",
|
||||
|
|
|
@ -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,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…
Reference in New Issue