This commit is contained in:
rootzoll 2022-03-23 22:14:41 +01:00
commit 6ceaacfc23
11 changed files with 132 additions and 74 deletions

View file

@ -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()

View file

@ -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,

View file

@ -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,30 +55,42 @@ 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 nameValid(password : str):
if len(password) < 3: return False
if password.find(' ') >= 0: return False
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)
def name_valid(password : str):
if len(password) < 3:
return False
if password.find(' ') >= 0:
return False
return re.match('^[\.a-zA-Z0-9-_]*$', password)
async def get_system_info() -> SystemInfo:
try:
return await get_system_info_impl()

View file

@ -1,3 +1,8 @@
import asyncio
import os
from decouple import config
from app.constants import API_VERSION
from app.models.system import (
APIPlatform,
@ -9,6 +14,8 @@ from app.models.system import (
from app.repositories.lightning import get_ln_info
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()
@ -34,3 +41,26 @@ async def get_system_info_impl() -> SystemInfo:
ssh_address=f"admin@{lan}",
chain=lninfo.chains[0].network,
)
async def shutdown(reboot: bool):
params = ""
if reboot:
params = "reboot"
script = os.path.join(SHELL_SCRIPT_PATH, "config.scripts", "blitz.shutdown.sh")
cmd = f"sudo bash {script} {params}"
proc = await asyncio.create_subprocess_shell(
cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await proc.communicate()
print(f"[{cmd!r} exited with {proc.returncode}]")
if stdout:
print(f"[stdout]\n{stdout.decode()}")
if stderr:
print(f"[stderr]\n{stderr.decode()}")

View file

@ -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"

View file

@ -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,

View file

@ -1,5 +1,6 @@
import asyncio
#from asyncio.windows_events import NULL
# from asyncio.windows_events import NULL
import logging
from pickle import FALSE
import re
@ -10,28 +11,22 @@ from fastapi.params import Depends
from fastapi_plugins import depends_redis
from setuptools import setup
from app.repositories.system import (
callScript,
parseKeyValueLines,
nameValid,
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, name_valid, parse_key_value_lines
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():
@ -46,6 +41,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():
@ -53,9 +49,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
@ -75,10 +69,11 @@ 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")
@ -102,18 +97,18 @@ async def setup_start_done(
#### SETUP ####
if setupPhase == "setup":
if nameValid(hostname) == False:
if name_valid(hostname) == False:
logging.warning(f"hostname is not valid")
return HTTPException(status.HTTP_400_BAD_REQUEST)
if lightning!="lnd" and lightning!="cl" and lightning!="none":
logging.warning(f"lightning is not valid")
if passwordValid(passwordA) == False:
if password_valid(passwordA) == False:
logging.warning(f"passwordA is not valid")
return HTTPException(status.HTTP_400_BAD_REQUEST)
if passwordValid(passwordB) == False:
if password_valid(passwordB) == False:
logging.warning(f"passwordB is not valid")
return HTTPException(status.HTTP_400_BAD_REQUEST)
if lightning!="none" and passwordValid(passwordC)==False:
if lightning!="none" and password_valid(passwordC)==False:
logging.warning(f"passwordC is not valid")
return HTTPException(status.HTTP_400_BAD_REQUEST)
if hddGotBlockchain!="1" and keepBlockchain:
@ -125,7 +120,7 @@ async def setup_start_done(
else:
formatHDD=1
cleanHDD=0
writeTextFile(setupFilePath,[
write_text_file(setupFilePath,[
f"formatHDD={formatHDD}",
f"cleanHDD={cleanHDD}",
"network=bitcoin",
@ -143,10 +138,10 @@ async def setup_start_done(
#### RECOVERY ####
elif 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,[
write_text_file(setupFilePath,[
"setPasswordA=1",
f"passwordA='{passwordA}'"
])
@ -156,7 +151,7 @@ async def setup_start_done(
return HTTPException(status.HTTP_405_METHOD_NOT_ALLOWED)
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")
# TODO: Following input parameters:
# lightning='lnd', 'cl' or 'none'
@ -174,7 +169,8 @@ 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
return signJWT()
return sign_jwt()
# WebUI now loops getting status until state=`waitfinal` then calls:
@ -190,13 +186,15 @@ 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:
seedwordsNEW = data["seedwordsNEW"]
@ -218,7 +216,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"}

View file

@ -1,24 +1,26 @@
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.models.system import LoginInput, RawDebugLogData, SystemInfo
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 (
get_debug_logs_raw_desc,
get_debug_logs_raw_resp_desc,
@ -48,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",
@ -66,7 +73,7 @@ async def login(i: LoginInput):
dependencies=[Depends(JWTBearer())],
)
def refresh_token():
return signJWT()
return sign_jwt()
@router.get(
@ -132,8 +139,14 @@ async def hw_info_sub(request: Request):
summary="Reboots the system",
dependencies=[Depends(JWTBearer())],
)
def reboot_system():
return HTTPException(status.HTTP_501_NOT_IMPLEMENTED)
async def reboot_system() -> bool:
if PLATFORM == APIPlatform.RASPIBLITZ:
await shutdown(True)
return True
else:
raise HTTPException(
status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented on native"
)
@router.post(
@ -142,5 +155,11 @@ def reboot_system():
summary="Shuts the system down",
dependencies=[Depends(JWTBearer())],
)
def reboot_system():
return HTTPException(status.HTTP_501_NOT_IMPLEMENTED)
async def shutdown() -> bool:
if PLATFORM == APIPlatform.RASPIBLITZ:
await shutdown(False)
return True
else:
raise HTTPException(
status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented on native"
)