2025-06-02 19:23:04 +08:00
|
|
|
import hashlib
|
|
|
|
import uuid
|
|
|
|
|
|
|
|
import bcrypt
|
|
|
|
import pyotp
|
2025-06-02 12:04:01 +08:00
|
|
|
import sqlalchemy as db
|
|
|
|
|
|
|
|
from .ConnectionString import ConnectionString
|
2025-06-05 15:57:17 +08:00
|
|
|
from .DashboardClientsPeerAssignment import DashboardClientsPeerAssignment
|
2025-06-02 19:23:04 +08:00
|
|
|
from .DashboardClientsTOTP import DashboardClientsTOTP
|
|
|
|
from .Utilities import ValidatePasswordStrength
|
|
|
|
from .DashboardLogger import DashboardLogger
|
2025-06-05 17:57:14 +08:00
|
|
|
from flask import session
|
2025-06-02 12:04:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
class DashboardClients:
|
2025-06-05 17:57:14 +08:00
|
|
|
def __init__(self, wireguardConfigurations):
|
2025-06-02 19:23:04 +08:00
|
|
|
self.logger = DashboardLogger()
|
2025-06-02 12:04:01 +08:00
|
|
|
self.engine = db.create_engine(ConnectionString("wgdashboard"))
|
|
|
|
self.metadata = db.MetaData()
|
2025-06-03 14:36:29 +08:00
|
|
|
|
2025-06-02 12:04:01 +08:00
|
|
|
self.dashboardClientsTable = db.Table(
|
|
|
|
'DashboardClients', self.metadata,
|
|
|
|
db.Column('ClientID', db.String(255), nullable=False, primary_key=True),
|
|
|
|
db.Column('Email', db.String(255), nullable=False, index=True),
|
|
|
|
db.Column('Password', db.String(500)),
|
|
|
|
db.Column('TotpKey', db.String(500)),
|
|
|
|
db.Column('TotpKeyVerified', db.Integer),
|
|
|
|
db.Column('CreatedDate',
|
|
|
|
(db.DATETIME if 'sqlite:///' in ConnectionString("wgdashboard") else db.TIMESTAMP),
|
|
|
|
server_default=db.func.now()),
|
|
|
|
db.Column('DeletedDate',
|
|
|
|
(db.DATETIME if 'sqlite:///' in ConnectionString("wgdashboard") else db.TIMESTAMP)),
|
|
|
|
extend_existing=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
self.dashboardClientsInfoTable = db.Table(
|
|
|
|
'DashboardClientsInfo', self.metadata,
|
|
|
|
db.Column('ClientID', db.String(255), nullable=False, primary_key=True),
|
|
|
|
db.Column('Firstname', db.String(500)),
|
|
|
|
db.Column('Lastname', db.String(500)),
|
2025-06-03 14:49:56 +08:00
|
|
|
extend_existing=True,
|
2025-06-02 12:04:01 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
self.metadata.create_all(self.engine)
|
|
|
|
self.Clients = []
|
|
|
|
self.__getClients()
|
2025-06-03 14:36:29 +08:00
|
|
|
self.DashboardClientsTOTP = DashboardClientsTOTP()
|
2025-06-05 17:57:14 +08:00
|
|
|
self.DashboardClientsPeerAssignment = DashboardClientsPeerAssignment(wireguardConfigurations)
|
2025-06-02 12:04:01 +08:00
|
|
|
|
|
|
|
def __getClients(self):
|
|
|
|
with self.engine.connect() as conn:
|
|
|
|
self.Clients = conn.execute(
|
|
|
|
db.select(
|
|
|
|
self.dashboardClientsTable.c.ClientID,
|
|
|
|
self.dashboardClientsTable.c.Email,
|
|
|
|
self.dashboardClientsTable.c.CreatedDate
|
|
|
|
).where(
|
|
|
|
self.dashboardClientsTable.c.DeletedDate is None)
|
|
|
|
).mappings().fetchall()
|
2025-06-06 15:49:55 +08:00
|
|
|
|
|
|
|
def GetClientProfile(self, ClientID):
|
|
|
|
with self.engine.connect() as conn:
|
|
|
|
return dict(conn.execute(
|
2025-06-19 16:51:59 +08:00
|
|
|
db.select(
|
|
|
|
*[c for c in self.dashboardClientsInfoTable.c if c.name != 'ClientID']
|
|
|
|
).where(
|
2025-06-06 15:49:55 +08:00
|
|
|
self.dashboardClientsInfoTable.c.ClientID == ClientID
|
|
|
|
)
|
|
|
|
).mappings().fetchone())
|
2025-06-20 16:00:19 +08:00
|
|
|
|
|
|
|
def SignIn_ValidatePassword(self, Email, Password) -> bool:
|
2025-06-02 19:23:04 +08:00
|
|
|
if not all([Email, Password]):
|
2025-06-20 16:00:19 +08:00
|
|
|
return False
|
|
|
|
existingClient = self.SignIn_UserExistence(Email)
|
|
|
|
if existingClient:
|
|
|
|
return bcrypt.checkpw(Password.encode("utf-8"), existingClient.get("Password").encode("utf-8"))
|
|
|
|
return False
|
|
|
|
|
|
|
|
def SignIn_UserExistence(self, Email):
|
2025-06-02 19:23:04 +08:00
|
|
|
with self.engine.connect() as conn:
|
|
|
|
existingClient = conn.execute(
|
|
|
|
self.dashboardClientsTable.select().where(
|
|
|
|
self.dashboardClientsTable.c.Email == Email
|
|
|
|
)
|
|
|
|
).mappings().fetchone()
|
2025-06-20 16:00:19 +08:00
|
|
|
return existingClient
|
|
|
|
|
|
|
|
def SignIn(self, Email, Password) -> tuple[bool, str]:
|
|
|
|
if not all([Email, Password]):
|
|
|
|
return False, "Please fill in all fields"
|
|
|
|
existingClient = self.SignIn_UserExistence(Email)
|
|
|
|
if existingClient:
|
|
|
|
checkPwd = self.SignIn_ValidatePassword(Email, Password)
|
|
|
|
if checkPwd:
|
|
|
|
session['Email'] = Email
|
|
|
|
session['ClientID'] = existingClient.get("ClientID")
|
2025-06-25 23:16:51 +08:00
|
|
|
return True, self.DashboardClientsTOTP.GenerateToken(existingClient.get("ClientID"))
|
2025-06-03 03:02:06 +08:00
|
|
|
return False, "Email or Password is incorrect"
|
|
|
|
|
|
|
|
def SignIn_GetTotp(self, Token: str, UserProvidedTotp: str = None) -> tuple[bool, str] or tuple[bool, None, str]:
|
|
|
|
status, data = self.DashboardClientsTOTP.GetTotp(Token)
|
2025-06-05 15:57:17 +08:00
|
|
|
|
2025-06-03 03:02:06 +08:00
|
|
|
if not status:
|
|
|
|
return False, "TOTP Token is invalid"
|
|
|
|
if UserProvidedTotp is None:
|
|
|
|
if data.get('TotpKeyVerified') is None:
|
|
|
|
return True, pyotp.totp.TOTP(data.get('TotpKey')).provisioning_uri(name=data.get('Email'),
|
|
|
|
issuer_name="WGDashboard Client")
|
|
|
|
else:
|
2025-06-05 15:57:17 +08:00
|
|
|
totpMatched = pyotp.totp.TOTP(data.get('TotpKey')).verify(UserProvidedTotp)
|
2025-06-03 03:02:06 +08:00
|
|
|
if not totpMatched:
|
|
|
|
return False, "TOTP is does not match"
|
2025-06-03 23:37:43 +08:00
|
|
|
else:
|
|
|
|
self.DashboardClientsTOTP.RevokeToken(Token)
|
2025-06-03 03:02:06 +08:00
|
|
|
if data.get('TotpKeyVerified') is None:
|
|
|
|
with self.engine.begin() as conn:
|
|
|
|
conn.execute(
|
|
|
|
self.dashboardClientsTable.update().values({
|
|
|
|
'TotpKeyVerified': 1
|
|
|
|
}).where(
|
|
|
|
self.dashboardClientsTable.c.ClientID == data.get('ClientID')
|
|
|
|
)
|
|
|
|
)
|
2025-06-03 23:37:43 +08:00
|
|
|
|
2025-06-03 03:02:06 +08:00
|
|
|
return True, None
|
|
|
|
|
2025-06-02 19:23:04 +08:00
|
|
|
def SignUp(self, Email, Password, ConfirmPassword) -> tuple[bool, str] or tuple[bool, None]:
|
|
|
|
try:
|
|
|
|
if not all([Email, Password, ConfirmPassword]):
|
|
|
|
return False, "Please fill in all fields"
|
|
|
|
if Password != ConfirmPassword:
|
|
|
|
return False, "Passwords does not match"
|
2025-06-20 16:00:19 +08:00
|
|
|
|
|
|
|
existingClient = self.SignIn_UserExistence(Email)
|
|
|
|
if existingClient:
|
|
|
|
return False, "Email already signed up"
|
2025-06-02 12:04:01 +08:00
|
|
|
|
2025-06-02 19:23:04 +08:00
|
|
|
pwStrength, msg = ValidatePasswordStrength(Password)
|
|
|
|
if not pwStrength:
|
|
|
|
return pwStrength, msg
|
2025-06-02 12:04:01 +08:00
|
|
|
|
2025-06-02 19:23:04 +08:00
|
|
|
with self.engine.begin() as conn:
|
|
|
|
newClientUUID = str(uuid.uuid4())
|
|
|
|
totpKey = pyotp.random_base32()
|
|
|
|
encodePassword = Password.encode('utf-8')
|
|
|
|
conn.execute(
|
|
|
|
self.dashboardClientsTable.insert().values({
|
|
|
|
"ClientID": newClientUUID,
|
|
|
|
"Email": Email,
|
|
|
|
"Password": bcrypt.hashpw(encodePassword, bcrypt.gensalt()).decode("utf-8"),
|
|
|
|
"TotpKey": totpKey
|
|
|
|
})
|
|
|
|
)
|
|
|
|
conn.execute(
|
|
|
|
self.dashboardClientsInfoTable.insert().values({
|
|
|
|
"ClientID": newClientUUID
|
|
|
|
})
|
|
|
|
)
|
2025-06-20 16:00:19 +08:00
|
|
|
self.logger.log(Message=f"User {Email} signed up")
|
2025-06-02 19:23:04 +08:00
|
|
|
except Exception as e:
|
|
|
|
self.logger.log(Status="false", Message=f"Signed up failed, reason: {str(e)}")
|
|
|
|
return False, "Signed up failed."
|
|
|
|
|
2025-06-03 23:37:43 +08:00
|
|
|
return True, None
|
|
|
|
|
2025-06-05 17:57:14 +08:00
|
|
|
def GetClientAssignedPeers(self, ClientID):
|
|
|
|
return self.DashboardClientsPeerAssignment.GetAssignedPeers(ClientID)
|
|
|
|
|
2025-06-20 16:00:19 +08:00
|
|
|
def UpdateClientPassword(self, Email, CurrentPassword, NewPassword, ConfirmNewPassword):
|
|
|
|
if not all([CurrentPassword, NewPassword, ConfirmNewPassword]):
|
|
|
|
return False, "Please fill in all fields"
|
|
|
|
|
|
|
|
if not self.SignIn_ValidatePassword(Email, CurrentPassword):
|
|
|
|
return False, "Current password does not match"
|
|
|
|
|
|
|
|
if NewPassword != ConfirmNewPassword:
|
|
|
|
return False, "New passwords does not match"
|
|
|
|
|
|
|
|
pwStrength, msg = ValidatePasswordStrength(NewPassword)
|
|
|
|
if not pwStrength:
|
|
|
|
return pwStrength, msg
|
|
|
|
try:
|
|
|
|
with self.engine.begin() as conn:
|
|
|
|
conn.execute(
|
|
|
|
self.dashboardClientsTable.update().values({
|
|
|
|
"Password": bcrypt.hashpw(NewPassword.encode('utf-8'), bcrypt.gensalt()).decode("utf-8"),
|
|
|
|
}).where(
|
|
|
|
self.dashboardClientsTable.c.Email == Email
|
|
|
|
)
|
|
|
|
)
|
|
|
|
self.logger.log(Message=f"User {Email} updated password")
|
|
|
|
except Exception as e:
|
|
|
|
self.logger.log(Status="false", Message=f"Signed up failed, reason: {str(e)}")
|
|
|
|
return False, "Signed up failed."
|
|
|
|
return True, None
|