Mbonea's picture
vpn ready?
afbb33a
raw
history blame
15.3 kB
from librouteros import connect
from librouteros.query import Key
import logging
from typing import Optional
from datetime import datetime
from ..schema import *
from ..schema import UserListData
from .helperfx import PhoneValidator
from .Config import RouterConfig
from time import sleep
class MikrotikAPI:
"""Class to handle MikroTik router API operations, inherits VPNManager."""
def __init__(
self,
config: RouterConfig,
):
"""Initialize with router configuration and VPN configuration."""
self.config = config
self.api = None
self.logger = self._setup_logging()
self.phone_validator = PhoneValidator()
def connect(self) -> bool:
"""Establish connection to VPN and MikroTik router."""
try:
self.api = connect(
username=self.config.username,
password=self.config.password,
host=self.config.host,
port=8728,
)
self.logger.info("Successfully connected to MikroTik router.")
return True
except Exception as e:
self.logger.error(f"Failed to connect to MikroTik router: {e}")
return False
def close(self):
"""Close the router connection and disconnect VPN."""
if self.api:
self.api.close()
self.logger.info("Router connection closed.")
def add_hotspot_user(
self, phone: str, password: str, profile: str = "default"
) -> BaseResponse:
"""Add a hotspot user to the router"""
if not self.api:
return ResponseBuilder.error(
BaseResponse, "Registration failed", "Not connected to router"
)
# Validate phone number
is_valid, formatted_phone = self.phone_validator.validate_and_format(phone)
if not is_valid:
return ResponseBuilder.error(
BaseResponse,
"Registration failed",
f"Invalid phone number format: {phone}",
)
users = self.api.path("ip", "hotspot", "user")
existing_users = users.select(Key("name"))
# Check if user already exists
if any(str(user["name"]) == formatted_phone for user in existing_users):
return ResponseBuilder.error(
BaseResponse,
"Registration failed",
f"User {formatted_phone} already exists",
)
try:
# Add user to router
users.add(
name=formatted_phone,
password=password,
profile=profile,
disabled=True,
)
return ResponseBuilder.success(BaseResponse, "User registered successfully")
except Exception as e:
self.logger.error(f"Failed to add user {formatted_phone}: {e}")
return ResponseBuilder.error(BaseResponse, "Registration failed", str(e))
def login_hotspot_user(
self, phone: str, password: str, mac_address: str, ip_address: str
) -> LoginResponse:
"""Login a hotspot user"""
if not self.api:
return ResponseBuilder.error(
LoginResponse, "Login failed", "Not connected to router"
)
is_valid, formatted_phone = self.phone_validator.validate_and_format(phone)
if not is_valid:
return ResponseBuilder.error(
LoginResponse, "Login failed", f"Invalid phone number format: {phone}"
)
try:
users = self.api.path("ip", "hotspot", "user")
active = self.api.path("ip", "hotspot", "active")
# Verify if user exists and password is correct
existing_users = users.select(Key("name"), Key("password"), Key("profile"))
user_data = next(
(
user
for user in existing_users
if str(user["name"]) == formatted_phone
and user["password"] == password
),
None,
)
if not user_data:
return ResponseBuilder.error(
LoginResponse, "Login failed", "Invalid credentials"
)
# Check if already logged in
active_users = active.select(Key("user"))
if any(str(user["user"]) == formatted_phone for user in active_users):
return ResponseBuilder.error(
LoginResponse,
"Login failed",
f"User {formatted_phone} is already logged in",
)
# Perform the login
self.api.path("ip", "hotspot", "host").call(
"login",
user=formatted_phone,
password=password,
mac_address=mac_address,
ip_address=ip_address,
)
login_time = datetime.now()
return LoginResponse(
code=200, message="Login successful", login_time=login_time
)
except Exception as e:
self.logger.error(f"Login failed for {formatted_phone}: {e}")
return ResponseBuilder.error(LoginResponse, "Login failed", str(e))
def logout_hotspot_user(self, phone: str) -> LogoutResponse:
"""Logout a hotspot user"""
if not self.api:
return ResponseBuilder.error(
LogoutResponse, "Logout failed", "Not connected to router"
)
is_valid, formatted_phone = self.phone_validator.validate_and_format(phone)
if not is_valid:
return ResponseBuilder.error(
LogoutResponse, "Logout failed", f"Invalid phone number format: {phone}"
)
active = self.api.path("ip", "hotspot", "active")
try:
active_users = active.select(Key("user"), Key(".id"))
user_session = next(
(
session
for session in active_users
if str(session["user"]) == formatted_phone
),
None,
)
if not user_session:
return ResponseBuilder.error(
LogoutResponse,
"Logout failed",
f"User {formatted_phone} is not logged in",
)
# Logout the user
active.remove(user_session[".id"])
logout_time = datetime.now()
return LogoutResponse(
code=200, message="Logout successful", logout_time=logout_time
)
except Exception as e:
self.logger.error(f"Logout failed for user {formatted_phone}: {e}")
return ResponseBuilder.error(LogoutResponse, "Logout failed", str(e))
def get_active_users(self) -> ActiveUsersResponse:
"""Get list of active hotspot users"""
if not self.api:
return ResponseBuilder.error(
ActiveUsersResponse,
"Failed to get active users",
"Not connected to router",
)
try:
active = self.api.path("ip", "hotspot", "active")
active_users = active.select(
Key("user"),
Key("uptime"),
Key("mac-address"),
Key("address"),
Key("bytes-in"),
Key("bytes-out"),
# Key("profile"),
)
users_list = [
UserSessionData(
phone=str(user["user"]),
uptime=user["uptime"],
mac_address=user["mac-address"],
ip_address=user["address"],
bytes_in=int(user["bytes-in"]),
bytes_out=int(user["bytes-out"]),
status="active",
profile=str("2mbps_profile"),
)
for user in active_users
]
print(users_list)
return ActiveUsersResponse(
code=200,
message="Active users retrieved successfully",
active_users=users_list,
)
except Exception as e:
self.logger.error(f"Failed to get active users: {e}")
return ResponseBuilder.error(
ActiveUsersResponse, "Failed to get active users", str(e)
)
def get_user_stats(self, phone: Optional[str] = None) -> UserStatsResponse:
"""Get statistics for all users or a specific user"""
if not self.api:
return ResponseBuilder.error(
UserStatsResponse,
"Failed to get user statistics",
"Not connected to router",
)
try:
users = self.api.path("ip", "hotspot", "user")
all_users = users.select(
Key("name"),
Key("profile"),
Key("disabled"),
Key("uptime"),
Key("bytes-in"),
Key("bytes-out"),
)
user_stats = [
UserStats(
phone=str(user["name"]),
profile=user.get("profile", "default"),
status=(
"disabled"
if user.get("disabled", "false") == "true"
else "enabled"
),
uptime=user.get("uptime", "0s"),
bytes_in=int(user.get("bytes-in", 0)),
bytes_out=int(user.get("bytes-out", 0)),
)
for user in all_users
if not phone or str(user["name"]) == phone
]
if phone and not user_stats:
return ResponseBuilder.error(
UserStatsResponse, "User not found", f"User {phone} does not exist"
)
return UserStatsResponse(
code=200,
message="User statistics retrieved successfully",
user_stats=user_stats,
)
except Exception as e:
self.logger.error(f"Failed to get user statistics: {e}")
return ResponseBuilder.error(
UserStatsResponse, "Failed to get user statistics", str(e)
)
# Existing initialization, connect, and other methods...
def set_user_status(self, phone: str, disabled: bool) -> UserStatusResponse:
"""Enable or disable a hotspot user."""
if not self.api:
return ResponseBuilder.error(
UserStatusResponse, "Status update failed", "Not connected to router"
)
# Validate phone number
is_valid, formatted_phone = self.phone_validator.validate_and_format(phone)
if not is_valid:
return ResponseBuilder.error(
UserStatusResponse,
"Status update failed",
f"Invalid phone number format: {phone}",
)
users = self.api.path("ip", "hotspot", "user")
try:
# Find the user
existing_users = users.select(
Key("name"),
Key(".id"),
Key("mbonea"),
)
for user in existing_users:
if str(user["name"]) == formatted_phone:
user_data = user
break
if not user_data:
return ResponseBuilder.error(
UserStatusResponse,
"Status update failed",
f"User {formatted_phone} does not exist",
)
user_id = user_data[".id"]
status_value = "true" if disabled else "false"
users.update(**{".id": user_id, "disabled": status_value})
status = "disabled" if disabled else "enabled"
self.logger.info(f"User {formatted_phone} successfully {status}")
# If disabling the user, log them out of any active session
if disabled:
self.logout_hotspot_user(formatted_phone)
return UserStatusResponse(
code=200, message=f"User successfully {status}", user_status=status
)
except Exception as e:
print(f"Failed to update status for user {formatted_phone}: {e}")
self.logger.error(
f"Failed to update status for user {formatted_phone}: {e}"
)
return ResponseBuilder.error(
UserStatusResponse, "Status update failed", str(e)
)
def activate_user(self, phone: str) -> UserStatusResponse:
"""Activate a user (set disabled to false)."""
return self.set_user_status(phone, disabled=False)
def deactivate_user(self, phone: str) -> UserStatusResponse:
"""Deactivate a user (set disabled to true and log them out)."""
return self.set_user_status(phone, disabled=True)
def get_users_list(self) -> UsersListResponse:
"""Get list of all hotspot users (both active and inactive)"""
if not self.api:
return UsersListResponse(
success=False,
message="Failed to get users list",
error="Not connected to router",
)
try:
users = self.api.path("ip", "hotspot", "user")
active = self.api.path("ip", "hotspot", "active")
# Retrieve all users
all_users = users.select(
Key("name"),
Key("profile"),
Key("disabled"),
Key("comment"),
Key("limit-bytes-total"),
Key("bytes-in"),
Key("bytes-out"),
)
# Retrieve active users to check statuses
active_users = active.select(Key("user"))
active_phones = [str(user.get("user", "")) for user in active_users]
# Format the response
users_list = []
for user in all_users:
phone_number = str(user["name"])
bytes_in = int(user.get("bytes-in", 0))
bytes_out = int(user.get("bytes-out", 0))
data_limit = (
int(user.get("limit-bytes-total", 0))
if user.get("limit-bytes-total")
else None
)
user_data = UserListData(
phoneNumber=phone_number,
profile=user.get("profile", "default"),
status="active" if phone_number in active_phones else "inactive",
disabled=user.get("disabled", "false") == "true",
comment=user.get("comment", ""),
data_limit=data_limit,
)
users_list.append(user_data)
return UsersListResponse(
code=200,
message=f"Retrieved {len(users_list)} users",
data=users,
)
except Exception as e:
return UsersListResponse(
success=False, message="Failed to get users list", error=str(e)
)