diff --git a/app/auth/auth_handler.py b/app/auth/auth_handler.py index b18df83..cf2794b 100644 --- a/app/auth/auth_handler.py +++ b/app/auth/auth_handler.py @@ -11,7 +11,7 @@ JWT_ALGORITHM = config("algorithm") JWT_EXPIRY_TIME = config("jwt_expiry_time", default=300, cast=int) -def signJWT() -> Dict[str, str]: +def sign_jwt() -> Dict[str, str]: payload = { "user_id": "admin", "expires": int(round(time.time() * 1000) + JWT_EXPIRY_TIME), @@ -45,7 +45,7 @@ def handle_local_cookie(): if enabled: f = open(full_cookie_file_path, "w") - f.write(signJWT()["access_token"]) + f.write(sign_jwt()["access_token"]) f.close() diff --git a/app/external/sse_startlette/LICENSE.md b/app/external/sse_starlette/LICENSE.md similarity index 100% rename from app/external/sse_startlette/LICENSE.md rename to app/external/sse_starlette/LICENSE.md diff --git a/app/external/sse_startlette/__init__.py b/app/external/sse_starlette/__init__.py similarity index 100% rename from app/external/sse_startlette/__init__.py rename to app/external/sse_starlette/__init__.py diff --git a/app/external/sse_startlette/sse_starlette.py b/app/external/sse_starlette/sse_starlette.py similarity index 100% rename from app/external/sse_startlette/sse_starlette.py rename to app/external/sse_starlette/sse_starlette.py diff --git a/app/main.py b/app/main.py index 243d650..2b13eee 100644 --- a/app/main.py +++ b/app/main.py @@ -22,7 +22,7 @@ from app.auth.auth_handler import ( remove_local_cookie, ) from app.external.fastapi_versioning import VersionedFastAPI -from app.external.sse_startlette import EventSourceResponse +from app.external.sse_starlette import EventSourceResponse from app.models.system import APIPlatform from app.repositories.bitcoin import ( register_bitcoin_status_gatherer, diff --git a/app/repositories/system.py b/app/repositories/system.py index fa407e6..60162ff 100644 --- a/app/repositories/system.py +++ b/app/repositories/system.py @@ -1,7 +1,7 @@ import asyncio -from os import path import logging import re +from os import path from decouple import config from fastapi import HTTPException, Request, status @@ -39,7 +39,8 @@ def _check_shell_scripts_status(): _check_shell_scripts_status() -async def callScript(scriptPath) -> str: + +async def call_script(scriptPath) -> str: cmd = f"bash {scriptPath}" logging.warning(f"running script: {cmd}") proc = await asyncio.create_subprocess_shell( @@ -54,24 +55,33 @@ async def callScript(scriptPath) -> str: logging.error(stderr.decode()) return "" -def parseKeyValueLines(lines:list) -> dict: + +def parse_key_value_lines(lines: list) -> dict: Dict = {} for line in lines: logging.warning(f"line({line})") - if len(line.strip()) == 0: continue - if line.strip().startswith('#'): continue - if line.find('=') <=0: continue - key, value = line.strip().split('=',1) + if len(line.strip()) == 0: + continue + if line.strip().startswith("#"): + continue + if line.find("=") <= 0: + continue + key, value = line.strip().split("=", 1) Dict[key] = value.strip('"').strip("'") return Dict -def parseKeyValueText(text:str) -> dict: - return parseKeyValueLines(text.splitlines()) -def passwordValid(password : str): - if len(password) < 8: return False - if password.find(' ') >= 0: return False - return re.match('^[a-zA-Z0-9]*$', password) +def parse_key_value_text(text: str) -> dict: + return parse_key_value_lines(text.splitlines()) + + +def password_valid(password: str): + if len(password) < 8: + return False + if password.find(" ") >= 0: + return False + return re.match("^[a-zA-Z0-9]*$", password) + async def get_system_info() -> SystemInfo: try: diff --git a/app/repositories/system_impl/raspiblitz.py b/app/repositories/system_impl/raspiblitz.py index 37342c0..59d2a8a 100644 --- a/app/repositories/system_impl/raspiblitz.py +++ b/app/repositories/system_impl/raspiblitz.py @@ -1,6 +1,8 @@ import asyncio import os +from decouple import config + from app.constants import API_VERSION from app.models.system import ( APIPlatform, @@ -10,9 +12,10 @@ from app.models.system import ( SystemInfo, ) from app.repositories.lightning import get_ln_info -from app.repositories.system import SHELL_SCRIPT_PATH from app.utils import redis_get +SHELL_SCRIPT_PATH = config("shell_script_path") + async def get_system_info_impl() -> SystemInfo: lninfo = await get_ln_info() diff --git a/app/routers/apps.py b/app/routers/apps.py index 8335237..3f93e63 100644 --- a/app/routers/apps.py +++ b/app/routers/apps.py @@ -4,7 +4,7 @@ from fastapi.params import Depends import app.repositories.apps as repo import app.routers.apps_docs as docs from app.auth.auth_bearer import JWTBearer -from app.external.sse_startlette import EventSourceResponse +from app.external.sse_starlette import EventSourceResponse _PREFIX = "apps" diff --git a/app/routers/bitcoin.py b/app/routers/bitcoin.py index 64deb44..5775831 100644 --- a/app/routers/bitcoin.py +++ b/app/routers/bitcoin.py @@ -2,7 +2,7 @@ from fastapi import APIRouter, HTTPException, Request, status from fastapi.params import Depends, Query from app.auth.auth_bearer import JWTBearer -from app.external.sse_startlette import EventSourceResponse +from app.external.sse_starlette import EventSourceResponse from app.models.bitcoind import BlockchainInfo, BtcInfo, FeeEstimationMode, NetworkInfo from app.repositories.bitcoin import ( estimate_fee, diff --git a/app/routers/setup.py b/app/routers/setup.py index e5a83a0..04721c8 100644 --- a/app/routers/setup.py +++ b/app/routers/setup.py @@ -1,5 +1,6 @@ import asyncio -#from asyncio.windows_events import NULL + +# from asyncio.windows_events import NULL import logging import re @@ -9,27 +10,22 @@ from fastapi.params import Depends from fastapi_plugins import depends_redis from setuptools import setup -from app.repositories.system import ( - callScript, - parseKeyValueLines, - passwordValid -) - from app.auth.auth_bearer import JWTBearer -from app.auth.auth_handler import signJWT +from app.auth.auth_handler import sign_jwt +from app.repositories.system import call_script, parse_key_value_lines, password_valid from app.utils import redis_get router = APIRouter(prefix="/setup", tags=["Setup"]) -setupFilePath="/var/cache/raspiblitz/temp/raspiblitz.setup" -configFilePath="/mnt/hdd/raspiblitz.conf" +setupFilePath = "/var/cache/raspiblitz/temp/raspiblitz.setup" +configFilePath = "/mnt/hdd/raspiblitz.conf" # can always be called without credentials to check if # the system needs or is in setup (setupPhase!="done") # for example in the beginning setupPhase can be (see controlSetupDialog.sh) # 1) recovery = same version on fresh sd card # 2) update = updated version on fresh sd card -# 3) migration = hdd got data from another node projcect +# 3) migration = hdd got data from another node project # 4) setup = a fresh blitz to setup @router.get("/status") async def get_status(): @@ -44,6 +40,7 @@ async def get_status(): # We can do the "MIGRATION" option later - because it would need an additional step after formatting hdd # People that need to migrate can do for now by SSH option + @router.get("/setup-start-info") async def setup_start_info(): @@ -51,9 +48,7 @@ async def setup_start_info(): setupPhase = await redis_get("setupPhase") state = await redis_get("state") if state != "waitsetup": - logging.warning( - f"/setup-start-info can only be called when nodes awaits setup" - ) + logging.warning(f"/setup-start-info can only be called when nodes awaits setup") return HTTPException(status.status.HTTP_405_METHOD_NOT_ALLOWED) # get all the additional info needed to do setup dialog @@ -73,17 +68,16 @@ async def setup_start_info(): } -def writeTextFile(filename: str, arrayOfLines): +def write_text_file(filename: str, arrayOfLines): logging.warning(f"writing {filename}") - with open(filename, 'w', encoding='utf-8') as f: - f.write('\n'.join(arrayOfLines)) + with open(filename, "w", encoding="utf-8") as f: + f.write("\n".join(arrayOfLines)) + # With all this info the WebUi can run its own runs its dialogs and in the end makes a call to @router.post("/setup-start-done") -async def setup_start_done( - passwordA : str = "" -): - logging.warning(f"START /setup-start-done") +async def setup_start_done(passwordA: str = ""): + logging.warning(f"START /setup-start-done") # first check that node is really in setup state setupPhase = await redis_get("setupPhase") @@ -92,18 +86,17 @@ async def setup_start_done( logging.warning(f"/setup-start-done can only be called when nodes awaits setup") return HTTPException(status.HTTP_405_METHOD_NOT_ALLOWED) - if setupPhase == "recovery": + if setupPhase == "recovery": logging.warning(f"check recovery data") - if passwordValid(passwordA) == False: + if password_valid(passwordA) == False: logging.warning(f"passwordA is not valid") return HTTPException(status.HTTP_400_BAD_REQUEST) - writeTextFile(setupFilePath,[ - f"setupType={setupPhase}", - "setPasswordA=1", - f"passwordA='{passwordA}'" - ]) + write_text_file( + setupFilePath, + [f"setupType={setupPhase}", "setPasswordA=1", f"passwordA='{passwordA}'"], + ) logging.warning(f"kicking off recovery") - await callScript("/home/admin/_cache.sh set state waitprovision") + await call_script("/home/admin/_cache.sh set state waitprovision") else: logging.warning(f"not handled setupPhase state ({setupPhase})") return HTTPException(status.HTTP_405_METHOD_NOT_ALLOWED) @@ -124,10 +117,10 @@ async def setup_start_done( # those values get stored in: /var/cache/raspiblitz/temp/raspiblitz.setup # also a skeleton raspiblitz.conf gets created (see controlSetupDialog.sh Line 318) # and then API sets state to `waitprovision` to kick-off provision - + # await redis.publish_json("default", {"data": "Starting setup"}) # await asyncio.sleep(1) - return signJWT() + return sign_jwt() # WebUI now loops getting status until state=`waitfinal` then calls: @@ -143,29 +136,26 @@ async def setup_final_info(): setupPhase = await redis_get("setupPhase") state = await redis_get("state") if state != "waitfinal": - logging.warning(f"/setup-final-info can only be called when nodes awaits final ({state})") + logging.warning( + f"/setup-final-info can only be called when nodes awaits final ({state})" + ) return HTTPException(status.HTTP_405_METHOD_NOT_ALLOWED) - resultlines=[] - with open (setupFilePath, "r") as setupfile: - resultlines=setupfile.readlines() - data=parseKeyValueLines(resultlines) + resultlines = [] + with open(setupFilePath, "r") as setupfile: + resultlines = setupfile.readlines() + data = parse_key_value_lines(resultlines) logging.warning(f"data({data})") try: setupType = data["setupType"] except: logging.warning("missing setupType in raspiblitz.setup") - setupType="" + setupType = "" if setupType == "setup": - return { - "setupType": setupType, - "seedwordsNEW": data["seedwordsNEW"] - } + return {"setupType": setupType, "seedwordsNEW": data["seedwordsNEW"]} else: - return { - "setupType": setupType, - "seedwordsNEW": "" - } + return {"setupType": setupType, "seedwordsNEW": ""} + # When WebUI displayed seed words & user confirmed write the calls: @router.post("/setup-final-done", dependencies=[Depends(JWTBearer())]) @@ -178,7 +168,5 @@ async def setup_final_done(): logging.warning(f"/setup-final-done can only be called when nodes awaits final") return HTTPException(status.HTTP_405_METHOD_NOT_ALLOWED) - await callScript("/home/admin/_cache.sh set state donefinal") - return { - "state": "donefinal" - } + await call_script("/home/admin/_cache.sh set state donefinal") + return {"state": "donefinal"} diff --git a/app/routers/system.py b/app/routers/system.py index b7383d1..8bdf686 100644 --- a/app/routers/system.py +++ b/app/routers/system.py @@ -1,24 +1,24 @@ -import secrets import logging +import secrets from decouple import config from fastapi import APIRouter, HTTPException, Request, status from fastapi.params import Depends from app.auth.auth_bearer import JWTBearer -from app.auth.auth_handler import signJWT -from app.external.sse_startlette import EventSourceResponse +from app.auth.auth_handler import sign_jwt +from app.external.sse_starlette import EventSourceResponse from app.models.system import APIPlatform, LoginInput, RawDebugLogData, SystemInfo from app.repositories.system import ( HW_INFO_YIELD_TIME, PLATFORM, + call_script, get_debug_logs_raw, get_hardware_info, get_system_info, + parse_key_value_text, + password_valid, subscribe_hardware_info, - callScript, - parseKeyValueText, - passwordValid ) from app.repositories.system_impl.raspiblitz import shutdown from app.routers.system_docs import ( @@ -50,16 +50,21 @@ async def login(i: LoginInput): if platform == "raspiblitz": # script does not work when called from api yet - if passwordValid(i.password): - result = await callScript(f"/home/admin/config.scripts/blitz.setpassword.sh check-a {i.password}") - data = parseKeyValueText(result) - if data["correct"] == "1": return signJWT() + if password_valid(i.password): + result = await call_script( + f"/home/admin/config.scripts/blitz.setpassword.sh check-a {i.password}" + ) + data = parse_key_value_text(result) + if data["correct"] == "1": + return sign_jwt() raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail="Password is wrong") else: match = secrets.compare_digest(i.password, config("login_password", cast=str)) - if match: return signJWT() + if match: + return sign_jwt() raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail="Password is wrong") + @router.post( "/refresh-token", name=f"{_PREFIX}.refresh-token", @@ -68,7 +73,7 @@ async def login(i: LoginInput): dependencies=[Depends(JWTBearer())], ) def refresh_token(): - return signJWT() + return sign_jwt() @router.get(