6 changed files with 186 additions and 25 deletions
@ -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() |
|||
|
|||
|
|||
@ -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()) |
|||
@ -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 |
|||
@ -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…
Reference in new issue