Files
firegex-traffic-viewer/backend/app.py

164 lines
5.5 KiB
Python
Raw Normal View History

2022-07-21 01:02:46 +02:00
import uvicorn, secrets, utils
import os, asyncio
from fastapi import FastAPI, HTTPException, Depends, APIRouter
2022-06-28 21:49:03 +02:00
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
2022-07-21 01:02:46 +02:00
from jose import jwt
2022-06-28 21:49:03 +02:00
from passlib.context import CryptContext
2022-07-08 15:13:46 +02:00
from fastapi_socketio import SocketManager
from utils.sqlite import SQLite
from utils import API_VERSION, FIREGEX_PORT, JWT_ALGORITHM, get_interfaces, socketio_emit, DEBUG, SysctlManager
2022-07-21 01:02:46 +02:00
from utils.loader import frontend_deploy, load_routers
from utils.models import ChangePasswordModel, IpInterface, PasswordChangeForm, PasswordForm, ResetRequest, StatusModel, StatusMessageModel
2023-06-29 13:44:12 +02:00
from fastapi.middleware.cors import CORSMiddleware
2022-06-28 13:26:06 +02:00
2022-06-12 19:16:25 +02:00
# DB init
2022-06-28 16:02:52 +02:00
db = SQLite('db/firegex.db')
2023-04-12 23:53:43 +02:00
sysctl = SysctlManager({
"net.ipv4.conf.all.forwarding": True,
"net.ipv6.conf.all.forwarding": True,
"net.ipv4.conf.all.route_localnet": True,
"net.ipv4.ip_forward": True
})
2022-06-28 21:49:03 +02:00
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/login", auto_error=False)
crypto = CryptContext(schemes=["bcrypt"], deprecated="auto")
2022-07-01 03:59:01 +02:00
app = FastAPI(debug=DEBUG, redoc_url=None)
2022-07-21 01:02:46 +02:00
utils.socketio = SocketManager(app, "/sock", socketio_path="")
2022-06-12 19:16:25 +02:00
2022-07-12 20:18:54 +02:00
def APP_STATUS(): return "init" if db.get("password") is None else "run"
def JWT_SECRET(): return db.get("secret")
def set_psw(psw: str):
hash_psw = crypto.hash(psw)
db.put("password",hash_psw)
2022-07-21 01:02:46 +02:00
@utils.socketio.on("update")
2022-07-08 15:13:46 +02:00
async def updater(): pass
2022-06-28 21:49:03 +02:00
def create_access_token(data: dict):
to_encode = data.copy()
2022-07-21 01:02:46 +02:00
encoded_jwt = jwt.encode(to_encode, JWT_SECRET(), algorithm=JWT_ALGORITHM)
2022-06-28 21:49:03 +02:00
return encoded_jwt
2022-06-28 13:36:17 +02:00
async def refresh_frontend(additional:list[str]=[]):
await socketio_emit([]+additional)
2022-06-28 21:49:03 +02:00
async def check_login(token: str = Depends(oauth2_scheme)):
if not token:
return False
try:
2022-07-21 01:02:46 +02:00
payload = jwt.decode(token, JWT_SECRET(), algorithms=[JWT_ALGORITHM])
2022-06-28 21:49:03 +02:00
logged_in: bool = payload.get("logged_in")
2022-07-20 21:19:22 +02:00
except Exception:
2022-06-28 21:49:03 +02:00
return False
return logged_in
async def is_loggined(auth: bool = Depends(check_login)):
if not auth:
raise HTTPException(
2022-07-21 01:02:46 +02:00
status_code=401,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
2022-06-28 21:49:03 +02:00
return True
2022-06-13 16:12:52 +02:00
2022-07-21 01:02:46 +02:00
api = APIRouter(prefix="/api", dependencies=[Depends(is_loggined)])
2022-07-01 03:59:01 +02:00
@app.get("/api/status", response_model=StatusModel)
async def get_app_status(auth: bool = Depends(check_login)):
"""Get the general status of firegex and your session with firegex"""
2022-06-28 13:26:06 +02:00
return {
"status": APP_STATUS(),
"loggined": auth,
2022-07-21 01:02:46 +02:00
"version": API_VERSION
2022-06-28 13:26:06 +02:00
}
2022-06-13 16:12:52 +02:00
2022-06-28 13:26:06 +02:00
@app.post("/api/login")
2022-06-28 21:49:03 +02:00
async def login_api(form: OAuth2PasswordRequestForm = Depends()):
2022-07-01 03:59:01 +02:00
"""Get a login token to use the firegex api"""
if APP_STATUS() != "run": raise HTTPException(status_code=400)
2022-06-28 13:26:06 +02:00
if form.password == "":
2022-06-13 16:12:52 +02:00
return {"status":"Cannot insert an empty password!"}
2022-06-28 13:26:06 +02:00
await asyncio.sleep(0.3) # No bruteforce :)
2022-07-12 20:18:54 +02:00
if crypto.verify(form.password, db.get("password")):
2022-06-28 21:49:03 +02:00
return {"access_token": create_access_token({"logged_in": True}), "token_type": "bearer"}
raise HTTPException(406,"Wrong password!")
2022-06-28 13:26:06 +02:00
2022-07-21 01:02:46 +02:00
@app.post('/api/set-password', response_model=ChangePasswordModel)
async def set_password(form: PasswordForm):
"""Set the password of firegex"""
if APP_STATUS() != "init": raise HTTPException(status_code=400)
2022-06-28 13:26:06 +02:00
if form.password == "":
2022-06-13 16:12:52 +02:00
return {"status":"Cannot insert an empty password!"}
set_psw(form.password)
2022-07-08 15:13:46 +02:00
await refresh_frontend()
2022-06-28 21:49:03 +02:00
return {"status":"ok", "access_token": create_access_token({"logged_in": True})}
2022-06-13 16:12:52 +02:00
2022-07-21 01:02:46 +02:00
@api.post('/change-password', response_model=ChangePasswordModel)
async def change_password(form: PasswordChangeForm):
"""Change the password of firegex"""
if APP_STATUS() != "run": raise HTTPException(status_code=400)
2022-06-13 16:12:52 +02:00
2022-06-28 13:26:06 +02:00
if form.password == "":
2022-06-13 16:12:52 +02:00
return {"status":"Cannot insert an empty password!"}
2022-07-21 01:02:46 +02:00
if form.expire:
db.put("secret", secrets.token_hex(32))
set_psw(form.password)
2022-07-08 15:13:46 +02:00
await refresh_frontend()
2022-06-28 21:49:03 +02:00
return {"status":"ok", "access_token": create_access_token({"logged_in": True})}
2022-06-13 16:12:52 +02:00
2022-06-15 08:47:13 +02:00
2023-09-22 20:46:50 +02:00
@api.get('/interfaces', response_model=list[IpInterface])
2022-07-21 01:02:46 +02:00
async def get_ip_interfaces():
"""Get a list of ip and ip6 interfaces"""
return get_interfaces()
2022-06-12 19:16:25 +02:00
2022-07-21 01:02:46 +02:00
#Routers Loader
reset, startup, shutdown = load_routers(api)
2022-07-10 15:05:56 +02:00
2022-07-21 01:02:46 +02:00
@app.on_event("startup")
async def startup_event():
db.init()
if os.getenv("HEX_SET_PSW"):
set_psw(bytes.fromhex(os.getenv("HEX_SET_PSW")).decode())
2023-04-12 23:53:43 +02:00
sysctl.set()
2022-07-21 01:02:46 +02:00
await startup()
if not JWT_SECRET(): db.put("secret", secrets.token_hex(32))
2022-07-08 15:13:46 +02:00
await refresh_frontend()
2022-07-21 01:02:46 +02:00
@app.on_event("shutdown")
async def shutdown_event():
await shutdown()
2023-04-12 23:53:43 +02:00
sysctl.reset()
2022-07-21 01:02:46 +02:00
db.disconnect()
2022-07-20 21:19:22 +02:00
2022-07-21 01:02:46 +02:00
@api.post('/reset', response_model=StatusMessageModel)
async def reset_firegex(form: ResetRequest):
2022-07-20 21:19:22 +02:00
"""Reset firegex nftables rules and optionally all the database"""
if form.delete:
db.delete()
db.init()
db.put("secret", secrets.token_hex(32))
2023-04-12 23:53:43 +02:00
sysctl.set()
2022-07-21 01:02:46 +02:00
await reset(form)
2022-07-20 21:19:22 +02:00
await refresh_frontend()
return {'status': 'ok'}
2022-07-21 01:02:46 +02:00
app.include_router(api)
frontend_deploy(app)
2022-06-12 19:16:25 +02:00
if __name__ == '__main__':
2022-06-28 13:26:06 +02:00
# os.environ {PORT = Backend Port (Main Port), F_PORT = Frontend Port}
2022-06-30 16:00:58 +02:00
os.chdir(os.path.dirname(os.path.realpath(__file__)))
2022-06-28 13:26:06 +02:00
uvicorn.run(
"app:app",
2023-09-23 15:00:40 +02:00
host="::",
2022-07-21 01:02:46 +02:00
port=FIREGEX_PORT,
2022-06-28 13:26:06 +02:00
reload=DEBUG,
access_log=True,
2023-06-29 13:44:12 +02:00
workers=1, # Multiple workers will cause a crash due to the creation
# of multiple processes with separated memory
2022-06-28 13:26:06 +02:00
)