From 1e495c546c111ee88741aa8462bd1c41ffcc44a8 Mon Sep 17 00:00:00 2001 From: Daan Selen Date: Tue, 31 Mar 2026 23:15:34 +0200 Subject: [PATCH] Revert "Merge branch 'v4.3.3-dev' into main" This reverts commit 3586ddce4d331cef9db73c558746a3bcf40fdf6d, reversing changes made to 27ec65a9700e0e431fd0cd5efa3a8726f29c4557. --- .dockerignore | 6 +- .github/workflows/docker.yml | 1 + README.md | 1 + docker/Dockerfile | 15 +- docker/README.md | 42 +- docker/compose.yaml | 1 - docker/entrypoint.sh | 76 +- .../wg-dashboard-oidc-providers.json.template | 16 - src/client.py | 21 +- src/dashboard.py | 120 +-- src/gunicorn.conf.py | 3 +- src/modules/AmneziaPeer.py | 120 --- src/modules/AmneziaWGPeer.py | 92 ++ ...on.py => AmneziaWireguardConfiguration.py} | 194 ++-- ...abaseConnection.py => ConnectionString.py} | 6 +- src/modules/DashboardClients.py | 2 +- src/modules/DashboardClientsPeerAssignment.py | 2 +- src/modules/DashboardClientsTOTP.py | 2 +- src/modules/DashboardConfig.py | 66 +- src/modules/DashboardLogger.py | 2 +- src/modules/DashboardWebHooks.py | 2 +- src/modules/Email.py | 147 ++- src/modules/NewConfigurationTemplates.py | 2 +- src/modules/Peer.py | 120 +-- src/modules/PeerJobLogger.py | 2 +- src/modules/PeerJobs.py | 2 +- src/modules/PeerShareLinks.py | 2 +- src/modules/Utilities.py | 60 +- src/modules/WireguardConfiguration.py | 274 +++--- src/static/app/index.html | 13 +- src/static/app/package-lock.json | 864 ++++++++++-------- src/static/app/package.json | 6 +- .../clientComponents/clientSettings.vue | 51 +- .../editConfiguration.vue | 4 +- .../newPeersComponents/notesInput.vue | 31 - .../configurationComponents/peer.vue | 7 +- .../configurationComponents/peerAddModal.vue | 6 +- .../configurationComponents/peerCreate.vue | 1 + .../peerDetailsModal.vue | 17 +- .../configurationComponents/peerSettings.vue | 11 - .../dashboardEmailSettings.vue | 7 +- ...ashboardWireguardConfigurationTracking.vue | 56 +- .../configurationTracking.vue | 10 +- .../systemStatusComponents/cpuCore.vue | 1 + .../systemStatusWidget.vue | 4 +- src/static/app/src/utilities/fetch.js | 7 +- src/static/app/src/views/newConfiguration.vue | 29 +- src/static/app/src/views/signin.vue | 23 +- src/static/app/src/views/systemStatus.vue | 4 +- src/static/app/vite.config.js | 2 +- src/static/client/index.html | 14 +- src/static/client/package-lock.json | 388 +++----- src/static/client/src/App.vue | 12 +- .../Configuration/configuration.vue | 4 +- .../src/components/SignIn/signInForm.vue | 2 +- src/static/client/src/main.js | 24 +- src/static/client/src/router/router.js | 5 - src/static/client/src/utilities/request.js | 16 +- src/static/client/vite.config.js | 2 +- ...ashboardClientAssignmentStore-DUZ4l3Ie.js} | 2 +- ...{Vector-5IlHN0Py.js => Vector-CuSZivra.js} | 2 +- ...rowser-D1LqIfJr.js => browser-Bjk3Qpx-.js} | 10 +- ...r-DDCtCjaZ.js => clientViewer-9rkMONa-.js} | 2 +- .../assets/clientViewer-BYW0BPeu.css | 1 - .../assets/clientViewer-C5axh9P7.css | 1 + .../assets/clients-B4F-hTZ7.js | 1 - .../assets/clients-CfS2KUuu.css | 1 + .../assets/clients-GrpHFg4T.js | 1 + .../assets/clients-cOyFX4sh.css | 1 - .../assets/configuration-BTQ9biUJ.js | 1 + .../assets/configuration-Dd6L2pZi.js | 1 - ...7oL.css => configurationList-BKKG6ypL.css} | 2 +- .../assets/configurationList-C2B5DAr3.js | 1 + .../assets/configurationList-DyQdKNSn.js | 1 - .../dashboardEmailSettings-BxtXuVtX.css | 1 + ....js => dashboardEmailSettings-C4AqU3LX.js} | 2 +- .../dashboardEmailSettings-CDozOzQ3.css | 1 - ...ireguardConfigurationAutostart-CDuYV_YE.js | 1 - ...ireguardConfigurationAutostart-CjeUiByL.js | 1 + ...Xn0Tq.js => dashboardWebHooks-DcvcMHf6.js} | 2 +- ....min-CDcSqwgf.js => dayjs.min-DFlK6ZAc.js} | 2 +- .../assets/editConfiguration-CJDa1AqT.css | 1 + ...ypueJ.js => editConfiguration-CUZJusT4.js} | 8 +- .../assets/editConfiguration-EQmmV61G.css | 1 - .../assets/index--rUYk9Qx.css | 1 - .../WGDashboardAdmin/assets/index-B2Cqollg.js | 14 - .../{index-HUe35Pyu.js => index-BB7WbKzC.js} | 4 +- .../assets/index-Bcu-vmis.css | 1 + .../WGDashboardAdmin/assets/index-Bno8fcdN.js | 1 + .../assets/index-C4wYuzDW.css | 1 - .../WGDashboardAdmin/assets/index-CmClDcBF.js | 14 + .../assets/index-CpoCtfuw.css | 1 + .../WGDashboardAdmin/assets/index-D5AZF9CB.js | 1 - .../{index-p8W6lPzW.js => index-DZpKhbG8.js} | 2 +- .../WGDashboardAdmin/assets/index-FJBKYE9o.js | 1 - .../WGDashboardAdmin/assets/index-TTbvcDTK.js | 1 + .../assets/localeText-TzABauzQ.js | 1 + .../assets/localeText-slIlNmvW.js | 1 - .../assets/message-BCpGvXyZ.js | 1 - ...sage-CGSzI01q.css => message-Bh5W0B3y.css} | 2 +- .../assets/message-DEVP5DWQ.js | 1 + .../assets/newConfiguration-CaR3VWG-.css | 1 + .../assets/newConfiguration-DKjGLwK7.css | 1 - .../assets/newConfiguration-YU34DWO4.js | 3 - .../assets/newConfiguration-y8o5vuqs.js | 3 + .../{osmap-BDnmDR8p.js => osmap-Bu2QO00-.js} | 2 +- .../assets/peerAddModal-CXvUfWtV.js | 1 + .../assets/peerAddModal-ClXPRA43.js | 1 - .../assets/peerAssignModal-DM4WXKnc.js | 1 + .../assets/peerAssignModal-enAr71Jz.js | 1 - .../assets/peerConfigurationFile-CwtQWcfG.js | 1 + .../assets/peerConfigurationFile-xWgZ3Hh2.js | 1 - .../assets/peerDefaultSettings-BXqDdwA7.js | 1 - .../assets/peerDefaultSettings-Bx2bXXBH.js | 1 + ...rJobs-qzL_mQHg.js => peerJobs-C12Ibrov.js} | 2 +- ...Oq98Zx.js => peerJobsAllModal-CIplFSG9.js} | 2 +- ...xL3n_.js => peerJobsLogsModal-Cfak8LTJ.js} | 2 +- .../assets/peerList-BKZOWgKJ.js | 2 + .../assets/peerList-CLhB8X-O.css | 1 - .../assets/peerList-Ch1hvsh1.css | 1 + .../assets/peerList-w_vobCBV.js | 2 - ...ode-qoaDBWPM.js => peerQRCode-BCjON0Ek.js} | 2 +- .../assets/peerQRCode-BmkCjxyX.css | 1 + .../assets/peerQRCode-CRiQ2C6J.css | 1 - .../assets/peerSearchBar-BDoCCHjh.js | 1 + .../assets/peerSearchBar-m-UpcEAw.js | 1 - .../assets/peerSettings-CSjO_Okh.css | 1 + .../assets/peerSettings-DDntelW6.js | 1 + .../assets/peerSettings-DxOHL3dW.css | 1 - .../assets/peerSettings-HxDukgk9.js | 1 - ...1IDH.js => peerShareLinkModal-B1slS6a3.js} | 2 +- ... => peersDefaultSettingsInput-B_ngK8i0.js} | 2 +- .../WGDashboardAdmin/assets/ping-1S8ErBlQ.js | 1 + .../WGDashboardAdmin/assets/ping-DEGrvraX.js | 1 - ...-DuF9r437.js => protocolBadge-BT5dRPZq.js} | 2 +- ...AG.js => restoreConfiguration-BQIQQb7R.js} | 8 +- ...pQlnKIQ.js => schedulePeerJob-u-FqcbUM.js} | 2 +- .../assets/selectPeers-BUmpb-wc.css | 1 - .../assets/selectPeers-ChWyERy7.css | 1 + ...rs-BJPsUxi9.js => selectPeers-Cuxb3NL4.js} | 2 +- .../assets/settings-CB2iakiW.js | 1 + .../assets/settings-Dq4CIGj-.js | 1 - .../{setup-CWf87_Nl.js => setup-DZGT3UFo.js} | 2 +- .../WGDashboardAdmin/assets/share-CF94rGU4.js | 1 - .../WGDashboardAdmin/assets/share-DThfaAgi.js | 1 + ...{signin-CXnKEaHi.js => signin-VWx2XRQz.js} | 2 +- ...ignin-BINnMVzu.css => signin-lFX8XtKM.css} | 2 +- .../assets/storageMount-CiBujS1C.css | 1 - .../assets/storageMount-wWOSNV8e.css | 1 + ...e_index_0_scoped_9509d7a0_lang-DAKSbVm1.js | 1 + ...e_index_0_scoped_9509d7a0_lang-DyIHT8Z6.js | 1 - .../assets/systemStatus-B6MXXYWb.js | 1 + .../assets/systemStatus-BX0luAHi.js | 1 - .../assets/systemStatus-DdUpBwt2.css | 1 + .../assets/systemStatus-Dve-9tnj.css | 1 - .../{totp-DslWxGxU.js => totp-DsHJoUrw.js} | 2 +- ...ute-7nQq-B8l.js => traceroute-C9P0I7-B.js} | 2 +- .../assets/vue-datepicker-BYHO-v3J.js | 1 + .../assets/vue-datepicker-vYVOdkpZ.js | 1 - .../assets/wgdashboardSettings-B6p7S_Rb.js | 1 + .../assets/wgdashboardSettings-DcOVnVX4.js | 1 - ...wireguardConfigurationSettings-CBlmmPH5.js | 1 - ...wireguardConfigurationSettings-oEcwqFue.js | 1 + src/static/dist/WGDashboardAdmin/index.html | 17 +- .../assets/index-C_FweDYM.js | 41 + ...{index-BGKUQnbP.css => index-CyU4Qwd1.css} | 2 +- .../assets/index-fHUccsgM.js | 41 - src/static/dist/WGDashboardClient/client.html | 16 +- src/test.sh | 22 + 169 files changed, 1436 insertions(+), 1909 deletions(-) delete mode 100644 docker/wg-dashboard-oidc-providers.json.template delete mode 100644 src/modules/AmneziaPeer.py create mode 100644 src/modules/AmneziaWGPeer.py rename src/modules/{AmneziaConfiguration.py => AmneziaWireguardConfiguration.py} (63%) rename src/modules/{DatabaseConnection.py => ConnectionString.py} (89%) delete mode 100644 src/static/app/src/components/configurationComponents/newPeersComponents/notesInput.vue rename src/static/dist/WGDashboardAdmin/assets/{DashboardClientAssignmentStore-NnvBQTas.js => DashboardClientAssignmentStore-DUZ4l3Ie.js} (95%) rename src/static/dist/WGDashboardAdmin/assets/{Vector-5IlHN0Py.js => Vector-CuSZivra.js} (81%) rename src/static/dist/WGDashboardAdmin/assets/{browser-D1LqIfJr.js => browser-Bjk3Qpx-.js} (94%) rename src/static/dist/WGDashboardAdmin/assets/{clientViewer-DDCtCjaZ.js => clientViewer-9rkMONa-.js} (85%) delete mode 100644 src/static/dist/WGDashboardAdmin/assets/clientViewer-BYW0BPeu.css create mode 100644 src/static/dist/WGDashboardAdmin/assets/clientViewer-C5axh9P7.css delete mode 100644 src/static/dist/WGDashboardAdmin/assets/clients-B4F-hTZ7.js create mode 100644 src/static/dist/WGDashboardAdmin/assets/clients-CfS2KUuu.css create mode 100644 src/static/dist/WGDashboardAdmin/assets/clients-GrpHFg4T.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/clients-cOyFX4sh.css create mode 100644 src/static/dist/WGDashboardAdmin/assets/configuration-BTQ9biUJ.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/configuration-Dd6L2pZi.js rename src/static/dist/WGDashboardAdmin/assets/{configurationList-CG9tP7oL.css => configurationList-BKKG6ypL.css} (69%) create mode 100644 src/static/dist/WGDashboardAdmin/assets/configurationList-C2B5DAr3.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/configurationList-DyQdKNSn.js create mode 100644 src/static/dist/WGDashboardAdmin/assets/dashboardEmailSettings-BxtXuVtX.css rename src/static/dist/WGDashboardAdmin/assets/{dashboardEmailSettings-ChBz-NGE.js => dashboardEmailSettings-C4AqU3LX.js} (66%) delete mode 100644 src/static/dist/WGDashboardAdmin/assets/dashboardEmailSettings-CDozOzQ3.css delete mode 100644 src/static/dist/WGDashboardAdmin/assets/dashboardSettingsWireguardConfigurationAutostart-CDuYV_YE.js create mode 100644 src/static/dist/WGDashboardAdmin/assets/dashboardSettingsWireguardConfigurationAutostart-CjeUiByL.js rename src/static/dist/WGDashboardAdmin/assets/{dashboardWebHooks-CJRXn0Tq.js => dashboardWebHooks-DcvcMHf6.js} (93%) rename src/static/dist/WGDashboardAdmin/assets/{dayjs.min-CDcSqwgf.js => dayjs.min-DFlK6ZAc.js} (99%) create mode 100644 src/static/dist/WGDashboardAdmin/assets/editConfiguration-CJDa1AqT.css rename src/static/dist/WGDashboardAdmin/assets/{editConfiguration-CWKypueJ.js => editConfiguration-CUZJusT4.js} (69%) delete mode 100644 src/static/dist/WGDashboardAdmin/assets/editConfiguration-EQmmV61G.css delete mode 100644 src/static/dist/WGDashboardAdmin/assets/index--rUYk9Qx.css delete mode 100644 src/static/dist/WGDashboardAdmin/assets/index-B2Cqollg.js rename src/static/dist/WGDashboardAdmin/assets/{index-HUe35Pyu.js => index-BB7WbKzC.js} (99%) create mode 100644 src/static/dist/WGDashboardAdmin/assets/index-Bcu-vmis.css create mode 100644 src/static/dist/WGDashboardAdmin/assets/index-Bno8fcdN.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/index-C4wYuzDW.css create mode 100644 src/static/dist/WGDashboardAdmin/assets/index-CmClDcBF.js create mode 100644 src/static/dist/WGDashboardAdmin/assets/index-CpoCtfuw.css delete mode 100644 src/static/dist/WGDashboardAdmin/assets/index-D5AZF9CB.js rename src/static/dist/WGDashboardAdmin/assets/{index-p8W6lPzW.js => index-DZpKhbG8.js} (82%) delete mode 100644 src/static/dist/WGDashboardAdmin/assets/index-FJBKYE9o.js create mode 100644 src/static/dist/WGDashboardAdmin/assets/index-TTbvcDTK.js create mode 100644 src/static/dist/WGDashboardAdmin/assets/localeText-TzABauzQ.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/localeText-slIlNmvW.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/message-BCpGvXyZ.js rename src/static/dist/WGDashboardAdmin/assets/{message-CGSzI01q.css => message-Bh5W0B3y.css} (56%) create mode 100644 src/static/dist/WGDashboardAdmin/assets/message-DEVP5DWQ.js create mode 100644 src/static/dist/WGDashboardAdmin/assets/newConfiguration-CaR3VWG-.css delete mode 100644 src/static/dist/WGDashboardAdmin/assets/newConfiguration-DKjGLwK7.css delete mode 100644 src/static/dist/WGDashboardAdmin/assets/newConfiguration-YU34DWO4.js create mode 100644 src/static/dist/WGDashboardAdmin/assets/newConfiguration-y8o5vuqs.js rename src/static/dist/WGDashboardAdmin/assets/{osmap-BDnmDR8p.js => osmap-Bu2QO00-.js} (84%) create mode 100644 src/static/dist/WGDashboardAdmin/assets/peerAddModal-CXvUfWtV.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/peerAddModal-ClXPRA43.js create mode 100644 src/static/dist/WGDashboardAdmin/assets/peerAssignModal-DM4WXKnc.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/peerAssignModal-enAr71Jz.js create mode 100644 src/static/dist/WGDashboardAdmin/assets/peerConfigurationFile-CwtQWcfG.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/peerConfigurationFile-xWgZ3Hh2.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/peerDefaultSettings-BXqDdwA7.js create mode 100644 src/static/dist/WGDashboardAdmin/assets/peerDefaultSettings-Bx2bXXBH.js rename src/static/dist/WGDashboardAdmin/assets/{peerJobs-qzL_mQHg.js => peerJobs-C12Ibrov.js} (71%) rename src/static/dist/WGDashboardAdmin/assets/{peerJobsAllModal-CBOq98Zx.js => peerJobsAllModal-CIplFSG9.js} (59%) rename src/static/dist/WGDashboardAdmin/assets/{peerJobsLogsModal-CH_xL3n_.js => peerJobsLogsModal-Cfak8LTJ.js} (82%) create mode 100644 src/static/dist/WGDashboardAdmin/assets/peerList-BKZOWgKJ.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/peerList-CLhB8X-O.css create mode 100644 src/static/dist/WGDashboardAdmin/assets/peerList-Ch1hvsh1.css delete mode 100644 src/static/dist/WGDashboardAdmin/assets/peerList-w_vobCBV.js rename src/static/dist/WGDashboardAdmin/assets/{peerQRCode-qoaDBWPM.js => peerQRCode-BCjON0Ek.js} (80%) create mode 100644 src/static/dist/WGDashboardAdmin/assets/peerQRCode-BmkCjxyX.css delete mode 100644 src/static/dist/WGDashboardAdmin/assets/peerQRCode-CRiQ2C6J.css create mode 100644 src/static/dist/WGDashboardAdmin/assets/peerSearchBar-BDoCCHjh.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/peerSearchBar-m-UpcEAw.js create mode 100644 src/static/dist/WGDashboardAdmin/assets/peerSettings-CSjO_Okh.css create mode 100644 src/static/dist/WGDashboardAdmin/assets/peerSettings-DDntelW6.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/peerSettings-DxOHL3dW.css delete mode 100644 src/static/dist/WGDashboardAdmin/assets/peerSettings-HxDukgk9.js rename src/static/dist/WGDashboardAdmin/assets/{peerShareLinkModal-lGyV1IDH.js => peerShareLinkModal-B1slS6a3.js} (86%) rename src/static/dist/WGDashboardAdmin/assets/{peersDefaultSettingsInput-CdPQfK5d.js => peersDefaultSettingsInput-B_ngK8i0.js} (57%) create mode 100644 src/static/dist/WGDashboardAdmin/assets/ping-1S8ErBlQ.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/ping-DEGrvraX.js rename src/static/dist/WGDashboardAdmin/assets/{protocolBadge-DuF9r437.js => protocolBadge-BT5dRPZq.js} (59%) rename src/static/dist/WGDashboardAdmin/assets/{restoreConfiguration-BZxfViAG.js => restoreConfiguration-BQIQQb7R.js} (64%) rename src/static/dist/WGDashboardAdmin/assets/{schedulePeerJob-BpQlnKIQ.js => schedulePeerJob-u-FqcbUM.js} (92%) delete mode 100644 src/static/dist/WGDashboardAdmin/assets/selectPeers-BUmpb-wc.css create mode 100644 src/static/dist/WGDashboardAdmin/assets/selectPeers-ChWyERy7.css rename src/static/dist/WGDashboardAdmin/assets/{selectPeers-BJPsUxi9.js => selectPeers-Cuxb3NL4.js} (67%) create mode 100644 src/static/dist/WGDashboardAdmin/assets/settings-CB2iakiW.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/settings-Dq4CIGj-.js rename src/static/dist/WGDashboardAdmin/assets/{setup-CWf87_Nl.js => setup-DZGT3UFo.js} (82%) delete mode 100644 src/static/dist/WGDashboardAdmin/assets/share-CF94rGU4.js create mode 100644 src/static/dist/WGDashboardAdmin/assets/share-DThfaAgi.js rename src/static/dist/WGDashboardAdmin/assets/{signin-CXnKEaHi.js => signin-VWx2XRQz.js} (58%) rename src/static/dist/WGDashboardAdmin/assets/{signin-BINnMVzu.css => signin-lFX8XtKM.css} (50%) delete mode 100644 src/static/dist/WGDashboardAdmin/assets/storageMount-CiBujS1C.css create mode 100644 src/static/dist/WGDashboardAdmin/assets/storageMount-wWOSNV8e.css create mode 100644 src/static/dist/WGDashboardAdmin/assets/storageMount.vue_vue_type_style_index_0_scoped_9509d7a0_lang-DAKSbVm1.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/storageMount.vue_vue_type_style_index_0_scoped_9509d7a0_lang-DyIHT8Z6.js create mode 100644 src/static/dist/WGDashboardAdmin/assets/systemStatus-B6MXXYWb.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/systemStatus-BX0luAHi.js create mode 100644 src/static/dist/WGDashboardAdmin/assets/systemStatus-DdUpBwt2.css delete mode 100644 src/static/dist/WGDashboardAdmin/assets/systemStatus-Dve-9tnj.css rename src/static/dist/WGDashboardAdmin/assets/{totp-DslWxGxU.js => totp-DsHJoUrw.js} (78%) rename src/static/dist/WGDashboardAdmin/assets/{traceroute-7nQq-B8l.js => traceroute-C9P0I7-B.js} (50%) create mode 100644 src/static/dist/WGDashboardAdmin/assets/vue-datepicker-BYHO-v3J.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/vue-datepicker-vYVOdkpZ.js create mode 100644 src/static/dist/WGDashboardAdmin/assets/wgdashboardSettings-B6p7S_Rb.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/wgdashboardSettings-DcOVnVX4.js delete mode 100644 src/static/dist/WGDashboardAdmin/assets/wireguardConfigurationSettings-CBlmmPH5.js create mode 100644 src/static/dist/WGDashboardAdmin/assets/wireguardConfigurationSettings-oEcwqFue.js create mode 100644 src/static/dist/WGDashboardClient/assets/index-C_FweDYM.js rename src/static/dist/WGDashboardClient/assets/{index-BGKUQnbP.css => index-CyU4Qwd1.css} (73%) delete mode 100644 src/static/dist/WGDashboardClient/assets/index-fHUccsgM.js create mode 100755 src/test.sh diff --git a/.dockerignore b/.dockerignore index 7aaadbd9..e2c608c2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,8 +2,4 @@ .github *.md tests/ -docs/ -src/db -src/wg-dashboard.ini -src/static/app -src/static/client +docs/ \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 8d09c180..b7604428 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -103,6 +103,7 @@ jobs: only-fixed: true write-comment: true github-token: ${{ secrets.GITHUB_TOKEN }} + exit-code: true - name: Docker Scout Compare uses: docker/scout-action@v1 diff --git a/README.md b/README.md index eddc2dcf..8e76da71 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ > 🎉 To help us better understand and improve WGDashboard’s performance, we’re launching the **WGDashboard Testing Program**. As part of this program, participants will receive free WireGuard VPN access to our server in Toronto, Canada, valid for **24 hours** or up to **1GB of total traffic**—whichever comes first. If you’d like to join, visit [https://wg.wgdashboard.dev/](https://wg.wgdashboard.dev/) for more details! + ![](https://wgdashboard-resources.tor1.cdn.digitaloceanspaces.com/Posters/Banner.png) diff --git a/docker/Dockerfile b/docker/Dockerfile index b6630c23..e0b0b3b4 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -4,7 +4,7 @@ # # Pull the current golang-alpine image. -FROM golang:1.26-alpine3.23 AS awg-go +FROM golang:1.25-alpine AS awg-go # Install build-dependencies. RUN apk add --no-cache \ @@ -30,7 +30,7 @@ RUN go version && \ # AWG TOOLS BUILDING STAGE # Base: Alpine # -FROM alpine:3.23 AS awg-tools +FROM alpine:latest AS awg-tools # Install needed dependencies. RUN apk add --no-cache \ @@ -55,7 +55,7 @@ RUN make && chmod +x wg* # # Use the python-alpine image for building pip dependencies -FROM python:3.14-alpine3.23 AS pip-builder +FROM python:3.14-alpine AS pip-builder ARG TARGETPLATFORM @@ -91,7 +91,7 @@ RUN . /opt/wgdashboard/src/venv/bin/activate && \ # # Running with the python-alpine image. -FROM python:3.14-alpine3.23 AS final +FROM python:3.14-alpine AS final LABEL maintainer="dselen@nerthus.nl" # Install only the runtime dependencies @@ -114,18 +114,15 @@ ENV TZ="Europe/Amsterdam" \ global_dns="9.9.9.9" \ wgd_port="10086" \ public_ip="" \ - WGDASH=/opt/wgdashboard \ - dynamic_config="true" + WGDASH=/opt/wgdashboard # Create directories needed for operation -RUN mkdir /data /configs -p ${WGDASH}/src /etc/amnezia/amneziawg \ - && echo "name_servers=${global_dns}" >> /etc/resolvconf.conf +RUN mkdir /data /configs -p ${WGDASH}/src /etc/amnezia/amneziawg # Copy the venv and source files from local compiled locations or repos COPY ./src ${WGDASH}/src COPY --from=pip-builder /opt/wgdashboard/src/venv /opt/wgdashboard/src/venv COPY ./docker/wg0.conf.template /tmp/wg0.conf.template -COPY ./docker/wg-dashboard-oidc-providers.json.template /tmp/wg-dashboard-oidc-providers.json.template # Copy in the runtime script, essential. COPY ./docker/entrypoint.sh /entrypoint.sh diff --git a/docker/README.md b/docker/README.md index b6966712..9a39a07c 100644 --- a/docker/README.md +++ b/docker/README.md @@ -23,7 +23,7 @@ To get the container running you either pull the pre-made image from a remote re - ghcr.io/wgdashboard/wgdashboard: - docker.io/donaldzou/wgdashboard: -> tags should be either: latest, main, , (if built) or . +> tags should be either: latest, main, or . From there either use the environment variables described below as parameters or use the Docker Compose file: `compose.yaml`.
Be careful, the default generated WireGuard configuration file uses port 51820/udp. So make sure to use this port if you want to use it out of the box.
@@ -95,29 +95,23 @@ Updating the WGDashboard container should be through 'The Docker Way' - by pulli ## ⚙️ Environment Variables -| Variable | Accepted Values | Default | Example | Description | -| ------------------ | ---------------------------------------- | ----------------------- | --------------------- | ----------------------------------------------------------------------- | -| `dynamic_config` | true, yes, false, no | `true` | `true` or `no` | Turns on or off the dynamic configuration feature, on by default for Docker | -| `tz` | Timezone | `Europe/Amsterdam` | `America/New_York` | Sets the container's timezone. Useful for accurate logs and scheduling. | -| `global_dns` | IPv4 and IPv6 addresses | `9.9.9.9` | `8.8.8.8`, `1.1.1.1` | Default DNS for WireGuard clients. | -| `public_ip` | Public IP address | Retrieved automatically | `253.162.134.73` | Used to generate accurate client configs. Needed if container is NAT’d. | -| `wgd_port` | Any port that is allowed for the process | `10086` | `443` | This port is used to set the WGDashboard web port. | -| `username` | Any non‐empty string | `-` | `admin` | Username for the WGDashboard web interface account. | -| `password` | Any non‐empty string | `-` | `s3cr3tP@ss` | Password for the WGDashboard web interface account (stored hashed). | -| `enable_totp` | `true`, `false` | `true` | `false` | Enable TOTP‐based two‐factor authentication for the account. | -| `wg_autostart` | Wireguard interface name | `false` | `true` | Auto‐start the WireGuard client when the container launches. | -| `email_server` | SMTP server address | `-` | `smtp.gmail.com` | SMTP server for sending email notifications. | -| `email_port` | SMTP port number | `-` | `587` | Port for connecting to the SMTP server. | -| `email_encryption` | `TLS`, `SSL`, etc. | `-` | `TLS` | Encryption method for email communication. | -| `email_username` | Any non-empty string | `-` | `user@example.com` | Username for SMTP authentication. | -| `email_password` | Any non-empty string | `-` | `app_password` | Password for SMTP authentication. | -| `email_from` | Valid email address | `-` | `noreply@example.com` | Email address used as the sender for notifications. | -| `email_template` | Path to template file | `-` | `your-template` | Custom template for email notifications. | -| `database_type` | `sqlite`, `postgresql`, `mariadb+mariadbconnector`, etc. | `-` | `postgresql` | Type of [sqlalchemy database engine](https://docs.sqlalchemy.org/en/21/core/engines.html). | -| `database_host` | Any non-empty string | `-` | `localhost` | IP-Address or hostname of the SQL-database server. | -| `database_port` | Any non-empty string (or int for port) | `-` | `5432` | Port for the database communication. | -| `database_username`| Valid database username | `-` | `database_user` | Database user username. | -| `database_password`| Valid database password | `-` | `database_password` | Database user password. | +| Variable | Accepted Values | Default | Example | Description | +| ------------------ | ---------------------------------------- | ----------------------- | ------------------------ | ----------------------------------------------------------------------- | +| `tz` | Timezone | `Europe/Amsterdam` | `America/New_York` | Sets the container's timezone. Useful for accurate logs and scheduling. | +| `global_dns` | IPv4 and IPv6 addresses | `9.9.9.9` | `8.8.8.8`, `1.1.1.1` | Default DNS for WireGuard clients. | +| `public_ip` | Public IP address | Retrieved automatically | `253.162.134.73` | Used to generate accurate client configs. Needed if container is NAT’d. | +| `wgd_port` | Any port that is allowed for the process | `10086` | `443` | This port is used to set the WGDashboard web port. | +| `username` | Any non‐empty string | `-` | `admin` | Username for the WGDashboard web interface account. | +| `password` | Any non‐empty string | `-` | `s3cr3tP@ss` | Password for the WGDashboard web interface account (stored hashed). | +| `enable_totp` | `true`, `false` | `true` | `false` | Enable TOTP‐based two‐factor authentication for the account. | +| `wg_autostart` | Wireguard interface name | `-` | `wg0` or `wg0\|\|wg1\|\|wg2` | Auto‐start the WireGuard interface when the container launches. | +| `email_server` | SMTP server address | `-` | `smtp.gmail.com` | SMTP server for sending email notifications. | +| `email_port` | SMTP port number | `-` | `587` | Port for connecting to the SMTP server. | +| `email_encryption` | `TLS`, `SSL`, etc. | `-` | `TLS` | Encryption method for email communication. | +| `email_username` | Any non-empty string | `-` | `user@example.com` | Username for SMTP authentication. | +| `email_password` | Any non-empty string | `-` | `app_password` | Password for SMTP authentication. | +| `email_from` | Valid email address | `-` | `noreply@example.com` | Email address used as the sender for notifications. | +| `email_template` | Path to template file | `-` | `your-template` | Custom template for email notifications. | --- diff --git a/docker/compose.yaml b/docker/compose.yaml index 445c3102..d8f2eac9 100644 --- a/docker/compose.yaml +++ b/docker/compose.yaml @@ -13,7 +13,6 @@ services: # By default its all disabled, but uncomment the following lines to apply these. (uncommenting is removing the # character) # Refer to the documentation on https://wgdashboard.dev/ for more info on what everything means. #environment: - #- wg_autostart=wg0 #- tz= # <--- Set container timezone, default: Europe/Amsterdam. #- public_ip= # <--- Set public IP to ensure the correct one is chosen, defaulting to the IP give by ifconfig.me. #- wgd_port= # <--- Set the port WGDashboard will use for its web-server. diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index fe2d2dff..8b34ad81 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -85,6 +85,8 @@ echo "------------------------- START ----------------------------" echo "Starting the WGDashboard Docker container." ensure_installation() { + echo "Quick-installing..." + # Make the wgd.sh script executable. chmod +x "${WGDASH}"/src/wgd.sh cd "${WGDASH}"/src || exit @@ -100,51 +102,23 @@ ensure_installation() { echo "Removing clear command from wgd.sh for better Docker logging." sed -i '/clear/d' ./wgd.sh - # PERSISTENCE FOR databases directory # Create required directories and links if [ ! -d "/data/db" ]; then echo "Creating database dir" mkdir -p /data/db fi - if [[ ! -L "${WGDASH}/src/db" ]] && [[ -d "${WGDASH}/src/db" ]]; then - echo "Removing ${WGDASH}/src/db since its not a symbolic link." - rm -rfv "${WGDASH}/src/db" - fi - if [[ -L "${WGDASH}/src/db" ]]; then - echo "${WGDASH}/src/db is a symbolic link." - else - ln -sv /data/db "${WGDASH}/src/db" + if [ ! -d "${WGDASH}/src/db" ]; then + ln -s /data/db "${WGDASH}/src/db" fi - # PERSISTENCE FOR wg-dashboard-oidc-providers.json - if [ ! -f "/data/wg-dashboard-oidc-providers.json" ]; then - echo "Creating wg-dashboard-oidc-providers.json file" - cp -v /tmp/wg-dashboard-oidc-providers.json.template /data/wg-dashboard-oidc-providers.json - fi - if [[ ! -L "${WGDASH}/src/wg-dashboard-oidc-providers.json" ]] && [[ -f "${WGDASH}/src/wg-dashboard-oidc-providers.json" ]]; then - echo "Removing ${WGDASH}/src/wg-dashboard-oidc-providers.json since its not a symbolic link." - rm -fv "${WGDASH}/src/wg-dashboard-oidc-providers.json" - fi - if [[ -L "${WGDASH}/src/wg-dashboard-oidc-providers.json" ]]; then - echo "${WGDASH}/src/wg-dashboard-oidc-providers.json is a symbolic link." - else - ln -sv /data/wg-dashboard-oidc-providers.json "${WGDASH}/src/wg-dashboard-oidc-providers.json" - fi - - # PERSISTENCE FOR wg-dashboard.ini if [ ! -f "${config_file}" ]; then echo "Creating wg-dashboard.ini file" touch "${config_file}" fi - if [[ ! -L "${WGDASH}/src/wg-dashboard.ini" ]] && [[ -f "${WGDASH}/src/wg-dashboard.ini" ]]; then - echo "Removing ${WGDASH}/src/wg-dashboard.ini since its not a symbolic link." - rm -fv "${WGDASH}/src/wg-dashboard.ini" - fi - if [[ -L "${WGDASH}/src/wg-dashboard.ini" ]]; then - echo "${WGDASH}/src/wg-dashboard.ini is a symbolic link." - else - ln -sv "${config_file}" "${WGDASH}/src/wg-dashboard.ini" + + if [ ! -f "${WGDASH}/src/wg-dashboard.ini" ]; then + ln -s "${config_file}" "${WGDASH}/src/wg-dashboard.ini" fi # Setup WireGuard if needed @@ -168,25 +142,14 @@ set_envvars() { # Check if config file is empty if [ ! -s "${config_file}" ]; then echo "Config file is empty. Creating initial structure." - elif [[ ${dynamic_config,,} =~ ^(false|no)$ ]]; then - echo "Dynamic configuration feature turned off, not changing anything" - return fi echo "Checking basic configuration:" set_ini Peers peer_global_dns "${global_dns}" if [ -z "${public_ip}" ]; then - public_ip=$(curl -s https://ifconfig.me) - if [ -z "${public_ip}" ]; then - echo "Using fallback public IP resolution website" - public_ip=$(curl -s https://api.ipify.org) - fi - if [ -z "${public_ip}" ]; then - echo "Failed to resolve publicly. Using private address." - public_ip=$(hostname -i) - fi - echo "Automatically detected public IP: ${public_ip}" + public_ip=$(curl -s ifconfig.me) + echo "Automatically detected public IP: ${public_ip}" fi set_ini Peers remote_endpoint "${public_ip}" @@ -220,24 +183,6 @@ set_envvars() { set_ini WireGuardConfiguration autostart "${wg_autostart}" fi - # Database (check if any settings need to be configured) - database_vars=("database_type" "database_host" "database_port" "database_username" "database_password") - for var in "${database_vars[@]}"; do - if [ -n "${!var}" ]; then - echo "Configuring database settings:" - break - fi - done - - # Database (iterate through all possible fields) - database_fields=("type:database_type" "host:database_host" "port:database_port" - "username:database_username" "password:database_password") - - for field_pair in "${database_fields[@]}"; do - IFS=: read -r field var <<< "$field_pair" - [[ -n "${!var}" ]] && set_ini Database "$field" "${!var}" - done - # Email (check if any settings need to be configured) email_vars=("email_server" "email_port" "email_encryption" "email_username" "email_password" "email_from" "email_template") for var in "${email_vars[@]}"; do @@ -262,9 +207,6 @@ set_envvars() { start_and_monitor() { printf "\n---------------------- STARTING CORE -----------------------\n" - # Due to resolvconf resetting the DNS we echo back the one we defined (or fallback to default). - resolvconf -u - # Due to some instances complaining about this, making sure its there every time. mkdir -p /dev/net mknod /dev/net/tun c 10 200 diff --git a/docker/wg-dashboard-oidc-providers.json.template b/docker/wg-dashboard-oidc-providers.json.template deleted file mode 100644 index 3764d0d2..00000000 --- a/docker/wg-dashboard-oidc-providers.json.template +++ /dev/null @@ -1,16 +0,0 @@ -{ - "Admin": { - "Provider": { - "client_id": "", - "client_secret": "", - "issuer": "" - } - }, - "Client": { - "Provider": { - "client_id": "", - "client_secret": "", - "issuer": "" - } - } -} diff --git a/src/client.py b/src/client.py index 7d089c29..cc7ac218 100644 --- a/src/client.py +++ b/src/client.py @@ -4,9 +4,8 @@ from tzlocal import get_localzone from functools import wraps -from flask import Blueprint, render_template, abort, request, Flask, current_app, session, redirect, url_for, send_from_directory +from flask import Blueprint, render_template, abort, request, Flask, current_app, session, redirect, url_for import os -import mimetypes from modules.WireguardConfiguration import WireguardConfiguration from modules.DashboardConfig import DashboardConfig @@ -54,8 +53,6 @@ def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration], @client.post(f'{prefix}/api/signup') def ClientAPI_SignUp(): - if not dashboardConfig.GetConfig("Clients", "sign_up")[1]: - abort(404) data = request.get_json() status, msg = dashboardClients.SignUp(**data) return ResponseObject(status, msg) @@ -195,26 +192,14 @@ def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration], }) return ResponseObject(status, msg) - @client.get(f'{prefix}/assets/') - @client.get(f'{prefix}/img/') - def serve_client_static(filename): - client_dist_folder = os.path.abspath("./static/dist/WGDashboardClient") - mimetype = mimetypes.guess_type(filename)[0] - subfolder = 'assets' if 'assets' in request.path else 'img' - return send_from_directory(os.path.join(client_dist_folder, subfolder), os.path.basename(filename), mimetype=mimetype) - @client.get(prefix) def ClientIndex(): - app_prefix = dashboardConfig.GetConfig("Server", "app_prefix")[1] - return render_template('client.html', APP_PREFIX=app_prefix) + return render_template('client.html') @client.get(f'{prefix}/api/serverInformation') def ClientAPI_ServerInformation(): return ResponseObject(data={ - "ServerTimezone": str(get_localzone()), - "SignUp": { - "enable": dashboardConfig.GetConfig("Clients", "sign_up")[1] - } + "ServerTimezone": str(get_localzone()) }) @client.get(f'{prefix}/api/validateAuthentication') diff --git a/src/dashboard.py b/src/dashboard.py index a61be851..ff718a15 100644 --- a/src/dashboard.py +++ b/src/dashboard.py @@ -8,7 +8,7 @@ from datetime import datetime, timedelta import sqlalchemy from jinja2 import Template -from flask import Flask, request, render_template, session, send_file, current_app +from flask import Flask, request, render_template, session, send_file from flask_cors import CORS from icmplib import ping, traceroute from flask.json.provider import DefaultJSONProvider @@ -17,7 +17,8 @@ from itertools import islice from sqlalchemy import RowMapping from modules.Utilities import ( - RegexMatch, StringToBoolean, ValidateDNSAddress, + RegexMatch, StringToBoolean, + ValidateIPAddressesWithRange, ValidateDNSAddress, GenerateWireguardPublicKey, GenerateWireguardPrivateKey ) from packaging import version @@ -29,7 +30,7 @@ from modules.PeerShareLinks import PeerShareLinks from modules.PeerJobs import PeerJobs from modules.DashboardConfig import DashboardConfig from modules.WireguardConfiguration import WireguardConfiguration -from modules.AmneziaConfiguration import AmneziaConfiguration +from modules.AmneziaWireguardConfiguration import AmneziaWireguardConfiguration from client import createClientBlueprint @@ -71,11 +72,7 @@ def ResponseObject(status=True, message=None, data=None, status_code = 200) -> F ''' Flask App ''' -_, APP_PREFIX_INIT = DashboardConfig().GetConfig("Server", "app_prefix") -app = Flask("WGDashboard", - template_folder=os.path.abspath("./static/dist/WGDashboardAdmin"), - static_folder=os.path.abspath("./static/dist/WGDashboardAdmin"), - static_url_path=APP_PREFIX_INIT if APP_PREFIX_INIT else '') +app = Flask("WGDashboard", template_folder=os.path.abspath("./static/dist/WGDashboardAdmin")) def peerInformationBackgroundThread(): global WireguardConfigurations @@ -95,13 +92,11 @@ def peerInformationBackgroundThread(): c.getPeersTransfer() c.getPeersEndpoint() c.getPeers() - if DashboardConfig.GetConfig('WireGuardConfiguration', 'peer_tracking')[1] is True: - print("[WGDashboard] Tracking Peers") - if delay == 6: - if c.configurationInfo.PeerTrafficTracking: - c.logPeersTraffic() - if c.configurationInfo.PeerHistoricalEndpointTracking: - c.logPeersHistoryEndpoint() + if delay == 6: + if c.configurationInfo.PeerTrafficTracking: + c.logPeersTraffic() + if c.configurationInfo.PeerHistoricalEndpointTracking: + c.logPeersHistoryEndpoint() c.getRestrictedPeersList() except Exception as e: app.logger.error(f"[WGDashboard] Background Thread #1 Error", e) @@ -166,10 +161,10 @@ def InitWireguardConfigurationsList(startup: bool = False): if i in WireguardConfigurations.keys(): if WireguardConfigurations[i].configurationFileChanged(): with app.app_context(): - WireguardConfigurations[i] = AmneziaConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, i) + WireguardConfigurations[i] = AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, i) else: with app.app_context(): - WireguardConfigurations[i] = AmneziaConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, i, startup=startup) + WireguardConfigurations[i] = AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, i, startup=startup) except WireguardConfiguration.InvalidConfigurationFileException as e: app.logger.error(f"{i} have an invalid configuration file.") @@ -269,8 +264,7 @@ def auth_req(): ("username" not in session or session.get("role") != "admin") and (f"{appPrefix}/" != request.path and f"{appPrefix}" != request.path) and not request.path.startswith(f'{appPrefix}/client') - and not request.path.startswith(f'{appPrefix}/img') - and not request.path.startswith(f'{appPrefix}/assets') + and not request.path.startswith(f'{appPrefix}/static') and request.path not in whiteList ): response = Flask.make_response(app, { @@ -428,11 +422,11 @@ def API_addWireguardConfiguration(): ) WireguardConfigurations[data['ConfigurationName']] = ( WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, data=data, name=data['ConfigurationName'])) if protocol == 'wg' else ( - AmneziaConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data, name=data['ConfigurationName'])) + AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data, name=data['ConfigurationName'])) else: WireguardConfigurations[data['ConfigurationName']] = ( WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data)) if data.get('Protocol') == 'wg' else ( - AmneziaConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data)) + AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data)) return ResponseObject() @app.get(f'{APP_PREFIX}/api/toggleWireguardConfiguration') @@ -529,7 +523,7 @@ def API_renameWireguardConfiguration(): status, message = rc.renameConfiguration(data.get("NewConfigurationName")) if status: - WireguardConfigurations[data.get("NewConfigurationName")] = (WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data.get("NewConfigurationName")) if rc.Protocol == 'wg' else AmneziaConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data.get("NewConfigurationName"))) + WireguardConfigurations[data.get("NewConfigurationName")] = (WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data.get("NewConfigurationName")) if rc.Protocol == 'wg' else AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data.get("NewConfigurationName"))) else: WireguardConfigurations[data.get("ConfigurationName")] = rc return ResponseObject(status, message) @@ -568,8 +562,8 @@ def API_getAllWireguardConfigurationBackup(): files.sort(key=lambda x: x[1], reverse=True) for f, ct in files: - if RegexMatch(r"^(.+)_(\d+)\.(conf)$", f): - s = re.search(r"^(.+)_(\d+)\.(conf)$", f) + if RegexMatch(r"^(.*)_(.*)\.(conf)$", f): + s = re.search(r"^(.*)_(.*)\.(conf)$", f) name = s.group(1) if name not in existingConfiguration: if name not in data['NonExistingConfigurations'].keys(): @@ -712,30 +706,15 @@ def API_updatePeerSettings(configName): preshared_key = data['preshared_key'] mtu = data['mtu'] keepalive = data['keepalive'] - notes = data.get('notes', '') wireguardConfig = WireguardConfigurations[configName] foundPeer, peer = wireguardConfig.searchPeer(id) if foundPeer: if wireguardConfig.Protocol == 'wg': - status, msg = peer.updatePeer(name, - private_key, - preshared_key, - dns_addresses, - allowed_ip, - endpoint_allowed_ip, - mtu, - keepalive, - notes) + status, msg = peer.updatePeer(name, private_key, preshared_key, dns_addresses, + allowed_ip, endpoint_allowed_ip, mtu, keepalive) else: - status, msg = peer.updatePeer(name, - private_key, - preshared_key, - dns_addresses, - allowed_ip, - endpoint_allowed_ip, - mtu, - keepalive, - notes) + status, msg = peer.updatePeer(name, private_key, preshared_key, dns_addresses, + allowed_ip, endpoint_allowed_ip, mtu, keepalive, "off") wireguardConfig.getPeers() DashboardWebHooks.RunWebHook('peer_updated', { "configuration": wireguardConfig.Name, @@ -885,7 +864,6 @@ def API_addPeers(configName): mtu: int = data.get('mtu', None) keep_alive: int = data.get('keepalive', None) - notes: str = data.get('notes', '') preshared_key: str = data.get('preshared_key', "") if type(mtu) is not int or mtu < 0 or mtu > 1460: @@ -941,7 +919,7 @@ def API_addPeers(configName): "endpoint_allowed_ip": endpoint_allowed_ip, "mtu": mtu, "keepalive": keep_alive, - "notes": "" + "advanced_security": "off" }) if addedCount == bulkAddAmount: break @@ -984,11 +962,8 @@ def API_addPeers(configName): for i in allowed_ips: found = False for subnet in availableIps.keys(): - try: - network = ipaddress.ip_network(subnet, False) - ap = ipaddress.ip_network(i) - except ValueError as e: - return ResponseObject(False, str(e)) + network = ipaddress.ip_network(subnet, False) + ap = ipaddress.ip_network(i) if network.version == ap.version and ap.subnet_of(network): found = True @@ -1006,13 +981,14 @@ def API_addPeers(configName): "DNS": dns_addresses, "mtu": mtu, "keepalive": keep_alive, - "notes": notes + "advanced_security": "off" }] ) return ResponseObject(status=status, message=message, data=addedPeers) except Exception as e: app.logger.error("Add peers failed", e) - return ResponseObject(False, f"Add peers failed.") + return ResponseObject(False, + f"Add peers failed. Reason: {message}") return ResponseObject(False, "Configuration does not exist") @@ -1149,24 +1125,13 @@ def API_GetPeerTraffics(): @app.get(f'{APP_PREFIX}/api/getPeerTrackingTableCounts') def API_GetPeerTrackingTableCounts(): configurationName = request.args.get("configurationName") - if configurationName and configurationName not in WireguardConfigurations.keys(): + if configurationName not in WireguardConfigurations.keys(): return ResponseObject(False, "Configuration does not exist") - - if configurationName: - c = WireguardConfigurations.get(configurationName) - return ResponseObject(data={ - "TrafficTrackingTableSize": c.getTransferTableSize(), - "HistoricalTrackingTableSize": c.getHistoricalEndpointTableSize() - }) - - d = {} - for i in WireguardConfigurations.keys(): - c = WireguardConfigurations.get(i) - d[i] = { - "TrafficTrackingTableSize": c.getTransferTableSize(), - "HistoricalTrackingTableSize": c.getHistoricalEndpointTableSize() - } - return ResponseObject(data=d) + c = WireguardConfigurations.get(configurationName) + return ResponseObject(data={ + "TrafficTrackingTableSize": c.getTransferTableSize(), + "HistoricalTrackingTableSize": c.getHistoricalEndpointTableSize() + }) @app.get(f'{APP_PREFIX}/api/downloadPeerTrackingTable') def API_DownloadPeerTackingTable(): @@ -1360,17 +1325,12 @@ def API_traceroute_execute(): data=json.dumps([x['ip'] for x in result])) d = r.json() for i in range(len(result)): - result[i]['geo'] = d[i] - - return ResponseObject(data=result) - + result[i]['geo'] = d[i] except Exception as e: - app.logger.error(f"Failed to gather the geolocation data: {e}") return ResponseObject(data=result, message="Failed to request IP address geolocation") - - except Exception as e: - app.logger.error(f"Failed to execute the traceroute: {e}") - return ResponseObject(data=[], message="Failed to traceroute the given parameter") + return ResponseObject(data=result) + except Exception as exp: + return ResponseObject(False, exp) else: return ResponseObject(False, "Please provide ipAddress") @@ -1740,9 +1700,9 @@ Index Page @app.get(f'{APP_PREFIX}/') def index(): - return render_template('index.html', APP_PREFIX=APP_PREFIX) + return render_template('index.html') if __name__ == "__main__": startThreads() DashboardPlugins.startThreads() - app.run(host=app_ip, debug=False, port=app_port) + app.run(host=app_ip, debug=False, port=app_port) \ No newline at end of file diff --git a/src/gunicorn.conf.py b/src/gunicorn.conf.py index f046bb86..cba84b21 100644 --- a/src/gunicorn.conf.py +++ b/src/gunicorn.conf.py @@ -1,5 +1,4 @@ import dashboard -import os from datetime import datetime global sqldb, cursor, DashboardConfig, WireguardConfigurations, AllPeerJobs, JobLogger, Dash app_host, app_port = dashboard.gunicornConfig() @@ -17,7 +16,7 @@ daemon = True pidfile = './gunicorn.pid' wsgi_app = "dashboard:app" accesslog = f"./log/access_{date}.log" -loglevel = os.environ['log_level'] if 'log_level' in os.environ else 'info' +loglevel = "info" capture_output = True errorlog = f"./log/error_{date}.log" pythonpath = "., ./modules" diff --git a/src/modules/AmneziaPeer.py b/src/modules/AmneziaPeer.py deleted file mode 100644 index 509f4305..00000000 --- a/src/modules/AmneziaPeer.py +++ /dev/null @@ -1,120 +0,0 @@ -import os -from flask import current_app -import random -import re -import subprocess -import uuid - -from flask import current_app -from .Peer import Peer -from .Utilities import CheckAddress, ValidateDNSAddress, GenerateWireguardPublicKey - - -class AmneziaPeer(Peer): - def __init__(self, tableData, configuration): - super().__init__(tableData, configuration) - - - def updatePeer(self, name: str, private_key: str, - preshared_key: str, - dns_addresses: str, - allowed_ip: str, - endpoint_allowed_ip: str, - mtu: int, - keepalive: int, - notes: str - ) -> tuple[bool, str | None]: - - if not self.configuration.getStatus(): - self.configuration.toggleConfiguration() - - # Before we do any compute, let us check if the given endpoint allowed ip is valid at all - if not CheckAddress(endpoint_allowed_ip): - return False, f"Endpoint Allowed IPs format is incorrect" - - peers = [] - for peer in self.configuration.getPeersList(): - # Make sure to exclude your own data when updating since its not really relevant - if peer.id != self.id: - continue - peers.append(peer) - - used_allowed_ips = [] - for peer in peers: - ips = peer.allowed_ip.split(',') - ips = [ip.strip() for ip in ips] - used_allowed_ips.append(ips) - - if allowed_ip in used_allowed_ips: - return False, "Allowed IP already taken by another peer" - - if not ValidateDNSAddress(dns_addresses): - return False, f"DNS IP-Address or FQDN is incorrect" - - if isinstance(mtu, str): - mtu = 0 - - if isinstance(keepalive, str): - keepalive = 0 - - if mtu not in range(0, 1461): - return False, "MTU format is not correct" - - if keepalive < 0: - return False, "Persistent Keepalive format is not correct" - - if len(private_key) > 0: - pubKey = GenerateWireguardPublicKey(private_key) - if not pubKey[0] or pubKey[1] != self.id: - return False, "Private key does not match with the public key" - - try: - rand = random.Random() - uid = str(uuid.UUID(int=rand.getrandbits(128), version=4)) - psk_exist = len(preshared_key) > 0 - - if psk_exist: - with open(uid, "w+") as f: - f.write(preshared_key) - - newAllowedIPs = allowed_ip.replace(" ", "") - if not CheckAddress(newAllowedIPs): - return False, "Allowed IPs entry format is incorrect" - - command = [self.configuration.Protocol, "set", self.configuration.Name, "peer", self.id, "allowed-ips", newAllowedIPs, "preshared-key", uid if psk_exist else "/dev/null"] - - updateAllowedIp = subprocess.check_output(command, stderr=subprocess.STDOUT) - - if psk_exist: os.remove(uid) - - if len(updateAllowedIp.decode().strip("\n")) != 0: - current_app.logger.error(f"Update peer failed when updating Allowed IPs.\nInput: {newAllowedIPs}\nOutput: {updateAllowedIp.decode().strip('\n')}") - return False, "Internal server error" - - command = [f"{self.configuration.Protocol}-quick", "save", self.configuration.Name] - saveConfig = subprocess.check_output(command, stderr=subprocess.STDOUT) - - if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'): - current_app.logger.error("Update peer failed when saving the configuration") - return False, "Internal server error" - - with self.configuration.engine.begin() as conn: - conn.execute( - self.configuration.peersTable.update().values({ - "name": name, - "private_key": private_key, - "DNS": dns_addresses, - "endpoint_allowed_ip": endpoint_allowed_ip, - "mtu": mtu, - "keepalive": keepalive, - "notes": notes, - "preshared_key": preshared_key - }).where( - self.configuration.peersTable.c.id == self.id - ) - ) - self.configuration.getPeers() - return True, None - except subprocess.CalledProcessError as exc: - current_app.logger.error(f"Subprocess call failed:\n{exc.output.decode("UTF-8")}") - return False, "Internal server error" diff --git a/src/modules/AmneziaWGPeer.py b/src/modules/AmneziaWGPeer.py new file mode 100644 index 00000000..17101b6b --- /dev/null +++ b/src/modules/AmneziaWGPeer.py @@ -0,0 +1,92 @@ +import os +import random +import re +import subprocess +import uuid + +from .Peer import Peer +from .Utilities import ValidateIPAddressesWithRange, ValidateDNSAddress, GenerateWireguardPublicKey + + +class AmneziaWGPeer(Peer): + def __init__(self, tableData, configuration): + self.advanced_security = tableData["advanced_security"] + super().__init__(tableData, configuration) + + + def updatePeer(self, name: str, private_key: str, + preshared_key: str, + dns_addresses: str, allowed_ip: str, endpoint_allowed_ip: str, mtu: int, + keepalive: int, advanced_security: str) -> tuple[bool, str] or tuple[bool, None]: + if not self.configuration.getStatus(): + self.configuration.toggleConfiguration() + + existingAllowedIps = [item for row in list( + map(lambda x: [q.strip() for q in x.split(',')], + map(lambda y: y.allowed_ip, + list(filter(lambda k: k.id != self.id, self.configuration.getPeersList()))))) for item in row] + + if allowed_ip in existingAllowedIps: + return False, "Allowed IP already taken by another peer" + if not ValidateIPAddressesWithRange(endpoint_allowed_ip): + return False, f"Endpoint Allowed IPs format is incorrect" + if len(dns_addresses) > 0 and not ValidateDNSAddress(dns_addresses): + return False, f"DNS format is incorrect" + + if type(mtu) is str: + mtu = 0 + + if type(keepalive) is str: + keepalive = 0 + + if mtu < 0 or mtu > 1460: + return False, "MTU format is not correct" + if keepalive < 0: + return False, "Persistent Keepalive format is not correct" + if advanced_security != "on" and advanced_security != "off": + return False, "Advanced Security can only be on or off" + if len(private_key) > 0: + pubKey = GenerateWireguardPublicKey(private_key) + if not pubKey[0] or pubKey[1] != self.id: + return False, "Private key does not match with the public key" + try: + rd = random.Random() + uid = str(uuid.UUID(int=rd.getrandbits(128), version=4)) + pskExist = len(preshared_key) > 0 + + if pskExist: + with open(uid, "w+") as f: + f.write(preshared_key) + newAllowedIPs = allowed_ip.replace(" ", "") + updateAllowedIp = subprocess.check_output( + f"{self.configuration.Protocol} set {self.configuration.Name} peer {self.id} allowed-ips {newAllowedIPs} {f'preshared-key {uid}' if pskExist else 'preshared-key /dev/null'}", + shell=True, stderr=subprocess.STDOUT) + + if pskExist: os.remove(uid) + + if len(updateAllowedIp.decode().strip("\n")) != 0: + return False, "Update peer failed when updating Allowed IPs" + saveConfig = subprocess.check_output(f"{self.configuration.Protocol}-quick save {self.configuration.Name}", + shell=True, stderr=subprocess.STDOUT) + if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'): + return False, "Update peer failed when saving the configuration" + + with self.configuration.engine.begin() as conn: + conn.execute( + self.configuration.peersTable.update().values({ + "name": name, + "private_key": private_key, + "DNS": dns_addresses, + "endpoint_allowed_ip": endpoint_allowed_ip, + "mtu": mtu, + "keepalive": keepalive, + "preshared_key": preshared_key, + "advanced_security": advanced_security + }).where( + self.configuration.peersTable.c.id == self.id + ) + ) + self.configuration.getPeers() + return True, None + except subprocess.CalledProcessError as exc: + return False, exc.output.decode("UTF-8").strip() \ No newline at end of file diff --git a/src/modules/AmneziaConfiguration.py b/src/modules/AmneziaWireguardConfiguration.py similarity index 63% rename from src/modules/AmneziaConfiguration.py rename to src/modules/AmneziaWireguardConfiguration.py index 6ddba994..6ada7d5f 100644 --- a/src/modules/AmneziaConfiguration.py +++ b/src/modules/AmneziaWireguardConfiguration.py @@ -4,39 +4,28 @@ AmneziaWG Configuration import random, sqlalchemy, os, subprocess, re, uuid from flask import current_app from .PeerJobs import PeerJobs -from .AmneziaPeer import AmneziaPeer +from .AmneziaWGPeer import AmneziaWGPeer from .PeerShareLinks import PeerShareLinks -from .Utilities import RegexMatch, CheckAddress, CheckPeerKey +from .Utilities import RegexMatch from .WireguardConfiguration import WireguardConfiguration from .DashboardWebHooks import DashboardWebHooks -class AmneziaConfiguration(WireguardConfiguration): - def __init__(self, - DashboardConfig, +class AmneziaWireguardConfiguration(WireguardConfiguration): + def __init__(self, DashboardConfig, AllPeerJobs: PeerJobs, AllPeerShareLinks: PeerShareLinks, DashboardWebHooks: DashboardWebHooks, - name: str = None, - data: dict = None, - backup: dict = None, - startup: bool = False): + name: str = None, data: dict = None, backup: dict = None, startup: bool = False): self.Jc = 0 self.Jmin = 0 self.Jmax = 0 self.S1 = 0 self.S2 = 0 - self.S3 = 0 - self.S4 = 0 self.H1 = 1 self.H2 = 2 self.H3 = 3 self.H4 = 4 - self.I1 = "0" - self.I2 = "0" - self.I3 = "0" - self.I4 = "0" - self.I5 = "0" super().__init__(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, name, data, backup, startup, wg=False) @@ -69,64 +58,65 @@ class AmneziaConfiguration(WireguardConfiguration): "Jmax": self.Jmax, "S1": self.S1, "S2": self.S2, - "S3": self.S3, - "S4": self.S4, "H1": self.H1, "H2": self.H2, "H3": self.H3, - "H4": self.H4, - "I1": self.I1, - "I2": self.I2, - "I3": self.I3, - "I4": self.I4, - "I5": self.I5 + "H4": self.H4 } def createDatabase(self, dbName = None): - def generate_column_obj(): - return [ - sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True), - sqlalchemy.Column('private_key', sqlalchemy.String(255)), - sqlalchemy.Column('DNS', sqlalchemy.Text), - sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text), - sqlalchemy.Column('name', sqlalchemy.Text), - sqlalchemy.Column('total_receive', sqlalchemy.Float), - sqlalchemy.Column('total_sent', sqlalchemy.Float), - sqlalchemy.Column('total_data', sqlalchemy.Float), - sqlalchemy.Column('endpoint', sqlalchemy.String(255)), - sqlalchemy.Column('status', sqlalchemy.String(255)), - sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)), - sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)), - sqlalchemy.Column('cumu_receive', sqlalchemy.Float), - sqlalchemy.Column('cumu_sent', sqlalchemy.Float), - sqlalchemy.Column('cumu_data', sqlalchemy.Float), - sqlalchemy.Column('mtu', sqlalchemy.Integer), - sqlalchemy.Column('keepalive', sqlalchemy.Integer), - sqlalchemy.Column('notes', sqlalchemy.Text), - sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)), - sqlalchemy.Column('preshared_key', sqlalchemy.String(255)) - ] - if dbName is None: dbName = self.Name + self.peersTable = sqlalchemy.Table( - f'{dbName}', self.metadata, *generate_column_obj(), extend_existing=True + dbName, self.metadata, + sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True), + sqlalchemy.Column('private_key', sqlalchemy.String(255)), + sqlalchemy.Column('DNS', sqlalchemy.Text), + sqlalchemy.Column('advanced_security', sqlalchemy.String(255)), + sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text), + sqlalchemy.Column('name', sqlalchemy.Text), + sqlalchemy.Column('total_receive', sqlalchemy.Float), + sqlalchemy.Column('total_sent', sqlalchemy.Float), + sqlalchemy.Column('total_data', sqlalchemy.Float), + sqlalchemy.Column('endpoint', sqlalchemy.String(255)), + sqlalchemy.Column('status', sqlalchemy.String(255)), + sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)), + sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)), + sqlalchemy.Column('cumu_receive', sqlalchemy.Float), + sqlalchemy.Column('cumu_sent', sqlalchemy.Float), + sqlalchemy.Column('cumu_data', sqlalchemy.Float), + sqlalchemy.Column('mtu', sqlalchemy.Integer), + sqlalchemy.Column('keepalive', sqlalchemy.Integer), + sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)), + sqlalchemy.Column('preshared_key', sqlalchemy.String(255)), + extend_existing=True ) - self.peersRestrictedTable = sqlalchemy.Table( - f'{dbName}_restrict_access', self.metadata, *generate_column_obj(), extend_existing=True + f'{dbName}_restrict_access', self.metadata, + sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True), + sqlalchemy.Column('private_key', sqlalchemy.String(255)), + sqlalchemy.Column('DNS', sqlalchemy.Text), + sqlalchemy.Column('advanced_security', sqlalchemy.String(255)), + sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text), + sqlalchemy.Column('name', sqlalchemy.Text), + sqlalchemy.Column('total_receive', sqlalchemy.Float), + sqlalchemy.Column('total_sent', sqlalchemy.Float), + sqlalchemy.Column('total_data', sqlalchemy.Float), + sqlalchemy.Column('endpoint', sqlalchemy.String(255)), + sqlalchemy.Column('status', sqlalchemy.String(255)), + sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)), + sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)), + sqlalchemy.Column('cumu_receive', sqlalchemy.Float), + sqlalchemy.Column('cumu_sent', sqlalchemy.Float), + sqlalchemy.Column('cumu_data', sqlalchemy.Float), + sqlalchemy.Column('mtu', sqlalchemy.Integer), + sqlalchemy.Column('keepalive', sqlalchemy.Integer), + sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)), + sqlalchemy.Column('preshared_key', sqlalchemy.String(255)), + extend_existing=True ) - - self.peersDeletedTable = sqlalchemy.Table( - f'{dbName}_deleted', self.metadata, *generate_column_obj(), extend_existing=True - ) - - if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite': - time_col_type = sqlalchemy.DATETIME - else: - time_col_type = sqlalchemy.TIMESTAMP - self.peersTransferTable = sqlalchemy.Table( f'{dbName}_transfer', self.metadata, sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False), @@ -136,7 +126,38 @@ class AmneziaConfiguration(WireguardConfiguration): sqlalchemy.Column('cumu_receive', sqlalchemy.Float), sqlalchemy.Column('cumu_sent', sqlalchemy.Float), sqlalchemy.Column('cumu_data', sqlalchemy.Float), - sqlalchemy.Column('time', time_col_type, server_default=sqlalchemy.func.now()), + sqlalchemy.Column('time', (sqlalchemy.DATETIME if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else sqlalchemy.TIMESTAMP), + server_default=sqlalchemy.func.now()), + extend_existing=True + ) + self.peersDeletedTable = sqlalchemy.Table( + f'{dbName}_deleted', self.metadata, + sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False), + sqlalchemy.Column('private_key', sqlalchemy.String(255)), + sqlalchemy.Column('DNS', sqlalchemy.Text), + sqlalchemy.Column('advanced_security', sqlalchemy.String(255)), + sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text), + sqlalchemy.Column('name', sqlalchemy.Text), + sqlalchemy.Column('total_receive', sqlalchemy.Float), + sqlalchemy.Column('total_sent', sqlalchemy.Float), + sqlalchemy.Column('total_data', sqlalchemy.Float), + sqlalchemy.Column('endpoint', sqlalchemy.String(255)), + sqlalchemy.Column('status', sqlalchemy.String(255)), + sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)), + sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)), + sqlalchemy.Column('cumu_receive', sqlalchemy.Float), + sqlalchemy.Column('cumu_sent', sqlalchemy.Float), + sqlalchemy.Column('cumu_data', sqlalchemy.Float), + sqlalchemy.Column('mtu', sqlalchemy.Integer), + sqlalchemy.Column('keepalive', sqlalchemy.Integer), + sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)), + sqlalchemy.Column('preshared_key', sqlalchemy.String(255)), + extend_existing=True + ) + self.infoTable = sqlalchemy.Table( + 'ConfigurationsInfo', self.metadata, + sqlalchemy.Column('ID', sqlalchemy.String(255), primary_key=True), + sqlalchemy.Column('Info', sqlalchemy.Text), extend_existing=True ) @@ -144,20 +165,15 @@ class AmneziaConfiguration(WireguardConfiguration): f'{dbName}_history_endpoint', self.metadata, sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False), sqlalchemy.Column('endpoint', sqlalchemy.String(255), nullable=False), - sqlalchemy.Column('time', time_col_type) - ) - - self.infoTable = sqlalchemy.Table( - 'ConfigurationsInfo', self.metadata, - sqlalchemy.Column('ID', sqlalchemy.String(255), primary_key=True), - sqlalchemy.Column('Info', sqlalchemy.Text), + sqlalchemy.Column('time', + (sqlalchemy.DATETIME if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else sqlalchemy.TIMESTAMP)), extend_existing=True ) self.metadata.create_all(self.engine) def getPeers(self): - self.Peers.clear() + self.Peers.clear() if self.configurationFileChanged(): with open(self.configPath, 'r') as configFile: p = [] @@ -195,9 +211,11 @@ class AmneziaConfiguration(WireguardConfiguration): if tempPeer is None: tempPeer = { "id": i['PublicKey'], + "advanced_security": i.get('AdvancedSecurity', 'off'), "private_key": "", "DNS": self.DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1], - "endpoint_allowed_ip": self.DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[1], + "endpoint_allowed_ip": self.DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[ + 1], "name": i.get("name"), "total_receive": 0, "total_sent": 0, @@ -211,7 +229,6 @@ class AmneziaConfiguration(WireguardConfiguration): "cumu_data": 0, "mtu": self.DashboardConfig.GetConfig("Peers", "peer_mtu")[1], "keepalive": self.DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1], - "notes": "", "remote_endpoint": self.DashboardConfig.GetConfig("Peers", "remote_endpoint")[1], "preshared_key": i["PresharedKey"] if "PresharedKey" in i.keys() else "" } @@ -226,14 +243,14 @@ class AmneziaConfiguration(WireguardConfiguration): self.peersTable.columns.id == i['PublicKey'] ) ) - self.Peers.append(AmneziaPeer(tempPeer, self)) + self.Peers.append(AmneziaWGPeer(tempPeer, self)) except Exception as e: current_app.logger.error(f"{self.Name} getPeers() Error", e) else: with self.engine.connect() as conn: existingPeers = conn.execute(self.peersTable.select()).mappings().fetchall() for i in existingPeers: - self.Peers.append(AmneziaPeer(i, self)) + self.Peers.append(AmneziaWGPeer(i, self)) def addPeers(self, peers: list) -> tuple[bool, list, str]: result = { @@ -241,15 +258,6 @@ class AmneziaConfiguration(WireguardConfiguration): "peers": [] } try: - cleanedAllowedIPs = {} - for p in peers: - newAllowedIPs = p['allowed_ip'].replace(" ", "") - if not CheckAddress(newAllowedIPs): - return False, [], "Allowed IPs entry format is incorrect" - if not CheckPeerKey(p["id"]): - return False, [], "Peer key format is incorrect" - cleanedAllowedIPs[p["id"]] = newAllowedIPs - with self.engine.begin() as conn: for i in peers: newPeer = { @@ -270,9 +278,9 @@ class AmneziaConfiguration(WireguardConfiguration): "cumu_data": 0, "mtu": i['mtu'], "keepalive": i['keepalive'], - "notes": i.get('notes', ''), "remote_endpoint": self.DashboardConfig.GetConfig("Peers", "remote_endpoint")[1], - "preshared_key": i["preshared_key"] + "preshared_key": i["preshared_key"], + "advanced_security": i['advanced_security'] } conn.execute( self.peersTable.insert().values(newPeer) @@ -285,15 +293,13 @@ class AmneziaConfiguration(WireguardConfiguration): with open(uid, "w+") as f: f.write(p['preshared_key']) - command = [self.Protocol, "set", self.Name, "peer", p['id'], "allowed-ips", cleanedAllowedIPs[p["id"]], "preshared-key", uid if presharedKeyExist else "/dev/null"] - subprocess.check_output(command, stderr=subprocess.STDOUT) - + subprocess.check_output( + f"{self.Protocol} set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}", + shell=True, stderr=subprocess.STDOUT) if presharedKeyExist: os.remove(uid) - - command = [f"{self.Protocol}-quick", "save", self.Name] - subprocess.check_output(command, stderr=subprocess.STDOUT) - + subprocess.check_output( + f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT) self.getPeers() for p in peers: p = self.searchPeer(p['id']) @@ -305,7 +311,7 @@ class AmneziaConfiguration(WireguardConfiguration): }) except Exception as e: current_app.logger.error("Add peers error", e) - return False, [], "Internal server error" + return False, [], str(e) return True, result['peers'], "" def getRestrictedPeers(self): @@ -313,4 +319,4 @@ class AmneziaConfiguration(WireguardConfiguration): with self.engine.connect() as conn: restricted = conn.execute(self.peersRestrictedTable.select()).mappings().fetchall() for i in restricted: - self.RestrictedPeers.append(AmneziaPeer(i, self)) + self.RestrictedPeers.append(AmneziaWGPeer(i, self)) \ No newline at end of file diff --git a/src/modules/DatabaseConnection.py b/src/modules/ConnectionString.py similarity index 89% rename from src/modules/DatabaseConnection.py rename to src/modules/ConnectionString.py index e3d7d1a0..77f69644 100644 --- a/src/modules/DatabaseConnection.py +++ b/src/modules/ConnectionString.py @@ -1,15 +1,14 @@ import configparser import os from sqlalchemy_utils import database_exists, create_database +from flask import current_app def ConnectionString(database) -> str: parser = configparser.ConfigParser(strict=False) parser.read_file(open('wg-dashboard.ini', "r+")) - sqlitePath = os.path.join("db") if not os.path.isdir(sqlitePath): os.mkdir(sqlitePath) - if parser.get("Database", "type") == "postgresql": cn = f'postgresql+psycopg://{parser.get("Database", "username")}:{parser.get("Database", "password")}@{parser.get("Database", "host")}/{database}' elif parser.get("Database", "type") == "mysql": @@ -20,6 +19,7 @@ def ConnectionString(database) -> str: if not database_exists(cn): create_database(cn) except Exception as e: + current_app.logger.error("Database error. Terminating...", e) exit(1) - + return cn \ No newline at end of file diff --git a/src/modules/DashboardClients.py b/src/modules/DashboardClients.py index 54ba4802..231141b1 100644 --- a/src/modules/DashboardClients.py +++ b/src/modules/DashboardClients.py @@ -8,7 +8,7 @@ import pyotp import sqlalchemy as db import requests -from .DatabaseConnection import ConnectionString +from .ConnectionString import ConnectionString from .DashboardClientsPeerAssignment import DashboardClientsPeerAssignment from .DashboardClientsTOTP import DashboardClientsTOTP from .DashboardOIDC import DashboardOIDC diff --git a/src/modules/DashboardClientsPeerAssignment.py b/src/modules/DashboardClientsPeerAssignment.py index c507e5aa..80722d06 100644 --- a/src/modules/DashboardClientsPeerAssignment.py +++ b/src/modules/DashboardClientsPeerAssignment.py @@ -1,7 +1,7 @@ import datetime import uuid -from .DatabaseConnection import ConnectionString +from .ConnectionString import ConnectionString from .DashboardLogger import DashboardLogger import sqlalchemy as db from .WireguardConfiguration import WireguardConfiguration diff --git a/src/modules/DashboardClientsTOTP.py b/src/modules/DashboardClientsTOTP.py index e3e1f5f6..e3830fb5 100644 --- a/src/modules/DashboardClientsTOTP.py +++ b/src/modules/DashboardClientsTOTP.py @@ -3,7 +3,7 @@ import hashlib import uuid import sqlalchemy as db -from .DatabaseConnection import ConnectionString +from .ConnectionString import ConnectionString class DashboardClientsTOTP: diff --git a/src/modules/DashboardConfig.py b/src/modules/DashboardConfig.py index 3ae7ab3f..5fe28626 100644 --- a/src/modules/DashboardConfig.py +++ b/src/modules/DashboardConfig.py @@ -7,15 +7,19 @@ import sqlalchemy as db from datetime import datetime from typing import Any from flask import current_app -from .DatabaseConnection import ConnectionString -from .Utilities import (GetRemoteEndpoint, ValidateDNSAddress) +from .ConnectionString import ConnectionString +from .Utilities import ( + GetRemoteEndpoint, ValidateDNSAddress +) from .DashboardAPIKey import DashboardAPIKey + + class DashboardConfig: - DashboardVersion = 'v4.3.3' + DashboardVersion = 'v4.3.2' ConfigurationPath = os.getenv('CONFIGURATION_PATH', '.') ConfigurationFilePath = os.path.join(ConfigurationPath, 'wg-dashboard.ini') - + def __init__(self): if not os.path.exists(DashboardConfig.ConfigurationFilePath): open(DashboardConfig.ConfigurationFilePath, "x") @@ -79,11 +83,9 @@ class DashboardConfig: }, "Clients": { "enable": "true", - "sign_up": "true" }, "WireGuardConfiguration": { - "autostart": "", - "peer_tracking": "false" + "autostart": "" } } @@ -100,54 +102,6 @@ class DashboardConfig: self.APIAccessed = False self.SetConfig("Server", "version", DashboardConfig.DashboardVersion) - def EnsureDatabaseIntegrity(self, wireguardConfigurations): - expected_columns = { - 'id': db.String(255), - 'private_key': db.String(255), - 'DNS': db.Text, - 'endpoint_allowed_ip': db.Text, - 'name': db.Text, - 'total_receive': db.Float, - 'total_sent': db.Float, - 'total_data': db.Float, - 'endpoint': db.String(255), - 'status': db.String(255), - 'latest_handshake': db.String(255), - 'allowed_ip': db.String(255), - 'cumu_receive': db.Float, - 'cumu_sent': db.Float, - 'cumu_data': db.Float, - 'mtu': db.Integer, - 'keepalive': db.Integer, - 'notes': db.Text, - 'remote_endpoint': db.String(255), - 'preshared_key': db.String(255) - } - - inspector = db.inspect(self.engine) - - with self.engine.begin() as conn: - for cfg_name, cfg_obj in wireguardConfigurations.items(): - tables_to_check = [ - cfg_name, - f'{cfg_name}_restrict_access', - f'{cfg_name}_deleted' - ] - - for table_name in tables_to_check: - if not table_name: - continue - if not inspector.has_table(table_name): - continue - - existing_columns = [c['name'] for c in inspector.get_columns(table_name)] - - for col_name, col_type in expected_columns.items(): - if col_name not in existing_columns: - type_str = col_type().compile(dialect=self.engine.dialect) - current_app.logger.info(f"Adding missing column '{col_name}' to table '{table_name}'") - conn.execute(db.text(f'ALTER TABLE "{table_name}" ADD COLUMN "{col_name}" {type_str}')) - def getConnectionString(self, database) -> str or None: sqlitePath = os.path.join(DashboardConfig.ConfigurationPath, "db") @@ -162,7 +116,7 @@ class DashboardConfig: cn = f'sqlite:///{os.path.join(sqlitePath, f"{database}.db")}' if not database_exists(cn): create_database(cn) - return cn + return cn def __createAPIKeyTable(self): self.apiKeyTable = db.Table('DashboardAPIKeys', self.dbMetadata, diff --git a/src/modules/DashboardLogger.py b/src/modules/DashboardLogger.py index 5d2b8ced..9b4e1f24 100644 --- a/src/modules/DashboardLogger.py +++ b/src/modules/DashboardLogger.py @@ -4,7 +4,7 @@ Dashboard Logger Class import uuid import sqlalchemy as db from flask import current_app -from .DatabaseConnection import ConnectionString +from .ConnectionString import ConnectionString class DashboardLogger: diff --git a/src/modules/DashboardWebHooks.py b/src/modules/DashboardWebHooks.py index ebaf43cd..a598444b 100644 --- a/src/modules/DashboardWebHooks.py +++ b/src/modules/DashboardWebHooks.py @@ -8,7 +8,7 @@ from datetime import datetime, timedelta import requests from pydantic import BaseModel, field_serializer import sqlalchemy as db -from .DatabaseConnection import ConnectionString +from .ConnectionString import ConnectionString from flask import current_app WebHookActions = ['peer_created', 'peer_deleted', 'peer_updated'] diff --git a/src/modules/Email.py b/src/modules/Email.py index d607973c..145a4d72 100644 --- a/src/modules/Email.py +++ b/src/modules/Email.py @@ -1,101 +1,76 @@ import os.path -import ssl import smtplib - -# Email libaries from email import encoders +from email.header import Header from email.mime.base import MIMEBase from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText -from email.utils import formatdate +from email.utils import formataddr class EmailSender: def __init__(self, DashboardConfig): + self.smtp = None self.DashboardConfig = DashboardConfig - if not os.path.exists('./attachments'): os.mkdir('./attachments') + + def Server(self): + return self.DashboardConfig.GetConfig("Email", "server")[1] + + def Port(self): + return self.DashboardConfig.GetConfig("Email", "port")[1] + + def Encryption(self): + return self.DashboardConfig.GetConfig("Email", "encryption")[1] + + def Username(self): + return self.DashboardConfig.GetConfig("Email", "username")[1] + + def Password(self): + return self.DashboardConfig.GetConfig("Email", "email_password")[1] + + def SendFrom(self): + return self.DashboardConfig.GetConfig("Email", "send_from")[1] + + # Thank you, @gdeeble from GitHub + def AuthenticationRequired(self): + return self.DashboardConfig.GetConfig("Email", "authentication_required")[1] - self.refresh_vals() + def ready(self): + if self.AuthenticationRequired(): + return all([self.Server(), self.Port(), self.Encryption(), self.Username(), self.Password(), self.SendFrom()]) + return all([self.Server(), self.Port(), self.Encryption(), self.SendFrom()]) - def refresh_vals(self) -> None: - self.Server = self.DashboardConfig.GetConfig("Email", "server")[1] - self.Port = self.DashboardConfig.GetConfig("Email", "port")[1] + def send(self, receiver, subject, body, includeAttachment = False, attachmentName = "") -> tuple[bool, str] | tuple[bool, None]: + if self.ready(): + try: + self.smtp = smtplib.SMTP(self.Server(), port=int(self.Port())) + self.smtp.ehlo() + if self.Encryption() == "STARTTLS": + self.smtp.starttls() + if self.AuthenticationRequired(): + self.smtp.login(self.Username(), self.Password()) + message = MIMEMultipart() + message['Subject'] = subject + message['From'] = self.SendFrom() + message["To"] = receiver + message.attach(MIMEText(body, "plain")) - self.Encryption = self.DashboardConfig.GetConfig("Email", "encryption")[1] - self.AuthRequired = self.DashboardConfig.GetConfig("Email", "authentication_required")[1] - self.Username = self.DashboardConfig.GetConfig("Email", "username")[1] - self.Password = self.DashboardConfig.GetConfig("Email", "email_password")[1] - - self.SendFrom = self.DashboardConfig.GetConfig("Email", "send_from")[1] - - def is_ready(self) -> bool: - self.refresh_vals() - - if self.AuthRequired: - ready = all([ - self.Server, self.Port, self.Encryption, - self.Username, self.Password, self.SendFrom - ]) - else: - ready = all([ - self.Server, self.Port, self.Encryption, self.SendFrom - ]) - return ready - - def send(self, receiver, subject, body, includeAttachment: bool = False, attachmentName: str = "") -> tuple[bool, str | None]: - if not self.is_ready(): - return False, "SMTP not configured" - - message = MIMEMultipart() - message['Subject'] = subject - message['From'] = self.SendFrom - message["To"] = receiver - message["Date"] = formatdate(localtime=True) - message.attach(MIMEText(body, "plain")) - - if includeAttachment and len(attachmentName) > 0: - attachmentPath = os.path.join('./attachments', attachmentName) - - if not os.path.exists(attachmentPath): - return False, "Attachment does not exist" - - attachment = MIMEBase("application", "octet-stream") - with open(os.path.join('./attachments', attachmentName), 'rb') as f: - attachment.set_payload(f.read()) - - encoders.encode_base64(attachment) - attachment.add_header("Content-Disposition", f"attachment; filename= {attachmentName}",) - message.attach(attachment) - - smtp = None - try: - context = ssl.create_default_context() - if self.Encryption == "IMPLICITTLS": - smtp = smtplib.SMTP_SSL(self.Server, port=int(self.Port), context=context) - else: - smtp = smtplib.SMTP(self.Server, port=int(self.Port)) - smtp.ehlo() - - # Configure SMTP encryption type - if self.Encryption == "STARTTLS": - smtp.starttls(context=context) - smtp.ehlo() - - # Log into the SMTP server if required - if self.AuthRequired: - smtp.login(self.Username, self.Password) - - # Send the actual email from the SMTP object - smtp.sendmail(self.SendFrom, receiver, message.as_string()) - return True, None - - except Exception as e: - return False, f"Send failed | Reason: {e}" - - finally: - if smtp: - try: - smtp.quit() - except Exception: - pass + if includeAttachment and len(attachmentName) > 0: + attachmentPath = os.path.join('./attachments', attachmentName) + if os.path.exists(attachmentPath): + attachment = MIMEBase("application", "octet-stream") + with open(os.path.join('./attachments', attachmentName), 'rb') as f: + attachment.set_payload(f.read()) + encoders.encode_base64(attachment) + attachment.add_header("Content-Disposition", f"attachment; filename= {attachmentName}",) + message.attach(attachment) + else: + self.smtp.close() + return False, "Attachment does not exist" + self.smtp.sendmail(self.SendFrom(), receiver, message.as_string()) + self.smtp.close() + return True, None + except Exception as e: + return False, f"Send failed | Reason: {e}" + return False, "SMTP not configured" \ No newline at end of file diff --git a/src/modules/NewConfigurationTemplates.py b/src/modules/NewConfigurationTemplates.py index 05acceaa..9c4511a4 100644 --- a/src/modules/NewConfigurationTemplates.py +++ b/src/modules/NewConfigurationTemplates.py @@ -2,7 +2,7 @@ import uuid from pydantic import BaseModel, field_serializer import sqlalchemy as db -from .DatabaseConnection import ConnectionString +from .ConnectionString import ConnectionString class NewConfigurationTemplate(BaseModel): diff --git a/src/modules/Peer.py b/src/modules/Peer.py index 970e747f..9201a9f0 100644 --- a/src/modules/Peer.py +++ b/src/modules/Peer.py @@ -10,9 +10,8 @@ from datetime import timedelta import jinja2 import sqlalchemy as db from .PeerJob import PeerJob -from flask import current_app from .PeerShareLink import PeerShareLink -from .Utilities import GenerateWireguardPublicKey, CheckAddress, ValidateDNSAddress +from .Utilities import GenerateWireguardPublicKey, ValidateIPAddressesWithRange, ValidateDNSAddress class Peer: @@ -35,7 +34,6 @@ class Peer: self.cumu_data = tableData["cumu_data"] self.mtu = tableData["mtu"] self.keepalive = tableData["keepalive"] - self.notes = tableData.get("notes", "") self.remote_endpoint = tableData["remote_endpoint"] self.preshared_key = tableData["preshared_key"] self.jobs: list[PeerJob] = [] @@ -51,89 +49,62 @@ class Peer: def __repr__(self): return str(self.toJson()) - def updatePeer(self, name: str, - private_key: str, + def updatePeer(self, name: str, private_key: str, preshared_key: str, - dns_addresses: str, - allowed_ip: str, - endpoint_allowed_ip: str, - mtu: int, - keepalive: int, - notes: str - ) -> tuple[bool, str | None]: - + dns_addresses: str, allowed_ip: str, endpoint_allowed_ip: str, mtu: int, + keepalive: int) -> tuple[bool, str] or tuple[bool, None]: if not self.configuration.getStatus(): self.configuration.toggleConfiguration() - # Before we do any compute, let us check if the given endpoint allowed ip is valid at all - if not CheckAddress(endpoint_allowed_ip): - return False, f"Endpoint Allowed IPs format is incorrect" + existingAllowedIps = [item for row in list( + map(lambda x: [q.strip() for q in x.split(',')], + map(lambda y: y.allowed_ip, + list(filter(lambda k: k.id != self.id, self.configuration.getPeersList()))))) for item in row] - peers = [] - for peer in self.configuration.getPeersList(): - # Make sure to exclude your own data when updating since its not really relevant - if peer.id != self.id: - continue - peers.append(peer) - - used_allowed_ips = [] - for peer in peers: - ips = peer.allowed_ip.split(',') - ips = [ip.strip() for ip in ips] - used_allowed_ips.append(ips) - - if allowed_ip in used_allowed_ips: + if allowed_ip in existingAllowedIps: return False, "Allowed IP already taken by another peer" - if not ValidateDNSAddress(dns_addresses): - return False, f"DNS IP-Address or FQDN is incorrect" + if not ValidateIPAddressesWithRange(endpoint_allowed_ip): + return False, f"Endpoint Allowed IPs format is incorrect" - if isinstance(mtu, str): + if len(dns_addresses) > 0 and not ValidateDNSAddress(dns_addresses): + return False, f"DNS format is incorrect" + + if type(mtu) is str or mtu is None: mtu = 0 - - if isinstance(keepalive, str): - keepalive = 0 - if mtu not in range(0, 1461): + if mtu < 0 or mtu > 1460: return False, "MTU format is not correct" + if type(keepalive) is str or keepalive is None: + keepalive = 0 + if keepalive < 0: return False, "Persistent Keepalive format is not correct" - if len(private_key) > 0: pubKey = GenerateWireguardPublicKey(private_key) if not pubKey[0] or pubKey[1] != self.id: return False, "Private key does not match with the public key" - try: - rand = random.Random() - uid = str(uuid.UUID(int=rand.getrandbits(128), version=4)) - psk_exist = len(preshared_key) > 0 + rd = random.Random() + uid = str(uuid.UUID(int=rd.getrandbits(128), version=4)) + pskExist = len(preshared_key) > 0 - if psk_exist: + if pskExist: with open(uid, "w+") as f: f.write(preshared_key) - newAllowedIPs = allowed_ip.replace(" ", "") - if not CheckAddress(newAllowedIPs): - return False, "Allowed IPs entry format is incorrect" - - command = [self.configuration.Protocol, "set", self.configuration.Name, "peer", self.id, "allowed-ips", newAllowedIPs, "preshared-key", uid if psk_exist else "/dev/null"] - updateAllowedIp = subprocess.check_output(command, stderr=subprocess.STDOUT) - - if psk_exist: os.remove(uid) + updateAllowedIp = subprocess.check_output( + f"{self.configuration.Protocol} set {self.configuration.Name} peer {self.id} allowed-ips {newAllowedIPs} {f'preshared-key {uid}' if pskExist else 'preshared-key /dev/null'}", + shell=True, stderr=subprocess.STDOUT) + if pskExist: os.remove(uid) if len(updateAllowedIp.decode().strip("\n")) != 0: - current_app.logger.error("Update peer failed when updating Allowed IPs") - return False, "Internal server error" - - command = [f"{self.configuration.Protocol}-quick", "save", self.configuration.Name] - saveConfig = subprocess.check_output(command, stderr=subprocess.STDOUT) - + return False, "Update peer failed when updating Allowed IPs" + saveConfig = subprocess.check_output(f"{self.configuration.Protocol}-quick save {self.configuration.Name}", + shell=True, stderr=subprocess.STDOUT) if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'): - current_app.logger.error("Update peer failed when saving the configuration") - return False, "Internal server error" - + return False, "Update peer failed when saving the configuration" with self.configuration.engine.begin() as conn: conn.execute( self.configuration.peersTable.update().values({ @@ -143,7 +114,6 @@ class Peer: "endpoint_allowed_ip": endpoint_allowed_ip, "mtu": mtu, "keepalive": keepalive, - "notes": notes, "preshared_key": preshared_key }).where( self.configuration.peersTable.c.id == self.id @@ -151,8 +121,7 @@ class Peer: ) return True, None except subprocess.CalledProcessError as exc: - current_app.logger.error(f"Subprocess call failed:\n{exc.output.decode("UTF-8")}") - return False, "Internal server error" + return False, exc.output.decode("UTF-8").strip() def downloadPeer(self) -> dict[str, str]: final = { @@ -163,14 +132,12 @@ class Peer: if len(filename) == 0: filename = "UntitledPeer" filename = "".join(filename.split(' ')) - - # use previous filtering code if code below is insufficient or faulty - filename = re.sub(r'[.,/?<>\\:*|"]', '', filename).rstrip(". ") # remove special characters - - reserved_pattern = r"^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\..*)?$" # match com1-9, lpt1-9, con, nul, prn, aux, nul - - if re.match(reserved_pattern, filename, re.IGNORECASE): - filename = f"file_{filename}" # prepend "file_" if it matches + filename = f"{filename}" + illegal_filename = [".", ",", "/", "?", "<", ">", "\\", ":", "*", '|' '\"', "com1", "com2", "com3", + "com4", "com5", "com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3", "lpt4", + "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "con", "nul", "prn"] + for i in illegal_filename: + filename = filename.replace(i, "") for i in filename: if re.match("^[a-zA-Z0-9_=+.-]$", i): @@ -196,17 +163,10 @@ class Peer: "Jmax": self.configuration.Jmax, "S1": self.configuration.S1, "S2": self.configuration.S2, - "S3": self.configuration.S3, - "S4": self.configuration.S4, "H1": self.configuration.H1, "H2": self.configuration.H2, "H3": self.configuration.H3, - "H4": self.configuration.H4, - "I1": self.configuration.I1, - "I2": self.configuration.I2, - "I3": self.configuration.I3, - "I4": self.configuration.I4, - "I5": self.configuration.I5 + "H4": self.configuration.H4 }) peerSection = { @@ -391,4 +351,4 @@ class Peer: hours, remainder = divmod(delta.total_seconds(), 3600) minutes, seconds = divmod(remainder, 60) - return f"{int(hours):02}:{int(minutes):02}:{int(seconds):02}" + return f"{int(hours):02}:{int(minutes):02}:{int(seconds):02}" \ No newline at end of file diff --git a/src/modules/PeerJobLogger.py b/src/modules/PeerJobLogger.py index 4e74b822..9f121971 100644 --- a/src/modules/PeerJobLogger.py +++ b/src/modules/PeerJobLogger.py @@ -8,7 +8,7 @@ import sqlalchemy as db from flask import current_app from sqlalchemy import RowMapping -from .DatabaseConnection import ConnectionString +from .ConnectionString import ConnectionString from .Log import Log class PeerJobLogger: diff --git a/src/modules/PeerJobs.py b/src/modules/PeerJobs.py index c24e47fc..274a4263 100644 --- a/src/modules/PeerJobs.py +++ b/src/modules/PeerJobs.py @@ -3,7 +3,7 @@ Peer Jobs """ import sqlalchemy -from .DatabaseConnection import ConnectionString +from .ConnectionString import ConnectionString from .PeerJob import PeerJob from .PeerJobLogger import PeerJobLogger import sqlalchemy as db diff --git a/src/modules/PeerShareLinks.py b/src/modules/PeerShareLinks.py index eec6e898..206e2fd0 100644 --- a/src/modules/PeerShareLinks.py +++ b/src/modules/PeerShareLinks.py @@ -1,4 +1,4 @@ -from .DatabaseConnection import ConnectionString +from .ConnectionString import ConnectionString from .PeerShareLink import PeerShareLink import sqlalchemy as db from datetime import datetime diff --git a/src/modules/Utilities.py b/src/modules/Utilities.py index 661d3500..0ba24066 100644 --- a/src/modules/Utilities.py +++ b/src/modules/Utilities.py @@ -1,6 +1,6 @@ import re, ipaddress import subprocess -import sqlalchemy + def RegexMatch(regex, text) -> bool: """ @@ -18,18 +18,10 @@ def GetRemoteEndpoint() -> str: @return: """ import socket - try: - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: - s.connect(("1.1.1.1", 80)) # Connecting to a public IP + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + s.connect(("1.1.1.1", 80)) # Connecting to a public IP wgd_remote_endpoint = s.getsockname()[0] return str(wgd_remote_endpoint) - except (socket.error, OSError): - pass - try: - return socket.gethostbyname(socket.gethostname()) - except (socket.error, OSError): - pass - return "127.0.0.1" def StringToBoolean(value: str): @@ -41,35 +33,31 @@ def StringToBoolean(value: str): return (value.strip().replace(" ", "").lower() in ("yes", "true", "t", "1", 1)) -def CheckAddress(ips_str: str) -> bool: - if len(ips_str) == 0: - return False - - for ip in ips_str.split(','): - stripped_ip = ip.strip() +def ValidateIPAddressesWithRange(ips: str) -> bool: + s = ips.replace(" ", "").split(",") + for ip in s: try: - # Verify the IP-address, with the strict flag as false also allows for /32 and /128 - ipaddress.ip_network(stripped_ip, strict=False) - except ValueError: + ipaddress.ip_network(ip) + except ValueError as e: return False return True -def CheckPeerKey(peer_key: str) -> bool: - return re.match(r"^[A-Za-z0-9+/]{43}=$", peer_key) - -def ValidateDNSAddress(addresses_str: str) -> tuple[bool, str | None]: - if len(addresses_str) == 0: - return False, "Got an empty list/string to check for valid DNS-addresses" - - addresses = addresses_str.split(',') - for address in addresses: - stripped_address = address.strip() - - if not CheckAddress(stripped_address) and not RegexMatch(r"(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z]{0,61}[a-z]", stripped_address): - return False, f"{stripped_address} does not appear to be a valid IP-address or FQDN" - - return True, None +def ValidateIPAddresses(ips) -> bool: + s = ips.replace(" ", "").split(",") + for ip in s: + try: + ipaddress.ip_address(ip) + except ValueError as e: + return False + return True +def ValidateDNSAddress(addresses) -> tuple[bool, str]: + s = addresses.replace(" ", "").split(",") + for address in s: + if not ValidateIPAddresses(address) and not RegexMatch( + r"(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z]{0,61}[a-z]", address): + return False, f"{address} does not appear to be an valid DNS address" + return True, "" def ValidateEndpointAllowedIPs(IPs) -> tuple[bool, str] | tuple[bool, None]: ips = IPs.replace(" ", "").split(",") @@ -113,4 +101,4 @@ def ValidatePasswordStrength(password: str) -> tuple[bool, str] | tuple[bool, No if not re.search(r'[$&+,:;=?@#|\'<>.\-^*()%!~_-]', password): return False, "Password must contain at least 1 special character from $&+,:;=?@#|'<>.-^*()%!~_-" - return True, None + return True, None \ No newline at end of file diff --git a/src/modules/WireguardConfiguration.py b/src/modules/WireguardConfiguration.py index 548dfcbe..f1fdfe16 100644 --- a/src/modules/WireguardConfiguration.py +++ b/src/modules/WireguardConfiguration.py @@ -10,18 +10,13 @@ from datetime import datetime, timedelta from itertools import islice from flask import current_app -from .DatabaseConnection import ConnectionString +from .ConnectionString import ConnectionString from .DashboardConfig import DashboardConfig from .Peer import Peer from .PeerJobs import PeerJobs from .PeerShareLinks import PeerShareLinks -from .Utilities import StringToBoolean, \ - GenerateWireguardPublicKey, \ - RegexMatch, \ - ValidateDNSAddress, \ - ValidateEndpointAllowedIPs, \ - CheckAddress, \ - CheckPeerKey +from .Utilities import StringToBoolean, GenerateWireguardPublicKey, RegexMatch, ValidateDNSAddress, \ + ValidateEndpointAllowedIPs from .WireguardConfigurationInfo import WireguardConfigurationInfo, PeerGroupsClass from .DashboardWebHooks import DashboardWebHooks @@ -66,14 +61,13 @@ class WireguardConfiguration: self.Protocol = "wg" if wg else "awg" self.AllPeerJobs = AllPeerJobs self.DashboardConfig = DashboardConfig - self.DashboardConfig.EnsureDatabaseIntegrity({self.Name: self}) self.AllPeerShareLinks = AllPeerShareLinks self.DashboardWebHooks = DashboardWebHooks self.configPath = os.path.join(self.__getProtocolPath(), f'{self.Name}.conf') self.engine: sqlalchemy.Engine = sqlalchemy.create_engine(ConnectionString("wgdashboard")) self.metadata: sqlalchemy.MetaData = sqlalchemy.MetaData() self.dbType = self.DashboardConfig.GetConfig("Database", "type")[1] - + if name is not None: if data is not None and "Backup" in data.keys(): db = self.__importDatabase( @@ -115,17 +109,10 @@ class WireguardConfiguration: self.__parser["Interface"]["Jmax"] = self.Jmax self.__parser["Interface"]["S1"] = self.S1 self.__parser["Interface"]["S2"] = self.S2 - self.__parser["Interface"]["S3"] = self.S3 - self.__parser["Interface"]["S4"] = self.S4 self.__parser["Interface"]["H1"] = self.H1 self.__parser["Interface"]["H2"] = self.H2 self.__parser["Interface"]["H3"] = self.H3 self.__parser["Interface"]["H4"] = self.H4 - self.__parser["Interface"]["I1"] = self.I1 - self.__parser["Interface"]["I2"] = self.I2 - self.__parser["Interface"]["I3"] = self.I3 - self.__parser["Interface"]["I4"] = self.I4 - self.__parser["Interface"]["I5"] = self.I5 if "Backup" not in data.keys(): self.createDatabase() @@ -140,11 +127,8 @@ class WireguardConfiguration: current_app.logger.info(f"Initialized Configuration: {name}") self.__dumpDatabase() if self.getAutostartStatus() and not self.getStatus() and startup: - status, ext = self.toggleConfiguration() - if not status: - current_app.logger.error(f"Failed to autostart configuration: {name}. Reason: {ext}") - else: - current_app.logger.info(f"Autostart Configuration: {name}") + self.toggleConfiguration() + current_app.logger.info(f"Autostart Configuration: {name}") self.configurationInfo: WireguardConfigurationInfo | None = None configurationInfoJson = self.readConfigurationInfo() @@ -156,6 +140,7 @@ class WireguardConfiguration: if self.Status: self.addAutostart() + def __getProtocolPath(self) -> str: _, path = self.DashboardConfig.GetConfig("Server", "wg_conf_path") if self.Protocol == "wg" \ @@ -247,50 +232,54 @@ class WireguardConfiguration: return True def createDatabase(self, dbName = None): - def generate_column_obj(): - return [ - sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True), - sqlalchemy.Column('private_key', sqlalchemy.String(255)), - sqlalchemy.Column('DNS', sqlalchemy.Text), - sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text), - sqlalchemy.Column('name', sqlalchemy.Text), - sqlalchemy.Column('total_receive', sqlalchemy.Float), - sqlalchemy.Column('total_sent', sqlalchemy.Float), - sqlalchemy.Column('total_data', sqlalchemy.Float), - sqlalchemy.Column('endpoint', sqlalchemy.String(255)), - sqlalchemy.Column('status', sqlalchemy.String(255)), - sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)), - sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)), - sqlalchemy.Column('cumu_receive', sqlalchemy.Float), - sqlalchemy.Column('cumu_sent', sqlalchemy.Float), - sqlalchemy.Column('cumu_data', sqlalchemy.Float), - sqlalchemy.Column('mtu', sqlalchemy.Integer), - sqlalchemy.Column('keepalive', sqlalchemy.Integer), - sqlalchemy.Column('notes', sqlalchemy.Text), - sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)), - sqlalchemy.Column('preshared_key', sqlalchemy.String(255)) - ] - if dbName is None: dbName = self.Name - self.peersTable = sqlalchemy.Table( - f'{dbName}', self.metadata, *generate_column_obj(), extend_existing=True + dbName, self.metadata, + sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True), + sqlalchemy.Column('private_key', sqlalchemy.String(255)), + sqlalchemy.Column('DNS', sqlalchemy.Text), + sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text), + sqlalchemy.Column('name', sqlalchemy.Text), + sqlalchemy.Column('total_receive', sqlalchemy.Float), + sqlalchemy.Column('total_sent', sqlalchemy.Float), + sqlalchemy.Column('total_data', sqlalchemy.Float), + sqlalchemy.Column('endpoint', sqlalchemy.String(255)), + sqlalchemy.Column('status', sqlalchemy.String(255)), + sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)), + sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)), + sqlalchemy.Column('cumu_receive', sqlalchemy.Float), + sqlalchemy.Column('cumu_sent', sqlalchemy.Float), + sqlalchemy.Column('cumu_data', sqlalchemy.Float), + sqlalchemy.Column('mtu', sqlalchemy.Integer), + sqlalchemy.Column('keepalive', sqlalchemy.Integer), + sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)), + sqlalchemy.Column('preshared_key', sqlalchemy.String(255)), + extend_existing=True ) - self.peersRestrictedTable = sqlalchemy.Table( - f'{dbName}_restrict_access', self.metadata, *generate_column_obj(), extend_existing=True + f'{dbName}_restrict_access', self.metadata, + sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True), + sqlalchemy.Column('private_key', sqlalchemy.String(255)), + sqlalchemy.Column('DNS', sqlalchemy.Text), + sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text), + sqlalchemy.Column('name', sqlalchemy.Text), + sqlalchemy.Column('total_receive', sqlalchemy.Float), + sqlalchemy.Column('total_sent', sqlalchemy.Float), + sqlalchemy.Column('total_data', sqlalchemy.Float), + sqlalchemy.Column('endpoint', sqlalchemy.String(255)), + sqlalchemy.Column('status', sqlalchemy.String(255)), + sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)), + sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)), + sqlalchemy.Column('cumu_receive', sqlalchemy.Float), + sqlalchemy.Column('cumu_sent', sqlalchemy.Float), + sqlalchemy.Column('cumu_data', sqlalchemy.Float), + sqlalchemy.Column('mtu', sqlalchemy.Integer), + sqlalchemy.Column('keepalive', sqlalchemy.Integer), + sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)), + sqlalchemy.Column('preshared_key', sqlalchemy.String(255)), + extend_existing=True ) - - self.peersDeletedTable = sqlalchemy.Table( - f'{dbName}_deleted', self.metadata, *generate_column_obj(), extend_existing=True - ) - - if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite': - time_col_type = sqlalchemy.DATETIME - else: - time_col_type = sqlalchemy.TIMESTAMP - self.peersTransferTable = sqlalchemy.Table( f'{dbName}_transfer', self.metadata, sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False), @@ -300,7 +289,8 @@ class WireguardConfiguration: sqlalchemy.Column('cumu_receive', sqlalchemy.Float), sqlalchemy.Column('cumu_sent', sqlalchemy.Float), sqlalchemy.Column('cumu_data', sqlalchemy.Float), - sqlalchemy.Column('time', time_col_type, server_default=sqlalchemy.func.now()), + sqlalchemy.Column('time', (sqlalchemy.DATETIME if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else sqlalchemy.TIMESTAMP), + server_default=sqlalchemy.func.now()), extend_existing=True ) @@ -308,9 +298,34 @@ class WireguardConfiguration: f'{dbName}_history_endpoint', self.metadata, sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False), sqlalchemy.Column('endpoint', sqlalchemy.String(255), nullable=False), - sqlalchemy.Column('time', time_col_type) + sqlalchemy.Column('time', + (sqlalchemy.DATETIME if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else sqlalchemy.TIMESTAMP)), + extend_existing=True ) + self.peersDeletedTable = sqlalchemy.Table( + f'{dbName}_deleted', self.metadata, + sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True), + sqlalchemy.Column('private_key', sqlalchemy.String(255)), + sqlalchemy.Column('DNS', sqlalchemy.Text), + sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text), + sqlalchemy.Column('name', sqlalchemy.Text), + sqlalchemy.Column('total_receive', sqlalchemy.Float), + sqlalchemy.Column('total_sent', sqlalchemy.Float), + sqlalchemy.Column('total_data', sqlalchemy.Float), + sqlalchemy.Column('endpoint', sqlalchemy.String(255)), + sqlalchemy.Column('status', sqlalchemy.String(255)), + sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)), + sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)), + sqlalchemy.Column('cumu_receive', sqlalchemy.Float), + sqlalchemy.Column('cumu_sent', sqlalchemy.Float), + sqlalchemy.Column('cumu_data', sqlalchemy.Float), + sqlalchemy.Column('mtu', sqlalchemy.Integer), + sqlalchemy.Column('keepalive', sqlalchemy.Integer), + sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)), + sqlalchemy.Column('preshared_key', sqlalchemy.String(255)), + extend_existing=True + ) self.infoTable = sqlalchemy.Table( 'ConfigurationsInfo', self.metadata, sqlalchemy.Column('ID', sqlalchemy.String(255), primary_key=True), @@ -389,7 +404,6 @@ class WireguardConfiguration: try: if "[Peer]" not in content: current_app.logger.info(f"{self.Name} config has no [Peer] section") - self.Peers = [] return peerStarts = content.index("[Peer]") @@ -425,7 +439,8 @@ class WireguardConfiguration: "id": i['PublicKey'], "private_key": "", "DNS": self.DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1], - "endpoint_allowed_ip": self.DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[1], + "endpoint_allowed_ip": self.DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[ + 1], "name": i.get("name"), "total_receive": 0, "total_sent": 0, @@ -439,7 +454,6 @@ class WireguardConfiguration: "cumu_data": 0, "mtu": self.DashboardConfig.GetConfig("Peers", "peer_mtu")[1] if len(self.DashboardConfig.GetConfig("Peers", "peer_mtu")[1]) > 0 else None, "keepalive": self.DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1] if len(self.DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1]) > 0 else None, - "notes": "", "remote_endpoint": self.DashboardConfig.GetConfig("Peers", "remote_endpoint")[1], "preshared_key": i["PresharedKey"] if "PresharedKey" in i.keys() else "" } @@ -512,15 +526,6 @@ class WireguardConfiguration: "peers": [] } try: - cleanedAllowedIPs = {} - for p in peers: - newAllowedIPs = p['allowed_ip'].replace(" ", "") - if not CheckAddress(newAllowedIPs): - return False, [], "Allowed IPs entry format is incorrect" - if not CheckPeerKey(p["id"]): - return False, [], "Peer key format is incorrect" - cleanedAllowedIPs[p["id"]] = newAllowedIPs - with self.engine.begin() as conn: for i in peers: newPeer = { @@ -541,7 +546,6 @@ class WireguardConfiguration: "cumu_data": 0, "mtu": i['mtu'], "keepalive": i['keepalive'], - "notes": i.get("notes", ""), "remote_endpoint": self.DashboardConfig.GetConfig("Peers", "remote_endpoint")[1], "preshared_key": i["preshared_key"] } @@ -556,15 +560,12 @@ class WireguardConfiguration: with open(uid, "w+") as f: f.write(p['preshared_key']) - command = [self.Protocol, "set", self.Name, "peer", p['id'], "allowed-ips", cleanedAllowedIPs[p["id"]], "preshared-key", uid if presharedKeyExist else "/dev/null"] - subprocess.check_output(command, stderr=subprocess.STDOUT) - + subprocess.check_output(f"{self.Protocol} set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}", + shell=True, stderr=subprocess.STDOUT) if presharedKeyExist: os.remove(uid) - - command = [f"{self.Protocol}-quick", "save", self.Name] - subprocess.check_output(command, stderr=subprocess.STDOUT) - + subprocess.check_output( + f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT) self.getPeers() for p in peers: p = self.searchPeer(p['id']) @@ -576,7 +577,7 @@ class WireguardConfiguration: }) except Exception as e: current_app.logger.error("Add peers error", e) - return False, [], "Internal server error" + return False, [], str(e) return True, result['peers'], "" def searchPeer(self, publicKey): @@ -614,16 +615,8 @@ class WireguardConfiguration: with open(uid, "w+") as f: f.write(restrictedPeer['preshared_key']) - newAllowedIPs = restrictedPeer['allowed_ip'].replace(" ", "") - if not CheckAddress(newAllowedIPs): - return False, "Allowed IPs entry format is incorrect" - - if not CheckPeerKey(restrictedPeer["id"]): - return False, "Peer key format is incorrect" - - command = [self.Protocol, "set", self.Name, "peer", restrictedPeer["id"], "allowed-ips", newAllowedIPs, "preshared-key", uid if presharedKeyExist else "/dev/null"] - subprocess.check_output(command, stderr=subprocess.STDOUT) - + subprocess.check_output(f"{self.Protocol} set {self.Name} peer {restrictedPeer['id']} allowed-ips {restrictedPeer['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}", + shell=True, stderr=subprocess.STDOUT) if presharedKeyExist: os.remove(uid) else: return False, "Failed to allow access of peer " + i @@ -643,9 +636,8 @@ class WireguardConfiguration: found, pf = self.searchPeer(p) if found: try: - command = [self.Protocol, "set", self.Name, "peer", pf.id, "remove"] - subprocess.check_output(command, stderr=subprocess.STDOUT) - + subprocess.check_output(f"{self.Protocol} set {self.Name} peer {pf.id} remove", + shell=True, stderr=subprocess.STDOUT) conn.execute( self.peersRestrictedTable.insert().from_select( [c.name for c in self.peersTable.columns], @@ -673,8 +665,9 @@ class WireguardConfiguration: if not self.__wgSave(): return False, "Failed to save configuration through WireGuard" - self.getRestrictedPeers() + self.getPeers() + if numOfRestrictedPeers == len(listOfPublicKeys): return True, f"Restricted {numOfRestrictedPeers} peer(s)" return False, f"Restricted {numOfRestrictedPeers} peer(s) successfully. Failed to restrict {numOfFailedToRestrictPeers} peer(s)" @@ -726,20 +719,17 @@ class WireguardConfiguration: def __wgSave(self) -> tuple[bool, str] | tuple[bool, None]: try: - command = [f"{self.Protocol}-quick", "save", self.Name] - subprocess.check_output(command, stderr=subprocess.STDOUT) - + subprocess.check_output(f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT) return True, None except subprocess.CalledProcessError as e: - current_app.logger.error(f"Failed to process command:\n{str(e)}") - return False, "Internal server error" + return False, str(e) def getPeersLatestHandshake(self): if not self.getStatus(): self.toggleConfiguration() try: - command = [self.Protocol, "show", self.Name, "latest-handshakes"] - latestHandshake = subprocess.check_output(command, stderr=subprocess.STDOUT) + latestHandshake = subprocess.check_output(f"{self.Protocol} show {self.Name} latest-handshakes", + shell=True, stderr=subprocess.STDOUT) except subprocess.CalledProcessError: return "stopped" latestHandshake = latestHandshake.decode("UTF-8").split() @@ -778,9 +768,8 @@ class WireguardConfiguration: if not self.getStatus(): self.toggleConfiguration() # try: - command = [self.Protocol, "show", self.Name, "transfer"] - data_usage = subprocess.check_output(command, stderr=subprocess.STDOUT) - + data_usage = subprocess.check_output(f"{self.Protocol} show {self.Name} transfer", + shell=True, stderr=subprocess.STDOUT) data_usage = data_usage.decode("UTF-8").split("\n") data_usage = [p.split("\t") for p in data_usage] @@ -794,13 +783,15 @@ class WireguardConfiguration: ) ).mappings().fetchone() if cur_i is not None: + # print(cur_i is None) total_sent = cur_i['total_sent'] + # print(cur_i is None) total_receive = cur_i['total_receive'] cur_total_sent = float(data_usage[i][2]) / (1024 ** 3) cur_total_receive = float(data_usage[i][1]) / (1024 ** 3) cumulative_receive = cur_i['cumu_receive'] + total_receive cumulative_sent = cur_i['cumu_sent'] + total_sent - if (total_sent * 0.999 ) <= cur_total_sent and (total_receive * 0.999) <= cur_total_receive: # An accuracy of 1K ppm is sufficient + if total_sent <= cur_total_sent and total_receive <= cur_total_receive: total_sent = cur_total_sent total_receive = cur_total_receive else: @@ -835,11 +826,10 @@ class WireguardConfiguration: if not self.getStatus(): self.toggleConfiguration() try: - command = [self.Protocol, "show", self.Name, "endpoints"] - data_usage = subprocess.check_output(command, stderr=subprocess.STDOUT) + data_usage = subprocess.check_output(f"{self.Protocol} show {self.Name} endpoints", + shell=True, stderr=subprocess.STDOUT) except subprocess.CalledProcessError: return "stopped" - data_usage = data_usage.decode("UTF-8").split() count = 0 with self.engine.begin() as conn: @@ -857,17 +847,14 @@ class WireguardConfiguration: self.getStatus() if self.Status: try: - command = [f"{self.Protocol}-quick", "down", self.Name] - check = subprocess.check_output(command, stderr=subprocess.STDOUT) - + check = subprocess.check_output(f"{self.Protocol}-quick down {self.Name}", + shell=True, stderr=subprocess.STDOUT) self.removeAutostart() except subprocess.CalledProcessError as exc: return False, str(exc.output.strip().decode("utf-8")) else: try: - command = [f"{self.Protocol}-quick", "up", self.Name] - check = subprocess.check_output(command, stderr=subprocess.STDOUT) - + check = subprocess.check_output(f"{self.Protocol}-quick up {self.Name}", shell=True, stderr=subprocess.STDOUT) self.addAutostart() except subprocess.CalledProcessError as exc: return False, str(exc.output.strip().decode("utf-8")) @@ -934,8 +921,8 @@ class WireguardConfiguration: files.sort(key=lambda x: x[1], reverse=True) for f, ct in files: - if RegexMatch(rf"^({self.Name})_(\d+)\\.(conf)$", f): - s = re.search(rf"^({self.Name})_(\d+)\\.(conf)$", f) + if RegexMatch(f"^({self.Name})_(.*)\\.(conf)$", f): + s = re.search(f"^({self.Name})_(.*)\\.(conf)$", f) date = s.group(2) d = { "filename": f, @@ -1008,7 +995,7 @@ class WireguardConfiguration: original = [l.rstrip("\n") for l in f.readlines()] allowEdit = ["Address", "PreUp", "PostUp", "PreDown", "PostDown", "ListenPort", "Table"] if self.Protocol == 'awg': - allowEdit += ["Jc", "Jmin", "Jmax", "S1", "S2", "S3", "S4", "H1", "H2", "H3", "H4", "I1", "I2", "I3", "I4", "I5"] + allowEdit += ["Jc", "Jmin", "Jmax", "S1", "S2", "H1", "H2", "H3", "H4"] start = original.index("[Interface]") try: end = original.index("[Peer]") @@ -1046,33 +1033,31 @@ class WireguardConfiguration: return True def renameConfiguration(self, newConfigurationName) -> tuple[bool, str]: - newConfigurationName = os.path.basename(newConfigurationName) - - if len(newConfigurationName) > 15 or not re.match(r'^[a-zA-Z0-9_=\+\.\-]{1,15}$', newConfigurationName): - return False, "Configuration name is either too long or contains an illegal character" - - newConfigurationName = newConfigurationName.replace("`", "") # double check - try: if self.getStatus(): self.toggleConfiguration() self.createDatabase(newConfigurationName) with self.engine.begin() as conn: - def doRenameStatement(suffix): - newConfig = f"{newConfigurationName}{suffix}" - oldConfig = f"{self.Name}{suffix}" - - conn.execute( - sqlalchemy.text( - f'INSERT INTO `{newConfig}` SELECT * FROM `{oldConfig}`' - ) + conn.execute( + sqlalchemy.text( + f'INSERT INTO "{newConfigurationName}" SELECT * FROM "{self.Name}"' ) - - doRenameStatement("") - doRenameStatement("_restrict_access") - doRenameStatement("_deleted") - doRenameStatement("_transfer") - + ) + conn.execute( + sqlalchemy.text( + f'INSERT INTO "{newConfigurationName}_restrict_access" SELECT * FROM "{self.Name}_restrict_access"' + ) + ) + conn.execute( + sqlalchemy.text( + f'INSERT INTO "{newConfigurationName}_deleted" SELECT * FROM "{self.Name}_deleted"' + ) + ) + conn.execute( + sqlalchemy.text( + f'INSERT INTO "{newConfigurationName}_transfer" SELECT * FROM "{self.Name}_transfer"' + ) + ) self.AllPeerJobs.updateJobConfigurationName(self.Name, newConfigurationName) shutil.copy( self.configPath, @@ -1080,8 +1065,8 @@ class WireguardConfiguration: ) self.deleteConfiguration() except Exception as e: - current_app.logger.error(f"Failed to rename configuration.\nNew Configuration Name: {newConfigurationName}\nError: {str(e)}") - return False, "Internal server error" + traceback.print_stack() + return False, str(e) return True, None def getNumberOfAvailableIP(self): @@ -1241,6 +1226,7 @@ class WireguardConfiguration: def __validateOverridePeerSettings(self, key: str, value: str | int) -> tuple[bool, None] | tuple[bool, str]: status = True msg = None + print(value) if key == "DNS" and value: status, msg = ValidateDNSAddress(value) elif key == "EndpointAllowedIPs" and value: @@ -1305,4 +1291,4 @@ class WireguardConfiguration: conn.execute(sqlalchemy.text('VACUUM;')) except Exception as e: return False - return True + return True \ No newline at end of file diff --git a/src/static/app/index.html b/src/static/app/index.html index afa86e31..0c731866 100644 --- a/src/static/app/index.html +++ b/src/static/app/index.html @@ -6,21 +6,14 @@ - - + + WGDashboard - -
+ diff --git a/src/static/app/package-lock.json b/src/static/app/package-lock.json index 94f3f647..d328fe46 100644 --- a/src/static/app/package-lock.json +++ b/src/static/app/package-lock.json @@ -1,22 +1,22 @@ { "name": "app", - "version": "4.3.3", + "version": "4.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "app", - "version": "4.3.3", + "version": "4.3.1", "dependencies": { "@volar/language-server": "2.4.28", "@vue/language-server": "3.2.4", "@vuepic/vue-datepicker": "^12.1.0", + "@vueuse/core": "^14.2.0", "@vueuse/shared": "^14.2.1", - "@vueuse/core": "^14.2.1", "animate.css": "^4.1.1", "bootstrap": "^5.3.2", "bootstrap-icons": "^1.11.3", - "cidr-tools": "^11.3.2", + "cidr-tools": "^11.0.8", "css-color-converter": "^2.0.0", "dayjs": "^1.11.19", "electron-builder": "^26.7.0", @@ -143,9 +143,9 @@ } }, "node_modules/@electron/asar/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -353,9 +353,9 @@ } }, "node_modules/@electron/universal/node_modules/brace-expansion": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", - "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -376,12 +376,12 @@ } }, "node_modules/@electron/universal/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.2" + "brace-expansion": "^2.0.1" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -986,6 +986,27 @@ } } }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1273,9 +1294,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", - "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.2.tgz", + "integrity": "sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A==", "cpu": [ "arm" ], @@ -1287,9 +1308,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", - "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.2.tgz", + "integrity": "sha512-oEouqQk2/zxxj22PNcGSskya+3kV0ZKH+nQxuCCOGJ4oTXBdNTbv+f/E3c74cNLeMO1S5wVWacSws10TTSB77g==", "cpu": [ "arm64" ], @@ -1301,9 +1322,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", - "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.2.tgz", + "integrity": "sha512-OZuTVTpj3CDSIxmPgGH8en/XtirV5nfljHZ3wrNwvgkT5DQLhIKAeuFSiwtbMto6oVexV0k1F1zqURPKf5rI1Q==", "cpu": [ "arm64" ], @@ -1315,9 +1336,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", - "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.2.tgz", + "integrity": "sha512-Wa/Wn8RFkIkr1vy1k1PB//VYhLnlnn5eaJkfTQKivirOvzu5uVd2It01ukeQstMursuz7S1bU+8WW+1UPXpa8A==", "cpu": [ "x64" ], @@ -1329,9 +1350,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", - "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.2.tgz", + "integrity": "sha512-QkzxvH3kYN9J1w7D1A+yIMdI1pPekD+pWx7G5rXgnIlQ1TVYVC6hLl7SOV9pi5q9uIDF9AuIGkuzcbF7+fAhow==", "cpu": [ "arm64" ], @@ -1343,9 +1364,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", - "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.2.tgz", + "integrity": "sha512-dkYXB0c2XAS3a3jmyDkX4Jk0m7gWLFzq1C3qUnJJ38AyxIF5G/dyS4N9B30nvFseCfgtCEdbYFhk0ChoCGxPog==", "cpu": [ "x64" ], @@ -1357,9 +1378,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", - "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.2.tgz", + "integrity": "sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w==", "cpu": [ "arm" ], @@ -1371,9 +1392,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", - "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.2.tgz", + "integrity": "sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw==", "cpu": [ "arm" ], @@ -1385,9 +1406,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", - "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.2.tgz", + "integrity": "sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg==", "cpu": [ "arm64" ], @@ -1399,9 +1420,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", - "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.2.tgz", + "integrity": "sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ==", "cpu": [ "arm64" ], @@ -1413,23 +1434,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", - "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", - "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.50.2.tgz", + "integrity": "sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw==", "cpu": [ "loong64" ], @@ -1441,23 +1448,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", - "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", - "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.2.tgz", + "integrity": "sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag==", "cpu": [ "ppc64" ], @@ -1469,9 +1462,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", - "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.2.tgz", + "integrity": "sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ==", "cpu": [ "riscv64" ], @@ -1483,9 +1476,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", - "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.2.tgz", + "integrity": "sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw==", "cpu": [ "riscv64" ], @@ -1497,9 +1490,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", - "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.2.tgz", + "integrity": "sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w==", "cpu": [ "s390x" ], @@ -1511,9 +1504,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", - "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.2.tgz", + "integrity": "sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA==", "cpu": [ "x64" ], @@ -1525,9 +1518,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", - "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.2.tgz", + "integrity": "sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw==", "cpu": [ "x64" ], @@ -1538,24 +1531,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", - "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", - "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.2.tgz", + "integrity": "sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA==", "cpu": [ "arm64" ], @@ -1567,9 +1546,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", - "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.2.tgz", + "integrity": "sha512-eFUvvnTYEKeTyHEijQKz81bLrUQOXKZqECeiWH6tb8eXXbZk+CXSG2aFrig2BQ/pjiVRj36zysjgILkqarS2YA==", "cpu": [ "arm64" ], @@ -1581,9 +1560,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", - "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.2.tgz", + "integrity": "sha512-cBaWmXqyfRhH8zmUxK3d3sAhEWLrtMjWBRwdMMHJIXSjvjLKvv49adxiEz+FJ8AP90apSDDBx2Tyd/WylV6ikA==", "cpu": [ "ia32" ], @@ -1594,24 +1573,10 @@ "win32" ] }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", - "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", - "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.2.tgz", + "integrity": "sha512-APwKy6YUhvZaEoHyM+9xqmTpviEI+9eL7LoCH+aLcvWYHJ663qG5zx7WzWZY+a9qkg5JtzcMyJ9z0WtQBMDmgA==", "cpu": [ "x64" ], @@ -2145,14 +2110,14 @@ } }, "node_modules/@vueuse/core": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.2.1.tgz", - "integrity": "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.2.0.tgz", + "integrity": "sha512-tpjzVl7KCQNVd/qcaCE9XbejL38V6KJAEq/tVXj7mDPtl6JtzmUdnXelSS+ULRkkrDgzYVK7EerQJvd2jR794Q==", "license": "MIT", "dependencies": { "@types/web-bluetooth": "^0.0.21", - "@vueuse/metadata": "14.2.1", - "@vueuse/shared": "14.2.1" + "@vueuse/metadata": "14.2.0", + "@vueuse/shared": "14.2.0" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -2174,9 +2139,9 @@ } }, "node_modules/@vueuse/metadata": { - "version": "14.2.1", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.2.1.tgz", - "integrity": "sha512-1ButlVtj5Sb/HDtIy1HFr1VqCP4G6Ypqt5MAo0lCgjokrk2mvQKsK2uuy0vqu/Ks+sHfuHo0B9Y9jn9xKdjZsw==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.2.0.tgz", + "integrity": "sha512-i3axTGjU8b13FtyR4Keeama+43iD+BwX9C2TmzBVKqjSHArF03hjkp2SBZ1m72Jk2UtrX0aYCugBq2R1fhkuAQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" @@ -2250,9 +2215,9 @@ } }, "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -2572,9 +2537,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2672,9 +2637,9 @@ } }, "node_modules/cacache/node_modules/brace-expansion": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", - "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -2707,12 +2672,12 @@ "license": "ISC" }, "node_modules/cacache/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.2" + "brace-expansion": "^2.0.1" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -2882,12 +2847,12 @@ } }, "node_modules/cidr-tools": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/cidr-tools/-/cidr-tools-11.3.2.tgz", - "integrity": "sha512-yCtb8tB3CngbbzGUsEz8koyxHDxEA3VUOOldXFeLqQREWl1HK/FZxLzxxWQgPDPMwuvMF7q9kyIsFLmmBPaN6A==", + "version": "11.0.8", + "resolved": "https://registry.npmjs.org/cidr-tools/-/cidr-tools-11.0.8.tgz", + "integrity": "sha512-z8TVpm6JAemTAuYsza3GL4KJH5VxSFwWge6YhA+9tFYoUICx8fraMdd6bCBb6Y/15o94POW32XghQt9LH4CHXQ==", "license": "BSD-2-Clause", "dependencies": { - "ip-bigint": "^8.3.2" + "ip-bigint": "^8.2.4" }, "engines": { "node": ">=18" @@ -3294,9 +3259,9 @@ } }, "node_modules/dir-compare/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -3781,18 +3746,18 @@ } }, "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", - "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -4037,9 +4002,9 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -4352,9 +4317,9 @@ } }, "node_modules/ip-bigint": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/ip-bigint/-/ip-bigint-8.3.3.tgz", - "integrity": "sha512-r1bCRPxLv4PcyIB95FbsxaVvymNhFfbsIxrFdHqgZhWOBvfEPnzIHDGLAP+85cBozVf+XGaM00em4ZoJHm9EHw==", + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/ip-bigint/-/ip-bigint-8.2.4.tgz", + "integrity": "sha512-uLnCfRdjiqRSX36+sKW3PBsotx58qEXSfbRWqdy1N5w6LtlIDCQnxuVce5OIBD50WA9VX2DWixtiBVtalWb1fA==", "license": "BSD-2-Clause", "engines": { "node": ">=18" @@ -4802,41 +4767,20 @@ } }, "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minimatch/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/minimatch/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -5147,9 +5091,9 @@ } }, "node_modules/npm": { - "version": "11.12.1", - "resolved": "https://registry.npmjs.org/npm/-/npm-11.12.1.tgz", - "integrity": "sha512-zcoUuF1kezGSAo0CqtvoLXX3mkRqzuqYdL6Y5tdo8g69NVV3CkjQ6ZBhBgB4d7vGkPcV6TcvLi3GRKPDFX+xTA==", + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-11.8.0.tgz", + "integrity": "sha512-n19sJeW+RGKdkHo8SCc5xhSwkKhQUFfZaFzSc+EsYXLjSqIV0tl72aDYQVuzVvfrbysGwdaQsNLNy58J10EBSQ==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", @@ -5167,6 +5111,7 @@ "cacache", "chalk", "ci-info", + "cli-columns", "fastest-levenshtein", "fs-minipass", "glob", @@ -5227,46 +5172,47 @@ ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^9.4.2", - "@npmcli/config": "^10.8.1", + "@npmcli/arborist": "^9.1.10", + "@npmcli/config": "^10.5.0", "@npmcli/fs": "^5.0.0", "@npmcli/map-workspaces": "^5.0.3", "@npmcli/metavuln-calculator": "^9.0.3", - "@npmcli/package-json": "^7.0.5", + "@npmcli/package-json": "^7.0.4", "@npmcli/promise-spawn": "^9.0.1", "@npmcli/redact": "^4.0.0", - "@npmcli/run-script": "^10.0.4", - "@sigstore/tuf": "^4.0.2", + "@npmcli/run-script": "^10.0.3", + "@sigstore/tuf": "^4.0.1", "abbrev": "^4.0.0", "archy": "~1.0.0", - "cacache": "^20.0.4", + "cacache": "^20.0.3", "chalk": "^5.6.2", - "ci-info": "^4.4.0", + "ci-info": "^4.3.1", + "cli-columns": "^4.0.0", "fastest-levenshtein": "^1.0.16", "fs-minipass": "^3.0.3", - "glob": "^13.0.6", + "glob": "^13.0.0", "graceful-fs": "^4.2.11", "hosted-git-info": "^9.0.2", "ini": "^6.0.0", - "init-package-json": "^8.2.5", - "is-cidr": "^6.0.3", + "init-package-json": "^8.2.4", + "is-cidr": "^6.0.1", "json-parse-even-better-errors": "^5.0.0", "libnpmaccess": "^10.0.3", - "libnpmdiff": "^8.1.5", - "libnpmexec": "^10.2.5", - "libnpmfund": "^7.0.19", + "libnpmdiff": "^8.0.13", + "libnpmexec": "^10.1.12", + "libnpmfund": "^7.0.13", "libnpmorg": "^8.0.1", - "libnpmpack": "^9.1.5", + "libnpmpack": "^9.0.13", "libnpmpublish": "^11.1.3", "libnpmsearch": "^9.0.1", "libnpmteam": "^8.0.2", "libnpmversion": "^8.0.3", - "make-fetch-happen": "^15.0.5", - "minimatch": "^10.2.4", - "minipass": "^7.1.3", + "make-fetch-happen": "^15.0.3", + "minimatch": "^10.1.1", + "minipass": "^7.1.1", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", - "node-gyp": "^12.2.0", + "node-gyp": "^12.1.0", "nopt": "^9.0.0", "npm-audit-report": "^7.0.0", "npm-install-checks": "^8.0.0", @@ -5276,21 +5222,21 @@ "npm-registry-fetch": "^19.1.1", "npm-user-validate": "^4.0.0", "p-map": "^7.0.4", - "pacote": "^21.5.0", + "pacote": "^21.0.4", "parse-conflict-json": "^5.0.1", "proc-log": "^6.1.0", "qrcode-terminal": "^0.12.0", "read": "^5.0.1", - "semver": "^7.7.4", + "semver": "^7.7.3", "spdx-expression-parse": "^4.0.0", - "ssri": "^13.0.1", + "ssri": "^13.0.0", "supports-color": "^10.2.2", - "tar": "^7.5.11", + "tar": "^7.5.4", "text-table": "~0.2.0", "tiny-relative-date": "^2.0.2", "treeverse": "^3.0.0", "validate-npm-package-name": "^7.0.2", - "which": "^6.0.1" + "which": "^6.0.0" }, "bin": { "npm": "bin/npm-cli.js", @@ -5300,12 +5246,23 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm/node_modules/@gar/promise-retry": { - "version": "1.0.3", + "node_modules/npm/node_modules/@isaacs/balanced-match": { + "version": "4.0.1", "inBundle": true, "license": "MIT", "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "20 || >=22" + } + }, + "node_modules/npm/node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" } }, "node_modules/npm/node_modules/@isaacs/fs-minipass": { @@ -5340,11 +5297,10 @@ } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "9.4.2", + "version": "9.1.10", "inBundle": true, "license": "ISC", "dependencies": { - "@gar/promise-retry": "^1.0.0", "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/fs": "^5.0.0", "@npmcli/installed-package-contents": "^4.0.0", @@ -5387,7 +5343,7 @@ } }, "node_modules/npm/node_modules/@npmcli/config": { - "version": "10.8.1", + "version": "10.5.0", "inBundle": true, "license": "ISC", "dependencies": { @@ -5416,16 +5372,16 @@ } }, "node_modules/npm/node_modules/@npmcli/git": { - "version": "7.0.2", + "version": "7.0.1", "inBundle": true, "license": "ISC", "dependencies": { - "@gar/promise-retry": "^1.0.0", "@npmcli/promise-spawn": "^9.0.0", "ini": "^6.0.0", "lru-cache": "^11.2.1", "npm-pick-manifest": "^11.0.1", "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", "semver": "^7.3.5", "which": "^6.0.0" }, @@ -5494,7 +5450,7 @@ } }, "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "7.0.5", + "version": "7.0.4", "inBundle": true, "license": "ISC", "dependencies": { @@ -5504,7 +5460,7 @@ "json-parse-even-better-errors": "^5.0.0", "proc-log": "^6.0.0", "semver": "^7.5.3", - "spdx-expression-parse": "^4.0.0" + "validate-npm-package-license": "^3.0.4" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -5541,7 +5497,7 @@ } }, "node_modules/npm/node_modules/@npmcli/run-script": { - "version": "10.0.4", + "version": "10.0.3", "inBundle": true, "license": "ISC", "dependencies": { @@ -5549,7 +5505,8 @@ "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "node-gyp": "^12.1.0", - "proc-log": "^6.0.0" + "proc-log": "^6.0.0", + "which": "^6.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -5567,7 +5524,7 @@ } }, "node_modules/npm/node_modules/@sigstore/core": { - "version": "3.2.0", + "version": "3.1.0", "inBundle": true, "license": "Apache-2.0", "engines": { @@ -5583,23 +5540,23 @@ } }, "node_modules/npm/node_modules/@sigstore/sign": { - "version": "4.1.1", + "version": "4.1.0", "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@gar/promise-retry": "^1.0.2", "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.2.0", + "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0", - "make-fetch-happen": "^15.0.4", - "proc-log": "^6.1.0" + "make-fetch-happen": "^15.0.3", + "proc-log": "^6.1.0", + "promise-retry": "^2.0.1" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm/node_modules/@sigstore/tuf": { - "version": "4.0.2", + "version": "4.0.1", "inBundle": true, "license": "Apache-2.0", "dependencies": { @@ -5659,6 +5616,14 @@ "node": ">= 14" } }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/npm/node_modules/aproba": { "version": "2.1.0", "inBundle": true, @@ -5669,14 +5634,6 @@ "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/balanced-match": { - "version": "4.0.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/npm/node_modules/bin-links": { "version": "6.0.0", "inBundle": true, @@ -5703,19 +5660,8 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm/node_modules/brace-expansion": { - "version": "5.0.4", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/npm/node_modules/cacache": { - "version": "20.0.4", + "version": "20.0.3", "inBundle": true, "license": "ISC", "dependencies": { @@ -5728,7 +5674,8 @@ "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", - "ssri": "^13.0.0" + "ssri": "^13.0.0", + "unique-filename": "^5.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -5754,7 +5701,7 @@ } }, "node_modules/npm/node_modules/ci-info": { - "version": "4.4.0", + "version": "4.3.1", "funding": [ { "type": "github", @@ -5768,13 +5715,28 @@ } }, "node_modules/npm/node_modules/cidr-regex": { - "version": "5.0.3", + "version": "5.0.1", "inBundle": true, "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "5.0.0" + }, "engines": { "node": ">=20" } }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/npm/node_modules/cmd-shim": { "version": "8.0.0", "inBundle": true, @@ -5826,6 +5788,20 @@ "node": ">=0.3.1" } }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, "node_modules/npm/node_modules/env-paths": { "version": "2.2.1", "inBundle": true, @@ -5834,6 +5810,11 @@ "node": ">=6" } }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "inBundle": true, + "license": "MIT" + }, "node_modules/npm/node_modules/exponential-backoff": { "version": "3.1.3", "inBundle": true, @@ -5859,16 +5840,16 @@ } }, "node_modules/npm/node_modules/glob": { - "version": "13.0.6", + "version": "13.0.0", "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -5920,7 +5901,7 @@ } }, "node_modules/npm/node_modules/iconv-lite": { - "version": "0.7.2", + "version": "0.6.3", "inBundle": true, "license": "MIT", "optional": true, @@ -5929,10 +5910,6 @@ }, "engines": { "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, "node_modules/npm/node_modules/ignore-walk": { @@ -5946,6 +5923,14 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/npm/node_modules/ini": { "version": "6.0.0", "inBundle": true, @@ -5955,7 +5940,7 @@ } }, "node_modules/npm/node_modules/init-package-json": { - "version": "8.2.5", + "version": "8.2.4", "inBundle": true, "license": "ISC", "dependencies": { @@ -5964,6 +5949,7 @@ "promzard": "^3.0.1", "read": "^5.0.1", "semver": "^7.7.2", + "validate-npm-package-license": "^3.0.4", "validate-npm-package-name": "^7.0.0" }, "engines": { @@ -5978,23 +5964,42 @@ "node": ">= 12" } }, + "node_modules/npm/node_modules/ip-regex": { + "version": "5.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/npm/node_modules/is-cidr": { - "version": "6.0.3", + "version": "6.0.1", "inBundle": true, "license": "BSD-2-Clause", "dependencies": { - "cidr-regex": "^5.0.1" + "cidr-regex": "5.0.1" }, "engines": { "node": ">=20" } }, - "node_modules/npm/node_modules/isexe": { - "version": "4.0.0", + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", "inBundle": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "engines": { - "node": ">=20" + "node": ">=8" + } + }, + "node_modules/npm/node_modules/isexe": { + "version": "3.1.1", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16" } }, "node_modules/npm/node_modules/json-parse-even-better-errors": { @@ -6044,11 +6049,11 @@ } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "8.1.5", + "version": "8.0.13", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.4.2", + "@npmcli/arborist": "^9.1.10", "@npmcli/installed-package-contents": "^4.0.0", "binary-extensions": "^3.0.0", "diff": "^8.0.2", @@ -6062,18 +6067,18 @@ } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "10.2.5", + "version": "10.1.12", "inBundle": true, "license": "ISC", "dependencies": { - "@gar/promise-retry": "^1.0.0", - "@npmcli/arborist": "^9.4.2", + "@npmcli/arborist": "^9.1.10", "@npmcli/package-json": "^7.0.0", "@npmcli/run-script": "^10.0.0", "ci-info": "^4.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2", "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", "read": "^5.0.1", "semver": "^7.3.7", "signal-exit": "^4.1.0", @@ -6084,11 +6089,11 @@ } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "7.0.19", + "version": "7.0.13", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.4.2" + "@npmcli/arborist": "^9.1.10" }, "engines": { "node": "^20.17.0 || >=22.9.0" @@ -6107,11 +6112,11 @@ } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "9.1.5", + "version": "9.0.13", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^9.4.2", + "@npmcli/arborist": "^9.1.10", "@npmcli/run-script": "^10.0.0", "npm-package-arg": "^13.0.0", "pacote": "^21.0.2" @@ -6177,7 +6182,7 @@ } }, "node_modules/npm/node_modules/lru-cache": { - "version": "11.2.7", + "version": "11.2.4", "inBundle": true, "license": "BlueOak-1.0.0", "engines": { @@ -6185,13 +6190,11 @@ } }, "node_modules/npm/node_modules/make-fetch-happen": { - "version": "15.0.5", + "version": "15.0.3", "inBundle": true, "license": "ISC", "dependencies": { - "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", - "@npmcli/redact": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", @@ -6200,6 +6203,7 @@ "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", "ssri": "^13.0.0" }, "engines": { @@ -6207,23 +6211,23 @@ } }, "node_modules/npm/node_modules/minimatch": { - "version": "10.2.4", + "version": "10.1.1", "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/npm/node_modules/minipass": { - "version": "7.1.3", + "version": "7.1.2", "inBundle": true, - "license": "BlueOak-1.0.0", + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } @@ -6240,19 +6244,19 @@ } }, "node_modules/npm/node_modules/minipass-fetch": { - "version": "5.0.2", + "version": "5.0.0", "inBundle": true, "license": "MIT", "dependencies": { "minipass": "^7.0.3", - "minipass-sized": "^2.0.0", + "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "engines": { "node": "^20.17.0 || >=22.9.0" }, "optionalDependencies": { - "iconv-lite": "^0.7.2" + "encoding": "^0.1.13" } }, "node_modules/npm/node_modules/minipass-flush": { @@ -6277,11 +6281,6 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/minipass-flush/node_modules/yallist": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/minipass-pipeline": { "version": "1.2.4", "inBundle": true, @@ -6304,17 +6303,23 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/minipass-pipeline/node_modules/yallist": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/minipass-sized": { - "version": "2.0.0", + "version": "1.0.3", "inBundle": true, "license": "ISC", "dependencies": { - "minipass": "^7.1.2" + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" }, "engines": { "node": ">=8" @@ -6353,7 +6358,7 @@ } }, "node_modules/npm/node_modules/node-gyp": { - "version": "12.2.0", + "version": "12.1.0", "inBundle": true, "license": "MIT", "dependencies": { @@ -6364,7 +6369,7 @@ "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", - "tar": "^7.5.4", + "tar": "^7.5.2", "tinyglobby": "^0.2.12", "which": "^6.0.0" }, @@ -6442,7 +6447,7 @@ } }, "node_modules/npm/node_modules/npm-packlist": { - "version": "10.0.4", + "version": "10.0.3", "inBundle": true, "license": "ISC", "dependencies": { @@ -6517,11 +6522,10 @@ } }, "node_modules/npm/node_modules/pacote": { - "version": "21.5.0", + "version": "21.0.4", "inBundle": true, "license": "ISC", "dependencies": { - "@gar/promise-retry": "^1.0.0", "@npmcli/git": "^7.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/package-json": "^7.0.0", @@ -6535,6 +6539,7 @@ "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", "sigstore": "^4.0.0", "ssri": "^13.0.0", "tar": "^7.4.3" @@ -6560,7 +6565,7 @@ } }, "node_modules/npm/node_modules/path-scurry": { - "version": "2.0.2", + "version": "2.0.1", "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -6568,7 +6573,7 @@ "minipass": "^7.1.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -6618,6 +6623,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/npm/node_modules/promzard": { "version": "3.0.1", "inBundle": true, @@ -6655,6 +6672,14 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/npm/node_modules/safer-buffer": { "version": "2.1.2", "inBundle": true, @@ -6662,7 +6687,7 @@ "optional": true }, "node_modules/npm/node_modules/semver": { - "version": "7.7.4", + "version": "7.7.3", "inBundle": true, "license": "ISC", "bin": { @@ -6734,6 +6759,24 @@ "node": ">= 14" } }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.2.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, "node_modules/npm/node_modules/spdx-exceptions": { "version": "2.5.0", "inBundle": true, @@ -6749,12 +6792,12 @@ } }, "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.23", + "version": "3.0.22", "inBundle": true, "license": "CC0-1.0" }, "node_modules/npm/node_modules/ssri": { - "version": "13.0.1", + "version": "13.0.0", "inBundle": true, "license": "ISC", "dependencies": { @@ -6764,6 +6807,30 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/npm/node_modules/supports-color": { "version": "10.2.2", "inBundle": true, @@ -6776,7 +6843,7 @@ } }, "node_modules/npm/node_modules/tar": { - "version": "7.5.11", + "version": "7.5.4", "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -6790,6 +6857,14 @@ "node": ">=18" } }, + "node_modules/npm/node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/npm/node_modules/text-table": { "version": "0.2.0", "inBundle": true, @@ -6863,11 +6938,51 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/npm/node_modules/unique-filename": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/npm/node_modules/util-deprecate": { "version": "1.0.2", "inBundle": true, "license": "MIT" }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, "node_modules/npm/node_modules/validate-npm-package-name": { "version": "7.0.2", "inBundle": true, @@ -6885,11 +7000,11 @@ } }, "node_modules/npm/node_modules/which": { - "version": "6.0.1", + "version": "6.0.0", "inBundle": true, "license": "ISC", "dependencies": { - "isexe": "^4.0.0" + "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" @@ -6899,10 +7014,11 @@ } }, "node_modules/npm/node_modules/write-file-atomic": { - "version": "7.0.1", + "version": "7.0.0", "inBundle": true, "license": "ISC", "dependencies": { + "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" }, "engines": { @@ -6910,21 +7026,9 @@ } }, "node_modules/npm/node_modules/yallist": { - "version": "5.0.0", + "version": "4.0.0", "inBundle": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/numcodecs": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/numcodecs/-/numcodecs-0.3.2.tgz", - "integrity": "sha512-6YSPnmZgg0P87jnNhi3s+FVLOcIn3y+1CTIgUulA3IdASzK9fJM87sUFkpyA+be9GibGRaST2wCgkD+6U+fWKw==", - "license": "MIT", - "dependencies": { - "fflate": "^0.8.0" - } + "license": "ISC" }, "node_modules/numcodecs": { "version": "0.3.2", @@ -7209,9 +7313,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", "engines": { "node": ">=12" @@ -7752,9 +7856,9 @@ } }, "node_modules/rollup": { - "version": "4.60.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", - "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "version": "4.50.2", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.50.2.tgz", + "integrity": "sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==", "dev": true, "license": "MIT", "dependencies": { @@ -7768,31 +7872,27 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.1", - "@rollup/rollup-android-arm64": "4.60.1", - "@rollup/rollup-darwin-arm64": "4.60.1", - "@rollup/rollup-darwin-x64": "4.60.1", - "@rollup/rollup-freebsd-arm64": "4.60.1", - "@rollup/rollup-freebsd-x64": "4.60.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", - "@rollup/rollup-linux-arm-musleabihf": "4.60.1", - "@rollup/rollup-linux-arm64-gnu": "4.60.1", - "@rollup/rollup-linux-arm64-musl": "4.60.1", - "@rollup/rollup-linux-loong64-gnu": "4.60.1", - "@rollup/rollup-linux-loong64-musl": "4.60.1", - "@rollup/rollup-linux-ppc64-gnu": "4.60.1", - "@rollup/rollup-linux-ppc64-musl": "4.60.1", - "@rollup/rollup-linux-riscv64-gnu": "4.60.1", - "@rollup/rollup-linux-riscv64-musl": "4.60.1", - "@rollup/rollup-linux-s390x-gnu": "4.60.1", - "@rollup/rollup-linux-x64-gnu": "4.60.1", - "@rollup/rollup-linux-x64-musl": "4.60.1", - "@rollup/rollup-openbsd-x64": "4.60.1", - "@rollup/rollup-openharmony-arm64": "4.60.1", - "@rollup/rollup-win32-arm64-msvc": "4.60.1", - "@rollup/rollup-win32-ia32-msvc": "4.60.1", - "@rollup/rollup-win32-x64-gnu": "4.60.1", - "@rollup/rollup-win32-x64-msvc": "4.60.1", + "@rollup/rollup-android-arm-eabi": "4.50.2", + "@rollup/rollup-android-arm64": "4.50.2", + "@rollup/rollup-darwin-arm64": "4.50.2", + "@rollup/rollup-darwin-x64": "4.50.2", + "@rollup/rollup-freebsd-arm64": "4.50.2", + "@rollup/rollup-freebsd-x64": "4.50.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.50.2", + "@rollup/rollup-linux-arm-musleabihf": "4.50.2", + "@rollup/rollup-linux-arm64-gnu": "4.50.2", + "@rollup/rollup-linux-arm64-musl": "4.50.2", + "@rollup/rollup-linux-loong64-gnu": "4.50.2", + "@rollup/rollup-linux-ppc64-gnu": "4.50.2", + "@rollup/rollup-linux-riscv64-gnu": "4.50.2", + "@rollup/rollup-linux-riscv64-musl": "4.50.2", + "@rollup/rollup-linux-s390x-gnu": "4.50.2", + "@rollup/rollup-linux-x64-gnu": "4.50.2", + "@rollup/rollup-linux-x64-musl": "4.50.2", + "@rollup/rollup-openharmony-arm64": "4.50.2", + "@rollup/rollup-win32-arm64-msvc": "4.50.2", + "@rollup/rollup-win32-ia32-msvc": "4.50.2", + "@rollup/rollup-win32-x64-msvc": "4.50.2", "fsevents": "~2.3.2" } }, @@ -8153,9 +8253,9 @@ } }, "node_modules/tar": { - "version": "7.5.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", - "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", + "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -8997,9 +9097,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "license": "ISC", "bin": { "yaml": "bin.mjs" diff --git a/src/static/app/package.json b/src/static/app/package.json index 4c73028e..a12f1fa4 100644 --- a/src/static/app/package.json +++ b/src/static/app/package.json @@ -1,6 +1,6 @@ { "name": "app", - "version": "4.3.3", + "version": "4.3.2", "private": true, "type": "module", "module": "es2022", @@ -15,12 +15,12 @@ "@volar/language-server": "2.4.28", "@vue/language-server": "3.2.4", "@vuepic/vue-datepicker": "^12.1.0", + "@vueuse/core": "^14.2.0", "@vueuse/shared": "^14.2.1", - "@vueuse/core": "^14.2.1", "animate.css": "^4.1.1", "bootstrap": "^5.3.2", "bootstrap-icons": "^1.11.3", - "cidr-tools": "^11.3.2", + "cidr-tools": "^11.0.8", "css-color-converter": "^2.0.0", "dayjs": "^1.11.19", "electron-builder": "^26.7.0", diff --git a/src/static/app/src/components/clientComponents/clientSettings.vue b/src/static/app/src/components/clientComponents/clientSettings.vue index d872a347..ae554db4 100644 --- a/src/static/app/src/components/clientComponents/clientSettings.vue +++ b/src/static/app/src/components/clientComponents/clientSettings.vue @@ -2,7 +2,7 @@ import { ref, reactive } from "vue" import LocaleText from "@/components/text/localeText.vue"; import OidcSettings from "@/components/clientComponents/clientSettingComponents/oidcSettings.vue"; -import { fetchGet, fetchPost } from "@/utilities/fetch.js" +import { fetchGet } from "@/utilities/fetch.js" const emits = defineEmits(['close']) import { DashboardConfigurationStore } from "@/stores/DashboardConfigurationStore" const dashboardConfigurationStore = DashboardConfigurationStore() @@ -12,16 +12,12 @@ const values = reactive({ }) const toggling = ref(false) -const updateSettings = async (key: string) => { +const toggleClientSideApp = async () => { toggling.value = true - await fetchPost("/api/updateDashboardConfigurationItem", { - section: "Clients", - key: key, - value: dashboardConfigurationStore.Configuration.Clients[key] - }, async (res) => { - await dashboardConfigurationStore.getConfiguration() - toggling.value = false + await fetchGet("/api/clients/toggleStatus", {}, (res) => { + values.enableClients = res.data }) + toggling.value = false } @@ -41,43 +37,16 @@ const updateSettings = async (key: string) => {
-
-
-
-
- -
-
- - -
-
- - - -
-
- - - - -
- + diff --git a/src/static/app/src/components/configurationComponents/editConfiguration.vue b/src/static/app/src/components/configurationComponents/editConfiguration.vue index f5d67dc4..164af07e 100644 --- a/src/static/app/src/components/configurationComponents/editConfiguration.vue +++ b/src/static/app/src/components/configurationComponents/editConfiguration.vue @@ -197,14 +197,14 @@ const deleteConfigurationModal = ref(false) v-model="data[key]" :id="'configuration_' + key"> -
- diff --git a/src/static/app/src/components/configurationComponents/newPeersComponents/notesInput.vue b/src/static/app/src/components/configurationComponents/newPeersComponents/notesInput.vue deleted file mode 100644 index 6d12adee..00000000 --- a/src/static/app/src/components/configurationComponents/newPeersComponents/notesInput.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/static/app/src/components/configurationComponents/peer.vue b/src/static/app/src/components/configurationComponents/peer.vue index 4f98f696..b514463b 100644 --- a/src/static/app/src/components/configurationComponents/peer.vue +++ b/src/static/app/src/components/configurationComponents/peer.vue @@ -134,17 +134,12 @@ export default {
- diff --git a/src/static/app/src/components/configurationComponents/peerAddModal.vue b/src/static/app/src/components/configurationComponents/peerAddModal.vue index e0ccded2..5212464c 100644 --- a/src/static/app/src/components/configurationComponents/peerAddModal.vue +++ b/src/static/app/src/components/configurationComponents/peerAddModal.vue @@ -15,7 +15,6 @@ import MtuInput from "@/components/configurationComponents/newPeersComponents/mt import PersistentKeepAliveInput from "@/components/configurationComponents/newPeersComponents/persistentKeepAliveInput.vue"; import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js"; -import NotesInput from "./newPeersComponents/notesInput.vue"; const dashboardStore = DashboardConfigurationStore() const wireguardStore = WireguardConfigurationsStore() @@ -28,11 +27,11 @@ const peerData = ref({ public_key: "", DNS: dashboardStore.Configuration.Peers.peer_global_dns, endpoint_allowed_ip: dashboardStore.Configuration.Peers.peer_endpoint_allowed_ip, - notes: "", keepalive: parseInt(dashboardStore.Configuration.Peers.peer_keep_alive), mtu: parseInt(dashboardStore.Configuration.Peers.peer_mtu), preshared_key: "", preshared_key_bulkAdd: false, + advanced_security: "off", allowed_ips_validation: true, }) const availableIp = ref([]) @@ -106,7 +105,6 @@ watch(() => { @@ -120,7 +118,7 @@ watch(() => { -