changes
Browse files- App/Authentication.py +0 -2
- App/Mikrotik/mikrotikRoutes.py +0 -133
- App/Mikrotik/schema.py +0 -246
- App/Mikrotik/utils/Config.py +0 -10
- App/Mikrotik/utils/api.py +0 -426
- App/Mikrotik/utils/client1-working.ovpn +0 -152
- App/Mikrotik/utils/helperfx.py +0 -75
- App/app.py +1 -28
App/Authentication.py
DELETED
@@ -1,2 +0,0 @@
|
|
1 |
-
from fastapi import APIRouter, status
|
2 |
-
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
|
|
|
|
|
App/Mikrotik/mikrotikRoutes.py
DELETED
@@ -1,133 +0,0 @@
|
|
1 |
-
from fastapi import APIRouter, HTTPException, status
|
2 |
-
from typing import List, Optional
|
3 |
-
from fastapi import FastAPI, HTTPException, Depends, Body, Path
|
4 |
-
from fastapi.responses import JSONResponse
|
5 |
-
from .utils.helperfx import get_mikrotik, MikrotikAPI
|
6 |
-
from .schema import (
|
7 |
-
UserCreate,
|
8 |
-
RegisterResponse,
|
9 |
-
LogoutResponse,
|
10 |
-
UserStatsResponse,
|
11 |
-
ActiveUsersResponse,
|
12 |
-
UserStatusResponse,
|
13 |
-
)
|
14 |
-
|
15 |
-
mikrotik_router = APIRouter(tags=["Mikrotik"])
|
16 |
-
|
17 |
-
|
18 |
-
# New route to remove a user's active session
|
19 |
-
@mikrotik_router.post("/users/{phone}/remove-session")
|
20 |
-
async def remove_active_session(
|
21 |
-
phone: str = Path(..., description="User phone number"),
|
22 |
-
mikrotik: MikrotikAPI = Depends(get_mikrotik),
|
23 |
-
):
|
24 |
-
"""Remove an active session for a hotspot user"""
|
25 |
-
response = mikrotik.remove_active_session(phone)
|
26 |
-
if not response["success"]:
|
27 |
-
return JSONResponse(
|
28 |
-
status_code=400,
|
29 |
-
content=response,
|
30 |
-
)
|
31 |
-
return JSONResponse(content={"message": f"User {phone} session removed"})
|
32 |
-
|
33 |
-
|
34 |
-
@mikrotik_router.post("/users/register", response_model=RegisterResponse)
|
35 |
-
async def register_user(
|
36 |
-
user: UserCreate = Body(...), mikrotik: MikrotikAPI = Depends(get_mikrotik)
|
37 |
-
):
|
38 |
-
"""Register a new hotspot user"""
|
39 |
-
response = mikrotik.add_hotspot_user(
|
40 |
-
phone=user.phoneNumber, password=user.password, profile=user.profile
|
41 |
-
)
|
42 |
-
print(response)
|
43 |
-
if not response.code == 200:
|
44 |
-
return JSONResponse(
|
45 |
-
status_code=400,
|
46 |
-
content=response.__dict__,
|
47 |
-
)
|
48 |
-
return response.__dict__
|
49 |
-
|
50 |
-
|
51 |
-
@mikrotik_router.post("/users/logout/{phone}", response_model=LogoutResponse)
|
52 |
-
async def logout_user(
|
53 |
-
phone: str = Path(..., description="User phone number"),
|
54 |
-
mikrotik: MikrotikAPI = Depends(get_mikrotik),
|
55 |
-
):
|
56 |
-
"""Logout a hotspot user"""
|
57 |
-
response = mikrotik.logout_hotspot_user(phone)
|
58 |
-
if not response.code == 200:
|
59 |
-
return JSONResponse(
|
60 |
-
status_code=400,
|
61 |
-
content=response.__dict__,
|
62 |
-
)
|
63 |
-
return response.__dict__
|
64 |
-
|
65 |
-
|
66 |
-
@mikrotik_router.post("/users/{phone}/disable", response_model=UserStatusResponse)
|
67 |
-
async def disable_user(
|
68 |
-
phone: str = Path(..., description="User phone number"),
|
69 |
-
mikrotik: MikrotikAPI = Depends(get_mikrotik),
|
70 |
-
):
|
71 |
-
"""Disable a hotspot user"""
|
72 |
-
response = mikrotik.set_user_status(phone, disabled=True)
|
73 |
-
print(response)
|
74 |
-
if not response.code == 200:
|
75 |
-
return JSONResponse(
|
76 |
-
status_code=400,
|
77 |
-
content=response,
|
78 |
-
)
|
79 |
-
return response.__dict__
|
80 |
-
|
81 |
-
|
82 |
-
@mikrotik_router.post("/users/{phone}/enable", response_model=UserStatusResponse)
|
83 |
-
async def enable_user(
|
84 |
-
phone: str = Path(..., description="User phone number"),
|
85 |
-
mikrotik: MikrotikAPI = Depends(get_mikrotik),
|
86 |
-
):
|
87 |
-
"""Enable a hotspot user"""
|
88 |
-
response = mikrotik.set_user_status(phone, disabled=False)
|
89 |
-
if not response.code == 200:
|
90 |
-
return JSONResponse(
|
91 |
-
status_code=400,
|
92 |
-
content=response.__dict__,
|
93 |
-
)
|
94 |
-
return response.__dict__
|
95 |
-
|
96 |
-
|
97 |
-
@mikrotik_router.get("/users/active", response_model=ActiveUsersResponse)
|
98 |
-
async def get_active_users(mikrotik: MikrotikAPI = Depends(get_mikrotik)):
|
99 |
-
"""Get list of active hotspot users"""
|
100 |
-
response = mikrotik.get_active_users()
|
101 |
-
print(response)
|
102 |
-
if not response.code == 200:
|
103 |
-
return JSONResponse(
|
104 |
-
status_code=400,
|
105 |
-
content=response.__dict__,
|
106 |
-
)
|
107 |
-
return response
|
108 |
-
|
109 |
-
|
110 |
-
@mikrotik_router.get("/users/stats", response_model=UserStatsResponse)
|
111 |
-
async def get_users_stats(
|
112 |
-
phone: Optional[str] = None, mikrotik: MikrotikAPI = Depends(get_mikrotik)
|
113 |
-
):
|
114 |
-
"""Get user statistics"""
|
115 |
-
response = mikrotik.get_user_stats(phone)
|
116 |
-
if not response.code == 200:
|
117 |
-
return JSONResponse(
|
118 |
-
status_code=400,
|
119 |
-
content=response.__dict__,
|
120 |
-
)
|
121 |
-
return response.__dict__
|
122 |
-
|
123 |
-
|
124 |
-
@mikrotik_router.get("/users")
|
125 |
-
async def get_users_list(mikrotik: MikrotikAPI = Depends(get_mikrotik)):
|
126 |
-
"""Get list of all hotspot users"""
|
127 |
-
response = mikrotik.get_users_list()
|
128 |
-
if not response.code == 200:
|
129 |
-
return JSONResponse(
|
130 |
-
status_code=400,
|
131 |
-
content=response.__dict__,
|
132 |
-
)
|
133 |
-
return response
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
App/Mikrotik/schema.py
DELETED
@@ -1,246 +0,0 @@
|
|
1 |
-
from pydantic import BaseModel, Field, constr
|
2 |
-
from typing import Optional, List
|
3 |
-
from datetime import datetime
|
4 |
-
from pydantic import BaseModel, Field, validator
|
5 |
-
from typing import Optional, List, Any, Dict
|
6 |
-
from .utils.helperfx import PhoneValidator
|
7 |
-
|
8 |
-
# Extend the existing schema
|
9 |
-
|
10 |
-
# Constants for phone and MAC address patterns
|
11 |
-
PHONE_PATTERN = r"^(?:\+255|0)\d{9}$"
|
12 |
-
MAC_PATTERN = r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$"
|
13 |
-
|
14 |
-
|
15 |
-
# Register User Request
|
16 |
-
class RegisterUserRequest(BaseModel):
|
17 |
-
name: str = Field(..., max_length=100)
|
18 |
-
password: str = Field(..., max_length=100)
|
19 |
-
phoneNumber: str = Field(
|
20 |
-
...,
|
21 |
-
pattern=PHONE_PATTERN,
|
22 |
-
description="Tanzanian phone number starting with +255 or 0 followed by 9 digits",
|
23 |
-
)
|
24 |
-
mac_address: str = Field(
|
25 |
-
..., pattern=MAC_PATTERN, description="MAC address in standard format"
|
26 |
-
)
|
27 |
-
|
28 |
-
def hash_password(self):
|
29 |
-
self.password = pwd_context.hash(self.password)
|
30 |
-
|
31 |
-
class Config:
|
32 |
-
schema_extra = {
|
33 |
-
"example": {
|
34 |
-
"name": "John Doe",
|
35 |
-
"password": "StrongPassword1!",
|
36 |
-
"phoneNumber": "+255123456789",
|
37 |
-
"mac_address": "00:1A:2B:3C:4D:5E",
|
38 |
-
}
|
39 |
-
}
|
40 |
-
|
41 |
-
|
42 |
-
# Login User Request
|
43 |
-
class LoginUserRequest(BaseModel):
|
44 |
-
phoneNumber: str = Field(..., pattern=PHONE_PATTERN)
|
45 |
-
password: str
|
46 |
-
mac_address: str = Field(..., pattern=MAC_PATTERN)
|
47 |
-
|
48 |
-
|
49 |
-
# User Response with session data and additional information
|
50 |
-
class UserSessionData(BaseModel):
|
51 |
-
phone: str
|
52 |
-
mac_address: str
|
53 |
-
ip_address: Optional[str] = None
|
54 |
-
uptime: Optional[str] = None
|
55 |
-
bytes_in: int = 0
|
56 |
-
bytes_out: int = 0
|
57 |
-
status: str # Active, Inactive, Disabled, etc.
|
58 |
-
profile: str
|
59 |
-
login_time: Optional[datetime] = None
|
60 |
-
logout_time: Optional[datetime] = None
|
61 |
-
|
62 |
-
|
63 |
-
# Extended response for listing active users and all users with detailed session data
|
64 |
-
class ActiveUsersResponse(BaseModel):
|
65 |
-
code: int
|
66 |
-
message: str
|
67 |
-
active_users: List[UserSessionData]
|
68 |
-
|
69 |
-
|
70 |
-
class UsersListResponse(BaseModel):
|
71 |
-
code: int
|
72 |
-
message: str
|
73 |
-
data: list
|
74 |
-
|
75 |
-
|
76 |
-
# Add User Status Response to manage user enabling/disabling status
|
77 |
-
class UserStatusResponse(BaseModel):
|
78 |
-
code: int
|
79 |
-
message: str
|
80 |
-
user_status: str # Indicates whether the user is enabled or disabled
|
81 |
-
|
82 |
-
|
83 |
-
# Base Response Schema for standardized responses
|
84 |
-
class BaseResponse(BaseModel):
|
85 |
-
code: int
|
86 |
-
message: str
|
87 |
-
payload: Optional[dict] = None
|
88 |
-
|
89 |
-
|
90 |
-
# Login and Logout Responses with tracking for login/logout times
|
91 |
-
class LoginResponse(BaseResponse):
|
92 |
-
login_time: Optional[datetime] = None
|
93 |
-
|
94 |
-
|
95 |
-
class LogoutResponse(BaseResponse):
|
96 |
-
logout_time: Optional[datetime] = None
|
97 |
-
|
98 |
-
|
99 |
-
# Forgot Password and Reset Password Requests for account management
|
100 |
-
class ForgotPasswordRequest(BaseModel):
|
101 |
-
phoneNumber: str = Field(..., pattern=PHONE_PATTERN)
|
102 |
-
|
103 |
-
|
104 |
-
class ResetPasswordRequest(BaseModel):
|
105 |
-
phoneNumber: str = Field(..., pattern=PHONE_PATTERN)
|
106 |
-
new_password: str = Field(..., max_length=100)
|
107 |
-
|
108 |
-
|
109 |
-
# User Statistics Response to get detailed statistics for specific or all users
|
110 |
-
class UserStats(BaseModel):
|
111 |
-
phone: str
|
112 |
-
profile: str
|
113 |
-
status: str
|
114 |
-
uptime: str
|
115 |
-
bytes_in: int
|
116 |
-
bytes_out: int
|
117 |
-
|
118 |
-
|
119 |
-
class UserListData(BaseModel):
|
120 |
-
phoneNumber: str = Field(..., description="User phone number")
|
121 |
-
profile: str = Field(..., description="User profile, e.g., bandwidth limit profile")
|
122 |
-
status: str = Field(..., description="User status, either 'active' or 'inactive'")
|
123 |
-
disabled: bool = Field(..., description="Whether the user is disabled")
|
124 |
-
comment: Optional[str] = Field(
|
125 |
-
None, description="Additional comments or notes for the user"
|
126 |
-
)
|
127 |
-
data_limit: Optional[int] = Field(
|
128 |
-
None, description="Total data limit for the user in bytes"
|
129 |
-
)
|
130 |
-
|
131 |
-
|
132 |
-
class UserStatsResponse(BaseResponse):
|
133 |
-
user_stats: List[UserStats]
|
134 |
-
|
135 |
-
|
136 |
-
# Example of a response builder for structured success and error responses
|
137 |
-
class ResponseBuilder:
|
138 |
-
@staticmethod
|
139 |
-
def success(response_type: BaseModel, message: str, data: dict = None):
|
140 |
-
return response_type(code=200, message=message, payload=data)
|
141 |
-
|
142 |
-
@staticmethod
|
143 |
-
def error(response_type: BaseModel, message: str, error_details: str):
|
144 |
-
return response_type(
|
145 |
-
code=400, message=message, payload={"error": error_details}
|
146 |
-
)
|
147 |
-
|
148 |
-
|
149 |
-
# Shared Phone Number Validator
|
150 |
-
class UserBase(BaseModel):
|
151 |
-
phoneNumber: str = Field(..., description="User phone number")
|
152 |
-
|
153 |
-
@validator("phoneNumber")
|
154 |
-
def validate_phone(cls, phoneNumber):
|
155 |
-
is_valid, formatted_phone = PhoneValidator.validate_and_format(phoneNumber)
|
156 |
-
if not is_valid:
|
157 |
-
raise ValueError("Invalid phone number format")
|
158 |
-
return formatted_phone
|
159 |
-
|
160 |
-
|
161 |
-
# Request Models
|
162 |
-
class UserCreate(UserBase):
|
163 |
-
password: str = Field(..., min_length=4, description="User password")
|
164 |
-
profile: str = Field(default="2mbps_profile", description="User profile")
|
165 |
-
|
166 |
-
|
167 |
-
class UserLogin(UserBase):
|
168 |
-
password: str = Field(..., description="User password")
|
169 |
-
mac_address: str = Field(..., description="Device MAC address")
|
170 |
-
ip_address: str = Field(..., description="User's IP address")
|
171 |
-
|
172 |
-
|
173 |
-
# Base API Response Model
|
174 |
-
class ApiResponse(BaseModel):
|
175 |
-
success: bool = True
|
176 |
-
message: Optional[str] = Field("success", description="Response message")
|
177 |
-
error: Optional[str] = None
|
178 |
-
data: Optional[Dict[str, Any]] = None
|
179 |
-
|
180 |
-
|
181 |
-
# Detailed User Models
|
182 |
-
class UserParams(BaseModel):
|
183 |
-
name: str
|
184 |
-
password: str
|
185 |
-
profile: str
|
186 |
-
disabled: bool
|
187 |
-
|
188 |
-
|
189 |
-
class ActiveUserData(BaseModel):
|
190 |
-
phone: str
|
191 |
-
uptime: str
|
192 |
-
mac_address: str
|
193 |
-
ip_address: str
|
194 |
-
usage: UserStats
|
195 |
-
|
196 |
-
|
197 |
-
class UserStatsData(BaseModel):
|
198 |
-
phoneNumber: str
|
199 |
-
profile: str
|
200 |
-
status: str # Enabled or Disabled
|
201 |
-
uptime: str
|
202 |
-
usage: UserStats
|
203 |
-
|
204 |
-
|
205 |
-
class UserListData(BaseModel):
|
206 |
-
phoneNumber: str
|
207 |
-
profile: str
|
208 |
-
status: str # Active or Inactive
|
209 |
-
disabled: bool
|
210 |
-
comment: Optional[str] = ""
|
211 |
-
data_limit: Optional[int] = None
|
212 |
-
|
213 |
-
|
214 |
-
# Specific Response Models
|
215 |
-
class RegisterResponse(ApiResponse):
|
216 |
-
data: Optional[Dict[str, Any]] = Field(None, description="Registration details")
|
217 |
-
|
218 |
-
|
219 |
-
class LoginResponse(ApiResponse):
|
220 |
-
data: Optional[Dict[str, Any]] = Field(None, description="Login details")
|
221 |
-
|
222 |
-
|
223 |
-
class LogoutResponse(ApiResponse):
|
224 |
-
data: Optional[Dict[str, Any]] = Field(None, description="Logout details")
|
225 |
-
|
226 |
-
|
227 |
-
class UserStatusResponse(ApiResponse):
|
228 |
-
data: Optional[Dict[str, Any]] = Field(
|
229 |
-
None, description="User status change details"
|
230 |
-
)
|
231 |
-
|
232 |
-
|
233 |
-
class ActiveUsersResponse(ApiResponse):
|
234 |
-
active_users: List[UserSessionData]
|
235 |
-
|
236 |
-
|
237 |
-
class UserStatsResponse(ApiResponse):
|
238 |
-
data: Optional[Dict[str, List[UserStatsData]]] = Field(
|
239 |
-
None, description="Statistics for users"
|
240 |
-
)
|
241 |
-
|
242 |
-
|
243 |
-
class UsersListResponse(ApiResponse):
|
244 |
-
data: Optional[Dict[str, List[UserListData]]] = Field(
|
245 |
-
None, description="List of all users with details"
|
246 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
App/Mikrotik/utils/Config.py
DELETED
@@ -1,10 +0,0 @@
|
|
1 |
-
from dataclasses import dataclass
|
2 |
-
|
3 |
-
|
4 |
-
@dataclass
|
5 |
-
class RouterConfig:
|
6 |
-
"""Class to store router configuration settings"""
|
7 |
-
|
8 |
-
host: str = "10.8.0.6"
|
9 |
-
username: str = "admin"
|
10 |
-
password: str = "G4TZ7QFJTW"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
App/Mikrotik/utils/api.py
DELETED
@@ -1,426 +0,0 @@
|
|
1 |
-
from librouteros import connect
|
2 |
-
from librouteros.query import Key
|
3 |
-
import logging
|
4 |
-
from typing import Optional
|
5 |
-
from datetime import datetime
|
6 |
-
from ..schema import *
|
7 |
-
from ..schema import UserListData
|
8 |
-
from .helperfx import PhoneValidator
|
9 |
-
from .Config import RouterConfig
|
10 |
-
from time import sleep
|
11 |
-
|
12 |
-
|
13 |
-
class MikrotikAPI:
|
14 |
-
"""Class to handle MikroTik router API operations, inherits VPNManager."""
|
15 |
-
|
16 |
-
def __init__(
|
17 |
-
self,
|
18 |
-
config: RouterConfig,
|
19 |
-
):
|
20 |
-
"""Initialize with router configuration and VPN configuration."""
|
21 |
-
self.config = config
|
22 |
-
self.api = None
|
23 |
-
self.logger = self._setup_logging()
|
24 |
-
self.phone_validator = PhoneValidator()
|
25 |
-
|
26 |
-
def connect(self) -> bool:
|
27 |
-
"""Establish connection to VPN and MikroTik router."""
|
28 |
-
|
29 |
-
try:
|
30 |
-
self.api = connect(
|
31 |
-
username=self.config.username,
|
32 |
-
password=self.config.password,
|
33 |
-
host=self.config.host,
|
34 |
-
port=8728,
|
35 |
-
)
|
36 |
-
self.logger.info("Successfully connected to MikroTik router.")
|
37 |
-
return True
|
38 |
-
except Exception as e:
|
39 |
-
self.logger.error(f"Failed to connect to MikroTik router: {e}")
|
40 |
-
return False
|
41 |
-
|
42 |
-
def close(self):
|
43 |
-
"""Close the router connection and disconnect VPN."""
|
44 |
-
if self.api:
|
45 |
-
self.api.close()
|
46 |
-
self.logger.info("Router connection closed.")
|
47 |
-
|
48 |
-
def add_hotspot_user(
|
49 |
-
self, phone: str, password: str, profile: str = "default"
|
50 |
-
) -> BaseResponse:
|
51 |
-
"""Add a hotspot user to the router"""
|
52 |
-
if not self.api:
|
53 |
-
return ResponseBuilder.error(
|
54 |
-
BaseResponse, "Registration failed", "Not connected to router"
|
55 |
-
)
|
56 |
-
|
57 |
-
# Validate phone number
|
58 |
-
is_valid, formatted_phone = self.phone_validator.validate_and_format(phone)
|
59 |
-
if not is_valid:
|
60 |
-
return ResponseBuilder.error(
|
61 |
-
BaseResponse,
|
62 |
-
"Registration failed",
|
63 |
-
f"Invalid phone number format: {phone}",
|
64 |
-
)
|
65 |
-
|
66 |
-
users = self.api.path("ip", "hotspot", "user")
|
67 |
-
existing_users = users.select(Key("name"))
|
68 |
-
|
69 |
-
# Check if user already exists
|
70 |
-
if any(str(user["name"]) == formatted_phone for user in existing_users):
|
71 |
-
return ResponseBuilder.error(
|
72 |
-
BaseResponse,
|
73 |
-
"Registration failed",
|
74 |
-
f"User {formatted_phone} already exists",
|
75 |
-
)
|
76 |
-
|
77 |
-
try:
|
78 |
-
# Add user to router
|
79 |
-
users.add(
|
80 |
-
name=formatted_phone,
|
81 |
-
password=password,
|
82 |
-
profile=profile,
|
83 |
-
disabled=True,
|
84 |
-
)
|
85 |
-
return ResponseBuilder.success(BaseResponse, "User registered successfully")
|
86 |
-
|
87 |
-
except Exception as e:
|
88 |
-
self.logger.error(f"Failed to add user {formatted_phone}: {e}")
|
89 |
-
return ResponseBuilder.error(BaseResponse, "Registration failed", str(e))
|
90 |
-
|
91 |
-
def login_hotspot_user(
|
92 |
-
self, phone: str, password: str, mac_address: str, ip_address: str
|
93 |
-
) -> LoginResponse:
|
94 |
-
"""Login a hotspot user"""
|
95 |
-
if not self.api:
|
96 |
-
return ResponseBuilder.error(
|
97 |
-
LoginResponse, "Login failed", "Not connected to router"
|
98 |
-
)
|
99 |
-
|
100 |
-
is_valid, formatted_phone = self.phone_validator.validate_and_format(phone)
|
101 |
-
if not is_valid:
|
102 |
-
return ResponseBuilder.error(
|
103 |
-
LoginResponse, "Login failed", f"Invalid phone number format: {phone}"
|
104 |
-
)
|
105 |
-
|
106 |
-
try:
|
107 |
-
users = self.api.path("ip", "hotspot", "user")
|
108 |
-
active = self.api.path("ip", "hotspot", "active")
|
109 |
-
|
110 |
-
# Verify if user exists and password is correct
|
111 |
-
existing_users = users.select(Key("name"), Key("password"), Key("profile"))
|
112 |
-
user_data = next(
|
113 |
-
(
|
114 |
-
user
|
115 |
-
for user in existing_users
|
116 |
-
if str(user["name"]) == formatted_phone
|
117 |
-
and user["password"] == password
|
118 |
-
),
|
119 |
-
None,
|
120 |
-
)
|
121 |
-
|
122 |
-
if not user_data:
|
123 |
-
return ResponseBuilder.error(
|
124 |
-
LoginResponse, "Login failed", "Invalid credentials"
|
125 |
-
)
|
126 |
-
|
127 |
-
# Check if already logged in
|
128 |
-
active_users = active.select(Key("user"))
|
129 |
-
if any(str(user["user"]) == formatted_phone for user in active_users):
|
130 |
-
return ResponseBuilder.error(
|
131 |
-
LoginResponse,
|
132 |
-
"Login failed",
|
133 |
-
f"User {formatted_phone} is already logged in",
|
134 |
-
)
|
135 |
-
|
136 |
-
# Perform the login
|
137 |
-
self.api.path("ip", "hotspot", "host").call(
|
138 |
-
"login",
|
139 |
-
user=formatted_phone,
|
140 |
-
password=password,
|
141 |
-
mac_address=mac_address,
|
142 |
-
ip_address=ip_address,
|
143 |
-
)
|
144 |
-
login_time = datetime.now()
|
145 |
-
return LoginResponse(
|
146 |
-
code=200, message="Login successful", login_time=login_time
|
147 |
-
)
|
148 |
-
|
149 |
-
except Exception as e:
|
150 |
-
self.logger.error(f"Login failed for {formatted_phone}: {e}")
|
151 |
-
return ResponseBuilder.error(LoginResponse, "Login failed", str(e))
|
152 |
-
|
153 |
-
def logout_hotspot_user(self, phone: str) -> LogoutResponse:
|
154 |
-
"""Logout a hotspot user"""
|
155 |
-
if not self.api:
|
156 |
-
return ResponseBuilder.error(
|
157 |
-
LogoutResponse, "Logout failed", "Not connected to router"
|
158 |
-
)
|
159 |
-
|
160 |
-
is_valid, formatted_phone = self.phone_validator.validate_and_format(phone)
|
161 |
-
if not is_valid:
|
162 |
-
return ResponseBuilder.error(
|
163 |
-
LogoutResponse, "Logout failed", f"Invalid phone number format: {phone}"
|
164 |
-
)
|
165 |
-
|
166 |
-
active = self.api.path("ip", "hotspot", "active")
|
167 |
-
|
168 |
-
try:
|
169 |
-
active_users = active.select(Key("user"), Key(".id"))
|
170 |
-
user_session = next(
|
171 |
-
(
|
172 |
-
session
|
173 |
-
for session in active_users
|
174 |
-
if str(session["user"]) == formatted_phone
|
175 |
-
),
|
176 |
-
None,
|
177 |
-
)
|
178 |
-
|
179 |
-
if not user_session:
|
180 |
-
return ResponseBuilder.error(
|
181 |
-
LogoutResponse,
|
182 |
-
"Logout failed",
|
183 |
-
f"User {formatted_phone} is not logged in",
|
184 |
-
)
|
185 |
-
|
186 |
-
# Logout the user
|
187 |
-
active.remove(user_session[".id"])
|
188 |
-
logout_time = datetime.now()
|
189 |
-
return LogoutResponse(
|
190 |
-
code=200, message="Logout successful", logout_time=logout_time
|
191 |
-
)
|
192 |
-
|
193 |
-
except Exception as e:
|
194 |
-
self.logger.error(f"Logout failed for user {formatted_phone}: {e}")
|
195 |
-
return ResponseBuilder.error(LogoutResponse, "Logout failed", str(e))
|
196 |
-
|
197 |
-
def get_active_users(self) -> ActiveUsersResponse:
|
198 |
-
"""Get list of active hotspot users"""
|
199 |
-
if not self.api:
|
200 |
-
return ResponseBuilder.error(
|
201 |
-
ActiveUsersResponse,
|
202 |
-
"Failed to get active users",
|
203 |
-
"Not connected to router",
|
204 |
-
)
|
205 |
-
|
206 |
-
try:
|
207 |
-
active = self.api.path("ip", "hotspot", "active")
|
208 |
-
active_users = active.select(
|
209 |
-
Key("user"),
|
210 |
-
Key("uptime"),
|
211 |
-
Key("mac-address"),
|
212 |
-
Key("address"),
|
213 |
-
Key("bytes-in"),
|
214 |
-
Key("bytes-out"),
|
215 |
-
# Key("profile"),
|
216 |
-
)
|
217 |
-
|
218 |
-
users_list = [
|
219 |
-
UserSessionData(
|
220 |
-
phone=str(user["user"]),
|
221 |
-
uptime=user["uptime"],
|
222 |
-
mac_address=user["mac-address"],
|
223 |
-
ip_address=user["address"],
|
224 |
-
bytes_in=int(user["bytes-in"]),
|
225 |
-
bytes_out=int(user["bytes-out"]),
|
226 |
-
status="active",
|
227 |
-
profile=str("2mbps_profile"),
|
228 |
-
)
|
229 |
-
for user in active_users
|
230 |
-
]
|
231 |
-
print(users_list)
|
232 |
-
return ActiveUsersResponse(
|
233 |
-
code=200,
|
234 |
-
message="Active users retrieved successfully",
|
235 |
-
active_users=users_list,
|
236 |
-
)
|
237 |
-
|
238 |
-
except Exception as e:
|
239 |
-
self.logger.error(f"Failed to get active users: {e}")
|
240 |
-
return ResponseBuilder.error(
|
241 |
-
ActiveUsersResponse, "Failed to get active users", str(e)
|
242 |
-
)
|
243 |
-
|
244 |
-
def get_user_stats(self, phone: Optional[str] = None) -> UserStatsResponse:
|
245 |
-
"""Get statistics for all users or a specific user"""
|
246 |
-
if not self.api:
|
247 |
-
return ResponseBuilder.error(
|
248 |
-
UserStatsResponse,
|
249 |
-
"Failed to get user statistics",
|
250 |
-
"Not connected to router",
|
251 |
-
)
|
252 |
-
|
253 |
-
try:
|
254 |
-
users = self.api.path("ip", "hotspot", "user")
|
255 |
-
all_users = users.select(
|
256 |
-
Key("name"),
|
257 |
-
Key("profile"),
|
258 |
-
Key("disabled"),
|
259 |
-
Key("uptime"),
|
260 |
-
Key("bytes-in"),
|
261 |
-
Key("bytes-out"),
|
262 |
-
)
|
263 |
-
|
264 |
-
user_stats = [
|
265 |
-
UserStats(
|
266 |
-
phone=str(user["name"]),
|
267 |
-
profile=user.get("profile", "default"),
|
268 |
-
status=(
|
269 |
-
"disabled"
|
270 |
-
if user.get("disabled", "false") == "true"
|
271 |
-
else "enabled"
|
272 |
-
),
|
273 |
-
uptime=user.get("uptime", "0s"),
|
274 |
-
bytes_in=int(user.get("bytes-in", 0)),
|
275 |
-
bytes_out=int(user.get("bytes-out", 0)),
|
276 |
-
)
|
277 |
-
for user in all_users
|
278 |
-
if not phone or str(user["name"]) == phone
|
279 |
-
]
|
280 |
-
|
281 |
-
if phone and not user_stats:
|
282 |
-
return ResponseBuilder.error(
|
283 |
-
UserStatsResponse, "User not found", f"User {phone} does not exist"
|
284 |
-
)
|
285 |
-
|
286 |
-
return UserStatsResponse(
|
287 |
-
code=200,
|
288 |
-
message="User statistics retrieved successfully",
|
289 |
-
user_stats=user_stats,
|
290 |
-
)
|
291 |
-
|
292 |
-
except Exception as e:
|
293 |
-
self.logger.error(f"Failed to get user statistics: {e}")
|
294 |
-
return ResponseBuilder.error(
|
295 |
-
UserStatsResponse, "Failed to get user statistics", str(e)
|
296 |
-
)
|
297 |
-
|
298 |
-
# Existing initialization, connect, and other methods...
|
299 |
-
|
300 |
-
def set_user_status(self, phone: str, disabled: bool) -> UserStatusResponse:
|
301 |
-
"""Enable or disable a hotspot user."""
|
302 |
-
if not self.api:
|
303 |
-
return ResponseBuilder.error(
|
304 |
-
UserStatusResponse, "Status update failed", "Not connected to router"
|
305 |
-
)
|
306 |
-
|
307 |
-
# Validate phone number
|
308 |
-
is_valid, formatted_phone = self.phone_validator.validate_and_format(phone)
|
309 |
-
if not is_valid:
|
310 |
-
return ResponseBuilder.error(
|
311 |
-
UserStatusResponse,
|
312 |
-
"Status update failed",
|
313 |
-
f"Invalid phone number format: {phone}",
|
314 |
-
)
|
315 |
-
users = self.api.path("ip", "hotspot", "user")
|
316 |
-
|
317 |
-
try:
|
318 |
-
# Find the user
|
319 |
-
existing_users = users.select(
|
320 |
-
Key("name"),
|
321 |
-
Key(".id"),
|
322 |
-
Key("mbonea"),
|
323 |
-
)
|
324 |
-
for user in existing_users:
|
325 |
-
if str(user["name"]) == formatted_phone:
|
326 |
-
user_data = user
|
327 |
-
break
|
328 |
-
|
329 |
-
if not user_data:
|
330 |
-
return ResponseBuilder.error(
|
331 |
-
UserStatusResponse,
|
332 |
-
"Status update failed",
|
333 |
-
f"User {formatted_phone} does not exist",
|
334 |
-
)
|
335 |
-
|
336 |
-
user_id = user_data[".id"]
|
337 |
-
status_value = "true" if disabled else "false"
|
338 |
-
users.update(**{".id": user_id, "disabled": status_value})
|
339 |
-
status = "disabled" if disabled else "enabled"
|
340 |
-
self.logger.info(f"User {formatted_phone} successfully {status}")
|
341 |
-
|
342 |
-
# If disabling the user, log them out of any active session
|
343 |
-
if disabled:
|
344 |
-
self.logout_hotspot_user(formatted_phone)
|
345 |
-
|
346 |
-
return UserStatusResponse(
|
347 |
-
code=200, message=f"User successfully {status}", user_status=status
|
348 |
-
)
|
349 |
-
|
350 |
-
except Exception as e:
|
351 |
-
print(f"Failed to update status for user {formatted_phone}: {e}")
|
352 |
-
self.logger.error(
|
353 |
-
f"Failed to update status for user {formatted_phone}: {e}"
|
354 |
-
)
|
355 |
-
return ResponseBuilder.error(
|
356 |
-
UserStatusResponse, "Status update failed", str(e)
|
357 |
-
)
|
358 |
-
|
359 |
-
def activate_user(self, phone: str) -> UserStatusResponse:
|
360 |
-
"""Activate a user (set disabled to false)."""
|
361 |
-
return self.set_user_status(phone, disabled=False)
|
362 |
-
|
363 |
-
def deactivate_user(self, phone: str) -> UserStatusResponse:
|
364 |
-
"""Deactivate a user (set disabled to true and log them out)."""
|
365 |
-
return self.set_user_status(phone, disabled=True)
|
366 |
-
|
367 |
-
def get_users_list(self) -> UsersListResponse:
|
368 |
-
"""Get list of all hotspot users (both active and inactive)"""
|
369 |
-
if not self.api:
|
370 |
-
return UsersListResponse(
|
371 |
-
success=False,
|
372 |
-
message="Failed to get users list",
|
373 |
-
error="Not connected to router",
|
374 |
-
)
|
375 |
-
|
376 |
-
try:
|
377 |
-
users = self.api.path("ip", "hotspot", "user")
|
378 |
-
active = self.api.path("ip", "hotspot", "active")
|
379 |
-
|
380 |
-
# Retrieve all users
|
381 |
-
all_users = users.select(
|
382 |
-
Key("name"),
|
383 |
-
Key("profile"),
|
384 |
-
Key("disabled"),
|
385 |
-
Key("comment"),
|
386 |
-
Key("limit-bytes-total"),
|
387 |
-
Key("bytes-in"),
|
388 |
-
Key("bytes-out"),
|
389 |
-
)
|
390 |
-
|
391 |
-
# Retrieve active users to check statuses
|
392 |
-
active_users = active.select(Key("user"))
|
393 |
-
active_phones = [str(user.get("user", "")) for user in active_users]
|
394 |
-
|
395 |
-
# Format the response
|
396 |
-
users_list = []
|
397 |
-
for user in all_users:
|
398 |
-
phone_number = str(user["name"])
|
399 |
-
bytes_in = int(user.get("bytes-in", 0))
|
400 |
-
bytes_out = int(user.get("bytes-out", 0))
|
401 |
-
data_limit = (
|
402 |
-
int(user.get("limit-bytes-total", 0))
|
403 |
-
if user.get("limit-bytes-total")
|
404 |
-
else None
|
405 |
-
)
|
406 |
-
|
407 |
-
user_data = UserListData(
|
408 |
-
phoneNumber=phone_number,
|
409 |
-
profile=user.get("profile", "default"),
|
410 |
-
status="active" if phone_number in active_phones else "inactive",
|
411 |
-
disabled=user.get("disabled", "false") == "true",
|
412 |
-
comment=user.get("comment", ""),
|
413 |
-
data_limit=data_limit,
|
414 |
-
)
|
415 |
-
users_list.append(user_data)
|
416 |
-
|
417 |
-
return UsersListResponse(
|
418 |
-
code=200,
|
419 |
-
message=f"Retrieved {len(users_list)} users",
|
420 |
-
data=users,
|
421 |
-
)
|
422 |
-
|
423 |
-
except Exception as e:
|
424 |
-
return UsersListResponse(
|
425 |
-
success=False, message="Failed to get users list", error=str(e)
|
426 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
App/Mikrotik/utils/client1-working.ovpn
DELETED
@@ -1,152 +0,0 @@
|
|
1 |
-
client
|
2 |
-
dev tun
|
3 |
-
proto tcp
|
4 |
-
remote 3.76.188.80 1194
|
5 |
-
resolv-retry infinite
|
6 |
-
nobind
|
7 |
-
persist-key
|
8 |
-
persist-tun
|
9 |
-
remote-cert-tls server
|
10 |
-
auth SHA1
|
11 |
-
cipher AES-256-CBC
|
12 |
-
data-ciphers AES-256-CBC
|
13 |
-
verb 3
|
14 |
-
|
15 |
-
<ca>
|
16 |
-
-----BEGIN CERTIFICATE-----
|
17 |
-
MIIDSzCCAjOgAwIBAgIURVMIu580Y2d7neve8yFnlspmVSkwDQYJKoZIhvcNAQEL
|
18 |
-
BQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMjQxMTIzMTYwODUzWhcNMzQx
|
19 |
-
MTIxMTYwODUzWjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcN
|
20 |
-
AQEBBQADggEPADCCAQoCggEBANcRjNoLIaWYkXXd1Th/24sXAkse4xK6FMUqaV6Y
|
21 |
-
gM8WPaepF4muyYrwDFgC+IloicFKqtfle5qL02MH16UqIJpNvulgMSgp4YwgzEtV
|
22 |
-
FZY4mzvE4ZjMc54tW8gf22R+iMFPumRnMtZuPq4U8i5lpKcZ1X5V7X/0Sc2aPbwO
|
23 |
-
LckbWkZhZdoVbJO8XhBo133Cur0FMnfEA/9dlrQ5ZMpj7xdO6Kpz/scmtw4JQ36q
|
24 |
-
mAF77LYYogiA/QvyxBAXv1UoElucIdIgMFQ8PiwBKi+FfShHlVr+m5OLlNN71eoC
|
25 |
-
N6TUOqtT4o3hLGGK31YWjoeSIJtvF251G+c3V99FecORdL0CAwEAAaOBkDCBjTAM
|
26 |
-
BgNVHRMEBTADAQH/MB0GA1UdDgQWBBRm5QQIqG+pv1VNdHI6tyuTz44HjzBRBgNV
|
27 |
-
HSMESjBIgBRm5QQIqG+pv1VNdHI6tyuTz44Hj6EapBgwFjEUMBIGA1UEAwwLRWFz
|
28 |
-
eS1SU0EgQ0GCFEVTCLufNGNne53r3vMhZ5bKZlUpMAsGA1UdDwQEAwIBBjANBgkq
|
29 |
-
hkiG9w0BAQsFAAOCAQEAIYaeBqeDLmdcqNZYP5Sf5h7NN32R9+MHkP2ZQ/MAgxjy
|
30 |
-
Ozr26L21t5jvuEw6qr+kix4pEnWnud9xTBeaTsO3TLjSrp0J0o5KBuwlXmFw1hr8
|
31 |
-
L8nJ52FlqpTv2FvhCLX5L+f5i6tL4Q5bC3AlD17eM1RWza6969NaAc6Gzl9I1sgj
|
32 |
-
3WHyKw6bnx4NTGyjCBnDH+gUK9htzVIpr31b3fEySg8U6eYbvDlX9jDZjV6Mnjrw
|
33 |
-
DvuiOZONMcmzlZvUTvHoOQHq5yIl8AG0CtaTd5YT2ksmMw18m6K/mMIwzzgENIiI
|
34 |
-
Zdf9tUqqzy+ckHTcCM8Bx0x0MUXsWzWJTs8pHmWYTQ==
|
35 |
-
-----END CERTIFICATE-----
|
36 |
-
</ca>
|
37 |
-
<cert>
|
38 |
-
Certificate:
|
39 |
-
Data:
|
40 |
-
Version: 3 (0x2)
|
41 |
-
Serial Number:
|
42 |
-
59:94:39:5f:89:05:2e:c7:bd:d4:8b:da:b3:2c:88:24
|
43 |
-
Signature Algorithm: sha256WithRSAEncryption
|
44 |
-
Issuer: CN=Easy-RSA CA
|
45 |
-
Validity
|
46 |
-
Not Before: Nov 23 16:09:16 2024 GMT
|
47 |
-
Not After : Feb 26 16:09:16 2027 GMT
|
48 |
-
Subject: CN=client1
|
49 |
-
Subject Public Key Info:
|
50 |
-
Public Key Algorithm: rsaEncryption
|
51 |
-
Public-Key: (2048 bit)
|
52 |
-
Modulus:
|
53 |
-
00:c7:2e:e4:e9:41:1e:2f:47:8f:3f:a5:59:d4:d2:
|
54 |
-
47:04:46:2b:fb:47:91:ce:5c:9d:94:6a:a6:99:28:
|
55 |
-
1d:73:ec:56:ad:07:7e:5c:42:ba:15:2d:f9:f9:dd:
|
56 |
-
67:d8:a4:ae:47:bb:21:db:de:9a:69:8b:fe:8d:8d:
|
57 |
-
91:dd:90:12:98:16:c6:e5:e5:36:85:4d:e0:f0:31:
|
58 |
-
07:bd:22:0b:c3:ad:07:d1:91:98:07:38:a9:b6:db:
|
59 |
-
0f:c8:a1:ed:16:2e:ff:f4:f1:d3:b8:ea:a4:44:f5:
|
60 |
-
2c:21:64:c5:b0:7c:6a:87:2d:28:3d:03:71:d1:12:
|
61 |
-
9d:be:9c:ed:e9:f8:fb:de:de:8e:d2:2d:39:de:f1:
|
62 |
-
23:36:a3:b1:74:03:97:61:db:90:bc:04:26:94:61:
|
63 |
-
d9:ee:62:7b:a7:d3:b7:f5:e8:f6:5d:e4:8b:0d:bf:
|
64 |
-
f9:cc:bb:f9:32:b2:2d:05:05:4c:29:cf:fb:89:5e:
|
65 |
-
dd:40:74:66:5d:04:16:7a:85:31:e2:ce:47:83:9c:
|
66 |
-
ff:72:8c:26:24:c2:94:9e:6c:6c:ce:fb:b2:9d:07:
|
67 |
-
dd:58:39:7b:05:fe:f5:0e:cd:98:97:9f:d9:c2:de:
|
68 |
-
a8:08:59:4a:c9:d2:c9:4f:b8:b3:69:1e:84:8c:e4:
|
69 |
-
d4:41:5a:2a:7f:43:13:04:b4:be:ae:ca:26:bb:a6:
|
70 |
-
9b:33
|
71 |
-
Exponent: 65537 (0x10001)
|
72 |
-
X509v3 extensions:
|
73 |
-
X509v3 Basic Constraints:
|
74 |
-
CA:FALSE
|
75 |
-
X509v3 Subject Key Identifier:
|
76 |
-
83:3E:E4:7C:7D:93:EE:E0:20:2F:CF:FB:E0:D4:90:D9:C5:26:5D:04
|
77 |
-
X509v3 Authority Key Identifier:
|
78 |
-
keyid:66:E5:04:08:A8:6F:A9:BF:55:4D:74:72:3A:B7:2B:93:CF:8E:07:8F
|
79 |
-
DirName:/CN=Easy-RSA CA
|
80 |
-
serial:45:53:08:BB:9F:34:63:67:7B:9D:EB:DE:F3:21:67:96:CA:66:55:29
|
81 |
-
X509v3 Extended Key Usage:
|
82 |
-
TLS Web Client Authentication
|
83 |
-
X509v3 Key Usage:
|
84 |
-
Digital Signature
|
85 |
-
Signature Algorithm: sha256WithRSAEncryption
|
86 |
-
Signature Value:
|
87 |
-
ba:36:55:f9:d6:9c:3f:81:ce:4f:27:36:8c:d6:d3:df:69:f9:
|
88 |
-
b7:e7:34:85:33:4b:c8:71:24:63:64:10:a8:69:6a:02:78:0f:
|
89 |
-
8c:c4:81:df:8b:70:c3:75:9b:77:00:48:bd:5d:0b:91:6a:bc:
|
90 |
-
13:c1:af:99:4c:ab:59:81:05:ca:89:9b:1d:1c:94:b0:3a:f6:
|
91 |
-
27:da:be:7b:f8:7f:6a:7b:37:01:f3:f6:9f:35:73:25:54:72:
|
92 |
-
b7:de:ae:a5:a5:98:0b:84:14:60:03:34:d3:d2:e6:07:8b:bb:
|
93 |
-
2c:65:50:4f:2a:07:b5:94:96:3e:58:e4:b7:c2:52:56:b6:9e:
|
94 |
-
23:e4:56:bb:e2:59:ba:38:c8:5d:f0:ac:21:ab:f0:4a:83:ef:
|
95 |
-
7f:a4:b0:4f:0a:22:fc:c7:aa:9f:47:28:2f:7f:70:e9:4e:12:
|
96 |
-
36:7d:f6:e3:97:6a:5f:90:7e:fc:91:26:8f:0f:1b:d0:85:a2:
|
97 |
-
6e:ed:b8:1b:00:8d:67:1b:0d:06:a9:a5:d1:4f:2b:4c:f5:ac:
|
98 |
-
8e:4f:6b:18:0e:7d:77:1a:45:7b:79:f0:ca:8b:e8:23:0e:aa:
|
99 |
-
e8:3e:14:3e:e5:ba:11:b7:74:a4:6c:89:af:b3:f7:1d:af:6d:
|
100 |
-
9b:69:30:97:6a:28:af:05:ae:4c:d8:d0:c3:53:cd:57:56:33:
|
101 |
-
8f:ae:e9:04
|
102 |
-
-----BEGIN CERTIFICATE-----
|
103 |
-
MIIDVTCCAj2gAwIBAgIQWZQ5X4kFLse91IvasyyIJDANBgkqhkiG9w0BAQsFADAW
|
104 |
-
MRQwEgYDVQQDDAtFYXN5LVJTQSBDQTAeFw0yNDExMjMxNjA5MTZaFw0yNzAyMjYx
|
105 |
-
NjA5MTZaMBIxEDAOBgNVBAMMB2NsaWVudDEwggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
106 |
-
DwAwggEKAoIBAQDHLuTpQR4vR48/pVnU0kcERiv7R5HOXJ2UaqaZKB1z7FatB35c
|
107 |
-
QroVLfn53WfYpK5HuyHb3pppi/6NjZHdkBKYFsbl5TaFTeDwMQe9IgvDrQfRkZgH
|
108 |
-
OKm22w/Ioe0WLv/08dO46qRE9SwhZMWwfGqHLSg9A3HREp2+nO3p+Pve3o7SLTne
|
109 |
-
8SM2o7F0A5dh25C8BCaUYdnuYnun07f16PZd5IsNv/nMu/kysi0FBUwpz/uJXt1A
|
110 |
-
dGZdBBZ6hTHizkeDnP9yjCYkwpSebGzO+7KdB91YOXsF/vUOzZiXn9nC3qgIWUrJ
|
111 |
-
0slPuLNpHoSM5NRBWip/QxMEtL6uyia7ppszAgMBAAGjgaIwgZ8wCQYDVR0TBAIw
|
112 |
-
ADAdBgNVHQ4EFgQUgz7kfH2T7uAgL8/74NSQ2cUmXQQwUQYDVR0jBEowSIAUZuUE
|
113 |
-
CKhvqb9VTXRyOrcrk8+OB4+hGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghRF
|
114 |
-
Uwi7nzRjZ3ud697zIWeWymZVKTATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNVHQ8E
|
115 |
-
BAMCB4AwDQYJKoZIhvcNAQELBQADggEBALo2VfnWnD+Bzk8nNozW099p+bfnNIUz
|
116 |
-
S8hxJGNkEKhpagJ4D4zEgd+LcMN1m3cASL1dC5FqvBPBr5lMq1mBBcqJmx0clLA6
|
117 |
-
9ifavnv4f2p7NwHz9p81cyVUcrferqWlmAuEFGADNNPS5geLuyxlUE8qB7WUlj5Y
|
118 |
-
5LfCUla2niPkVrviWbo4yF3wrCGr8EqD73+ksE8KIvzHqp9HKC9/cOlOEjZ99uOX
|
119 |
-
al+QfvyRJo8PG9CFom7tuBsAjWcbDQappdFPK0z1rI5PaxgOfXcaRXt58MqL6CMO
|
120 |
-
qug+FD7luhG3dKRsia+z9x2vbZtpMJdqKK8FrkzY0MNTzVdWM4+u6QQ=
|
121 |
-
-----END CERTIFICATE-----
|
122 |
-
</cert>
|
123 |
-
<key>
|
124 |
-
-----BEGIN PRIVATE KEY-----
|
125 |
-
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDHLuTpQR4vR48/
|
126 |
-
pVnU0kcERiv7R5HOXJ2UaqaZKB1z7FatB35cQroVLfn53WfYpK5HuyHb3pppi/6N
|
127 |
-
jZHdkBKYFsbl5TaFTeDwMQe9IgvDrQfRkZgHOKm22w/Ioe0WLv/08dO46qRE9Swh
|
128 |
-
ZMWwfGqHLSg9A3HREp2+nO3p+Pve3o7SLTne8SM2o7F0A5dh25C8BCaUYdnuYnun
|
129 |
-
07f16PZd5IsNv/nMu/kysi0FBUwpz/uJXt1AdGZdBBZ6hTHizkeDnP9yjCYkwpSe
|
130 |
-
bGzO+7KdB91YOXsF/vUOzZiXn9nC3qgIWUrJ0slPuLNpHoSM5NRBWip/QxMEtL6u
|
131 |
-
yia7ppszAgMBAAECggEAIF40gNs+JnzAgJ1EPdt2AvHMT+dPgHN4gBfcvuLP9nif
|
132 |
-
lTq0hBWr26k/CCW8rG4GjE2SsQI5oZFIaoRpAdJZ0zFQXSekdoEzXpT5JvkTZFcI
|
133 |
-
ADxisjm5CqgKppX5yzMUESADQfePfk1BQKP5pDZzsUfbVB7tLgaSb9lcqDr34z2J
|
134 |
-
yf2Ref6dIL5BKdjwQm/fOzeRbjio6bXHyJ6fxrPUPjGt7GXRBkOrofXJJ7bb/x4L
|
135 |
-
ND6cClKSiM+jd/i/sOapNiqceqIWvmlvLl5hLJVIQUz505io9S9f8fv1gMy3hVZ6
|
136 |
-
j7P/LgJWlDRHDLhMgqTkNr1vz5gPnjx/kiOEROe40QKBgQD4czLuB7Fm3FqsjYkD
|
137 |
-
hvgBUji9Y6GbQM91RfXlRcvhIYwFmBfnl7Z2mIUeCwLLWO5Vzc9lvkKF/27qnls4
|
138 |
-
pr+GMaVmpmlcL7DKPSrrm5K/8cPfln2TezHn64tIjbFi8xQWw1XLEv4i2ndgSEyV
|
139 |
-
MqEen77g0Hc+2pPigVQZmkzKXQKBgQDNPG1aUmpNprECA2rnCj1fXCuLqlSsbLRO
|
140 |
-
GcrGltXot7VZ/3IxW5vwjxWqzNGeJG9deAT0oC3RSOf/fv05R3FnEDRleKnxajlq
|
141 |
-
CseAQ6X7sXAMjNb1GmSzfjguXjOvQCSRBlu+Wx1r/FNe7kK82d7Hedtb1JYZh6G0
|
142 |
-
vZy98L9CzwKBgQC2QHNMzxHgvaY6S/0FPF3zQihjLZHf/JPymCaAUEn11RENDXwD
|
143 |
-
pHPx3YJQ/ozHNG5pPPd10DKmbzEjJJUQIqn+O670dQB24nkScfppKQ9mhGhGPPPT
|
144 |
-
WxzJ3yymRWKpjlzfMd1egYkxcgb99ytOivxMJaz055eB4P94uZxCx8Cq9QKBgAri
|
145 |
-
rZogzOqZcMH+lGj0rhSkutqJijwq99U8oPivf2D8fW3sko3zoe28aRXKD0QoApAe
|
146 |
-
kYS4CjYTe9qdTakAFQ+2WFEZeUoIrErnj3VKIT+cRakkvzH42GZ8x1YOQQeGi2n1
|
147 |
-
wF/0TTcxBur+ECQcGijSWcQhHmT0QKtpcyrP3hUZAoGAW8NqevJyg3PDQI9kbShn
|
148 |
-
c08sVwV6jHniAnQHTNPKQ/KR8fzO0PS8B7eaImD4ZJOKjIAp5AfmKpwM4dvWl/i4
|
149 |
-
Vs1gY9eHFkDfxDDoCbcn0R6tPMR2LeqPbqZvhEyJaDVEd6Lh/Y9X7PQhhONCityJ
|
150 |
-
DhsXoTwnE70b3QezzBi6yk0=
|
151 |
-
-----END PRIVATE KEY-----
|
152 |
-
</key>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
App/Mikrotik/utils/helperfx.py
DELETED
@@ -1,75 +0,0 @@
|
|
1 |
-
from .api import MikrotikAPI
|
2 |
-
from .Config import RouterConfig
|
3 |
-
from fastapi import HTTPException
|
4 |
-
|
5 |
-
import re
|
6 |
-
from typing import Tuple
|
7 |
-
|
8 |
-
|
9 |
-
class PhoneValidator:
|
10 |
-
"""Validator for Tanzanian phone numbers"""
|
11 |
-
|
12 |
-
# Valid Tanzanian mobile operator prefixes
|
13 |
-
VALID_PREFIXES = [
|
14 |
-
"071",
|
15 |
-
"074",
|
16 |
-
"075",
|
17 |
-
"076",
|
18 |
-
"077", # Vodacom
|
19 |
-
"068",
|
20 |
-
"069", # Airtel
|
21 |
-
"065",
|
22 |
-
"067", # Tigo
|
23 |
-
"078",
|
24 |
-
"079", # TTCL
|
25 |
-
"073", # Zantel
|
26 |
-
"061",
|
27 |
-
"062", # Halotel
|
28 |
-
]
|
29 |
-
|
30 |
-
@staticmethod
|
31 |
-
def validate_and_format(phone: str) -> Tuple[bool, str]:
|
32 |
-
"""
|
33 |
-
Validates and formats Tanzanian phone numbers.
|
34 |
-
Returns (is_valid, formatted_number)
|
35 |
-
|
36 |
-
Valid format:
|
37 |
-
- 255712345678 (12 digits starting with 255)
|
38 |
-
"""
|
39 |
-
# Remove any spaces or special characters
|
40 |
-
phone = re.sub(r"[\s\-\(\)]", "", phone)
|
41 |
-
|
42 |
-
# Convert all formats to 255 format
|
43 |
-
if phone.startswith("+255"):
|
44 |
-
phone = "255" + phone[4:]
|
45 |
-
elif phone.startswith("0"):
|
46 |
-
phone = "255" + phone[1:]
|
47 |
-
elif not phone.startswith("255"):
|
48 |
-
return False, ""
|
49 |
-
|
50 |
-
# Check if it matches the basic pattern (12 digits starting with 255)
|
51 |
-
if not re.match(r"^255\d{9}$", phone):
|
52 |
-
return False, ""
|
53 |
-
|
54 |
-
# Check if the prefix is valid (check the digits after 255)
|
55 |
-
prefix = "0" + phone[3:5]
|
56 |
-
if prefix not in PhoneValidator.VALID_PREFIXES:
|
57 |
-
return False, ""
|
58 |
-
|
59 |
-
return True, phone
|
60 |
-
|
61 |
-
|
62 |
-
# Dependency to get MikrotikAPI instance
|
63 |
-
def get_mikrotik():
|
64 |
-
config = RouterConfig()
|
65 |
-
mikrotik = MikrotikAPI(config)
|
66 |
-
try:
|
67 |
-
if mikrotik.connect():
|
68 |
-
yield mikrotik
|
69 |
-
else:
|
70 |
-
raise HTTPException(
|
71 |
-
status_code=503,
|
72 |
-
detail={"success": False, "message": "Could not connect to router"},
|
73 |
-
)
|
74 |
-
finally:
|
75 |
-
mikrotik.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
App/app.py
CHANGED
@@ -10,7 +10,6 @@ from .Plans.PlanRoutes import plan_router
|
|
10 |
from .Portals.PortalRoutes import portal_router
|
11 |
from .Metrics.MetricsRoutes import metrics_router
|
12 |
from .Messages.MessagesRoute import message_router
|
13 |
-
from .Mikrotik.mikrotikRoutes import mikrotik_router
|
14 |
|
15 |
# from .Subscriptions.background_tasks import check_expiring_subscriptions
|
16 |
import asyncio, os
|
@@ -24,42 +23,17 @@ app = FastAPI()
|
|
24 |
# Configure CORS to allow all origins
|
25 |
app.add_middleware(
|
26 |
CORSMiddleware,
|
27 |
-
allow_origins=[
|
28 |
-
"http://localhost:3000", # Localhost for development
|
29 |
-
"https://captive-hotspot.vercel.app", # Your production domain
|
30 |
-
], # Allows all origins
|
31 |
allow_credentials=True, # Allows cookies and authentication headers
|
32 |
allow_methods=["*"], # Allows all HTTP methods (GET, POST, PUT, DELETE, etc.)
|
33 |
allow_headers=["*"], # Allows all headers
|
34 |
)
|
35 |
|
36 |
|
37 |
-
async def connect_to_vpn():
|
38 |
-
"""Connect to the VPN on startup."""
|
39 |
-
try:
|
40 |
-
# Replace 'your_vpn_command' with the actual command to connect to your VPN
|
41 |
-
# For example, if using OpenVPN:
|
42 |
-
command = [
|
43 |
-
"openvpn",
|
44 |
-
"--config",
|
45 |
-
f"{os.path.dirname(__file__)}/Mikrotik/utils/client1-working.ovpn",
|
46 |
-
"--dev",
|
47 |
-
"tun", # Simulate device without needing /dev/net/tun
|
48 |
-
]
|
49 |
-
|
50 |
-
print(f"{os.path.dirname(__file__)}/Mikrotik/utils/client1-working.ovpn")
|
51 |
-
process = subprocess.Popen(command)
|
52 |
-
await asyncio.sleep(5) # Wait for a few seconds to ensure the VPN connects
|
53 |
-
logging.info("VPN connected successfully.")
|
54 |
-
except Exception as e:
|
55 |
-
logging.error(f"Failed to connect to VPN: {str(e)}")
|
56 |
-
|
57 |
-
|
58 |
@app.on_event("startup")
|
59 |
async def startup_event():
|
60 |
await Tortoise.init(config=TORTOISE_ORM)
|
61 |
await Tortoise.generate_schemas()
|
62 |
-
await connect_to_vpn() # Connect to VPN on startup
|
63 |
|
64 |
|
65 |
@app.get("/")
|
@@ -76,7 +50,6 @@ app.include_router(plan_router)
|
|
76 |
app.include_router(portal_router)
|
77 |
app.include_router(metrics_router)
|
78 |
app.include_router(message_router)
|
79 |
-
app.include_router(mikrotik_router)
|
80 |
# if __name__ == "__main__":
|
81 |
# import uvicorn
|
82 |
|
|
|
10 |
from .Portals.PortalRoutes import portal_router
|
11 |
from .Metrics.MetricsRoutes import metrics_router
|
12 |
from .Messages.MessagesRoute import message_router
|
|
|
13 |
|
14 |
# from .Subscriptions.background_tasks import check_expiring_subscriptions
|
15 |
import asyncio, os
|
|
|
23 |
# Configure CORS to allow all origins
|
24 |
app.add_middleware(
|
25 |
CORSMiddleware,
|
26 |
+
allow_origins=["*"], # Allows all origins
|
|
|
|
|
|
|
27 |
allow_credentials=True, # Allows cookies and authentication headers
|
28 |
allow_methods=["*"], # Allows all HTTP methods (GET, POST, PUT, DELETE, etc.)
|
29 |
allow_headers=["*"], # Allows all headers
|
30 |
)
|
31 |
|
32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
@app.on_event("startup")
|
34 |
async def startup_event():
|
35 |
await Tortoise.init(config=TORTOISE_ORM)
|
36 |
await Tortoise.generate_schemas()
|
|
|
37 |
|
38 |
|
39 |
@app.get("/")
|
|
|
50 |
app.include_router(portal_router)
|
51 |
app.include_router(metrics_router)
|
52 |
app.include_router(message_router)
|
|
|
53 |
# if __name__ == "__main__":
|
54 |
# import uvicorn
|
55 |
|