gsd 4 months ago
parent
commit
62210c7550
  1. 91
      authManager.py
  2. 20
      service.py
  3. 18
      utils.py
  4. 13
      webExtensions/nodeList.py
  5. 58
      webExtensions/publicEndpoints.py
  6. 11
      webExtensions/webPing.py

91
authManager.py

@ -0,0 +1,91 @@
from functools import wraps
from fastapi.requests import Request
from fastapi.responses import Response
from fastapi.exceptions import HTTPException
from utils import md5hash
from random import randint
from time import time
import asyncio
from logger import logger
class NotValidCode(Exception):
pass
class AuthManager:
NUM = "NUM"
NUM_SECURED = "NUM_SECURED"
SECRET_KEY = "SECRET_KEY"#todo
MAX_CODE_LIFE = 60
def __init__(self, args):
self.salt = args.web_salt
self.code_store = {}
async def storeCleaner(self):
logger.info("Code store cleaner working...")
run = True
while run:
try:
l = list(self.code_store.keys())
for code in l:
if code in self.code_store.keys():#check mb is not exists one time
if time() - self.code_store[code]["ts"] > self.MAX_CODE_LIFE:
logger.info(f"Code {code} is ended")
del self.code_store[code]
await asyncio.sleep(1)
except asyncio.exceptions.CancelledError:
run = False
except:
logger.error("Cannot check code store")
pass
def authRequest(self, method=[]):#todo cookie or secret_key
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
request: Request = kwargs.get("request", None)
if request is None:
raise HTTPException(status_code=500, detail="Authed endpoint need request arg, but is missing")
if request.cookies.get(self.NUM, None) and request.cookies.get(self.NUM_SECURED, None):
#check cookie is valid
if md5hash(request.cookies[self.NUM] + self.salt) == request.cookies.get(self.NUM_SECURED):
return await func(*args, **kwargs)
raise HTTPException(status_code=401)
return wrapper
return decorator
def setAuth(self, response: Response, num:int, clear = False):
if clear:
response.set_cookie(self.NUM, "")
response.set_cookie(self.NUM_SECURED, "")
else:
response.set_cookie(self.NUM, str(num))
response.set_cookie(self.NUM_SECURED, md5hash(str(num)+self.salt))
return response
def request_auth(self, num: int):
code = randint(1000, 9999)
while code in self.code_store.keys():
code = randint(1000, 9999)
self.code_store[code] = {
"code": code,
"num": num,
"ts": time()
}
#logger.info(code)
return code
def accept_code(self, code:int):
if code in self.code_store.keys():
num = self.code_store[code]["num"]
del self.code_store[code]
return num
raise NotValidCode()

20
service.py

@ -24,14 +24,7 @@ from pymongo import AsyncMongoClient
#other
from botManager import BotManager
def isInt(any):
try:
int(any)
return True
except:
return False
from utils import isInt, generate_random_string
class MeshArgsParse:
def __init__(self, args):
@ -42,6 +35,7 @@ class MeshListener(MeshArgsParse):
super().__init__(args)
self.meshState = NOT_CONNECTED
self.PUB_CH = PUB_CH
self.last_packet_catch = time()
if args.transport == "serial":
from transport_serial import SerialTransport
@ -62,7 +56,7 @@ class MeshListener(MeshArgsParse):
#task
async def meshWorker(self, queue: asyncio.Queue):
logger.info("Start mesh queue listener")
run = not self.args.disable_mesh
run = not self.args.disable_mesh or self.args.enable_mesh
while run:
try:
await self.device.start()
@ -78,6 +72,7 @@ class MeshListener(MeshArgsParse):
from_radio, _ = await self.device.recv()
#logger.debug(from_radio)
await queue.put(from_radio)
self.last_packet_catch = time()
except asyncio.exceptions.CancelledError:
logger.info("Kill mesh device")
run = False
@ -99,6 +94,8 @@ class MeshApi(MeshArgsParse):
super().__init__(args)
self.app = FastAPI(lifespan=self.lifespan)
self.tasks = []
from authManager import AuthManager
self.authManager = AuthManager(args)
@asynccontextmanager
async def lifespan(self, app: FastAPI):
@ -213,6 +210,7 @@ class MeshCenter(MeshListener, MeshApi, MongoDriver, MeshArgsParse):
asyncio.create_task(self.bot.handleMessage(from_radio))
self.tasks.append(handlerTask)
self.tasks.append(self.authManager.storeCleaner)
def extensionLoader(self, search_paths = []):
logger.info("Search fastapiExt")
@ -239,7 +237,8 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser()
#mesh
parser.add_argument("--transport", default="tcp")
parser.add_argument("--disable-mesh", action="store_true", default=False)
parser.add_argument("--disable-mesh", action="store_true", default=True)
parser.add_argument("--enable-mesh", action="store_true", default=False, help="Need to run in docker if git is bullshit updates")
#serial transport
parser.add_argument("--serial-port", default="/dev/tty.usbmodemD0CF1309DC141")
parser.add_argument("--serial-baudrate", default=115200)
@ -251,6 +250,7 @@ if __name__ == "__main__":
#fastapi
parser.add_argument("--web-host", default="0.0.0.0")
parser.add_argument("--web-port", default=8680)
parser.add_argument("--web-salt", default=generate_random_string(32))
#mongodb
parser.add_argument("--mongo-url")
parser.add_argument("--mongo-host", default="192.168.3.2")

18
utils.py

@ -0,0 +1,18 @@
import random
import string
import hashlib
def generate_random_string(length):
characters = string.ascii_letters + string.digits
random_string = ''.join(random.choices(characters, k=length))
return random_string
def isInt(any):
try:
int(any)
return True
except:
return False
def md5hash(string: str):
return str(hashlib.md5(string.encode()).hexdigest())

13
webExtensions/nodeList.py

@ -1,5 +1,6 @@
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from fastapi.requests import Request
from fastapi import Query
from pymongo.asynchronous.database import AsyncDatabase
@ -15,7 +16,8 @@ class WebExtension:
self.dbStore = core.dbStore
@self.app.get(f"{self.core.context}/nodes/list")
async def listOfNodes():
@self.core.authManager.authRequest()
async def listOfNodes(request: Request):
pipeline = [ #ai slooop
# Сортировка для каждого num по убыванию ts
{"$sort": {"num": 1, "ts": -1}},
@ -36,7 +38,8 @@ class WebExtension:
return [NodeDTO(node) for node in l]
@self.app.get(f"{self.core.context}/nodes/direct")
async def listOfDirectNodes():
@self.core.authManager.authRequest()
async def listOfDirectNodes(request: Request):
pipeline = [ #ai slooop
{"$match": {"hops_away": 0}}, # Фильтруем по списку
# Сортировка для каждого num по убыванию ts
@ -58,7 +61,8 @@ class WebExtension:
return [NodeDTO(node) for node in l]
@self.app.get(self.core.context + "/nodes/{num}")
async def oneNode(num: int):
@self.core.authManager.authRequest()
async def oneNode(request: Request, num: int):
collection = self.dbStore['node_info']
c = await collection.find_one(
{"num":num},
@ -70,7 +74,8 @@ class WebExtension:
raise HTMLResponse(status_code=404)
@self.app.get(self.core.context + "/nodes")
async def listOfSelectedNodes(nums: List[int] = Query(None)):
@self.core.authManager.authRequest()
async def listOfSelectedNodes(request: Request, nums: List[int] = Query(None)):
pipeline = [
{"$match": {"num": {"$in": nums}}}, # Фильтруем по списку
{"$sort": {"num": 1, "ts": -1}}, # Сортируем по num и ts (по убыванию)

58
webExtensions/publicEndpoints.py

@ -0,0 +1,58 @@
from fastapi import FastAPI
from fastapi.requests import Request
from fastapi.responses import Response, JSONResponse
from fastapi.exceptions import HTTPException
class WebExtension:
app: FastAPI
def __init__(self, core):
self.core = core
self.app = core.app
@self.app.get(f"{self.core.context}/ping")
async def pong(request:Request):
return {"pong": True}
@self.app.get(f"{self.core.context}/status")
async def status(request:Request):
return {
"status": self.core.meshState,
"last_packet_catch": self.core.last_packet_catch
}
@self.app.get(f"{self.core.context}/auth/code")
async def sendCodeToNode(request:Request, num: int):
#в сообщение одноразовый код и юзер агент кто слал, так-же чтобы не спапили по фингерпринту один код в минуту
if num:
node = await self.getNodeInfo(num)
if node:
code = self.core.authManager.request_auth(num)
await self.core.device.sendMsgToDM(f"Auth code: {code}", num)
return {"status": f"Code sended to {node['long_name']}"}
raise HTTPException(status_code=400)
@self.app.get(f"{self.core.context}/auth/code/check")
async def acceptCodeFromNode(request:Request, code: int):
#если такой код есть то авторизуем пользователя, иначе шлем н***й
if code:
try:
num = self.core.authManager.accept_code(code)
respond = self.core.authManager.setAuth(JSONResponse({"status":True}), num)
return respond
except:
return {"status": False}
return {"status": False}
@self.app.get(f"{self.core.context}/auth/logout")
async def clearSession(request:Request):
return self.core.authManager.setAuth(JSONResponse({"status":True}), 0, True)
async def getNodeInfo(self, num:int):
collection = self.core.dbStore['node_info']
c = await collection.find_one(
{"num":num},
sort=[("ts", -1)]
)
return c

11
webExtensions/webPing.py

@ -1,11 +0,0 @@
from fastapi import FastAPI
class WebExtension:
app: FastAPI
def __init__(self, core):
self.core = core
self.app = core.app
@self.app.get(f"{self.core.context}/ping")
async def pong():
return {"pong": True}
Loading…
Cancel
Save