WGDashboard/src/dashboard.py

3250 lines
149 KiB
Python
Raw Normal View History

import random, shutil, sqlite3, configparser, hashlib, ipaddress, json, os, secrets, subprocess
2024-11-25 22:11:51 +08:00
import time, re, urllib.error, uuid, bcrypt, psutil, pyotp, threading
2025-01-13 16:47:15 +08:00
from uuid import uuid4
2024-12-26 00:06:37 +08:00
from zipfile import ZipFile
2021-12-28 22:53:51 +03:00
from datetime import datetime, timedelta
2024-06-18 03:40:25 +08:00
from typing import Any
2025-01-13 16:47:15 +08:00
from jinja2 import Template
from flask import Flask, request, render_template, session, send_file
2024-06-18 03:16:42 +08:00
from json import JSONEncoder
2024-07-31 02:27:44 -04:00
from flask_cors import CORS
2021-12-26 02:26:39 +03:00
from icmplib import ping, traceroute
2024-06-18 03:16:42 +08:00
from flask.json.provider import DefaultJSONProvider
2025-02-16 17:42:32 +08:00
from itertools import islice
2024-11-25 22:11:51 +08:00
from Utilities import (
RegexMatch, GetRemoteEndpoint, StringToBoolean,
ValidateIPAddressesWithRange, ValidateDNSAddress,
2024-11-25 22:11:51 +08:00
GenerateWireguardPublicKey, GenerateWireguardPrivateKey
)
from packaging import version
from modules.Email import EmailSender
from modules.Log import Log
from modules.DashboardLogger import DashboardLogger
from modules.PeerJobLogger import PeerJobLogger
2025-01-22 15:46:04 +08:00
from modules.PeerJob import PeerJob
2025-02-08 15:45:09 +08:00
from modules.SystemStatus import SystemStatus
from modules.PeerShareLinks import PeerShareLinks
2025-05-08 19:05:46 +08:00
from modules.DashboardAPIKey import DashboardAPIKey
2025-02-08 15:45:09 +08:00
SystemStatus = SystemStatus()
2022-02-11 09:35:58 -05:00
from sqlalchemy_utils import database_exists, create_database
import sqlalchemy as db
2025-05-02 14:34:23 +08:00
DASHBOARD_VERSION = 'v4.2.3'
2022-02-11 09:35:58 -05:00
2024-06-18 03:16:42 +08:00
CONFIGURATION_PATH = os.getenv('CONFIGURATION_PATH', '.')
DB_PATH = os.path.join(CONFIGURATION_PATH, 'db')
2022-01-01 02:57:59 +03:00
if not os.path.isdir(DB_PATH):
os.mkdir(DB_PATH)
2024-06-18 03:16:42 +08:00
DASHBOARD_CONF = os.path.join(CONFIGURATION_PATH, 'wg-dashboard.ini')
2021-12-28 22:53:51 +03:00
UPDATE = None
2024-11-07 10:37:11 +08:00
app = Flask("WGDashboard", template_folder=os.path.abspath("./static/app/dist"))
2021-12-25 14:44:14 -05:00
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 5206928
2024-06-18 03:16:42 +08:00
app.secret_key = secrets.token_urlsafe(32)
2024-08-14 01:17:47 -04:00
2024-06-18 03:16:42 +08:00
class CustomJsonEncoder(DefaultJSONProvider):
def __init__(self, app):
super().__init__(app)
def default(self, o):
if callable(getattr(o, "toJson", None)):
2024-06-18 03:16:42 +08:00
return o.toJson()
2025-03-12 00:44:36 +08:00
return super().default(self)
2024-06-18 03:16:42 +08:00
app.json = CustomJsonEncoder(app)
2024-11-25 22:11:51 +08:00
'''
Response Object
'''
2025-04-19 02:54:47 +08:00
def ResponseObject(status=True, message=None, data=None, status_code = 200) -> Flask.response_class:
2024-11-25 22:11:51 +08:00
response = Flask.make_response(app, {
"status": status,
"message": message,
"data": data
})
2025-04-19 02:54:47 +08:00
response.status_code = status_code
2024-11-25 22:11:51 +08:00
response.content_type = "application/json"
return response
2025-01-08 18:09:05 +08:00
2024-11-25 22:11:51 +08:00
"""
Peer Jobs
"""
2024-06-18 03:16:42 +08:00
class PeerJobs:
def __init__(self):
self.Jobs: list[PeerJob] = []
self.engine = db.create_engine(DashboardConfig.getConnectionString('wgdashboard_job'))
self.metadata = db.MetaData()
self.peerJobTable = db.Table('PeerJobs', self.metadata,
db.Column('JobID', db.String, nullable=False, primary_key=True),
db.Column('Configuration', db.String, nullable=False),
db.Column('Peer', db.String, nullable=False),
db.Column('Field', db.String, nullable=False),
db.Column('Operator', db.String, nullable=False),
db.Column('Value', db.String, nullable=False),
db.Column('CreationDate', (db.DATETIME if DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else db.TIMESTAMP), nullable=False),
db.Column('ExpireDate', (db.DATETIME if DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else db.TIMESTAMP)),
db.Column('Action', db.String, nullable=False),
)
self.metadata.create_all(self.engine)
# self.jobdb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard_job.db'),
# check_same_thread=False)
# self.jobdb.row_factory = sqlite3.Row
# self.__createPeerJobsDatabase()
2024-06-18 03:16:42 +08:00
self.__getJobs()
def __getJobs(self):
self.Jobs.clear()
with self.engine.connect() as conn:
# jobdbCursor = self.jobdb.cursor()
# jobs = jobdbCursor.execute("SELECT * FROM PeerJobs WHERE ExpireDate IS NULL").fetchall()
jobs = conn.execute(self.peerJobTable.select().where(
self.peerJobTable.columns.ExpireDate == None
)).mappings().fetchall()
print(jobs)
2024-07-29 18:40:07 -04:00
for job in jobs:
2024-08-20 00:02:00 -07:00
self.Jobs.append(PeerJob(
2024-07-29 18:40:07 -04:00
job['JobID'], job['Configuration'], job['Peer'], job['Field'], job['Operator'], job['Value'],
job['CreationDate'], job['ExpireDate'], job['Action']))
2024-08-20 00:02:00 -07:00
def getAllJobs(self, configuration: str = None):
if configuration is not None:
with self.engine.connect() as conn:
jobs = conn.execute(self.peerJobTable.select().where(
self.peerJobTable.columns.Configuration == configuration
)).mappings().fetchall()
# jobdbCursor = self.jobdb.cursor()
# jobs = jobdbCursor.execute(
# f"SELECT * FROM PeerJobs WHERE Configuration = ?", (configuration, )).fetchall()
2024-08-20 00:02:00 -07:00
j = []
for job in jobs:
j.append(PeerJob(
job['JobID'], job['Configuration'], job['Peer'], job['Field'], job['Operator'], job['Value'],
job['CreationDate'], job['ExpireDate'], job['Action']))
return j
2024-07-29 18:40:07 -04:00
return []
2024-06-18 03:16:42 +08:00
# def __createPeerJobsDatabase(self):
# with self.jobdb:
# jobdbCursor = self.jobdb.cursor()
#
# existingTable = jobdbCursor.execute("SELECT name from sqlite_master where type='table'").fetchall()
# existingTable = [t['name'] for t in existingTable]
#
# if "PeerJobs" not in existingTable:
# jobdbCursor.execute('''
# CREATE TABLE PeerJobs (JobID VARCHAR NOT NULL, Configuration VARCHAR NOT NULL, Peer VARCHAR NOT NULL,
# Field VARCHAR NOT NULL, Operator VARCHAR NOT NULL, Value VARCHAR NOT NULL, CreationDate DATETIME,
# ExpireDate DATETIME, Action VARCHAR NOT NULL, PRIMARY KEY (JobID))
# ''')
# self.jobdb.commit()
2024-06-18 03:16:42 +08:00
def toJson(self):
return [x.toJson() for x in self.Jobs]
def searchJob(self, Configuration: str, Peer: str):
return list(filter(lambda x: x.Configuration == Configuration and x.Peer == Peer, self.Jobs))
2025-04-19 02:54:47 +08:00
def searchJobById(self, JobID):
return list(filter(lambda x: x.JobID == JobID, self.Jobs))
def saveJob(self, Job: PeerJob) -> tuple[bool, list] | tuple[bool, str]:
import traceback
try:
with self.engine.begin() as conn:
# jobdbCursor = self.jobdb.cursor()
# if len(self.searchJobById(Job.JobID)) == 0:
# jobdbCursor.execute('''
# INSERT INTO PeerJobs VALUES (?, ?, ?, ?, ?, ?, strftime('%Y-%m-%d %H:%M:%S','now'), NULL, ?)
# ''', (Job.JobID, Job.Configuration, Job.Peer, Job.Field, Job.Operator, Job.Value, Job.Action,))
# JobLogger.log(Job.JobID, Message=f"Job is created if {Job.Field} {Job.Operator} {Job.Value} then {Job.Action}")
# else:
# currentJob = jobdbCursor.execute('SELECT * FROM PeerJobs WHERE JobID = ?', (Job.JobID, )).fetchone()
# if currentJob is not None:
# jobdbCursor.execute('''
# UPDATE PeerJobs SET Field = ?, Operator = ?, Value = ?, Action = ? WHERE JobID = ?
# ''', (Job.Field, Job.Operator, Job.Value, Job.Action, Job.JobID))
# JobLogger.log(Job.JobID,
# Message=f"Job is updated from if {currentJob['Field']} {currentJob['Operator']} {currentJob['value']} then {currentJob['Action']}; to if {Job.Field} {Job.Operator} {Job.Value} then {Job.Action}")
#
# self.jobdb.commit()
currentJob = self.searchJobById(Job.JobID)
if len(currentJob) == 0:
conn.execute(
self.peerJobTable.insert().values(
{
"JobID": Job.JobID,
"Configuration": Job.Configuration,
"Peer": Job.Peer,
"Field": Job.Field,
"Operator": Job.Operator,
"Value": Job.Value,
"CreationDate": datetime.now(),
"ExpireDate": None,
"Action": Job.Action
}
)
)
2024-08-20 00:02:00 -07:00
JobLogger.log(Job.JobID, Message=f"Job is created if {Job.Field} {Job.Operator} {Job.Value} then {Job.Action}")
else:
conn.execute(
self.peerJobTable.update().values({
"Field": Job.Field,
"Operator": Job.Operator,
"Value": Job.Value,
"Action": Job.Action
}).where(self.peerJobTable.columns.JobID == Job.JobID)
)
JobLogger.log(Job.JobID, Message=f"Job is updated from if {currentJob[0].Field} {currentJob[0].Operator} {currentJob[0].Value} then {currentJob[0].Action}; to if {Job.Field} {Job.Operator} {Job.Value} then {Job.Action}")
2025-04-19 02:54:47 +08:00
self.__getJobs()
return True, list(
filter(lambda x: x.Configuration == Job.Configuration and x.Peer == Job.Peer and x.JobID == Job.JobID,
self.Jobs))
except Exception as e:
traceback.print_exc()
return False, str(e)
def deleteJob(self, Job: PeerJob) -> tuple[bool, None] | tuple[bool, str]:
try:
if len(self.searchJobById(Job.JobID)) == 0:
return False, "Job does not exist"
with self.engine.begin() as conn:
# jobdbCursor = self.jobdb.cursor()
# jobdbCursor.execute('''
# UPDATE PeerJobs SET ExpireDate = strftime('%Y-%m-%d %H:%M:%S','now') WHERE JobID = ?
# ''', (Job.JobID,))
# self.jobdb.commit()
conn.execute(
self.peerJobTable.update().values(
{
"ExpireDate": datetime.now()
}
).where(self.peerJobTable.columns.JobID == Job.JobID)
)
JobLogger.log(Job.JobID, Message=f"Job is removed due to being deleted or finshed.")
self.__getJobs()
return True, None
except Exception as e:
return False, str(e)
2024-11-06 18:36:55 +08:00
2025-04-20 02:52:22 +08:00
def updateJobConfigurationName(self, ConfigurationName: str, NewConfigurationName: str) -> tuple[bool, str] | tuple[bool, None]:
2024-11-06 18:36:55 +08:00
try:
with self.engine.begin() as conn:
# jobdbCursor = self.jobdb.cursor()
# jobdbCursor.execute('''
# UPDATE PeerJobs SET Configuration = ? WHERE Configuration = ?
# ''', (NewConfigurationName, ConfigurationName, ))
# self.jobdb.commit()
conn.execute(
self.peerJobTable.update().values({
"Configuration": NewConfigurationName
}).where(self.peerJobTable.columns.Configuration == ConfigurationName)
)
2024-11-06 18:36:55 +08:00
self.__getJobs()
2025-04-20 02:52:22 +08:00
return True, None
2024-11-06 18:36:55 +08:00
except Exception as e:
return False, str(e)
def runJob(self):
needToDelete = []
2025-05-11 00:04:37 +08:00
self.__getJobs()
for job in self.Jobs:
c = WireguardConfigurations.get(job.Configuration)
if c is not None:
f, fp = c.searchPeer(job.Peer)
if f:
if job.Field in ["total_receive", "total_sent", "total_data"]:
s = job.Field.split("_")[1]
x: float = getattr(fp, f"total_{s}") + getattr(fp, f"cumu_{s}")
y: float = float(job.Value)
else:
x: datetime = datetime.now()
2024-08-07 00:37:05 -04:00
y: datetime = datetime.strptime(job.Value, "%Y-%m-%d %H:%M:%S")
runAction: bool = self.__runJob_Compare(x, y, job.Operator)
if runAction:
s = False
if job.Action == "restrict":
s = c.restrictPeers([fp.id]).get_json()
elif job.Action == "delete":
s = c.deletePeers([fp.id]).get_json()
if s['status'] is True:
JobLogger.log(job.JobID, s["status"],
f"Peer {fp.id} from {c.Name} is successfully {job.Action}ed."
)
needToDelete.append(job)
else:
JobLogger.log(job.JobID, s["status"],
f"Peer {fp.id} from {c.Name} failed {job.Action}ed."
)
2024-11-06 18:36:55 +08:00
else:
JobLogger.log(job.JobID, False,
f"Somehow can't find this peer {job.Peer} from {c.Name} failed {job.Action}ed."
)
2024-11-06 18:36:55 +08:00
else:
JobLogger.log(job.JobID, False,
f"Somehow can't find this peer {job.Peer} from {job.Configuration} failed {job.Action}ed."
)
for j in needToDelete:
self.deleteJob(j)
def __runJob_Compare(self, x: float | datetime, y: float | datetime, operator: str):
if operator == "eq":
return x == y
if operator == "neq":
return x != y
if operator == "lgt":
return x > y
if operator == "lst":
return x < y
2024-11-25 22:11:51 +08:00
"""
WireGuard Configuration
"""
2024-06-18 03:16:42 +08:00
class WireguardConfiguration:
class InvalidConfigurationFileException(Exception):
def __init__(self, m):
self.message = m
def __str__(self):
return self.message
def __init__(self, name: str = None, data: dict = None, backup: dict = None, startup: bool = False, wg: bool = True):
2024-10-29 14:57:29 +08:00
2025-03-11 17:52:53 +08:00
self.__parser: configparser.ConfigParser = configparser.RawConfigParser(strict=False)
2024-06-18 03:16:42 +08:00
self.__parser.optionxform = str
self.__configFileModifiedTime = None
2024-10-29 14:57:29 +08:00
2024-06-18 03:16:42 +08:00
self.Status: bool = False
self.Name: str = ""
self.PrivateKey: str = ""
self.PublicKey: str = ""
2025-03-12 00:44:36 +08:00
2024-06-18 03:16:42 +08:00
self.ListenPort: str = ""
self.Address: str = ""
self.DNS: str = ""
self.Table: str = ""
self.MTU: str = ""
self.PreUp: str = ""
self.PostUp: str = ""
self.PreDown: str = ""
self.PostDown: str = ""
self.SaveConfig: bool = True
self.Name = name
self.Protocol = "wg" if wg else "awg"
self.configPath = os.path.join(self.__getProtocolPath(), f'{self.Name}.conf') if wg else os.path.join(DashboardConfig.GetConfig("Server", "awg_conf_path")[1], f'{self.Name}.conf')
2024-06-18 03:16:42 +08:00
if name is not None:
if data is not None and "Backup" in data.keys():
db = self.__importDatabase(
os.path.join(
self.__getProtocolPath(),
'WGDashboard_Backup',
data["Backup"].replace(".conf", ".sql")))
else:
self.createDatabase()
self.__parseConfigurationFile()
self.__initPeersList()
2021-07-02 13:23:04 -04:00
else:
2024-06-18 03:16:42 +08:00
self.Name = data["ConfigurationName"]
self.configPath = os.path.join(self.__getProtocolPath(), f'{self.Name}.conf')
2024-06-18 03:16:42 +08:00
for i in dir(self):
if str(i) in data.keys():
if isinstance(getattr(self, i), bool):
2024-11-25 22:11:51 +08:00
setattr(self, i, StringToBoolean(data[i]))
2024-06-18 03:16:42 +08:00
else:
setattr(self, i, str(data[i]))
2024-06-18 03:16:42 +08:00
self.__parser["Interface"] = {
"PrivateKey": self.PrivateKey,
"Address": self.Address,
"ListenPort": self.ListenPort,
2025-03-11 17:52:53 +08:00
"PreUp": f"{self.PreUp}",
"PreDown": f"{self.PreDown}",
"PostUp": f"{self.PostUp}",
"PostDown": f"{self.PostDown}",
2024-06-18 03:16:42 +08:00
"SaveConfig": "true"
}
2024-12-05 01:50:31 +08:00
if self.Protocol == 'awg':
self.__parser["Interface"]["Jc"] = self.Jc
self.__parser["Interface"]["Jc"] = self.Jc
self.__parser["Interface"]["Jmin"] = self.Jmin
self.__parser["Interface"]["Jmax"] = self.Jmax
self.__parser["Interface"]["S1"] = self.S1
self.__parser["Interface"]["S2"] = self.S2
self.__parser["Interface"]["H1"] = self.H1
self.__parser["Interface"]["H2"] = self.H2
self.__parser["Interface"]["H3"] = self.H3
self.__parser["Interface"]["H4"] = self.H4
if "Backup" not in data.keys():
self.createDatabase()
with open(self.configPath, "w+") as configFile:
self.__parser.write(configFile)
2024-12-04 17:50:16 +08:00
print(f"[WGDashboard] Configuration file {self.configPath} created")
self.__initPeersList()
if not os.path.exists(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup')):
os.mkdir(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup'))
2024-10-29 14:57:29 +08:00
print(f"[WGDashboard] Initialized Configuration: {name}")
2024-10-31 23:28:30 +08:00
if self.getAutostartStatus() and not self.getStatus() and startup:
2024-10-29 14:57:29 +08:00
self.toggleConfiguration()
print(f"[WGDashboard] Autostart Configuration: {name}")
2024-11-25 22:11:51 +08:00
def __getProtocolPath(self):
return DashboardConfig.GetConfig("Server", "wg_conf_path")[1] if self.Protocol == "wg" \
else DashboardConfig.GetConfig("Server", "awg_conf_path")[1]
def __initPeersList(self):
self.Peers: list[Peer] = []
2024-06-18 03:16:42 +08:00
self.getPeersList()
self.getRestrictedPeersList()
2024-12-06 20:27:04 +08:00
def getRawConfigurationFile(self):
return open(self.configPath, 'r').read()
def updateRawConfigurationFile(self, newRawConfiguration):
backupStatus, backup = self.backupConfigurationFile()
if not backupStatus:
return False, "Cannot create backup"
if self.Status:
self.toggleConfiguration()
with open(self.configPath, 'w') as f:
f.write(newRawConfiguration)
status, err = self.toggleConfiguration()
if not status:
restoreStatus = self.restoreBackup(backup['filename'])
print(f"Restore status: {restoreStatus}")
self.toggleConfiguration()
return False, err
return True, None
def __parseConfigurationFile(self):
with open(self.configPath, 'r') as f:
2024-11-26 21:27:32 +08:00
original = [l.rstrip("\n") for l in f.readlines()]
try:
start = original.index("[Interface]")
# Clean
for i in range(start, len(original)):
if original[i] == "[Peer]":
break
split = re.split(r'\s*=\s*', original[i], 1)
if len(split) == 2:
key = split[0]
if key in dir(self):
if isinstance(getattr(self, key), bool):
setattr(self, key, False)
else:
setattr(self, key, "")
# Set
for i in range(start, len(original)):
if original[i] == "[Peer]":
break
split = re.split(r'\s*=\s*', original[i], 1)
if len(split) == 2:
key = split[0]
value = split[1]
if key in dir(self):
if isinstance(getattr(self, key), bool):
setattr(self, key, StringToBoolean(value))
else:
if len(getattr(self, key)) > 0:
setattr(self, key, f"{getattr(self, key)}, {value}")
else:
setattr(self, key, value)
except ValueError as e:
raise self.InvalidConfigurationFileException(
"[Interface] section not found in " + self.configPath)
2024-11-26 21:27:32 +08:00
if self.PrivateKey:
self.PublicKey = self.__getPublicKey()
self.Status = self.getStatus()
def __dropDatabase(self):
existingTables = [self.Name, f'{self.Name}_restrict_access', f'{self.Name}_transfer', f'{self.Name}_deleted']
# existingTables = sqlSelect(f"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '{self.Name}%'").fetchall()
for t in existingTables:
sqlUpdate("DROP TABLE '%s'" % t)
# existingTables = sqlSelect(f"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '{self.Name}%'").fetchall()
2024-06-18 03:16:42 +08:00
def createDatabase(self, dbName = None):
2024-11-06 18:36:55 +08:00
if dbName is None:
dbName = self.Name
2024-08-20 00:02:00 -07:00
existingTables = sqlSelect("SELECT name FROM sqlite_master WHERE type='table'").fetchall()
2024-06-18 03:16:42 +08:00
existingTables = [t['name'] for t in existingTables]
2024-11-06 18:36:55 +08:00
if dbName not in existingTables:
2024-08-20 00:02:00 -07:00
sqlUpdate(
2022-01-12 19:53:36 -05:00
"""
2024-08-11 19:20:42 -04:00
CREATE TABLE '%s'(
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL,
endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL,
total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL,
status VARCHAR NULL, latest_handshake VARCHAR NULL, allowed_ip VARCHAR NULL,
cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, mtu INT NULL,
2022-03-21 22:33:19 -04:00
keepalive INT NULL, remote_endpoint VARCHAR NULL, preshared_key VARCHAR NULL,
PRIMARY KEY (id)
)
2024-11-06 18:36:55 +08:00
""" % dbName
2024-06-18 03:16:42 +08:00
)
2024-11-06 18:36:55 +08:00
if f'{dbName}_restrict_access' not in existingTables:
2024-08-20 00:02:00 -07:00
sqlUpdate(
2024-06-18 03:16:42 +08:00
"""
2024-08-11 19:20:42 -04:00
CREATE TABLE '%s_restrict_access' (
2022-03-21 22:33:19 -04:00
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL,
endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL,
total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL,
status VARCHAR NULL, latest_handshake VARCHAR NULL, allowed_ip VARCHAR NULL,
cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, mtu INT NULL,
keepalive INT NULL, remote_endpoint VARCHAR NULL, preshared_key VARCHAR NULL,
PRIMARY KEY (id)
)
2024-11-06 18:36:55 +08:00
""" % dbName
2024-06-18 03:16:42 +08:00
)
2024-11-06 18:36:55 +08:00
if f'{dbName}_transfer' not in existingTables:
2024-08-20 00:02:00 -07:00
sqlUpdate(
2024-06-18 03:16:42 +08:00
"""
2024-08-11 19:20:42 -04:00
CREATE TABLE '%s_transfer' (
2024-06-18 03:16:42 +08:00
id VARCHAR NOT NULL, total_receive FLOAT NULL,
2022-04-05 21:39:47 -04:00
total_sent FLOAT NULL, total_data FLOAT NULL,
cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, time DATETIME
)
2024-11-06 18:36:55 +08:00
""" % dbName
2024-06-18 03:16:42 +08:00
)
2024-11-06 18:36:55 +08:00
if f'{dbName}_deleted' not in existingTables:
2024-08-20 00:02:00 -07:00
sqlUpdate(
2024-06-18 03:16:42 +08:00
"""
2024-08-11 19:20:42 -04:00
CREATE TABLE '%s_deleted' (
2024-06-18 03:16:42 +08:00
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL,
endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL,
total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL,
status VARCHAR NULL, latest_handshake VARCHAR NULL, allowed_ip VARCHAR NULL,
cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, mtu INT NULL,
keepalive INT NULL, remote_endpoint VARCHAR NULL, preshared_key VARCHAR NULL,
PRIMARY KEY (id)
)
2024-11-06 18:36:55 +08:00
""" % dbName
2024-06-18 03:16:42 +08:00
)
def __dumpDatabase(self):
for line in sqldb.iterdump():
if (line.startswith(f"INSERT INTO \"{self.Name}\"")
or line.startswith(f'INSERT INTO "{self.Name}_restrict_access"')
or line.startswith(f'INSERT INTO "{self.Name}_transfer"')
or line.startswith(f'INSERT INTO "{self.Name}_deleted"')
):
yield line
def __importDatabase(self, sqlFilePath) -> bool:
self.__dropDatabase()
self.createDatabase()
if not os.path.exists(sqlFilePath):
return False
with open(sqlFilePath, 'r') as f:
for l in f.readlines():
l = l.rstrip("\n")
if len(l) > 0:
sqlUpdate(l)
return True
2024-06-18 03:16:42 +08:00
def __getPublicKey(self) -> str:
2024-11-25 22:11:51 +08:00
return GenerateWireguardPublicKey(self.PrivateKey)[1]
2024-06-18 03:16:42 +08:00
def getStatus(self) -> bool:
self.Status = self.Name in psutil.net_if_addrs().keys()
return self.Status
2024-10-29 14:57:29 +08:00
def getAutostartStatus(self):
s, d = DashboardConfig.GetConfig("WireGuardConfiguration", "autostart")
return self.Name in d
2024-06-18 03:16:42 +08:00
2024-12-03 02:34:45 +08:00
def getRestrictedPeers(self):
2024-06-18 03:16:42 +08:00
self.RestrictedPeers = []
2024-08-20 00:02:00 -07:00
restricted = sqlSelect("SELECT * FROM '%s_restrict_access'" % self.Name).fetchall()
2024-06-18 03:16:42 +08:00
for i in restricted:
self.RestrictedPeers.append(Peer(i, self))
def configurationFileChanged(self) :
mt = os.path.getmtime(self.configPath)
changed = self.__configFileModifiedTime is None or self.__configFileModifiedTime != mt
self.__configFileModifiedTime = mt
return changed
def getPeers(self):
if self.configurationFileChanged():
self.Peers = []
with open(self.configPath, 'r') as configFile:
p = []
pCounter = -1
content = configFile.read().split('\n')
try:
peerStarts = content.index("[Peer]")
content = content[peerStarts:]
for i in content:
2024-11-25 22:11:51 +08:00
if not RegexMatch("#(.*)", i) and not RegexMatch(";(.*)", i):
if i == "[Peer]":
pCounter += 1
p.append({})
p[pCounter]["name"] = ""
else:
if len(i) > 0:
split = re.split(r'\s*=\s*', i, 1)
if len(split) == 2:
p[pCounter][split[0]] = split[1]
2024-11-25 22:11:51 +08:00
if RegexMatch("#Name# = (.*)", i):
split = re.split(r'\s*=\s*', i, 1)
if len(split) == 2:
p[pCounter]["name"] = split[1]
2024-08-15 17:45:54 -04:00
for i in p:
if "PublicKey" in i.keys():
checkIfExist = sqlSelect("SELECT * FROM '%s' WHERE id = ?" % self.Name,
((i['PublicKey']),)).fetchone()
if checkIfExist is None:
newPeer = {
"id": i['PublicKey'],
"private_key": "",
"DNS": DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1],
"endpoint_allowed_ip": DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[
1],
"name": i.get("name"),
"total_receive": 0,
"total_sent": 0,
"total_data": 0,
"endpoint": "N/A",
"status": "stopped",
"latest_handshake": "N/A",
"allowed_ip": i.get("AllowedIPs", "N/A"),
"cumu_receive": 0,
"cumu_sent": 0,
"cumu_data": 0,
"traffic": [],
"mtu": DashboardConfig.GetConfig("Peers", "peer_mtu")[1],
"keepalive": DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1],
"remote_endpoint": DashboardConfig.GetConfig("Peers", "remote_endpoint")[1],
"preshared_key": i["PresharedKey"] if "PresharedKey" in i.keys() else ""
}
sqlUpdate(
"""
INSERT INTO '%s'
VALUES (:id, :private_key, :DNS, :endpoint_allowed_ip, :name, :total_receive, :total_sent,
:total_data, :endpoint, :status, :latest_handshake, :allowed_ip, :cumu_receive, :cumu_sent,
:cumu_data, :mtu, :keepalive, :remote_endpoint, :preshared_key);
""" % self.Name
, newPeer)
self.Peers.append(Peer(newPeer, self))
else:
sqlUpdate("UPDATE '%s' SET allowed_ip = ? WHERE id = ?" % self.Name,
(i.get("AllowedIPs", "N/A"), i['PublicKey'],))
self.Peers.append(Peer(checkIfExist, self))
except Exception as e:
2024-09-05 16:17:56 +08:00
if __name__ == '__main__':
print(f"[WGDashboard] {self.Name} Error: {str(e)}")
else:
self.Peers.clear()
checkIfExist = sqlSelect("SELECT * FROM '%s'" % self.Name).fetchall()
for i in checkIfExist:
self.Peers.append(Peer(i, self))
2024-08-08 23:27:13 -04:00
def addPeers(self, peers: list) -> tuple[bool, dict]:
result = {
"message": None,
"peers": []
}
2024-11-06 20:58:53 +08:00
try:
for i in peers:
newPeer = {
"id": i['id'],
"private_key": i['private_key'],
"DNS": i['DNS'],
"endpoint_allowed_ip": i['endpoint_allowed_ip'],
"name": i['name'],
"total_receive": 0,
"total_sent": 0,
"total_data": 0,
"endpoint": "N/A",
"status": "stopped",
"latest_handshake": "N/A",
"allowed_ip": i.get("allowed_ip", "N/A"),
"cumu_receive": 0,
"cumu_sent": 0,
"cumu_data": 0,
"traffic": [],
"mtu": i['mtu'],
"keepalive": i['keepalive'],
"remote_endpoint": DashboardConfig.GetConfig("Peers", "remote_endpoint")[1],
"preshared_key": i["preshared_key"]
}
sqlUpdate(
"""
INSERT INTO '%s'
VALUES (:id, :private_key, :DNS, :endpoint_allowed_ip, :name, :total_receive, :total_sent,
:total_data, :endpoint, :status, :latest_handshake, :allowed_ip, :cumu_receive, :cumu_sent,
:cumu_data, :mtu, :keepalive, :remote_endpoint, :preshared_key);
""" % self.Name
, newPeer)
for p in peers:
presharedKeyExist = len(p['preshared_key']) > 0
rd = random.Random()
uid = str(uuid.UUID(int=rd.getrandbits(128), version=4))
if presharedKeyExist:
with open(uid, "w+") as f:
f.write(p['preshared_key'])
2024-12-02 16:34:26 +08:00
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 ''}",
2024-11-06 20:58:53 +08:00
shell=True, stderr=subprocess.STDOUT)
if presharedKeyExist:
os.remove(uid)
2024-11-06 20:58:53 +08:00
subprocess.check_output(
2024-12-02 16:34:26 +08:00
f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT)
2024-11-06 20:58:53 +08:00
self.getPeersList()
for p in peers:
p = self.searchPeer(p['id'])
if p[0]:
result['peers'].append(p[1])
return True, result
2024-11-06 20:58:53 +08:00
except Exception as e:
result['message'] = str(e)
return False, result
2024-08-08 23:27:13 -04:00
2024-06-18 03:16:42 +08:00
def searchPeer(self, publicKey):
for i in self.Peers:
if i.id == publicKey:
return True, i
return False, None
def allowAccessPeers(self, listOfPublicKeys):
2024-08-08 23:27:13 -04:00
if not self.getStatus():
self.toggleConfiguration()
2024-06-18 03:16:42 +08:00
for i in listOfPublicKeys:
2024-08-20 00:02:00 -07:00
p = sqlSelect("SELECT * FROM '%s_restrict_access' WHERE id = ?" % self.Name, (i,)).fetchone()
2024-06-18 03:16:42 +08:00
if p is not None:
2025-01-19 20:52:04 +08:00
sqlUpdate("INSERT INTO '%s' SELECT * FROM '%s_restrict_access' WHERE id = ?"
2024-06-18 03:16:42 +08:00
% (self.Name, self.Name,), (p['id'],))
2024-08-20 00:02:00 -07:00
sqlUpdate("DELETE FROM '%s_restrict_access' WHERE id = ?"
2024-06-18 03:16:42 +08:00
% self.Name, (p['id'],))
2024-09-17 14:42:25 +08:00
presharedKeyExist = len(p['preshared_key']) > 0
rd = random.Random()
uid = str(uuid.UUID(int=rd.getrandbits(128), version=4))
2024-09-17 14:42:25 +08:00
if presharedKeyExist:
with open(uid, "w+") as f:
2024-09-17 14:42:25 +08:00
f.write(p['preshared_key'])
2024-12-02 16:34:26 +08:00
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 ''}",
2024-06-18 03:16:42 +08:00
shell=True, stderr=subprocess.STDOUT)
if presharedKeyExist: os.remove(uid)
2024-06-18 03:16:42 +08:00
else:
return ResponseObject(False, "Failed to allow access of peer " + i)
if not self.__wgSave():
return ResponseObject(False, "Failed to save configuration through WireGuard")
self.getPeers()
2024-11-03 17:36:03 +08:00
return ResponseObject(True, "Allow access successfully")
2024-06-18 03:16:42 +08:00
def restrictPeers(self, listOfPublicKeys):
numOfRestrictedPeers = 0
numOfFailedToRestrictPeers = 0
2024-08-08 23:27:13 -04:00
if not self.getStatus():
self.toggleConfiguration()
2024-06-18 03:16:42 +08:00
for p in listOfPublicKeys:
found, pf = self.searchPeer(p)
if found:
try:
2024-12-02 16:34:26 +08:00
subprocess.check_output(f"{self.Protocol} set {self.Name} peer {pf.id} remove",
2024-06-18 03:16:42 +08:00
shell=True, stderr=subprocess.STDOUT)
2025-01-19 20:52:04 +08:00
sqlUpdate("INSERT INTO '%s_restrict_access' SELECT * FROM '%s' WHERE id = ?" %
2024-06-18 03:16:42 +08:00
(self.Name, self.Name,), (pf.id,))
2024-08-20 00:02:00 -07:00
sqlUpdate("UPDATE '%s_restrict_access' SET status = 'stopped' WHERE id = ?" %
2024-06-18 03:16:42 +08:00
(self.Name,), (pf.id,))
2024-08-20 00:02:00 -07:00
sqlUpdate("DELETE FROM '%s' WHERE id = ?" % self.Name, (pf.id,))
2024-06-18 03:16:42 +08:00
numOfRestrictedPeers += 1
except Exception as e:
numOfFailedToRestrictPeers += 1
if not self.__wgSave():
return ResponseObject(False, "Failed to save configuration through WireGuard")
self.getPeers()
2024-06-18 03:16:42 +08:00
if numOfRestrictedPeers == len(listOfPublicKeys):
return ResponseObject(True, f"Restricted {numOfRestrictedPeers} peer(s)")
return ResponseObject(False,
f"Restricted {numOfRestrictedPeers} peer(s) successfully. Failed to restrict {numOfFailedToRestrictPeers} peer(s)")
2025-05-11 00:04:37 +08:00
2024-06-18 03:16:42 +08:00
def deletePeers(self, listOfPublicKeys):
numOfDeletedPeers = 0
numOfFailedToDeletePeers = 0
2024-08-08 23:27:13 -04:00
if not self.getStatus():
self.toggleConfiguration()
2024-06-18 03:16:42 +08:00
for p in listOfPublicKeys:
found, pf = self.searchPeer(p)
if found:
try:
2024-12-02 16:34:26 +08:00
subprocess.check_output(f"{self.Protocol} set {self.Name} peer {pf.id} remove",
2024-06-18 03:16:42 +08:00
shell=True, stderr=subprocess.STDOUT)
2024-08-20 00:02:00 -07:00
sqlUpdate("DELETE FROM '%s' WHERE id = ?" % self.Name, (pf.id,))
2024-06-18 03:16:42 +08:00
numOfDeletedPeers += 1
except Exception as e:
numOfFailedToDeletePeers += 1
if not self.__wgSave():
return ResponseObject(False, "Failed to save configuration through WireGuard")
self.getPeers()
if numOfDeletedPeers == 0 and numOfFailedToDeletePeers == 0:
return ResponseObject(False, "No peer(s) to delete found", responseCode=404)
2024-06-18 03:16:42 +08:00
if numOfDeletedPeers == len(listOfPublicKeys):
return ResponseObject(True, f"Deleted {numOfDeletedPeers} peer(s)")
return ResponseObject(False,
f"Deleted {numOfDeletedPeers} peer(s) successfully. Failed to delete {numOfFailedToDeletePeers} peer(s)")
def __wgSave(self) -> tuple[bool, str] | tuple[bool, None]:
try:
2024-12-02 16:34:26 +08:00
subprocess.check_output(f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT)
2024-06-18 03:16:42 +08:00
return True, None
except subprocess.CalledProcessError as e:
return False, str(e)
2021-08-14 23:30:05 -04:00
2024-06-18 03:16:42 +08:00
def getPeersLatestHandshake(self):
2024-08-08 23:27:13 -04:00
if not self.getStatus():
self.toggleConfiguration()
2024-06-18 03:16:42 +08:00
try:
2024-12-02 16:34:26 +08:00
latestHandshake = subprocess.check_output(f"{self.Protocol} show {self.Name} latest-handshakes",
2024-06-18 03:16:42 +08:00
shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError:
return "stopped"
latestHandshake = latestHandshake.decode("UTF-8").split()
count = 0
2024-06-18 03:16:42 +08:00
now = datetime.now()
time_delta = timedelta(minutes=2)
for _ in range(int(len(latestHandshake) / 2)):
minus = now - datetime.fromtimestamp(int(latestHandshake[count + 1]))
if minus < time_delta:
status = "running"
else:
status = "stopped"
if int(latestHandshake[count + 1]) > 0:
sqlUpdate("UPDATE '%s' SET latest_handshake = ?, status = ? WHERE id= ?" % self.Name
2024-06-18 03:16:42 +08:00
, (str(minus).split(".", maxsplit=1)[0], status, latestHandshake[count],))
else:
sqlUpdate("UPDATE '%s' SET latest_handshake = 'No Handshake', status = ? WHERE id= ?" % self.Name
2024-06-18 03:16:42 +08:00
, (status, latestHandshake[count],))
count += 2
2024-06-18 03:16:42 +08:00
def getPeersTransfer(self):
2024-08-08 23:27:13 -04:00
if not self.getStatus():
self.toggleConfiguration()
2024-06-18 03:16:42 +08:00
try:
2024-12-02 16:34:26 +08:00
data_usage = subprocess.check_output(f"{self.Protocol} show {self.Name} transfer",
2024-06-18 03:16:42 +08:00
shell=True, stderr=subprocess.STDOUT)
data_usage = data_usage.decode("UTF-8").split("\n")
data_usage = [p.split("\t") for p in data_usage]
for i in range(len(data_usage)):
if len(data_usage[i]) == 3:
2024-08-20 00:02:00 -07:00
cur_i = sqlSelect(
2024-08-11 19:20:42 -04:00
"SELECT total_receive, total_sent, cumu_receive, cumu_sent, status FROM '%s' WHERE id= ? "
2024-06-18 03:16:42 +08:00
% self.Name, (data_usage[i][0],)).fetchone()
if cur_i is not None:
cur_i = dict(cur_i)
2024-06-18 03:16:42 +08:00
total_sent = cur_i['total_sent']
total_receive = cur_i['total_receive']
2024-08-09 16:09:08 -04:00
cur_total_sent = float(data_usage[i][2]) / (1024 ** 3)
cur_total_receive = float(data_usage[i][1]) / (1024 ** 3)
2024-06-18 03:16:42 +08:00
cumulative_receive = cur_i['cumu_receive'] + total_receive
cumulative_sent = cur_i['cumu_sent'] + total_sent
if total_sent <= cur_total_sent and total_receive <= cur_total_receive:
total_sent = cur_total_sent
total_receive = cur_total_receive
else:
2024-08-20 00:02:00 -07:00
sqlUpdate(
2024-08-11 19:20:42 -04:00
"UPDATE '%s' SET cumu_receive = ?, cumu_sent = ?, cumu_data = ? WHERE id = ?" %
2024-08-09 16:09:08 -04:00
self.Name, (cumulative_receive, cumulative_sent,
2024-08-09 22:16:38 -04:00
cumulative_sent + cumulative_receive,
2024-06-18 03:16:42 +08:00
data_usage[i][0],))
total_sent = 0
total_receive = 0
_, p = self.searchPeer(data_usage[i][0])
2024-08-09 16:09:08 -04:00
if p.total_receive != total_receive or p.total_sent != total_sent:
2024-08-20 00:02:00 -07:00
sqlUpdate(
2024-08-11 19:20:42 -04:00
"UPDATE '%s' SET total_receive = ?, total_sent = ?, total_data = ? WHERE id = ?"
2024-08-09 16:09:08 -04:00
% self.Name, (total_receive, total_sent,
total_receive + total_sent, data_usage[i][0],))
2024-06-18 03:16:42 +08:00
except Exception as e:
print(f"[WGDashboard] {self.Name} Error: {str(e)} {str(e.__traceback__)}")
2024-06-18 03:16:42 +08:00
def getPeersEndpoint(self):
2024-08-08 23:27:13 -04:00
if not self.getStatus():
self.toggleConfiguration()
2024-06-18 03:16:42 +08:00
try:
2024-12-02 16:34:26 +08:00
data_usage = subprocess.check_output(f"{self.Protocol} show {self.Name} endpoints",
2024-06-18 03:16:42 +08:00
shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError:
return "stopped"
data_usage = data_usage.decode("UTF-8").split()
count = 0
for _ in range(int(len(data_usage) / 2)):
2024-09-05 14:51:00 +08:00
sqlUpdate("UPDATE '%s' SET endpoint = ? WHERE id = ?" % self.Name
2024-06-18 03:16:42 +08:00
, (data_usage[count + 1], data_usage[count],))
count += 2
def toggleConfiguration(self) -> [bool, str]:
self.getStatus()
if self.Status:
try:
2024-12-02 16:34:26 +08:00
check = subprocess.check_output(f"{self.Protocol}-quick down {self.Name}",
2024-06-18 03:16:42 +08:00
shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as exc:
return False, str(exc.output.strip().decode("utf-8"))
else:
try:
2024-12-02 16:34:26 +08:00
check = subprocess.check_output(f"{self.Protocol}-quick up {self.Name}", shell=True, stderr=subprocess.STDOUT)
2024-06-18 03:16:42 +08:00
except subprocess.CalledProcessError as exc:
return False, str(exc.output.strip().decode("utf-8"))
2024-11-26 21:27:32 +08:00
self.__parseConfigurationFile()
2024-06-18 03:16:42 +08:00
self.getStatus()
return True, None
def getPeersList(self):
self.getPeers()
2024-06-18 03:16:42 +08:00
return self.Peers
def getRestrictedPeersList(self) -> list:
2024-12-03 02:34:45 +08:00
self.getRestrictedPeers()
2024-06-18 03:16:42 +08:00
return self.RestrictedPeers
def toJson(self):
self.Status = self.getStatus()
return {
"Status": self.Status,
"Name": self.Name,
"PrivateKey": self.PrivateKey,
"PublicKey": self.PublicKey,
"Address": self.Address,
"ListenPort": self.ListenPort,
"PreUp": self.PreUp,
"PreDown": self.PreDown,
"PostUp": self.PostUp,
"PostDown": self.PostDown,
2024-08-09 16:09:55 -04:00
"SaveConfig": self.SaveConfig,
2024-08-09 16:09:23 -04:00
"DataUsage": {
"Total": sum(list(map(lambda x: x.cumu_data + x.total_data, self.Peers))),
"Sent": sum(list(map(lambda x: x.cumu_sent + x.total_sent, self.Peers))),
"Receive": sum(list(map(lambda x: x.cumu_receive + x.total_receive, self.Peers)))
},
2024-10-05 16:10:36 +08:00
"ConnectedPeers": len(list(filter(lambda x: x.status == "running", self.Peers))),
2024-12-02 16:34:26 +08:00
"TotalPeers": len(self.Peers),
"Protocol": self.Protocol,
"Table": self.Table,
2024-06-18 03:16:42 +08:00
}
2024-10-04 16:58:47 +08:00
2024-12-06 20:27:04 +08:00
def backupConfigurationFile(self) -> tuple[bool, dict[str, str]]:
if not os.path.exists(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup')):
os.mkdir(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup'))
time = datetime.now().strftime("%Y%m%d%H%M%S")
2024-10-15 00:30:20 +08:00
shutil.copy(
self.configPath,
os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', f'{self.Name}_{time}.conf')
2024-10-15 00:30:20 +08:00
)
with open(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', f'{self.Name}_{time}.sql'), 'w+') as f:
for l in self.__dumpDatabase():
f.write(l + "\n")
2024-12-06 20:27:04 +08:00
return True, {
"filename": f'{self.Name}_{time}.conf',
"backupDate": datetime.now().strftime("%Y%m%d%H%M%S")
}
2024-12-30 20:30:09 +08:00
2024-10-25 00:19:27 +08:00
def getBackups(self, databaseContent: bool = False) -> list[dict[str: str, str: str, str: str]]:
2024-10-15 00:30:20 +08:00
backups = []
directory = os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup')
2024-10-15 00:30:20 +08:00
files = [(file, os.path.getctime(os.path.join(directory, file)))
for file in os.listdir(directory) if os.path.isfile(os.path.join(directory, file))]
files.sort(key=lambda x: x[1], reverse=True)
for f, ct in files:
2024-11-25 22:11:51 +08:00
if RegexMatch(f"^({self.Name})_(.*)\\.(conf)$", f):
2024-11-07 18:33:39 +08:00
s = re.search(f"^({self.Name})_(.*)\\.(conf)$", f)
2024-10-15 00:30:20 +08:00
date = s.group(2)
2024-10-25 00:19:27 +08:00
d = {
2024-10-15 00:30:20 +08:00
"filename": f,
"backupDate": date,
"content": open(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', f), 'r').read()
2024-10-25 00:19:27 +08:00
}
if f.replace(".conf", ".sql") in list(os.listdir(directory)):
d['database'] = True
if databaseContent:
d['databaseContent'] = open(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', f.replace(".conf", ".sql")), 'r').read()
2024-10-25 00:19:27 +08:00
backups.append(d)
2024-10-15 00:30:20 +08:00
return backups
def restoreBackup(self, backupFileName: str) -> bool:
backups = list(map(lambda x : x['filename'], self.getBackups()))
if backupFileName not in backups:
return False
if self.Status:
self.toggleConfiguration()
target = os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', backupFileName)
targetSQL = os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', backupFileName.replace(".conf", ".sql"))
if not os.path.exists(target):
return False
targetContent = open(target, 'r').read()
try:
with open(self.configPath, 'w') as f:
f.write(targetContent)
except Exception as e:
return False
self.__parseConfigurationFile()
self.__dropDatabase()
self.__importDatabase(targetSQL)
self.__initPeersList()
return True
def deleteBackup(self, backupFileName: str) -> bool:
backups = list(map(lambda x : x['filename'], self.getBackups()))
if backupFileName not in backups:
return False
try:
os.remove(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', backupFileName))
except Exception as e:
return False
return True
2024-10-15 00:30:20 +08:00
2025-03-16 00:42:40 +08:00
def downloadBackup(self, backupFileName: str) -> tuple[bool, str] | tuple[bool, None]:
2024-12-26 00:06:37 +08:00
backup = list(filter(lambda x : x['filename'] == backupFileName, self.getBackups()))
if len(backup) == 0:
return False, None
zip = f'{str(uuid.UUID(int=random.Random().getrandbits(128), version=4))}.zip'
with ZipFile(os.path.join('download', zip), 'w') as zipF:
zipF.write(
os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', backup[0]['filename']),
os.path.basename(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', backup[0]['filename']))
)
if backup[0]['database']:
zipF.write(
os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', backup[0]['filename'].replace('.conf', '.sql')),
os.path.basename(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', backup[0]['filename'].replace('.conf', '.sql')))
)
return True, zip
2024-10-04 16:58:47 +08:00
def updateConfigurationSettings(self, newData: dict) -> tuple[bool, str]:
if self.Status:
self.toggleConfiguration()
original = []
dataChanged = False
with open(self.configPath, 'r') as f:
2024-11-26 21:27:32 +08:00
original = [l.rstrip("\n") for l in f.readlines()]
allowEdit = ["Address", "PreUp", "PostUp", "PreDown", "PostDown", "ListenPort", "Table"]
2024-12-05 01:50:31 +08:00
if self.Protocol == 'awg':
allowEdit += ["Jc", "Jmin", "Jmax", "S1", "S2", "H1", "H2", "H3", "H4"]
2024-10-04 16:58:47 +08:00
start = original.index("[Interface]")
2024-11-26 21:27:32 +08:00
try:
end = original.index("[Peer]")
except ValueError as e:
end = len(original)
new = ["[Interface]"]
peerFound = False
for line in range(start, end):
2024-10-04 16:58:47 +08:00
split = re.split(r'\s*=\s*', original[line], 1)
if len(split) == 2:
2024-11-26 21:27:32 +08:00
if split[0] not in allowEdit:
new.append(original[line])
for key in allowEdit:
2024-12-05 01:50:31 +08:00
new.insert(1, f"{key} = {str(newData[key]).strip()}")
2024-11-26 21:27:32 +08:00
new.append("")
for line in range(end, len(original)):
new.append(original[line])
2024-10-15 00:30:20 +08:00
self.backupConfigurationFile()
with open(self.configPath, 'w') as f:
2024-11-26 21:27:32 +08:00
f.write("\n".join(new))
2024-10-04 16:58:47 +08:00
status, msg = self.toggleConfiguration()
if not status:
return False, msg
2024-12-30 20:30:09 +08:00
for i in allowEdit:
if isinstance(getattr(self, i), bool):
setattr(self, i, _strToBool(newData[i]))
else:
setattr(self, i, str(newData[i]))
2024-10-04 16:58:47 +08:00
return True, ""
2024-10-25 00:19:27 +08:00
def deleteConfiguration(self):
if self.getStatus():
self.toggleConfiguration()
os.remove(self.configPath)
2024-10-25 00:19:27 +08:00
self.__dropDatabase()
return True
2024-11-06 18:36:55 +08:00
def renameConfiguration(self, newConfigurationName) -> tuple[bool, str]:
if newConfigurationName in WireguardConfigurations.keys():
return False, "Configuration name already exist"
try:
if self.getStatus():
self.toggleConfiguration()
self.createDatabase(newConfigurationName)
2024-11-06 18:36:55 +08:00
sqlUpdate(f'INSERT INTO "{newConfigurationName}" SELECT * FROM "{self.Name}"')
sqlUpdate(f'INSERT INTO "{newConfigurationName}_restrict_access" SELECT * FROM "{self.Name}_restrict_access"')
sqlUpdate(f'INSERT INTO "{newConfigurationName}_deleted" SELECT * FROM "{self.Name}_deleted"')
sqlUpdate(f'INSERT INTO "{newConfigurationName}_transfer" SELECT * FROM "{self.Name}_transfer"')
AllPeerJobs.updateJobConfigurationName(self.Name, newConfigurationName)
shutil.copy(
self.configPath,
os.path.join(self.__getProtocolPath(), f'{newConfigurationName}.conf')
2024-11-06 18:36:55 +08:00
)
self.deleteConfiguration()
except Exception as e:
return False, str(e)
return True, None
2024-11-25 22:11:51 +08:00
2025-02-16 17:42:32 +08:00
def getNumberOfAvailableIP(self):
2024-11-25 22:11:51 +08:00
if len(self.Address) < 0:
return False, None
2025-02-16 17:42:32 +08:00
existedAddress = set()
availableAddress = {}
for p in self.Peers + self.getRestrictedPeersList():
peerAllowedIP = p.allowed_ip.split(',')
for pip in peerAllowedIP:
ppip = pip.strip().split('/')
if len(ppip) == 2:
2024-11-25 22:11:51 +08:00
try:
2025-02-16 17:42:32 +08:00
check = ipaddress.ip_network(ppip[0])
existedAddress.add(check)
except Exception as e:
2024-12-26 00:06:37 +08:00
print(f"[WGDashboard] Error: {self.Name} peer {p.id} have invalid ip")
2025-02-16 17:42:32 +08:00
configurationAddresses = self.Address.split(',')
for ca in configurationAddresses:
ca = ca.strip()
caSplit = ca.split('/')
try:
if len(caSplit) == 2:
network = ipaddress.ip_network(ca, False)
existedAddress.add(ipaddress.ip_network(caSplit[0]))
availableAddress[ca] = network.num_addresses
for p in existedAddress:
2025-02-16 17:43:22 +08:00
if p.version == network.version and p.subnet_of(network):
2025-02-16 23:07:16 +08:00
availableAddress[ca] -= 1
2025-02-16 17:42:32 +08:00
except Exception as e:
print(e)
print(f"[WGDashboard] Error: Failed to parse IP address {ca} from {self.Name}")
return True, availableAddress
2025-02-16 23:07:16 +08:00
def getAvailableIP(self, threshold = 255):
2025-02-16 17:42:32 +08:00
if len(self.Address) < 0:
return False, None
existedAddress = set()
availableAddress = {}
for p in self.Peers + self.getRestrictedPeersList():
peerAllowedIP = p.allowed_ip.split(',')
for pip in peerAllowedIP:
ppip = pip.strip().split('/')
if len(ppip) == 2:
try:
check = ipaddress.ip_network(ppip[0])
2025-02-17 15:25:33 +08:00
existedAddress.add(check.compressed)
2025-02-16 17:42:32 +08:00
except Exception as e:
print(f"[WGDashboard] Error: {self.Name} peer {p.id} have invalid ip")
configurationAddresses = self.Address.split(',')
for ca in configurationAddresses:
ca = ca.strip()
caSplit = ca.split('/')
try:
if len(caSplit) == 2:
network = ipaddress.ip_network(ca, False)
existedAddress.add(ipaddress.ip_network(caSplit[0]).compressed)
2025-02-16 23:07:16 +08:00
if threshold == -1:
availableAddress[ca] = filter(lambda ip : ip not in existedAddress,
map(lambda iph : ipaddress.ip_network(iph).compressed, network.hosts()))
else:
availableAddress[ca] = list(islice(filter(lambda ip : ip not in existedAddress,
map(lambda iph : ipaddress.ip_network(iph).compressed, network.hosts())), threshold))
2025-02-16 17:42:32 +08:00
except Exception as e:
print(e)
print(f"[WGDashboard] Error: Failed to parse IP address {ca} from {self.Name}")
2025-02-16 23:07:16 +08:00
print("Generated IP")
2024-11-25 22:11:51 +08:00
return True, availableAddress
2024-12-30 20:30:09 +08:00
def getRealtimeTrafficUsage(self):
stats = psutil.net_io_counters(pernic=True, nowrap=True)
if self.Name in stats.keys():
stat = stats[self.Name]
recv1 = stat.bytes_recv
sent1 = stat.bytes_sent
time.sleep(1)
stats = psutil.net_io_counters(pernic=True, nowrap=True)
if self.Name in stats.keys():
stat = stats[self.Name]
recv2 = stat.bytes_recv
sent2 = stat.bytes_sent
net_in = round((recv2 - recv1) / 1024 / 1024, 3)
net_out = round((sent2 - sent1) / 1024 / 1024, 3)
return {
"sent": net_out,
"recv": net_in
}
else:
return { "sent": 0, "recv": 0 }
else:
return { "sent": 0, "recv": 0 }
"""
AmneziaWG Configuration
"""
class AmneziaWireguardConfiguration(WireguardConfiguration):
def __init__(self, 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.H1 = 1
self.H2 = 2
self.H3 = 3
self.H4 = 4
super().__init__(name, data, backup, startup, wg=False)
2024-12-02 16:34:26 +08:00
def toJson(self):
self.Status = self.getStatus()
return {
"Status": self.Status,
"Name": self.Name,
"PrivateKey": self.PrivateKey,
"PublicKey": self.PublicKey,
"Address": self.Address,
"ListenPort": self.ListenPort,
"PreUp": self.PreUp,
"PreDown": self.PreDown,
"PostUp": self.PostUp,
"PostDown": self.PostDown,
"SaveConfig": self.SaveConfig,
"DataUsage": {
"Total": sum(list(map(lambda x: x.cumu_data + x.total_data, self.Peers))),
"Sent": sum(list(map(lambda x: x.cumu_sent + x.total_sent, self.Peers))),
"Receive": sum(list(map(lambda x: x.cumu_receive + x.total_receive, self.Peers)))
},
"ConnectedPeers": len(list(filter(lambda x: x.status == "running", self.Peers))),
"TotalPeers": len(self.Peers),
"Protocol": self.Protocol,
"Jc": self.Jc,
"Jmin": self.Jmin,
"Jmax": self.Jmax,
"S1": self.S1,
"S2": self.S2,
"H1": self.H1,
"H2": self.H2,
"H3": self.H3,
"H4": self.H4
}
def createDatabase(self, dbName = None):
if dbName is None:
dbName = self.Name
existingTables = sqlSelect("SELECT name FROM sqlite_master WHERE type='table'").fetchall()
existingTables = [t['name'] for t in existingTables]
if dbName not in existingTables:
sqlUpdate(
"""
CREATE TABLE '%s'(
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL, advanced_security VARCHAR NULL,
endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL,
total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL,
status VARCHAR NULL, latest_handshake VARCHAR NULL, allowed_ip VARCHAR NULL,
cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, mtu INT NULL,
keepalive INT NULL, remote_endpoint VARCHAR NULL, preshared_key VARCHAR NULL,
PRIMARY KEY (id)
)
""" % dbName
)
if f'{dbName}_restrict_access' not in existingTables:
sqlUpdate(
"""
CREATE TABLE '%s_restrict_access' (
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL, advanced_security VARCHAR NULL,
endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL,
total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL,
status VARCHAR NULL, latest_handshake VARCHAR NULL, allowed_ip VARCHAR NULL,
cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, mtu INT NULL,
keepalive INT NULL, remote_endpoint VARCHAR NULL, preshared_key VARCHAR NULL,
PRIMARY KEY (id)
)
""" % dbName
)
if f'{dbName}_transfer' not in existingTables:
sqlUpdate(
"""
CREATE TABLE '%s_transfer' (
id VARCHAR NOT NULL, total_receive FLOAT NULL,
total_sent FLOAT NULL, total_data FLOAT NULL,
cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, time DATETIME
)
""" % dbName
)
if f'{dbName}_deleted' not in existingTables:
sqlUpdate(
"""
CREATE TABLE '%s_deleted' (
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL, advanced_security VARCHAR NULL,
endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL,
total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL,
status VARCHAR NULL, latest_handshake VARCHAR NULL, allowed_ip VARCHAR NULL,
cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, mtu INT NULL,
keepalive INT NULL, remote_endpoint VARCHAR NULL, preshared_key VARCHAR NULL,
PRIMARY KEY (id)
)
""" % dbName
)
def getPeers(self):
if self.configurationFileChanged():
self.Peers = []
with open(self.configPath, 'r') as configFile:
p = []
pCounter = -1
content = configFile.read().split('\n')
try:
peerStarts = content.index("[Peer]")
content = content[peerStarts:]
for i in content:
if not RegexMatch("#(.*)", i) and not RegexMatch(";(.*)", i):
if i == "[Peer]":
pCounter += 1
p.append({})
p[pCounter]["name"] = ""
else:
if len(i) > 0:
split = re.split(r'\s*=\s*', i, 1)
if len(split) == 2:
p[pCounter][split[0]] = split[1]
if RegexMatch("#Name# = (.*)", i):
split = re.split(r'\s*=\s*', i, 1)
if len(split) == 2:
p[pCounter]["name"] = split[1]
for i in p:
if "PublicKey" in i.keys():
checkIfExist = sqlSelect("SELECT * FROM '%s' WHERE id = ?" % self.Name,
((i['PublicKey']),)).fetchone()
if checkIfExist is None:
newPeer = {
"id": i['PublicKey'],
"advanced_security": i.get('AdvancedSecurity', 'off'),
"private_key": "",
"DNS": DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1],
"endpoint_allowed_ip": DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[
1],
"name": i.get("name"),
"total_receive": 0,
"total_sent": 0,
"total_data": 0,
"endpoint": "N/A",
"status": "stopped",
"latest_handshake": "N/A",
"allowed_ip": i.get("AllowedIPs", "N/A"),
"cumu_receive": 0,
"cumu_sent": 0,
"cumu_data": 0,
"traffic": [],
"mtu": DashboardConfig.GetConfig("Peers", "peer_mtu")[1],
"keepalive": DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1],
"remote_endpoint": DashboardConfig.GetConfig("Peers", "remote_endpoint")[1],
"preshared_key": i["PresharedKey"] if "PresharedKey" in i.keys() else ""
}
sqlUpdate(
"""
INSERT INTO '%s'
VALUES (:id, :private_key, :DNS, :advanced_security, :endpoint_allowed_ip, :name, :total_receive, :total_sent,
:total_data, :endpoint, :status, :latest_handshake, :allowed_ip, :cumu_receive, :cumu_sent,
:cumu_data, :mtu, :keepalive, :remote_endpoint, :preshared_key);
""" % self.Name
, newPeer)
2024-12-03 02:34:45 +08:00
self.Peers.append(AmneziaWGPeer(newPeer, self))
else:
sqlUpdate("UPDATE '%s' SET allowed_ip = ? WHERE id = ?" % self.Name,
(i.get("AllowedIPs", "N/A"), i['PublicKey'],))
2024-12-03 02:34:45 +08:00
self.Peers.append(AmneziaWGPeer(checkIfExist, self))
except Exception as e:
if __name__ == '__main__':
print(f"[WGDashboard] {self.Name} Error: {str(e)}")
else:
self.Peers.clear()
checkIfExist = sqlSelect("SELECT * FROM '%s'" % self.Name).fetchall()
for i in checkIfExist:
2024-12-03 02:34:45 +08:00
self.Peers.append(AmneziaWGPeer(i, self))
2025-02-12 20:07:37 +08:00
def addPeers(self, peers: list) -> tuple[bool, dict]:
result = {
"message": None,
"peers": []
}
2024-12-03 02:34:45 +08:00
try:
for i in peers:
newPeer = {
"id": i['id'],
"private_key": i['private_key'],
"DNS": i['DNS'],
"endpoint_allowed_ip": i['endpoint_allowed_ip'],
"name": i['name'],
"total_receive": 0,
"total_sent": 0,
"total_data": 0,
"endpoint": "N/A",
"status": "stopped",
"latest_handshake": "N/A",
"allowed_ip": i.get("allowed_ip", "N/A"),
"cumu_receive": 0,
"cumu_sent": 0,
"cumu_data": 0,
"traffic": [],
"mtu": i['mtu'],
"keepalive": i['keepalive'],
"remote_endpoint": DashboardConfig.GetConfig("Peers", "remote_endpoint")[1],
"preshared_key": i["preshared_key"],
"advanced_security": i['advanced_security']
}
sqlUpdate(
"""
INSERT INTO '%s'
VALUES (:id, :private_key, :DNS, :advanced_security, :endpoint_allowed_ip, :name, :total_receive, :total_sent,
:total_data, :endpoint, :status, :latest_handshake, :allowed_ip, :cumu_receive, :cumu_sent,
:cumu_data, :mtu, :keepalive, :remote_endpoint, :preshared_key);
""" % self.Name
, newPeer)
for p in peers:
presharedKeyExist = len(p['preshared_key']) > 0
rd = random.Random()
uid = str(uuid.UUID(int=rd.getrandbits(128), version=4))
if presharedKeyExist:
with open(uid, "w+") as f:
f.write(p['preshared_key'])
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 ''}",
2024-12-03 02:34:45 +08:00
shell=True, stderr=subprocess.STDOUT)
if presharedKeyExist:
os.remove(uid)
subprocess.check_output(
f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT)
self.getPeersList()
2025-02-12 20:07:37 +08:00
for p in peers:
p = self.searchPeer(p['id'])
if p[0]:
result['peers'].append(p[1])
return True, result
2024-12-03 02:34:45 +08:00
except Exception as e:
2025-02-12 20:07:37 +08:00
result['message'] = str(e)
return False, result
2024-12-03 02:34:45 +08:00
def getRestrictedPeers(self):
self.RestrictedPeers = []
restricted = sqlSelect("SELECT * FROM '%s_restrict_access'" % self.Name).fetchall()
for i in restricted:
self.RestrictedPeers.append(AmneziaWGPeer(i, self))
2024-11-25 22:11:51 +08:00
"""
Peer
"""
2024-06-18 03:16:42 +08:00
class Peer:
def __init__(self, tableData, configuration: WireguardConfiguration):
self.configuration = configuration
self.id = tableData["id"]
self.private_key = tableData["private_key"]
self.DNS = tableData["DNS"]
self.endpoint_allowed_ip = tableData["endpoint_allowed_ip"]
self.name = tableData["name"]
self.total_receive = tableData["total_receive"]
self.total_sent = tableData["total_sent"]
self.total_data = tableData["total_data"]
self.endpoint = tableData["endpoint"]
self.status = tableData["status"]
self.latest_handshake = tableData["latest_handshake"]
self.allowed_ip = tableData["allowed_ip"]
self.cumu_receive = tableData["cumu_receive"]
self.cumu_sent = tableData["cumu_sent"]
self.cumu_data = tableData["cumu_data"]
self.mtu = tableData["mtu"]
self.keepalive = tableData["keepalive"]
self.remote_endpoint = tableData["remote_endpoint"]
self.preshared_key = tableData["preshared_key"]
self.jobs: list[PeerJob] = []
2024-08-06 10:17:14 -04:00
self.ShareLink: list[PeerShareLink] = []
2024-06-18 03:16:42 +08:00
self.getJobs()
2024-08-06 10:17:14 -04:00
self.getShareLink()
2024-06-18 03:16:42 +08:00
def toJson(self):
self.getJobs()
2024-08-06 10:17:14 -04:00
self.getShareLink()
2024-06-18 03:16:42 +08:00
return self.__dict__
def __repr__(self):
return str(self.toJson())
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) -> ResponseObject:
2024-08-08 23:27:13 -04:00
if not self.configuration.getStatus():
self.configuration.toggleConfiguration()
2024-06-18 03:16:42 +08:00
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:
2024-09-22 16:33:22 +08:00
return ResponseObject(False, "Allowed IP already taken by another peer")
2024-11-25 22:11:51 +08:00
if not ValidateIPAddressesWithRange(endpoint_allowed_ip):
2024-09-22 16:33:22 +08:00
return ResponseObject(False, f"Endpoint Allowed IPs format is incorrect")
2024-11-25 22:11:51 +08:00
if len(dns_addresses) > 0 and not ValidateDNSAddress(dns_addresses):
2024-09-22 16:33:22 +08:00
return ResponseObject(False, f"DNS format is incorrect")
2024-06-18 03:16:42 +08:00
if mtu < 0 or mtu > 1460:
2024-09-22 16:33:22 +08:00
return ResponseObject(False, "MTU format is not correct")
2024-06-18 03:16:42 +08:00
if keepalive < 0:
2024-09-22 16:33:22 +08:00
return ResponseObject(False, "Persistent Keepalive format is not correct")
2024-06-18 03:16:42 +08:00
if len(private_key) > 0:
2024-11-25 22:11:51 +08:00
pubKey = GenerateWireguardPublicKey(private_key)
2024-06-18 03:16:42 +08:00
if not pubKey[0] or pubKey[1] != self.id:
2024-09-22 16:33:22 +08:00
return ResponseObject(False, "Private key does not match with the public key")
2024-06-18 03:16:42 +08:00
try:
2024-09-24 22:54:18 +08:00
rd = random.Random()
uid = str(uuid.UUID(int=rd.getrandbits(128), version=4))
2024-09-24 22:54:18 +08:00
pskExist = len(preshared_key) > 0
if pskExist:
with open(uid, "w+") as f:
2024-06-18 03:16:42 +08:00
f.write(preshared_key)
2024-09-24 22:54:18 +08:00
newAllowedIPs = allowed_ip.replace(" ", "")
2024-06-18 03:16:42 +08:00
updateAllowedIp = subprocess.check_output(
2024-12-07 21:59:03 +08:00
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'}",
2024-06-18 03:16:42 +08:00
shell=True, stderr=subprocess.STDOUT)
2024-09-24 22:54:18 +08:00
2025-01-24 00:01:29 +08:00
if pskExist: os.remove(uid)
2024-06-18 03:16:42 +08:00
if len(updateAllowedIp.decode().strip("\n")) != 0:
return ResponseObject(False,
2024-09-22 16:44:36 +08:00
"Update peer failed when updating Allowed IPs")
2024-12-02 16:34:26 +08:00
saveConfig = subprocess.check_output(f"{self.configuration.Protocol}-quick save {self.configuration.Name}",
2024-06-18 03:16:42 +08:00
shell=True, stderr=subprocess.STDOUT)
if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'):
return ResponseObject(False,
2024-09-22 16:44:36 +08:00
"Update peer failed when saving the configuration")
2024-08-20 00:02:00 -07:00
sqlUpdate(
2024-08-11 19:20:42 -04:00
'''UPDATE '%s' SET name = ?, private_key = ?, DNS = ?, endpoint_allowed_ip = ?, mtu = ?,
2024-06-18 03:16:42 +08:00
keepalive = ?, preshared_key = ? WHERE id = ?''' % self.configuration.Name,
(name, private_key, dns_addresses, endpoint_allowed_ip, mtu,
keepalive, preshared_key, self.id,)
)
return ResponseObject()
except subprocess.CalledProcessError as exc:
return ResponseObject(False, exc.output.decode("UTF-8").strip())
2021-08-14 17:13:16 -04:00
2024-06-18 03:16:42 +08:00
def downloadPeer(self) -> dict[str, str]:
filename = self.name
if len(filename) == 0:
filename = "UntitledPeer"
filename = "".join(filename.split(' '))
2025-01-16 18:36:06 +08:00
filename = f"{filename}"
2024-06-18 03:16:42 +08:00
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, "")
2025-01-16 18:36:06 +08:00
finalFilename = ""
for i in filename:
2025-02-12 20:07:37 +08:00
if re.match("^[a-zA-Z0-9_=+.-]$", i):
2025-01-16 18:36:06 +08:00
finalFilename += i
2024-06-18 03:16:42 +08:00
peerConfiguration = f'''[Interface]
PrivateKey = {self.private_key}
Address = {self.allowed_ip}
MTU = {str(self.mtu)}
'''
if len(self.DNS) > 0:
peerConfiguration += f"DNS = {self.DNS}\n"
2024-12-03 02:34:45 +08:00
2024-06-18 03:16:42 +08:00
peerConfiguration += f'''
[Peer]
PublicKey = {self.configuration.PublicKey}
AllowedIPs = {self.endpoint_allowed_ip}
Endpoint = {DashboardConfig.GetConfig("Peers", "remote_endpoint")[1]}:{self.configuration.ListenPort}
PersistentKeepalive = {str(self.keepalive)}
'''
if len(self.preshared_key) > 0:
peerConfiguration += f"PresharedKey = {self.preshared_key}\n"
return {
2025-01-16 18:36:06 +08:00
"fileName": finalFilename,
2024-06-18 03:16:42 +08:00
"file": peerConfiguration
}
2022-01-02 14:44:27 -05:00
2024-06-18 03:16:42 +08:00
def getJobs(self):
self.jobs = AllPeerJobs.searchJob(self.configuration.Name, self.id)
2024-08-06 10:17:14 -04:00
def getShareLink(self):
self.ShareLink = AllPeerShareLinks.getLink(self.configuration.Name, self.id)
2024-08-08 23:27:13 -04:00
def resetDataUsage(self, type):
try:
if type == "total":
2024-08-20 00:02:00 -07:00
sqlUpdate("UPDATE '%s' SET total_data = 0, cumu_data = 0, total_receive = 0, cumu_receive = 0, total_sent = 0, cumu_sent = 0 WHERE id = ?" % self.configuration.Name, (self.id, ))
self.total_data = 0
self.total_receive = 0
self.total_sent = 0
self.cumu_data = 0
self.cumu_sent = 0
self.cumu_receive = 0
2024-08-08 23:27:13 -04:00
elif type == "receive":
2024-08-20 00:02:00 -07:00
sqlUpdate("UPDATE '%s' SET total_receive = 0, cumu_receive = 0 WHERE id = ?" % self.configuration.Name, (self.id, ))
self.cumu_receive = 0
self.total_receive = 0
2024-08-08 23:27:13 -04:00
elif type == "sent":
2024-08-20 00:02:00 -07:00
sqlUpdate("UPDATE '%s' SET total_sent = 0, cumu_sent = 0 WHERE id = ?" % self.configuration.Name, (self.id, ))
self.cumu_sent = 0
self.total_sent = 0
2024-08-08 23:27:13 -04:00
else:
return False
except Exception as e:
print(e)
2024-08-08 23:27:13 -04:00
return False
2025-03-07 00:52:51 +08:00
2024-08-08 23:27:13 -04:00
return True
2024-12-03 02:34:45 +08:00
class AmneziaWGPeer(Peer):
def __init__(self, tableData, configuration: AmneziaWireguardConfiguration):
self.advanced_security = tableData["advanced_security"]
super().__init__(tableData, configuration)
def downloadPeer(self) -> dict[str, str]:
filename = self.name
if len(filename) == 0:
filename = "UntitledPeer"
filename = "".join(filename.split(' '))
filename = f"{filename}_{self.configuration.Name}"
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, "")
2025-02-12 20:07:37 +08:00
finalFilename = ""
for i in filename:
if re.match("^[a-zA-Z0-9_=+.-]$", i):
finalFilename += i
2024-12-03 02:34:45 +08:00
peerConfiguration = f'''[Interface]
PrivateKey = {self.private_key}
Address = {self.allowed_ip}
MTU = {str(self.mtu)}
Jc = {self.configuration.Jc}
Jmin = {self.configuration.Jmin}
Jmax = {self.configuration.Jmax}
S1 = {self.configuration.S1}
S2 = {self.configuration.S2}
H1 = {self.configuration.H1}
H2 = {self.configuration.H2}
H3 = {self.configuration.H3}
H4 = {self.configuration.H4}
'''
if len(self.DNS) > 0:
peerConfiguration += f"DNS = {self.DNS}\n"
peerConfiguration += f'''
[Peer]
PublicKey = {self.configuration.PublicKey}
AllowedIPs = {self.endpoint_allowed_ip}
Endpoint = {DashboardConfig.GetConfig("Peers", "remote_endpoint")[1]}:{self.configuration.ListenPort}
PersistentKeepalive = {str(self.keepalive)}
'''
if len(self.preshared_key) > 0:
peerConfiguration += f"PresharedKey = {self.preshared_key}\n"
return {
2025-02-12 20:07:37 +08:00
"fileName": finalFilename,
2024-12-03 02:34:45 +08:00
"file": peerConfiguration
}
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) -> ResponseObject:
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 ResponseObject(False, "Allowed IP already taken by another peer")
if not ValidateIPAddressesWithRange(endpoint_allowed_ip):
return ResponseObject(False, f"Endpoint Allowed IPs format is incorrect")
if len(dns_addresses) > 0 and not ValidateDNSAddress(dns_addresses):
return ResponseObject(False, f"DNS format is incorrect")
if mtu < 0 or mtu > 1460:
return ResponseObject(False, "MTU format is not correct")
if keepalive < 0:
return ResponseObject(False, "Persistent Keepalive format is not correct")
if advanced_security != "on" and advanced_security != "off":
return ResponseObject(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 ResponseObject(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'}",
2024-12-03 02:34:45 +08:00
shell=True, stderr=subprocess.STDOUT)
if pskExist: os.remove(uid)
2024-11-23 17:30:22 +08:00
2024-12-03 02:34:45 +08:00
if len(updateAllowedIp.decode().strip("\n")) != 0:
return ResponseObject(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 ResponseObject(False,
"Update peer failed when saving the configuration")
sqlUpdate(
'''UPDATE '%s' SET name = ?, private_key = ?, DNS = ?, endpoint_allowed_ip = ?, mtu = ?,
keepalive = ?, preshared_key = ?, advanced_security = ? WHERE id = ?''' % self.configuration.Name,
(name, private_key, dns_addresses, endpoint_allowed_ip, mtu,
keepalive, preshared_key, advanced_security, self.id,)
)
return ResponseObject()
except subprocess.CalledProcessError as exc:
return ResponseObject(False, exc.output.decode("UTF-8").strip())
2024-06-18 03:16:42 +08:00
2024-11-25 22:11:51 +08:00
"""
Dashboard Configuration
"""
2024-06-18 03:16:42 +08:00
class DashboardConfig:
def __init__(self):
if not os.path.exists(DASHBOARD_CONF):
open(DASHBOARD_CONF, "x")
self.__config = configparser.ConfigParser(strict=False)
self.__config.read_file(open(DASHBOARD_CONF, "r+"))
2024-11-24 00:22:33 +08:00
self.hiddenAttribute = ["totp_key", "auth_req"]
2024-06-18 03:16:42 +08:00
self.__default = {
"Account": {
"username": "admin",
"password": "admin",
"enable_totp": "false",
2024-08-05 15:39:11 -04:00
"totp_verified": "false",
2024-06-18 03:16:42 +08:00
"totp_key": pyotp.random_base32()
},
"Server": {
"wg_conf_path": "/etc/wireguard",
"awg_conf_path": "/etc/amnezia/amneziawg",
2024-08-14 01:17:47 -04:00
"app_prefix": "",
2024-06-18 03:16:42 +08:00
"app_ip": "0.0.0.0",
"app_port": "10086",
"auth_req": "true",
"version": DASHBOARD_VERSION,
"dashboard_refresh_interval": "60000",
2025-02-17 13:47:05 +08:00
"dashboard_peer_list_display": "grid",
2024-06-18 03:16:42 +08:00
"dashboard_sort": "status",
"dashboard_theme": "dark",
"dashboard_api_key": "false",
"dashboard_language": "en"
2024-06-18 03:16:42 +08:00
},
"Peers": {
"peer_global_DNS": "1.1.1.1",
"peer_endpoint_allowed_ip": "0.0.0.0/0",
"peer_display_mode": "grid",
2024-11-25 22:11:51 +08:00
"remote_endpoint": GetRemoteEndpoint(),
2024-06-18 03:16:42 +08:00
"peer_MTU": "1420",
"peer_keep_alive": "21"
},
"Other": {
"welcome_session": "true"
2024-08-15 16:55:34 -04:00
},
"Database":{
"type": "sqlite",
"host": "",
"port": "",
"username": "",
"password": ""
2024-10-29 14:57:29 +08:00
},
2024-12-07 16:54:00 +08:00
"Email":{
"server": "",
"port": "",
"encryption": "",
"username": "",
2025-01-08 18:09:05 +08:00
"email_password": "",
2025-01-16 00:44:22 +08:00
"send_from": "",
"email_template": ""
2024-12-07 16:54:00 +08:00
},
2024-10-29 14:57:29 +08:00
"WireGuardConfiguration": {
"autostart": ""
2024-06-18 03:16:42 +08:00
}
}
2022-01-02 14:44:27 -05:00
2024-06-18 03:16:42 +08:00
for section, keys in self.__default.items():
for key, value in keys.items():
exist, currentData = self.GetConfig(section, key)
if not exist:
self.SetConfig(section, key, value, True)
self.engine = db.create_engine(self.getConnectionString('wgdashboard'))
self.dbMetadata = db.MetaData()
self.__createAPIKeyTable()
self.DashboardAPIKeys = self.__getAPIKeys()
2024-08-11 01:48:13 -04:00
self.APIAccessed = False
self.SetConfig("Server", "version", DASHBOARD_VERSION)
def getConnectionString(self, database) -> str or None:
cn = None
if self.GetConfig("Database", "type")[1] == "sqlite":
cn = f'sqlite:///{os.path.join(CONFIGURATION_PATH, "db", f"{database}.db")}'
elif self.GetConfig("Database", "type")[1] == "postgresql":
cn = f'postgresql+psycopg2://{self.GetConfig("Database", "username")[1]}:{self.GetConfig("Database", "password")[1]}@{self.GetConfig("Database", "host")[1]}/{database}'
if not database_exists(cn):
create_database(cn)
return cn
def __createAPIKeyTable(self):
self.apiKeyTable = db.Table('DashboardAPIKeys', self.dbMetadata,
db.Column("Key", db.String, nullable=False, primary_key=True),
db.Column("CreatedAt",
(db.DATETIME if self.GetConfig('Database', 'type')[1] == 'sqlite' else db.TIMESTAMP),
server_default=db.func.now()
),
db.Column("ExpiredAt",
(db.DATETIME if self.GetConfig('Database', 'type')[1] == 'sqlite' else db.TIMESTAMP)
)
)
self.dbMetadata.create_all(self.engine)
# existingTable = sqlSelect("SELECT name FROM sqlite_master WHERE type='table' AND name = 'DashboardAPIKeys'").fetchall()
# if len(existingTable) == 0:
# sqlUpdate("CREATE TABLE DashboardAPIKeys (Key VARCHAR NOT NULL PRIMARY KEY, CreatedAt DATETIME NOT NULL DEFAULT (datetime('now', 'localtime')), ExpiredAt VARCHAR)")
def __getAPIKeys(self) -> list[DashboardAPIKey]:
# keys = sqlSelect("SELECT * FROM DashboardAPIKeys WHERE ExpiredAt IS NULL OR ExpiredAt > datetime('now', 'localtime') ORDER BY CreatedAt DESC").fetchall()
try:
with self.engine.connect() as conn:
keys = conn.execute(self.apiKeyTable.select().where(
db.or_(self.apiKeyTable.columns.ExpiredAt == None, self.apiKeyTable.columns.ExpiredAt > datetime.now())
)).fetchall()
fKeys = []
for k in keys:
fKeys.append(DashboardAPIKey(k[0], k[1].strftime("%Y-%m-%d %H:%M:%S"), (k[2].strftime("%Y-%m-%d %H:%M:%S") if k[2] else None)))
return fKeys
except Exception as e:
print("")
return []
def createAPIKeys(self, ExpiredAt = None):
newKey = secrets.token_urlsafe(32)
# sqlUpdate('INSERT INTO DashboardAPIKeys (Key, ExpiredAt) VALUES (?, ?)', (newKey, ExpiredAt,))
with self.engine.begin() as conn:
conn.execute(
self.apiKeyTable.insert().values({
"Key": newKey,
"ExpiredAt": ExpiredAt
})
)
2024-09-05 14:51:00 +08:00
self.DashboardAPIKeys = self.__getAPIKeys()
def deleteAPIKey(self, key):
# sqlUpdate("UPDATE DashboardAPIKeys SET ExpiredAt = datetime('now', 'localtime') WHERE Key = ?", (key, ))
with self.engine.begin() as conn:
conn.execute(
self.apiKeyTable.update().values({
"ExpiredAt": datetime.now(),
}).where(self.apiKeyTable.columns.Key == key)
)
self.DashboardAPIKeys = self.__getAPIKeys()
2025-01-08 18:09:05 +08:00
def __configValidation(self, section : str, key: str, value: Any) -> [bool, str]:
2025-02-16 23:07:16 +08:00
if (type(value) is str and len(value) == 0
and section not in ['Email', 'WireGuardConfiguration'] and
(section == 'Peer' and key == 'peer_global_dns')):
2024-06-18 03:16:42 +08:00
return False, "Field cannot be empty!"
2025-02-16 23:07:16 +08:00
if section == "Peers" and key == "peer_global_dns" and len(value) > 0:
2024-11-25 22:11:51 +08:00
return ValidateDNSAddress(value)
2025-01-08 18:09:05 +08:00
if section == "Peers" and key == "peer_endpoint_allowed_ip":
2024-06-18 03:16:42 +08:00
value = value.split(",")
for i in value:
2025-02-14 23:59:21 +08:00
i = i.strip()
2024-06-18 03:16:42 +08:00
try:
ipaddress.ip_network(i, strict=False)
except Exception as e:
return False, str(e)
2025-01-08 18:09:05 +08:00
if section == "Server" and key == "wg_conf_path":
2024-06-18 03:16:42 +08:00
if not os.path.exists(value):
return False, f"{value} is not a valid path"
2025-01-08 18:09:05 +08:00
if section == "Account" and key == "password":
2024-06-18 03:16:42 +08:00
if self.GetConfig("Account", "password")[0]:
if not self.__checkPassword(
value["currentPassword"], self.GetConfig("Account", "password")[1].encode("utf-8")):
return False, "Current password does not match."
if value["newPassword"] != value["repeatNewPassword"]:
return False, "New passwords does not match"
return True, ""
def generatePassword(self, plainTextPassword: str):
2024-08-02 17:27:28 -04:00
return bcrypt.hashpw(plainTextPassword.encode("utf-8"), bcrypt.gensalt())
2024-06-18 03:16:42 +08:00
def __checkPassword(self, plainTextPassword: str, hashedPassword: bytes):
return bcrypt.checkpw(plainTextPassword.encode("utf-8"), hashedPassword)
def SetConfig(self, section: str, key: str, value: any, init: bool = False) -> [bool, str]:
if key in self.hiddenAttribute and not init:
return False, None
if not init:
2025-01-08 18:09:05 +08:00
valid, msg = self.__configValidation(section, key, value)
2024-06-18 03:16:42 +08:00
if not valid:
return False, msg
if section == "Account" and key == "password":
if not init:
value = self.generatePassword(value["newPassword"]).decode("utf-8")
else:
2024-06-18 03:16:42 +08:00
value = self.generatePassword(value).decode("utf-8")
2025-04-19 02:54:47 +08:00
if section == "Email" and key == "email_template":
value = value.encode('unicode_escape').decode('utf-8')
2024-09-06 16:31:54 +08:00
if section == "Server" and key == "wg_conf_path":
if not os.path.exists(value):
return False, "Path does not exist"
2024-06-18 03:16:42 +08:00
if section not in self.__config:
2025-04-19 02:54:47 +08:00
if init:
self.__config[section] = {}
else:
return False, "Section does not exist"
2025-04-19 02:54:47 +08:00
if ((key not in self.__config[section].keys() and init) or
(key in self.__config[section].keys())):
2024-06-18 03:16:42 +08:00
if type(value) is bool:
if value:
self.__config[section][key] = "true"
else:
self.__config[section][key] = "false"
2024-10-29 14:57:29 +08:00
elif type(value) in [int, float]:
self.__config[section][key] = str(value)
2024-10-29 14:57:29 +08:00
elif type(value) is list:
2024-11-02 14:26:47 +06:00
self.__config[section][key] = "||".join(value).strip("||")
2024-06-18 03:16:42 +08:00
else:
self.__config[section][key] = value
return self.SaveConfig(), ""
2025-04-19 02:54:47 +08:00
else:
return False, f"{key} does not exist under {section}"
2024-06-18 03:16:42 +08:00
return True, ""
2021-12-26 02:26:39 +03:00
2024-06-18 03:16:42 +08:00
def SaveConfig(self) -> bool:
try:
with open(DASHBOARD_CONF, "w+", encoding='utf-8') as configFile:
self.__config.write(configFile)
return True
except Exception as e:
return False
2024-09-06 16:31:54 +08:00
def GetConfig(self, section, key) -> [bool, any]:
2024-06-18 03:16:42 +08:00
if section not in self.__config:
return False, None
if key not in self.__config[section]:
return False, None
2025-04-19 02:54:47 +08:00
if section == "Email" and key == "email_template":
return True, self.__config[section][key].encode('utf-8').decode('unicode_escape')
if section == "WireGuardConfiguration" and key == "autostart":
return True, list(filter(lambda x: len(x) > 0, self.__config[section][key].split("||")))
2024-06-18 03:16:42 +08:00
if self.__config[section][key] in ["1", "yes", "true", "on"]:
return True, True
if self.__config[section][key] in ["0", "no", "false", "off"]:
return True, False
2024-10-29 14:57:29 +08:00
2024-06-18 03:16:42 +08:00
return True, self.__config[section][key]
def toJson(self) -> dict[str, dict[Any, Any]]:
the_dict = {}
for section in self.__config.sections():
the_dict[section] = {}
for key, val in self.__config.items(section):
if key not in self.hiddenAttribute:
2024-10-29 14:57:29 +08:00
the_dict[section][key] = self.GetConfig(section, key)[1]
2024-06-18 03:16:42 +08:00
return the_dict
2025-03-16 00:42:40 +08:00
2021-12-26 02:26:39 +03:00
2024-11-25 22:11:51 +08:00
"""
Database Connection Functions
"""
2024-12-29 15:57:57 +08:00
sqldb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard.db'), check_same_thread=False)
sqldb.row_factory = sqlite3.Row
2024-08-14 01:17:47 -04:00
2024-08-20 00:02:00 -07:00
def sqlSelect(statement: str, paramters: tuple = ()) -> sqlite3.Cursor:
2024-12-29 15:57:57 +08:00
result = []
2024-12-31 10:31:24 +08:00
try:
cursor = sqldb.cursor()
result = cursor.execute(statement, paramters)
except Exception as error:
print("[WGDashboard] SQLite Error:" + str(error) + " | Statement: " + statement)
2024-12-29 15:57:57 +08:00
return result
2024-11-08 00:29:01 +08:00
2024-08-20 00:02:00 -07:00
def sqlUpdate(statement: str, paramters: tuple = ()) -> sqlite3.Cursor:
2024-12-29 13:12:40 +08:00
sqldb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard.db'))
sqldb.row_factory = sqlite3.Row
cursor = sqldb.cursor()
2024-08-20 00:02:00 -07:00
with sqldb:
cursor = sqldb.cursor()
2024-09-05 14:51:00 +08:00
try:
2024-10-25 00:19:27 +08:00
statement = statement.rstrip(';')
s = f'BEGIN TRANSACTION;{statement};END TRANSACTION;'
2024-09-05 16:17:56 +08:00
cursor.execute(statement, paramters)
2024-12-29 13:12:40 +08:00
# sqldb.commit()
except Exception as error:
print("[WGDashboard] SQLite Error:" + str(error) + " | Statement: " + statement)
2024-12-29 15:57:57 +08:00
sqldb.close()
2024-08-20 00:02:00 -07:00
2024-08-14 01:17:47 -04:00
DashboardConfig = DashboardConfig()
2025-01-08 18:09:05 +08:00
EmailSender = EmailSender(DashboardConfig)
2024-08-14 01:17:47 -04:00
_, APP_PREFIX = DashboardConfig.GetConfig("Server", "app_prefix")
cors = CORS(app, resources={rf"{APP_PREFIX}/api/*": {
"origins": "*",
"methods": "DELETE, POST, GET, OPTIONS",
"allow_headers": ["Content-Type", "wg-dashboard-apikey"]
}})
2024-06-18 03:16:42 +08:00
'''
API Routes
'''
2021-05-04 01:32:34 -04:00
2024-06-18 03:16:42 +08:00
@app.before_request
def auth_req():
2024-08-10 19:03:21 -04:00
if request.method.lower() == 'options':
2024-11-24 00:22:33 +08:00
return ResponseObject(True)
2024-08-11 01:48:13 -04:00
DashboardConfig.APIAccessed = False
2024-08-03 17:03:39 -04:00
if "api" in request.path:
if str(request.method) == "GET":
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=str(request.args))
elif str(request.method) == "POST":
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Request Args: {str(request.args)} Body:{str(request.get_json())}")
2024-06-18 03:16:42 +08:00
authenticationRequired = DashboardConfig.GetConfig("Server", "auth_req")[1]
2024-08-02 17:27:28 -04:00
d = request.headers
2024-06-18 03:16:42 +08:00
if authenticationRequired:
2024-08-02 17:27:28 -04:00
apiKey = d.get('wg-dashboard-apikey')
2024-07-31 02:27:44 -04:00
apiKeyEnabled = DashboardConfig.GetConfig("Server", "dashboard_api_key")[1]
if apiKey is not None and len(apiKey) > 0 and apiKeyEnabled:
apiKeyExist = len(list(filter(lambda x : x.Key == apiKey, DashboardConfig.DashboardAPIKeys))) == 1
2024-08-03 17:03:39 -04:00
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"API Key Access: {('true' if apiKeyExist else 'false')} - Key: {apiKey}")
2024-07-31 02:27:44 -04:00
if not apiKeyExist:
2024-08-11 01:48:13 -04:00
DashboardConfig.APIAccessed = False
2024-07-31 02:27:44 -04:00
response = Flask.make_response(app, {
"status": False,
"message": "API Key does not exist",
"data": None
})
response.content_type = "application/json"
response.status_code = 401
return response
2024-08-11 01:48:13 -04:00
DashboardConfig.APIAccessed = True
2024-07-31 02:27:44 -04:00
else:
2024-08-11 01:48:13 -04:00
DashboardConfig.APIAccessed = False
2024-12-26 00:06:37 +08:00
whiteList = [
'/static/', 'validateAuthentication', 'authenticate', 'getDashboardConfiguration',
'getDashboardTheme', 'getDashboardVersion', 'sharePeer/get', 'isTotpEnabled', 'locale',
'/fileDownload'
]
if ("username" not in session
2024-08-14 01:17:47 -04:00
and (f"{(APP_PREFIX if len(APP_PREFIX) > 0 else '')}/" != request.path
2024-12-26 00:06:37 +08:00
and f"{(APP_PREFIX if len(APP_PREFIX) > 0 else '')}" != request.path)
and len(list(filter(lambda x : x not in request.path, whiteList))) == len(whiteList)
2024-07-31 02:27:44 -04:00
):
response = Flask.make_response(app, {
"status": False,
"message": "Unauthorized access.",
"data": None
})
response.content_type = "application/json"
response.status_code = 401
return response
2021-05-04 21:26:40 -04:00
2024-08-14 01:17:47 -04:00
@app.route(f'{APP_PREFIX}/api/handshake', methods=["GET", "OPTIONS"])
2024-11-24 00:22:33 +08:00
def API_Handshake():
2024-08-10 19:03:21 -04:00
return ResponseObject(True)
2024-09-24 22:54:18 +08:00
@app.get(f'{APP_PREFIX}/api/validateAuthentication')
2024-06-18 03:16:42 +08:00
def API_ValidateAuthentication():
2024-11-24 00:22:33 +08:00
token = request.cookies.get("authToken")
if DashboardConfig.GetConfig("Server", "auth_req")[1]:
if token is None or token == "" or "username" not in session or session["username"] != token:
return ResponseObject(False, "Invalid authentication.")
2024-06-18 03:16:42 +08:00
return ResponseObject(True)
2021-08-14 17:13:16 -04:00
2024-11-24 00:22:33 +08:00
@app.get(f'{APP_PREFIX}/api/requireAuthentication')
def API_RequireAuthentication():
return ResponseObject(data=DashboardConfig.GetConfig("Server", "auth_req")[1])
2024-09-24 22:54:18 +08:00
@app.post(f'{APP_PREFIX}/api/authenticate')
2024-06-18 03:16:42 +08:00
def API_AuthenticateLogin():
data = request.get_json()
2024-11-25 02:48:55 +08:00
if not DashboardConfig.GetConfig("Server", "auth_req")[1]:
return ResponseObject(True, DashboardConfig.GetConfig("Other", "welcome_session")[1])
2024-08-11 01:48:13 -04:00
if DashboardConfig.APIAccessed:
authToken = hashlib.sha256(f"{request.headers.get('wg-dashboard-apikey')}{datetime.now()}".encode()).hexdigest()
session['username'] = authToken
resp = ResponseObject(True, DashboardConfig.GetConfig("Other", "welcome_session")[1])
2024-08-15 16:55:34 -04:00
resp.set_cookie("authToken", authToken)
2024-08-11 01:48:13 -04:00
session.permanent = True
return resp
2024-06-18 03:16:42 +08:00
valid = bcrypt.checkpw(data['password'].encode("utf-8"),
DashboardConfig.GetConfig("Account", "password")[1].encode("utf-8"))
totpEnabled = DashboardConfig.GetConfig("Account", "enable_totp")[1]
totpValid = False
if totpEnabled:
totpValid = pyotp.TOTP(DashboardConfig.GetConfig("Account", "totp_key")[1]).now() == data['totp']
if (valid
and data['username'] == DashboardConfig.GetConfig("Account", "username")[1]
and ((totpEnabled and totpValid) or not totpEnabled)
):
authToken = hashlib.sha256(f"{data['username']}{datetime.now()}".encode()).hexdigest()
session['username'] = authToken
resp = ResponseObject(True, DashboardConfig.GetConfig("Other", "welcome_session")[1])
resp.set_cookie("authToken", authToken)
session.permanent = True
2024-08-03 17:03:39 -04:00
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Login success: {data['username']}")
2024-06-18 03:16:42 +08:00
return resp
2024-08-03 17:03:39 -04:00
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Login failed: {data['username']}")
2024-06-18 03:16:42 +08:00
if totpEnabled:
return ResponseObject(False, "Sorry, your username, password or OTP is incorrect.")
else:
2024-06-18 03:16:42 +08:00
return ResponseObject(False, "Sorry, your username or password is incorrect.")
2021-05-13 18:00:40 -04:00
2024-08-15 16:55:34 -04:00
@app.get(f'{APP_PREFIX}/api/signout')
2024-06-18 03:16:42 +08:00
def API_SignOut():
resp = ResponseObject(True, "")
resp.delete_cookie("authToken")
session.clear()
2024-06-18 03:16:42 +08:00
return resp
2024-08-14 01:17:47 -04:00
@app.route(f'{APP_PREFIX}/api/getWireguardConfigurations', methods=["GET"])
2024-06-18 03:16:42 +08:00
def API_getWireguardConfigurations():
2024-11-25 22:11:51 +08:00
InitWireguardConfigurationsList()
2024-06-18 03:16:42 +08:00
return ResponseObject(data=[wc for wc in WireguardConfigurations.values()])
2022-01-02 14:44:27 -05:00
2024-08-14 01:17:47 -04:00
@app.route(f'{APP_PREFIX}/api/addWireguardConfiguration', methods=["POST"])
2024-06-18 03:16:42 +08:00
def API_addWireguardConfiguration():
data = request.get_json()
2024-06-18 03:16:42 +08:00
requiredKeys = [
2024-12-04 17:50:16 +08:00
"ConfigurationName", "Address", "ListenPort", "PrivateKey", "Protocol"
2024-06-18 03:16:42 +08:00
]
for i in requiredKeys:
if i not in data.keys():
2024-06-18 03:16:42 +08:00
return ResponseObject(False, "Please provide all required parameters.")
2024-12-04 17:50:16 +08:00
if data.get("Protocol") not in ProtocolsEnabled():
return ResponseObject(False, "Please provide a valid protocol: wg / awg.")
2024-06-18 03:16:42 +08:00
# Check duplicate names, ports, address
for i in WireguardConfigurations.values():
if i.Name == data['ConfigurationName']:
return ResponseObject(False,
f"Already have a configuration with the name \"{data['ConfigurationName']}\"",
"ConfigurationName")
if str(i.ListenPort) == str(data["ListenPort"]):
return ResponseObject(False,
f"Already have a configuration with the port \"{data['ListenPort']}\"",
"ListenPort")
if i.Address == data["Address"]:
return ResponseObject(False,
f"Already have a configuration with the address \"{data['Address']}\"",
"Address")
if "Backup" in data.keys():
2024-12-04 17:50:16 +08:00
path = {
"wg": DashboardConfig.GetConfig("Server", "wg_conf_path")[1],
"awg": DashboardConfig.GetConfig("Server", "awg_conf_path")[1]
}
if (os.path.exists(os.path.join(path['wg'], 'WGDashboard_Backup', data["Backup"])) and
os.path.exists(os.path.join(path['wg'], 'WGDashboard_Backup', data["Backup"].replace('.conf', '.sql')))):
protocol = "wg"
elif (os.path.exists(os.path.join(path['awg'], 'WGDashboard_Backup', data["Backup"])) and
os.path.exists(os.path.join(path['awg'], 'WGDashboard_Backup', data["Backup"].replace('.conf', '.sql')))):
protocol = "awg"
else:
return ResponseObject(False, "Backup does not exist")
shutil.copy(
2024-12-04 17:50:16 +08:00
os.path.join(path[protocol], 'WGDashboard_Backup', data["Backup"]),
os.path.join(path[protocol], f'{data["ConfigurationName"]}.conf')
)
2024-12-04 17:50:16 +08:00
WireguardConfigurations[data['ConfigurationName']] = WireguardConfiguration(data=data, name=data['ConfigurationName']) if protocol == 'wg' else AmneziaWireguardConfiguration(data=data, name=data['ConfigurationName'])
else:
2024-12-04 17:50:16 +08:00
WireguardConfigurations[data['ConfigurationName']] = WireguardConfiguration(data=data) if data.get('Protocol') == 'wg' else AmneziaWireguardConfiguration(data=data)
2024-06-18 03:16:42 +08:00
return ResponseObject()
2025-04-23 19:24:50 +08:00
@app.get(f'{APP_PREFIX}/api/toggleWireguardConfiguration')
2024-06-18 03:16:42 +08:00
def API_toggleWireguardConfiguration():
configurationName = request.args.get('configurationName')
if configurationName is None or len(
configurationName) == 0 or configurationName not in WireguardConfigurations.keys():
2025-04-19 02:54:47 +08:00
return ResponseObject(False, "Please provide a valid configuration name", status_code=404)
2024-06-18 03:16:42 +08:00
toggleStatus, msg = WireguardConfigurations[configurationName].toggleConfiguration()
return ResponseObject(toggleStatus, msg, WireguardConfigurations[configurationName].Status)
2024-10-04 16:58:47 +08:00
@app.post(f'{APP_PREFIX}/api/updateWireguardConfiguration')
def API_updateWireguardConfiguration():
data = request.get_json()
requiredKeys = ["Name"]
for i in requiredKeys:
if i not in data.keys():
return ResponseObject(False, "Please provide these following field: " + ", ".join(requiredKeys))
name = data.get("Name")
if name not in WireguardConfigurations.keys():
2025-04-19 02:54:47 +08:00
return ResponseObject(False, "Configuration does not exist", status_code=404)
2024-10-04 16:58:47 +08:00
status, msg = WireguardConfigurations[name].updateConfigurationSettings(data)
return ResponseObject(status, message=msg, data=WireguardConfigurations[name])
2024-06-18 03:16:42 +08:00
2024-12-06 20:27:04 +08:00
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurationRawFile')
def API_GetWireguardConfigurationRawFile():
configurationName = request.args.get('configurationName')
if configurationName is None or len(
configurationName) == 0 or configurationName not in WireguardConfigurations.keys():
2025-04-19 02:54:47 +08:00
return ResponseObject(False, "Please provide a valid configuration name", status_code=404)
2024-12-06 20:27:04 +08:00
return ResponseObject(data={
"path": WireguardConfigurations[configurationName].configPath,
"content": WireguardConfigurations[configurationName].getRawConfigurationFile()
})
@app.post(f'{APP_PREFIX}/api/updateWireguardConfigurationRawFile')
def API_UpdateWireguardConfigurationRawFile():
data = request.get_json()
configurationName = data.get('configurationName')
rawConfiguration = data.get('rawConfiguration')
if configurationName is None or len(
configurationName) == 0 or configurationName not in WireguardConfigurations.keys():
return ResponseObject(False, "Please provide a valid configuration name")
if rawConfiguration is None or len(rawConfiguration) == 0:
return ResponseObject(False, "Please provide content")
status, err = WireguardConfigurations[configurationName].updateRawConfigurationFile(rawConfiguration)
return ResponseObject(status=status, message=err)
2024-10-25 00:19:27 +08:00
@app.post(f'{APP_PREFIX}/api/deleteWireguardConfiguration')
def API_deleteWireguardConfiguration():
data = request.get_json()
2025-03-28 00:13:38 +08:00
if "ConfigurationName" not in data.keys() or data.get("ConfigurationName") is None or data.get("ConfigurationName") not in WireguardConfigurations.keys():
2025-04-19 02:54:47 +08:00
return ResponseObject(False, "Please provide the configuration name you want to delete", status_code=404)
2025-03-28 00:13:38 +08:00
status = WireguardConfigurations[data.get("ConfigurationName")].deleteConfiguration()
2024-10-25 00:19:27 +08:00
if status:
2025-03-28 00:13:38 +08:00
WireguardConfigurations.pop(data.get("ConfigurationName"))
2024-10-25 00:19:27 +08:00
return ResponseObject(status)
2024-11-06 18:36:55 +08:00
@app.post(f'{APP_PREFIX}/api/renameWireguardConfiguration')
def API_renameWireguardConfiguration():
data = request.get_json()
2025-03-28 00:13:38 +08:00
keys = ["ConfigurationName", "NewConfigurationName"]
2024-11-06 18:36:55 +08:00
for k in keys:
if (k not in data.keys() or data.get(k) is None or len(data.get(k)) == 0 or
2025-03-28 00:13:38 +08:00
(k == "ConfigurationName" and data.get(k) not in WireguardConfigurations.keys())):
2025-04-19 02:54:47 +08:00
return ResponseObject(False, "Please provide the configuration name you want to rename", status_code=404)
2024-11-06 18:36:55 +08:00
2025-03-28 00:13:38 +08:00
status, message = WireguardConfigurations[data.get("ConfigurationName")].renameConfiguration(data.get("NewConfigurationName"))
2024-11-06 18:36:55 +08:00
if status:
2025-03-28 00:13:38 +08:00
WireguardConfigurations.pop(data.get("ConfigurationName"))
2024-11-06 18:36:55 +08:00
WireguardConfigurations[data.get("NewConfigurationName")] = WireguardConfiguration(data.get("NewConfigurationName"))
return ResponseObject(status, message)
2024-12-30 20:30:09 +08:00
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurationRealtimeTraffic')
def API_getWireguardConfigurationRealtimeTraffic():
configurationName = request.args.get('configurationName')
if configurationName is None or configurationName not in WireguardConfigurations.keys():
2025-04-19 02:54:47 +08:00
return ResponseObject(False, "Configuration does not exist", status_code=404)
2024-12-30 20:30:09 +08:00
return ResponseObject(data=WireguardConfigurations[configurationName].getRealtimeTrafficUsage())
2024-10-15 00:30:20 +08:00
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurationBackup')
def API_getWireguardConfigurationBackup():
configurationName = request.args.get('configurationName')
if configurationName is None or configurationName not in WireguardConfigurations.keys():
2025-04-19 02:54:47 +08:00
return ResponseObject(False, "Configuration does not exist", status_code=404)
2024-10-15 00:30:20 +08:00
return ResponseObject(data=WireguardConfigurations[configurationName].getBackups())
2024-10-25 00:19:27 +08:00
@app.get(f'{APP_PREFIX}/api/getAllWireguardConfigurationBackup')
def API_getAllWireguardConfigurationBackup():
data = {
"ExistingConfigurations": {},
"NonExistingConfigurations": {}
}
existingConfiguration = WireguardConfigurations.keys()
for i in existingConfiguration:
b = WireguardConfigurations[i].getBackups(True)
if len(b) > 0:
data['ExistingConfigurations'][i] = WireguardConfigurations[i].getBackups(True)
2024-12-04 17:50:16 +08:00
for protocol in ProtocolsEnabled():
directory = os.path.join(DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")[1], 'WGDashboard_Backup')
files = [(file, os.path.getctime(os.path.join(directory, file)))
for file in os.listdir(directory) if os.path.isfile(os.path.join(directory, file))]
files.sort(key=lambda x: x[1], reverse=True)
for f, ct in files:
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():
data['NonExistingConfigurations'][name] = []
date = s.group(2)
d = {
"protocol": protocol,
"filename": f,
"backupDate": date,
"content": open(os.path.join(DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")[1], 'WGDashboard_Backup', f), 'r').read()
}
if f.replace(".conf", ".sql") in list(os.listdir(directory)):
d['database'] = True
d['databaseContent'] = open(os.path.join(DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")[1], 'WGDashboard_Backup', f.replace(".conf", ".sql")), 'r').read()
data['NonExistingConfigurations'][name].append(d)
2024-10-25 00:19:27 +08:00
return ResponseObject(data=data)
@app.get(f'{APP_PREFIX}/api/createWireguardConfigurationBackup')
def API_createWireguardConfigurationBackup():
configurationName = request.args.get('configurationName')
if configurationName is None or configurationName not in WireguardConfigurations.keys():
2025-04-19 02:54:47 +08:00
return ResponseObject(False, "Configuration does not exist", status_code=404)
2024-12-06 20:27:04 +08:00
return ResponseObject(status=WireguardConfigurations[configurationName].backupConfigurationFile()[0],
data=WireguardConfigurations[configurationName].getBackups())
@app.post(f'{APP_PREFIX}/api/deleteWireguardConfigurationBackup')
def API_deleteWireguardConfigurationBackup():
data = request.get_json()
2025-04-07 18:45:12 +08:00
if ("ConfigurationName" not in data.keys() or
"BackupFileName" not in data.keys() or
len(data['ConfigurationName']) == 0 or
len(data['BackupFileName']) == 0):
return ResponseObject(False,
2025-04-19 02:54:47 +08:00
"Please provide configurationName and backupFileName in body", status_code=400)
2025-04-07 18:45:12 +08:00
configurationName = data['ConfigurationName']
backupFileName = data['BackupFileName']
if configurationName not in WireguardConfigurations.keys():
2025-04-19 02:54:47 +08:00
return ResponseObject(False, "Configuration does not exist", status_code=404)
2025-04-19 02:54:47 +08:00
status = WireguardConfigurations[configurationName].deleteBackup(backupFileName)
return ResponseObject(status=status, message=(None if status else 'Backup file does not exist'),
status_code = (200 if status else 404))
2024-12-26 00:06:37 +08:00
@app.get(f'{APP_PREFIX}/api/downloadWireguardConfigurationBackup')
def API_downloadWireguardConfigurationBackup():
configurationName = request.args.get('configurationName')
backupFileName = request.args.get('backupFileName')
if configurationName is None or configurationName not in WireguardConfigurations.keys():
2025-04-19 02:54:47 +08:00
return ResponseObject(False, "Configuration does not exist", status_code=404)
2024-12-26 00:06:37 +08:00
status, zip = WireguardConfigurations[configurationName].downloadBackup(backupFileName)
2025-04-19 02:54:47 +08:00
return ResponseObject(status, data=zip, status_code=(200 if status else 404))
2024-12-26 00:06:37 +08:00
@app.post(f'{APP_PREFIX}/api/restoreWireguardConfigurationBackup')
def API_restoreWireguardConfigurationBackup():
data = request.get_json()
2025-04-07 18:45:12 +08:00
if ("ConfigurationName" not in data.keys() or
"BackupFileName" not in data.keys() or
len(data['ConfigurationName']) == 0 or
len(data['BackupFileName']) == 0):
return ResponseObject(False,
2025-04-19 02:54:47 +08:00
"Please provide ConfigurationName and BackupFileName in body", status_code=400)
2025-04-07 18:45:12 +08:00
configurationName = data['ConfigurationName']
backupFileName = data['BackupFileName']
if configurationName not in WireguardConfigurations.keys():
2025-04-19 02:54:47 +08:00
return ResponseObject(False, "Configuration does not exist", status_code=404)
status = WireguardConfigurations[configurationName].restoreBackup(backupFileName)
return ResponseObject(status=status, message=(None if status else 'Restore backup failed'))
2024-09-24 22:54:18 +08:00
@app.get(f'{APP_PREFIX}/api/getDashboardConfiguration')
2024-06-18 03:16:42 +08:00
def API_getDashboardConfiguration():
return ResponseObject(data=DashboardConfig.toJson())
2024-09-24 22:54:18 +08:00
@app.post(f'{APP_PREFIX}/api/updateDashboardConfigurationItem')
2024-06-18 03:16:42 +08:00
def API_updateDashboardConfigurationItem():
2020-12-26 23:42:41 -05:00
data = request.get_json()
2024-06-18 03:16:42 +08:00
if "section" not in data.keys() or "key" not in data.keys() or "value" not in data.keys():
return ResponseObject(False, "Invalid request.")
valid, msg = DashboardConfig.SetConfig(
data["section"], data["key"], data['value'])
if not valid:
2025-04-19 02:54:47 +08:00
return ResponseObject(False, msg, status_code=404)
2024-09-06 16:31:54 +08:00
if data['section'] == "Server":
if data['key'] == 'wg_conf_path':
WireguardConfigurations.clear()
2024-09-06 16:31:54 +08:00
WireguardConfigurations.clear()
2025-01-24 00:01:29 +08:00
InitWireguardConfigurationsList()
2024-11-02 14:26:47 +06:00
return ResponseObject(True, data=DashboardConfig.GetConfig(data["section"], data["key"])[1])
2024-09-24 22:54:18 +08:00
@app.get(f'{APP_PREFIX}/api/getDashboardAPIKeys')
def API_getDashboardAPIKeys():
if DashboardConfig.GetConfig('Server', 'dashboard_api_key'):
return ResponseObject(data=DashboardConfig.DashboardAPIKeys)
2024-09-22 21:50:30 +08:00
return ResponseObject(False, "WGDashboard API Keys function is disabled")
2024-09-24 22:54:18 +08:00
@app.post(f'{APP_PREFIX}/api/newDashboardAPIKey')
def API_newDashboardAPIKey():
data = request.get_json()
if DashboardConfig.GetConfig('Server', 'dashboard_api_key'):
try:
2025-04-19 02:54:47 +08:00
if data['NeverExpire']:
expiredAt = None
else:
expiredAt = datetime.strptime(data['ExpiredAt'], '%Y-%m-%d %H:%M:%S')
DashboardConfig.createAPIKeys(expiredAt)
return ResponseObject(True, data=DashboardConfig.DashboardAPIKeys)
except Exception as e:
return ResponseObject(False, str(e))
return ResponseObject(False, "Dashboard API Keys function is disbaled")
2024-09-24 22:54:18 +08:00
@app.post(f'{APP_PREFIX}/api/deleteDashboardAPIKey')
def API_deleteDashboardAPIKey():
data = request.get_json()
if DashboardConfig.GetConfig('Server', 'dashboard_api_key'):
if len(data['Key']) > 0 and len(list(filter(lambda x : x.Key == data['Key'], DashboardConfig.DashboardAPIKeys))) > 0:
DashboardConfig.deleteAPIKey(data['Key'])
return ResponseObject(True, data=DashboardConfig.DashboardAPIKeys)
2025-04-19 02:54:47 +08:00
else:
return ResponseObject(False, "API Key does not exist", status_code=404)
return ResponseObject(False, "Dashboard API Keys function is disbaled")
2024-09-24 22:54:18 +08:00
@app.post(f'{APP_PREFIX}/api/updatePeerSettings/<configName>')
2024-06-18 03:16:42 +08:00
def API_updatePeerSettings(configName):
2021-04-02 20:48:00 -04:00
data = request.get_json()
id = data['id']
2024-06-18 03:16:42 +08:00
if len(id) > 0 and configName in WireguardConfigurations.keys():
name = data['name']
private_key = data['private_key']
dns_addresses = data['DNS']
allowed_ip = data['allowed_ip']
endpoint_allowed_ip = data['endpoint_allowed_ip']
preshared_key = data['preshared_key']
mtu = data['mtu']
keepalive = data['keepalive']
wireguardConfig = WireguardConfigurations[configName]
foundPeer, peer = wireguardConfig.searchPeer(id)
if foundPeer:
2024-12-03 02:34:45 +08:00
if wireguardConfig.Protocol == 'wg':
return peer.updatePeer(name, private_key, preshared_key, dns_addresses,
allowed_ip, endpoint_allowed_ip, mtu, keepalive)
2024-06-18 03:16:42 +08:00
return peer.updatePeer(name, private_key, preshared_key, dns_addresses,
allowed_ip, endpoint_allowed_ip, mtu, keepalive, "off")
2024-12-03 02:34:45 +08:00
2024-06-18 03:16:42 +08:00
return ResponseObject(False, "Peer does not exist")
2024-09-24 22:54:18 +08:00
@app.post(f'{APP_PREFIX}/api/resetPeerData/<configName>')
2024-08-08 23:27:13 -04:00
def API_resetPeerData(configName):
data = request.get_json()
id = data['id']
type = data['type']
if len(id) == 0 or configName not in WireguardConfigurations.keys():
return ResponseObject(False, "Configuration/Peer does not exist")
wgc = WireguardConfigurations.get(configName)
foundPeer, peer = wgc.searchPeer(id)
if not foundPeer:
return ResponseObject(False, "Configuration/Peer does not exist")
2025-03-07 00:52:51 +08:00
resetStatus = peer.resetDataUsage(type)
if resetStatus:
wgc.restrictPeers([id])
wgc.allowAccessPeers([id])
return ResponseObject(status=resetStatus)
2024-06-18 03:16:42 +08:00
2024-09-24 22:54:18 +08:00
@app.post(f'{APP_PREFIX}/api/deletePeers/<configName>')
2024-06-18 03:16:42 +08:00
def API_deletePeers(configName: str) -> ResponseObject:
data = request.get_json()
peers = data['peers']
if configName in WireguardConfigurations.keys():
if len(peers) == 0:
return ResponseObject(False, "Please specify one or more peers", status_code=400)
2024-06-18 03:16:42 +08:00
configuration = WireguardConfigurations.get(configName)
return configuration.deletePeers(peers)
2021-04-02 20:48:00 -04:00
return ResponseObject(False, "Configuration does not exist", status_code=404)
2021-12-26 02:26:39 +03:00
2024-09-24 22:54:18 +08:00
@app.post(f'{APP_PREFIX}/api/restrictPeers/<configName>')
2024-06-18 03:16:42 +08:00
def API_restrictPeers(configName: str) -> ResponseObject:
2021-04-02 20:48:00 -04:00
data = request.get_json()
2024-06-18 03:16:42 +08:00
peers = data['peers']
if configName in WireguardConfigurations.keys():
if len(peers) == 0:
2024-09-22 21:50:30 +08:00
return ResponseObject(False, "Please specify one or more peers")
2024-06-18 03:16:42 +08:00
configuration = WireguardConfigurations.get(configName)
return configuration.restrictPeers(peers)
2025-04-19 02:54:47 +08:00
return ResponseObject(False, "Configuration does not exist", status_code=404)
2024-09-24 22:54:18 +08:00
@app.post(f'{APP_PREFIX}/api/sharePeer/create')
2024-08-06 10:17:14 -04:00
def API_sharePeer_create():
data: dict[str, str] = request.get_json()
Configuration = data.get('Configuration')
Peer = data.get('Peer')
ExpireDate = data.get('ExpireDate')
if Configuration is None or Peer is None:
2024-09-22 21:50:30 +08:00
return ResponseObject(False, "Please specify configuration and peers")
2024-08-06 10:17:14 -04:00
activeLink = AllPeerShareLinks.getLink(Configuration, Peer)
if len(activeLink) > 0:
return ResponseObject(True,
"This peer is already sharing. Please view data for shared link.",
data=activeLink[0]
)
status, message = AllPeerShareLinks.addLink(Configuration, Peer, datetime.strptime(ExpireDate, "%Y-%m-%d %H:%M:%S"))
2024-08-06 10:17:14 -04:00
if not status:
return ResponseObject(status, message)
return ResponseObject(data=AllPeerShareLinks.getLinkByID(message))
2024-09-24 22:54:18 +08:00
@app.post(f'{APP_PREFIX}/api/sharePeer/update')
2024-08-06 10:17:14 -04:00
def API_sharePeer_update():
data: dict[str, str] = request.get_json()
ShareID: str = data.get("ShareID")
ExpireDate: str = data.get("ExpireDate")
if ShareID is None:
return ResponseObject(False, "Please specify ShareID")
if len(AllPeerShareLinks.getLinkByID(ShareID)) == 0:
return ResponseObject(False, "ShareID does not exist")
status, message = AllPeerShareLinks.updateLinkExpireDate(ShareID, datetime.strptime(ExpireDate, "%Y-%m-%d %H:%M:%S"))
2024-08-06 10:17:14 -04:00
if not status:
return ResponseObject(status, message)
return ResponseObject(data=AllPeerShareLinks.getLinkByID(ShareID))
2024-06-18 03:16:42 +08:00
2024-09-24 22:54:18 +08:00
@app.get(f'{APP_PREFIX}/api/sharePeer/get')
2024-08-07 00:37:05 -04:00
def API_sharePeer_get():
data = request.args
ShareID = data.get("ShareID")
if ShareID is None or len(ShareID) == 0:
return ResponseObject(False, "Please provide ShareID")
link = AllPeerShareLinks.getLinkByID(ShareID)
if len(link) == 0:
return ResponseObject(False, "This link is either expired to invalid")
l = link[0]
if l.Configuration not in WireguardConfigurations.keys():
return ResponseObject(False, "The peer you're looking for does not exist")
c = WireguardConfigurations.get(l.Configuration)
fp, p = c.searchPeer(l.Peer)
if not fp:
return ResponseObject(False, "The peer you're looking for does not exist")
return ResponseObject(data=p.downloadPeer())
2024-09-24 22:54:18 +08:00
@app.post(f'{APP_PREFIX}/api/allowAccessPeers/<configName>')
2024-06-18 03:16:42 +08:00
def API_allowAccessPeers(configName: str) -> ResponseObject:
data = request.get_json()
2024-06-18 03:16:42 +08:00
peers = data['peers']
if configName in WireguardConfigurations.keys():
if len(peers) == 0:
2024-09-22 21:50:30 +08:00
return ResponseObject(False, "Please specify one or more peers")
2024-06-18 03:16:42 +08:00
configuration = WireguardConfigurations.get(configName)
return configuration.allowAccessPeers(peers)
return ResponseObject(False, "Configuration does not exist")
2021-08-14 17:13:16 -04:00
2024-09-24 22:54:18 +08:00
@app.post(f'{APP_PREFIX}/api/addPeers/<configName>')
2024-06-18 03:16:42 +08:00
def API_addPeers(configName):
if configName in WireguardConfigurations.keys():
try:
data: dict = request.get_json()
bulkAdd: bool = data.get("bulkAdd", False)
bulkAddAmount: int = data.get('bulkAddAmount', 0)
preshared_key_bulkAdd: bool = data.get('preshared_key_bulkAdd', False)
2025-02-08 16:40:20 +08:00
public_key: str = data.get('public_key', "")
allowed_ips: list[str] = data.get('allowed_ips', [])
2025-02-16 17:42:32 +08:00
allowed_ips_validation: bool = data.get('allowed_ips_validation', True)
2024-08-08 23:27:13 -04:00
endpoint_allowed_ip: str = data.get('endpoint_allowed_ip', DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[1])
dns_addresses: str = data.get('DNS', DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1])
mtu: int = data.get('mtu', int(DashboardConfig.GetConfig("Peers", "peer_MTU")[1]))
keep_alive: int = data.get('keepalive', int(DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1]))
2025-02-08 16:40:20 +08:00
preshared_key: str = data.get('preshared_key', "")
if type(mtu) is not int or mtu < 0 or mtu > 1460:
mtu = int(DashboardConfig.GetConfig("Peers", "peer_MTU")[1])
if type(keep_alive) is not int or keep_alive < 0:
keep_alive = int(DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1])
config = WireguardConfigurations.get(configName)
if not config.getStatus():
config.toggleConfiguration()
2025-02-16 23:07:16 +08:00
ipStatus, availableIps = config.getAvailableIP(-1)
ipCountStatus, numberOfAvailableIPs = config.getNumberOfAvailableIP()
2025-02-16 17:42:32 +08:00
defaultIPSubnet = list(availableIps.keys())[0]
if bulkAdd:
if type(preshared_key_bulkAdd) is not bool:
preshared_key_bulkAdd = False
if type(bulkAddAmount) is not int or bulkAddAmount < 1:
return ResponseObject(False, "Please specify amount of peers you want to add")
2025-02-16 17:42:32 +08:00
if not ipStatus:
return ResponseObject(False, "No more available IP can assign")
2025-02-16 17:42:32 +08:00
if len(availableIps.keys()) == 0:
return ResponseObject(False, "This configuration does not have any IP address available")
2025-02-16 23:07:16 +08:00
if bulkAddAmount > sum(list(numberOfAvailableIPs.values())):
return ResponseObject(False,
2025-02-16 23:07:16 +08:00
f"The maximum number of peers can add is {sum(list(numberOfAvailableIPs.values()))}")
keyPairs = []
2025-02-16 23:07:16 +08:00
addedCount = 0
for subnet in availableIps.keys():
for ip in availableIps[subnet]:
newPrivateKey = GenerateWireguardPrivateKey()[1]
addedCount += 1
keyPairs.append({
"private_key": newPrivateKey,
"id": GenerateWireguardPublicKey(newPrivateKey)[1],
"preshared_key": (GenerateWireguardPrivateKey()[1] if preshared_key_bulkAdd else ""),
"allowed_ip": ip,
"name": f"BulkPeer_{(addedCount + 1)}_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
"DNS": dns_addresses,
"endpoint_allowed_ip": endpoint_allowed_ip,
"mtu": mtu,
"keepalive": keep_alive,
"advanced_security": "off"
2025-02-16 23:07:16 +08:00
})
if addedCount == bulkAddAmount:
break
if addedCount == bulkAddAmount:
break
if len(keyPairs) == 0 or (bulkAdd and len(keyPairs) != bulkAddAmount):
return ResponseObject(False, "Generating key pairs by bulk failed")
status, result = config.addPeers(keyPairs)
return ResponseObject(status=status, message=result['message'], data=result['peers'])
else:
if config.searchPeer(public_key)[0] is True:
return ResponseObject(False, f"This peer already exist")
name = data.get("name", "")
private_key = data.get("private_key", "")
2025-04-19 02:54:47 +08:00
if len(public_key) == 0:
if len(private_key) == 0:
private_key = GenerateWireguardPrivateKey()[1]
public_key = GenerateWireguardPublicKey(private_key)[1]
else:
public_key = GenerateWireguardPublicKey(private_key)[1]
else:
if len(private_key) > 0:
genPub = GenerateWireguardPublicKey(private_key)[1]
# Check if provided pubkey match provided private key
if public_key != genPub:
return ResponseObject(False, "Provided Public Key does not match provided Private Key")
# if len(public_key) == 0 and len(private_key) == 0:
# private_key = GenerateWireguardPrivateKey()[1]
# public_key = GenerateWireguardPublicKey(private_key)[1]
# elif len(public_key) == 0 and len(private_key) > 0:
# public_key = GenerateWireguardPublicKey(private_key)[1]
if len(allowed_ips) == 0:
2025-02-16 17:42:32 +08:00
if ipStatus:
2025-02-16 23:07:16 +08:00
for subnet in availableIps.keys():
for ip in availableIps[subnet]:
allowed_ips = [ip]
break
break
else:
return ResponseObject(False, "No more available IP can assign")
2025-02-08 16:40:20 +08:00
2025-02-16 17:42:32 +08:00
if allowed_ips_validation:
2025-02-08 16:40:20 +08:00
for i in allowed_ips:
2025-02-16 17:42:32 +08:00
found = False
2025-02-16 23:07:16 +08:00
for subnet in availableIps.keys():
network = ipaddress.ip_network(subnet, False)
ap = ipaddress.ip_network(i)
if network.version == ap.version and ap.subnet_of(network):
2025-02-16 17:42:32 +08:00
found = True
2025-02-16 23:07:16 +08:00
2025-02-16 17:42:32 +08:00
if not found:
2025-02-08 16:40:20 +08:00
return ResponseObject(False, f"This IP is not available: {i}")
status, result = config.addPeers([
2024-11-06 20:58:53 +08:00
{
"name": name,
"id": public_key,
"private_key": private_key,
"allowed_ip": ','.join(allowed_ips),
"preshared_key": preshared_key,
"endpoint_allowed_ip": endpoint_allowed_ip,
"DNS": dns_addresses,
"mtu": mtu,
2024-12-03 02:34:45 +08:00
"keepalive": keep_alive,
"advanced_security": "off"
2024-11-06 20:58:53 +08:00
}]
)
return ResponseObject(status=status, message=result['message'], data=result['peers'])
except Exception as e:
2025-02-16 17:42:32 +08:00
print(e, str(e.__traceback__))
return ResponseObject(False, "Add peers failed. Please see data for specific issue")
2024-06-18 03:16:42 +08:00
return ResponseObject(False, "Configuration does not exist")
2024-09-24 22:54:18 +08:00
@app.get(f"{APP_PREFIX}/api/downloadPeer/<configName>")
2024-06-18 03:16:42 +08:00
def API_downloadPeer(configName):
data = request.args
if configName not in WireguardConfigurations.keys():
2024-09-22 21:50:30 +08:00
return ResponseObject(False, "Configuration does not exist")
2024-06-18 03:16:42 +08:00
configuration = WireguardConfigurations[configName]
peerFound, peer = configuration.searchPeer(data['id'])
if len(data['id']) == 0 or not peerFound:
2024-09-22 21:50:30 +08:00
return ResponseObject(False, "Peer does not exist")
2024-06-18 03:16:42 +08:00
return ResponseObject(data=peer.downloadPeer())
2024-09-24 22:54:18 +08:00
@app.get(f"{APP_PREFIX}/api/downloadAllPeers/<configName>")
2024-06-18 03:16:42 +08:00
def API_downloadAllPeers(configName):
if configName not in WireguardConfigurations.keys():
2024-09-22 21:50:30 +08:00
return ResponseObject(False, "Configuration does not exist")
2024-06-18 03:16:42 +08:00
configuration = WireguardConfigurations[configName]
peerData = []
untitledPeer = 0
for i in configuration.Peers:
file = i.downloadPeer()
2025-01-16 18:36:06 +08:00
if file["fileName"] == "UntitledPeer":
2024-06-18 03:16:42 +08:00
file["fileName"] = str(untitledPeer) + "_" + file["fileName"]
untitledPeer += 1
peerData.append(file)
return ResponseObject(data=peerData)
2024-09-24 22:54:18 +08:00
@app.get(f"{APP_PREFIX}/api/getAvailableIPs/<configName>")
2024-06-18 03:16:42 +08:00
def API_getAvailableIPs(configName):
2024-11-25 22:11:51 +08:00
if configName not in WireguardConfigurations.keys():
return ResponseObject(False, "Configuration does not exist")
status, ips = WireguardConfigurations.get(configName).getAvailableIP()
2024-06-18 03:16:42 +08:00
return ResponseObject(status=status, data=ips)
2025-02-16 17:42:32 +08:00
@app.get(f"{APP_PREFIX}/api/getNumberOfAvailableIPs/<configName>")
def API_getNumberOfAvailableIPs(configName):
if configName not in WireguardConfigurations.keys():
return ResponseObject(False, "Configuration does not exist")
status, ips = WireguardConfigurations.get(configName).getNumberOfAvailableIP()
return ResponseObject(status=status, data=ips)
2024-09-24 22:54:18 +08:00
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurationInfo')
2024-06-18 03:16:42 +08:00
def API_getConfigurationInfo():
configurationName = request.args.get("configurationName")
if not configurationName or configurationName not in WireguardConfigurations.keys():
return ResponseObject(False, "Please provide configuration name")
return ResponseObject(data={
"configurationInfo": WireguardConfigurations[configurationName],
"configurationPeers": WireguardConfigurations[configurationName].getPeersList(),
"configurationRestrictedPeers": WireguardConfigurations[configurationName].getRestrictedPeersList()
})
2024-09-24 22:54:18 +08:00
@app.get(f'{APP_PREFIX}/api/getDashboardTheme')
2024-06-18 03:16:42 +08:00
def API_getDashboardTheme():
return ResponseObject(data=DashboardConfig.GetConfig("Server", "dashboard_theme")[1])
2024-09-24 22:54:18 +08:00
@app.get(f'{APP_PREFIX}/api/getDashboardVersion')
def API_getDashboardVersion():
return ResponseObject(data=DashboardConfig.GetConfig("Server", "version")[1])
2025-04-19 02:54:47 +08:00
@app.post(f'{APP_PREFIX}/api/savePeerScheduleJob')
def API_savePeerScheduleJob():
data = request.json
2025-04-19 02:54:47 +08:00
if "Job" not in data.keys():
return ResponseObject(False, "Please specify job")
job: dict = data['Job']
if "Peer" not in job.keys() or "Configuration" not in job.keys():
return ResponseObject(False, "Please specify peer and configuration")
configuration = WireguardConfigurations.get(job['Configuration'])
2025-04-19 02:54:47 +08:00
if configuration is None:
return ResponseObject(False, "Configuration does not exist")
f, fp = configuration.searchPeer(job['Peer'])
if not f:
2024-09-22 21:50:30 +08:00
return ResponseObject(False, "Peer does not exist")
2025-04-19 02:54:47 +08:00
s, p = AllPeerJobs.saveJob(PeerJob(
job['JobID'], job['Configuration'], job['Peer'], job['Field'], job['Operator'], job['Value'],
job['CreationDate'], job['ExpireDate'], job['Action']))
if s:
return ResponseObject(s, data=p)
return ResponseObject(s, message=p)
2025-04-19 02:54:47 +08:00
@app.post(f'{APP_PREFIX}/api/deletePeerScheduleJob')
def API_deletePeerScheduleJob():
data = request.json
2025-04-19 02:54:47 +08:00
if "Job" not in data.keys():
return ResponseObject(False, "Please specify job")
job: dict = data['Job']
if "Peer" not in job.keys() or "Configuration" not in job.keys():
return ResponseObject(False, "Please specify peer and configuration")
configuration = WireguardConfigurations.get(job['Configuration'])
2025-04-19 02:54:47 +08:00
if configuration is None:
return ResponseObject(False, "Configuration does not exist")
f, fp = configuration.searchPeer(job['Peer'])
if not f:
2024-09-22 21:50:30 +08:00
return ResponseObject(False, "Peer does not exist")
s, p = AllPeerJobs.deleteJob(PeerJob(
job['JobID'], job['Configuration'], job['Peer'], job['Field'], job['Operator'], job['Value'],
job['CreationDate'], job['ExpireDate'], job['Action']))
if s:
return ResponseObject(s)
return ResponseObject(s, message=p)
2024-09-24 22:54:18 +08:00
@app.get(f'{APP_PREFIX}/api/getPeerScheduleJobLogs/<configName>')
2024-07-29 18:40:07 -04:00
def API_getPeerScheduleJobLogs(configName):
if configName not in WireguardConfigurations.keys():
return ResponseObject(False, "Configuration does not exist")
data = request.args.get("requestAll")
requestAll = False
if data is not None and data == "true":
requestAll = True
return ResponseObject(data=JobLogger.getLogs(requestAll, configName))
2024-12-26 00:06:37 +08:00
'''
File Download
'''
@app.get(f'{APP_PREFIX}/fileDownload')
def API_download():
file = request.args.get('file')
if file is None or len(file) == 0:
return ResponseObject(False, "Please specify a file")
if os.path.exists(os.path.join('download', file)):
return send_file(os.path.join('download', file), as_attachment=True)
else:
return ResponseObject(False, "File does not exist")
'''
Tools
'''
2024-09-24 22:54:18 +08:00
@app.get(f'{APP_PREFIX}/api/ping/getAllPeersIpAddress')
2024-06-18 03:16:42 +08:00
def API_ping_getAllPeersIpAddress():
ips = {}
for c in WireguardConfigurations.values():
cips = {}
for p in c.Peers:
allowed_ip = p.allowed_ip.replace(" ", "").split(",")
parsed = []
for x in allowed_ip:
try:
ip = ipaddress.ip_network(x, strict=False)
except ValueError as e:
print(f"{p.id} - {c.Name}")
2024-06-18 03:16:42 +08:00
if len(list(ip.hosts())) == 1:
parsed.append(str(ip.hosts()[0]))
endpoint = p.endpoint.replace(" ", "").replace("(none)", "")
if len(p.name) > 0:
cips[f"{p.name} - {p.id}"] = {
"allowed_ips": parsed,
"endpoint": endpoint
}
2021-08-14 17:13:16 -04:00
else:
2024-06-18 03:16:42 +08:00
cips[f"{p.id}"] = {
"allowed_ips": parsed,
"endpoint": endpoint
}
ips[c.Name] = cips
return ResponseObject(data=ips)
import requests
2024-09-24 22:54:18 +08:00
@app.get(f'{APP_PREFIX}/api/ping/execute')
2024-06-18 03:16:42 +08:00
def API_ping_execute():
if "ipAddress" in request.args.keys() and "count" in request.args.keys():
ip = request.args['ipAddress']
count = request.args['count']
try:
if ip is not None and len(ip) > 0 and count is not None and count.isnumeric():
result = ping(ip, count=int(count), source=None)
data = {
2024-06-18 03:16:42 +08:00
"address": result.address,
"is_alive": result.is_alive,
"min_rtt": result.min_rtt,
"avg_rtt": result.avg_rtt,
"max_rtt": result.max_rtt,
"package_sent": result.packets_sent,
"package_received": result.packets_received,
"package_loss": result.packet_loss,
"geo": None
}
try:
r = requests.get(f"http://ip-api.com/json/{result.address}?field=city")
data['geo'] = r.json()
except Exception as e:
pass
return ResponseObject(data=data)
2024-06-18 03:16:42 +08:00
return ResponseObject(False, "Please specify an IP Address (v4/v6)")
except Exception as exp:
return ResponseObject(False, exp)
return ResponseObject(False, "Please provide ipAddress and count")
2024-09-24 22:54:18 +08:00
@app.get(f'{APP_PREFIX}/api/traceroute/execute')
2024-06-18 03:16:42 +08:00
def API_traceroute_execute():
if "ipAddress" in request.args.keys() and len(request.args.get("ipAddress")) > 0:
ipAddress = request.args.get('ipAddress')
try:
tracerouteResult = traceroute(ipAddress, timeout=1, max_hops=64)
2024-06-18 03:16:42 +08:00
result = []
for hop in tracerouteResult:
if len(result) > 1:
skipped = False
for i in range(result[-1]["hop"] + 1, hop.distance):
result.append(
{
"hop": i,
"ip": "*",
"avg_rtt": "*",
"min_rtt": "*",
"max_rtt": "*"
}
)
skip = True
if skipped: continue
result.append(
{
"hop": hop.distance,
"ip": hop.address,
"avg_rtt": hop.avg_rtt,
"min_rtt": hop.min_rtt,
"max_rtt": hop.max_rtt
})
try:
r = requests.post(f"http://ip-api.com/batch?fields=city,country,lat,lon,query",
data=json.dumps([x['ip'] for x in result]))
d = r.json()
for i in range(len(result)):
2025-02-12 20:07:37 +08:00
result[i]['geo'] = d[i]
except Exception as e:
2025-02-12 20:07:37 +08:00
return ResponseObject(data=result, message="Failed to request IP address geolocation")
2024-06-18 03:16:42 +08:00
return ResponseObject(data=result)
except Exception as exp:
return ResponseObject(False, exp)
else:
return ResponseObject(False, "Please provide ipAddress")
2021-09-03 17:32:51 -04:00
2024-09-24 22:54:18 +08:00
@app.get(f'{APP_PREFIX}/api/getDashboardUpdate')
2024-08-17 00:31:46 -04:00
def API_getDashboardUpdate():
2024-08-19 21:30:47 -04:00
import urllib.request as req
2024-08-17 00:31:46 -04:00
try:
r = req.urlopen("https://api.github.com/repos/donaldzou/WGDashboard/releases/latest", timeout=5).read()
data = dict(json.loads(r))
tagName = data.get('tag_name')
htmlUrl = data.get('html_url')
if tagName is not None and htmlUrl is not None:
if version.parse(tagName) > version.parse(DASHBOARD_VERSION):
return ResponseObject(message=f"{tagName} is now available for update!", data=htmlUrl)
2024-08-17 00:31:46 -04:00
else:
return ResponseObject(message="You're on the latest version")
return ResponseObject(False)
2025-02-12 20:07:37 +08:00
except Exception as e:
2024-08-19 21:30:47 -04:00
return ResponseObject(False, f"Request to GitHub API failed.")
2021-09-03 17:32:51 -04:00
2024-06-18 03:16:42 +08:00
'''
Sign Up
'''
2022-03-24 02:10:52 -04:00
2024-09-24 22:54:18 +08:00
@app.get(f'{APP_PREFIX}/api/isTotpEnabled')
2024-06-18 03:16:42 +08:00
def API_isTotpEnabled():
2024-08-05 15:39:11 -04:00
return (
ResponseObject(data=DashboardConfig.GetConfig("Account", "enable_totp")[1] and DashboardConfig.GetConfig("Account", "totp_verified")[1]))
2022-03-21 22:33:19 -04:00
2022-03-24 02:10:52 -04:00
2024-09-24 22:54:18 +08:00
@app.get(f'{APP_PREFIX}/api/Welcome_GetTotpLink')
2024-06-18 03:16:42 +08:00
def API_Welcome_GetTotpLink():
2024-08-05 15:39:11 -04:00
if not DashboardConfig.GetConfig("Account", "totp_verified")[1]:
DashboardConfig.SetConfig("Account", "totp_key", pyotp.random_base32(), True)
2024-06-18 03:16:42 +08:00
return ResponseObject(
data=pyotp.totp.TOTP(DashboardConfig.GetConfig("Account", "totp_key")[1]).provisioning_uri(
issuer_name="WGDashboard"))
return ResponseObject(False)
2022-03-24 02:10:52 -04:00
2024-09-24 22:54:18 +08:00
@app.post(f'{APP_PREFIX}/api/Welcome_VerifyTotpLink')
2024-06-18 03:16:42 +08:00
def API_Welcome_VerifyTotpLink():
2023-11-28 16:37:16 -05:00
data = request.get_json()
2024-08-05 15:39:11 -04:00
totp = pyotp.TOTP(DashboardConfig.GetConfig("Account", "totp_key")[1]).now()
if totp == data['totp']:
DashboardConfig.SetConfig("Account", "totp_verified", "true")
DashboardConfig.SetConfig("Account", "enable_totp", "true")
return ResponseObject(totp == data['totp'])
2023-11-28 16:37:16 -05:00
2024-09-24 22:54:18 +08:00
@app.post(f'{APP_PREFIX}/api/Welcome_Finish')
2024-06-18 03:16:42 +08:00
def API_Welcome_Finish():
2022-04-23 00:34:11 -04:00
data = request.get_json()
2024-06-18 03:16:42 +08:00
if DashboardConfig.GetConfig("Other", "welcome_session")[1]:
if data["username"] == "":
return ResponseObject(False, "Username cannot be blank.")
2022-04-23 00:34:11 -04:00
2024-06-18 03:16:42 +08:00
if data["newPassword"] == "" or len(data["newPassword"]) < 8:
return ResponseObject(False, "Password must be at least 8 characters")
2022-04-21 15:11:01 -04:00
2024-06-18 03:16:42 +08:00
updateUsername, updateUsernameErr = DashboardConfig.SetConfig("Account", "username", data["username"])
updatePassword, updatePasswordErr = DashboardConfig.SetConfig("Account", "password",
{
"newPassword": data["newPassword"],
2024-08-02 17:27:28 -04:00
"repeatNewPassword": data["repeatNewPassword"],
2024-06-18 03:16:42 +08:00
"currentPassword": "admin"
})
2024-08-05 15:39:11 -04:00
if not updateUsername or not updatePassword:
return ResponseObject(False, f"{updateUsernameErr},{updatePasswordErr}".strip(","))
2021-12-26 02:26:39 +03:00
2024-06-18 03:16:42 +08:00
DashboardConfig.SetConfig("Other", "welcome_session", False)
return ResponseObject()
class Locale:
def __init__(self):
self.localePath = './static/locale/'
self.activeLanguages = {}
with open(os.path.join(f"{self.localePath}active_languages.json"), "r") as f:
self.activeLanguages = sorted(json.loads(''.join(f.readlines())), key=lambda x : x['lang_name'])
def getLanguage(self) -> dict | None:
currentLanguage = DashboardConfig.GetConfig("Server", "dashboard_language")[1]
if currentLanguage == "en":
return None
if os.path.exists(os.path.join(f"{self.localePath}{currentLanguage}.json")):
with open(os.path.join(f"{self.localePath}{currentLanguage}.json"), "r") as f:
return dict(json.loads(''.join(f.readlines())))
else:
return None
def updateLanguage(self, lang_id):
if not os.path.exists(os.path.join(f"{self.localePath}{lang_id}.json")):
DashboardConfig.SetConfig("Server", "dashboard_language", "en")
else:
DashboardConfig.SetConfig("Server", "dashboard_language", lang_id)
Locale = Locale()
@app.get(f'{APP_PREFIX}/api/locale')
def API_Locale_CurrentLang():
return ResponseObject(data=Locale.getLanguage())
@app.get(f'{APP_PREFIX}/api/locale/available')
def API_Locale_Available():
return ResponseObject(data=Locale.activeLanguages)
@app.post(f'{APP_PREFIX}/api/locale/update')
def API_Locale_Update():
data = request.get_json()
if 'lang_id' not in data.keys():
return ResponseObject(False, "Please specify a lang_id")
Locale.updateLanguage(data['lang_id'])
return ResponseObject(data=Locale.getLanguage())
2024-09-09 23:43:55 +08:00
2025-01-08 18:09:05 +08:00
@app.get(f'{APP_PREFIX}/api/email/ready')
def API_Email_Ready():
return ResponseObject(EmailSender.ready())
@app.post(f'{APP_PREFIX}/api/email/send')
def API_Email_Send():
data = request.get_json()
2025-01-13 16:47:15 +08:00
if "Receiver" not in data.keys():
2025-01-08 18:09:05 +08:00
return ResponseObject(False, "Please at least specify receiver")
2025-01-13 16:47:15 +08:00
body = data.get('Body', '')
2025-01-16 00:44:22 +08:00
download = None
2025-04-19 02:54:47 +08:00
if ("ConfigurationName" in data.keys()
and "Peer" in data.keys()):
2025-01-13 16:47:15 +08:00
if data.get('ConfigurationName') in WireguardConfigurations.keys():
configuration = WireguardConfigurations.get(data.get('ConfigurationName'))
attachmentName = ""
if configuration is not None:
fp, p = configuration.searchPeer(data.get('Peer'))
if fp:
template = Template(body)
download = p.downloadPeer()
body = template.render(peer=p.toJson(), configurationFile=download)
if data.get('IncludeAttachment', False):
u = str(uuid4())
attachmentName = f'{u}.conf'
2025-04-19 02:54:47 +08:00
with open(os.path.join('./attachments', attachmentName,), 'w+') as f:
f.write(download['file'])
2025-01-13 16:47:15 +08:00
s, m = EmailSender.send(data.get('Receiver'), data.get('Subject', ''), body,
2025-04-19 02:54:47 +08:00
data.get('IncludeAttachment', False), (attachmentName if download else ''))
2025-01-08 18:09:05 +08:00
return ResponseObject(s, m)
2025-01-16 00:44:22 +08:00
@app.post(f'{APP_PREFIX}/api/email/previewBody')
def API_Email_PreviewBody():
data = request.get_json()
body = data.get('Body', '')
if len(body) == 0:
return ResponseObject(False, "Nothing to preview")
if ("ConfigurationName" not in data.keys()
or "Peer" not in data.keys() or data.get('ConfigurationName') not in WireguardConfigurations.keys()):
return ResponseObject(False, "Please specify configuration and peer")
configuration = WireguardConfigurations.get(data.get('ConfigurationName'))
fp, p = configuration.searchPeer(data.get('Peer'))
if not fp:
return ResponseObject(False, "Peer does not exist")
try:
template = Template(body)
download = p.downloadPeer()
body = template.render(peer=p.toJson(), configurationFile=download)
return ResponseObject(data=body)
except Exception as e:
return ResponseObject(False, message=str(e))
2024-11-27 18:26:13 +08:00
@app.get(f'{APP_PREFIX}/api/systemStatus')
def API_SystemStatus():
return ResponseObject(data=SystemStatus)
2024-11-27 18:26:13 +08:00
2024-12-04 17:50:16 +08:00
@app.get(f'{APP_PREFIX}/api/protocolsEnabled')
def API_ProtocolsEnabled():
return ResponseObject(data=ProtocolsEnabled())
2024-11-27 18:26:13 +08:00
2024-09-24 22:54:18 +08:00
@app.get(f'{APP_PREFIX}/')
2024-06-18 03:16:42 +08:00
def index():
2024-11-07 18:33:39 +08:00
return render_template('index.html')
2025-02-12 20:07:37 +08:00
def peerInformationBackgroundThread():
2024-09-06 16:31:54 +08:00
global WireguardConfigurations
print(f"[WGDashboard] Background Thread #1 Started", flush=True)
time.sleep(10)
while True:
with app.app_context():
2024-06-18 03:16:42 +08:00
for c in WireguardConfigurations.values():
if c.getStatus():
try:
c.getPeersTransfer()
c.getPeersLatestHandshake()
c.getPeersEndpoint()
c.getPeersList()
c.getRestrictedPeersList()
2024-06-18 03:16:42 +08:00
except Exception as e:
2024-08-04 20:17:29 -04:00
print(f"[WGDashboard] Background Thread #1 Error: {str(e)}", flush=True)
2024-09-06 16:31:54 +08:00
time.sleep(10)
def peerJobScheduleBackgroundThread():
with app.app_context():
2024-08-04 20:17:29 -04:00
print(f"[WGDashboard] Background Thread #2 Started", flush=True)
time.sleep(10)
while True:
AllPeerJobs.runJob()
2024-08-04 01:31:31 -04:00
time.sleep(180)
2024-06-18 03:40:25 +08:00
def gunicornConfig():
_, app_ip = DashboardConfig.GetConfig("Server", "app_ip")
_, app_port = DashboardConfig.GetConfig("Server", "app_port")
return app_ip, app_port
2024-12-04 17:50:16 +08:00
def ProtocolsEnabled() -> list[str]:
2024-12-02 16:34:26 +08:00
from shutil import which
2024-12-04 17:50:16 +08:00
protocols = []
if which('awg') is not None and which('awg-quick') is not None:
protocols.append("awg")
if which('wg') is not None and which('wg-quick') is not None:
protocols.append("wg")
return protocols
2024-12-02 16:34:26 +08:00
2024-12-26 00:06:37 +08:00
def InitWireguardConfigurationsList(startup: bool = False):
if os.path.exists(DashboardConfig.GetConfig("Server", "wg_conf_path")[1]):
confs = os.listdir(DashboardConfig.GetConfig("Server", "wg_conf_path")[1])
confs.sort()
for i in confs:
if RegexMatch("^(.{1,}).(conf)$", i):
i = i.replace('.conf', '')
try:
if i in WireguardConfigurations.keys():
if WireguardConfigurations[i].configurationFileChanged():
WireguardConfigurations[i] = WireguardConfiguration(i)
else:
WireguardConfigurations[i] = WireguardConfiguration(i, startup=startup)
except WireguardConfiguration.InvalidConfigurationFileException as e:
print(f"{i} have an invalid configuration file.")
2024-11-25 22:11:51 +08:00
2024-12-04 17:50:16 +08:00
if "awg" in ProtocolsEnabled():
2024-12-02 16:34:26 +08:00
confs = os.listdir(DashboardConfig.GetConfig("Server", "awg_conf_path")[1])
confs.sort()
for i in confs:
if RegexMatch("^(.{1,}).(conf)$", i):
i = i.replace('.conf', '')
try:
if i in WireguardConfigurations.keys():
if WireguardConfigurations[i].configurationFileChanged():
WireguardConfigurations[i] = AmneziaWireguardConfiguration(i)
else:
WireguardConfigurations[i] = AmneziaWireguardConfiguration(i, startup=startup)
except WireguardConfigurations.InvalidConfigurationFileException as e:
print(f"{i} have an invalid configuration file.")
AllPeerShareLinks: PeerShareLinks = PeerShareLinks(DashboardConfig)
AllPeerJobs: PeerJobs = PeerJobs()
JobLogger: PeerJobLogger = PeerJobLogger(CONFIGURATION_PATH, AllPeerJobs, DashboardConfig)
DashboardLogger: DashboardLogger = DashboardLogger(CONFIGURATION_PATH, DashboardConfig)
_, app_ip = DashboardConfig.GetConfig("Server", "app_ip")
_, app_port = DashboardConfig.GetConfig("Server", "app_port")
_, WG_CONF_PATH = DashboardConfig.GetConfig("Server", "wg_conf_path")
2024-08-03 13:25:57 -04:00
WireguardConfigurations: dict[str, WireguardConfiguration] = {}
AmneziaWireguardConfigurations: dict[str, AmneziaWireguardConfiguration] = {}
2024-11-25 22:11:51 +08:00
InitWireguardConfigurationsList(startup=True)
2024-06-18 03:40:25 +08:00
2024-08-05 15:39:11 -04:00
def startThreads():
2025-02-12 20:07:37 +08:00
bgThread = threading.Thread(target=peerInformationBackgroundThread, daemon=True)
2024-08-05 15:39:11 -04:00
bgThread.start()
2025-02-12 20:07:37 +08:00
scheduleJobThread = threading.Thread(target=peerJobScheduleBackgroundThread, daemon=True)
2024-08-05 15:39:11 -04:00
scheduleJobThread.start()
2021-10-18 02:24:09 +03:00
if __name__ == "__main__":
2024-08-05 15:39:11 -04:00
startThreads()
app.run(host=app_ip, debug=False, port=app_port)