mirror of
https://github.com/cryptoadvance/specter-desktop.git
synced 2026-08-13 12:33:29 +02:00
Refactor preparation for observers (#544)
* move node update to a separate method * split specter.check * split specter.check * finish check separation * monitor wallets for all users * typo * add user manager * black * fix default user * check only current user by default
This commit is contained in:
parent
7fca3333e9
commit
7baa4e2cf3
10 changed files with 350 additions and 224 deletions
|
|
@ -277,7 +277,7 @@ def login():
|
|||
# TODO: This way both "User" and "user" will pass as usernames, should there be strict check on that here? Or should we keep it like this?
|
||||
username = request.form["username"]
|
||||
password = request.form["password"]
|
||||
user = User.get_user_by_name(app.specter, username)
|
||||
user = app.specter.user_manager.get_user_by_username(username)
|
||||
if user:
|
||||
if verify_password(user.password, password):
|
||||
app.login(user.id)
|
||||
|
|
@ -320,9 +320,7 @@ def register():
|
|||
password = hash_password(request.form["password"])
|
||||
otp = request.form["otp"]
|
||||
user_id = alias(username)
|
||||
if User.get_user(app.specter, user_id) or User.get_user_by_name(
|
||||
app.specter, username
|
||||
):
|
||||
if app.specter.user_manager.get_user(user_id):
|
||||
flash("Username is already taken, please choose another one", "error")
|
||||
return redirect("register?otp={}".format(otp))
|
||||
if app.specter.burn_new_user_otp(otp):
|
||||
|
|
@ -336,7 +334,7 @@ def register():
|
|||
"hwi_bridge_url": "/hwi/api/",
|
||||
}
|
||||
user = User(user_id, username, password, config)
|
||||
user.save_info(app.specter)
|
||||
app.specter.add_user(user)
|
||||
flash(
|
||||
"You have registered successfully, \
|
||||
please login with your new account to start using Specter"
|
||||
|
|
@ -356,7 +354,6 @@ please request a new link from the node operator.",
|
|||
def logout():
|
||||
logout_user()
|
||||
flash("You were logged out", "info")
|
||||
app.specter.clear_user_session()
|
||||
return redirect("login")
|
||||
|
||||
|
||||
|
|
@ -592,7 +589,7 @@ def auth_settings():
|
|||
new_otp = -1
|
||||
users = None
|
||||
if current_user.is_admin and auth == "usernamepassword":
|
||||
users = [user for user in User.get_all_users(app.specter) if not user.is_admin]
|
||||
users = [user for user in app.specter.user_manager.users if not user.is_admin]
|
||||
if request.method == "POST":
|
||||
action = request.form["action"]
|
||||
|
||||
|
|
@ -607,7 +604,7 @@ def auth_settings():
|
|||
auth = request.form["auth"]
|
||||
if specter_username:
|
||||
if current_user.username != specter_username:
|
||||
if User.get_user_by_name(app.specter, specter_username):
|
||||
if app.specter.user_manager.get_user(specter_username):
|
||||
flash(
|
||||
"Username is already taken, please choose another one",
|
||||
"error",
|
||||
|
|
@ -631,7 +628,7 @@ def auth_settings():
|
|||
if auth == "usernamepassword":
|
||||
users = [
|
||||
user
|
||||
for user in User.get_all_users(app.specter)
|
||||
for user in app.specter.user_manager.users
|
||||
if not user.is_admin
|
||||
]
|
||||
else:
|
||||
|
|
@ -661,23 +658,11 @@ def auth_settings():
|
|||
)
|
||||
elif action == "deleteuser":
|
||||
delete_user = request.form["deleteuser"]
|
||||
user = app.specter.user_manager.get_user(delete_user)
|
||||
if current_user.is_admin:
|
||||
user = User.get_user(app.specter, delete_user)
|
||||
if user:
|
||||
user.delete(app.specter)
|
||||
users = [
|
||||
user
|
||||
for user in User.get_all_users(app.specter)
|
||||
if not user.is_admin
|
||||
]
|
||||
flash(
|
||||
"User {} was deleted successfully".format(user.username), "info"
|
||||
)
|
||||
else:
|
||||
flash(
|
||||
"Error: failed to delete user, invalid user ID was given",
|
||||
"error",
|
||||
)
|
||||
app.specter.delete_user(user)
|
||||
users.remove(user)
|
||||
flash("User {} was deleted successfully".format(user.username), "info")
|
||||
else:
|
||||
flash("Error: Only the admin account can delete users", "error")
|
||||
return render_template(
|
||||
|
|
@ -979,7 +964,7 @@ def new_wallet(wallet_type):
|
|||
if "use_explorer" in request.form:
|
||||
explorer = app.specter.get_default_explorer()
|
||||
wallet.rescanutxo(explorer)
|
||||
app.specter._info["utxorescan"] = 1
|
||||
app.specter.info["utxorescan"] = 1
|
||||
app.specter.utxorescanwallet = wallet.alias
|
||||
else:
|
||||
app.logger.info("Rescanning Blockchain ...")
|
||||
|
|
@ -1103,7 +1088,7 @@ def singlesig_setup_wizard():
|
|||
if "use_explorer" in request.form:
|
||||
explorer = app.specter.get_default_explorer()
|
||||
wallet.rescanutxo(explorer)
|
||||
app.specter._info["utxorescan"] = 1
|
||||
app.specter.info["utxorescan"] = 1
|
||||
app.specter.utxorescanwallet = wallet.alias
|
||||
return redirect(url_for("wallet", wallet_alias=wallet.alias))
|
||||
return render_template(
|
||||
|
|
@ -1218,12 +1203,12 @@ def txout_set_info():
|
|||
@login_required
|
||||
def get_scantxoutset_status():
|
||||
status = app.specter.rpc.scantxoutset("status", [])
|
||||
app.specter._info["utxorescan"] = status.get("progress", None) if status else None
|
||||
if app.specter._info["utxorescan"] is None:
|
||||
app.specter.info["utxorescan"] = status.get("progress", None) if status else None
|
||||
if app.specter.info["utxorescan"] is None:
|
||||
app.specter.utxorescanwallet = None
|
||||
return {
|
||||
"active": app.specter._info["utxorescan"] is not None,
|
||||
"progress": app.specter._info["utxorescan"],
|
||||
"active": app.specter.info["utxorescan"] is not None,
|
||||
"progress": app.specter.info["utxorescan"],
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1513,11 +1498,11 @@ def wallet_settings(wallet_alias):
|
|||
if "use_explorer" in request.form:
|
||||
explorer = app.specter.get_default_explorer()
|
||||
wallet.rescanutxo(explorer)
|
||||
app.specter._info["utxorescan"] = 1
|
||||
app.specter.info["utxorescan"] = 1
|
||||
app.specter.utxorescanwallet = wallet.alias
|
||||
elif action == "abortrescanutxo":
|
||||
app.specter.abortrescanutxo()
|
||||
app.specter._info["utxorescan"] = None
|
||||
app.specter.info["utxorescan"] = None
|
||||
app.specter.utxorescanwallet = None
|
||||
elif action == "keypoolrefill":
|
||||
delta = int(request.form["keypooladd"])
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class Device:
|
|||
hwi_support = False
|
||||
supports_hwi_toggle_passphrase = False
|
||||
supports_hwi_multisig_display_address = False
|
||||
hot_wallet = False
|
||||
|
||||
def __init__(self, name, alias, keys, fullpath, manager):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from .rpc import get_default_datadir
|
|||
|
||||
from .devices import __all__ as device_classes
|
||||
from .devices.generic import GenericDevice # default device type
|
||||
from .persistence import write_device, delete_json_file
|
||||
from .persistence import write_device, delete_json_file, delete_folder
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -95,3 +95,10 @@ class DeviceManager:
|
|||
@property
|
||||
def supported_devices(self):
|
||||
return device_classes
|
||||
|
||||
def delete(self, specter):
|
||||
"""Deletes all the devices"""
|
||||
for d in self.devices:
|
||||
device = self.devices[d]
|
||||
self.remove_device(device)
|
||||
delete_folder(self.data_folder)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import logging
|
|||
from contextlib import nullcontext
|
||||
from flask import current_app as app
|
||||
from .util.shell import run_shell
|
||||
import shutil
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ def init_app(app, hwibridge=False, specter=None):
|
|||
|
||||
@login_manager.user_loader
|
||||
def user_loader(id):
|
||||
return User.get_user(specter, id)
|
||||
return specter.user_manager.get_user(id)
|
||||
|
||||
def login(id):
|
||||
login_user(user_loader(id))
|
||||
|
|
|
|||
|
|
@ -13,8 +13,9 @@ from requests.exceptions import ConnectionError
|
|||
from .rpc import BitcoinRPC
|
||||
from .device_manager import DeviceManager
|
||||
from .wallet_manager import WalletManager
|
||||
from .user_manager import UserManager
|
||||
from .persistence import write_json_file, read_json_file
|
||||
from flask_login import current_user
|
||||
from .user import User
|
||||
import threading
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -66,7 +67,6 @@ def get_rpc(conf, old_rpc=None):
|
|||
class Specter:
|
||||
""" A central Object mostly holding app-settings """
|
||||
|
||||
CONFIG_FILE_NAME = "config.json"
|
||||
# use this lock for all fs operations
|
||||
lock = threading.Lock()
|
||||
|
||||
|
|
@ -74,14 +74,21 @@ class Specter:
|
|||
if data_folder.startswith("~"):
|
||||
data_folder = os.path.expanduser(data_folder)
|
||||
data_folder = os.path.abspath(data_folder)
|
||||
|
||||
# creating folders if they don't exist
|
||||
if not os.path.isdir(data_folder):
|
||||
os.makedirs(data_folder)
|
||||
|
||||
self.data_folder = data_folder
|
||||
|
||||
self.rpc = None
|
||||
self.device_manager = None
|
||||
self.wallet_manager = None
|
||||
self._current_version = None
|
||||
self.user_manager = UserManager(self)
|
||||
|
||||
self.file_config = None # what comes from config file
|
||||
self.arg_config = config # what comes from arguments
|
||||
|
||||
# wallet that is currently rescnning with utxorescan
|
||||
# can be only one at a time
|
||||
self.utxorescanwallet = None
|
||||
|
||||
# default config
|
||||
|
|
@ -105,42 +112,36 @@ class Specter:
|
|||
"validate_merkle_proofs": False,
|
||||
}
|
||||
|
||||
# creating folders if they don't exist
|
||||
if not os.path.isdir(data_folder):
|
||||
os.makedirs(data_folder)
|
||||
# health check: loads config, tests rpc
|
||||
# also loads and checks wallets for all users
|
||||
self.check(check_all=True)
|
||||
|
||||
self.is_checking = False
|
||||
# health check: loads config and tests rpc
|
||||
self.check()
|
||||
|
||||
@property
|
||||
def bitcoin_datadir(self):
|
||||
if "datadir" in self.config["rpc"]:
|
||||
return os.path.expanduser(self.config["rpc"]["datadir"])
|
||||
return get_default_datadir()
|
||||
|
||||
def check(self, user=current_user):
|
||||
# if config.json file exists - load from it
|
||||
if os.path.isfile(os.path.join(self.data_folder, "config.json")):
|
||||
with self.lock:
|
||||
self.file_config = read_json_file(
|
||||
os.path.join(self.data_folder, "config.json")
|
||||
)
|
||||
deep_update(self.config, self.file_config)
|
||||
# otherwise - create one and assign unique id
|
||||
else:
|
||||
if self.config["uid"] == "":
|
||||
self.config["uid"] = (
|
||||
random.randint(0, 256 ** 8).to_bytes(8, "big").hex()
|
||||
)
|
||||
self._save()
|
||||
|
||||
# init arguments
|
||||
deep_update(self.config, self.arg_config) # override loaded config
|
||||
def check(self, user=None, check_all=False):
|
||||
"""
|
||||
Checks and updates everything for a particular user:
|
||||
- config if changed
|
||||
- rpc including check if it's connected
|
||||
- node info
|
||||
- wallet manager
|
||||
- device manager
|
||||
"""
|
||||
# find proper user
|
||||
user = self.user_manager.get_user(user)
|
||||
# check if config file have changed
|
||||
self.check_config()
|
||||
|
||||
# update rpc if something doesn't work
|
||||
if self.rpc is None or not self.rpc.test_connection():
|
||||
self.rpc = get_rpc(self.config["rpc"], self.rpc)
|
||||
|
||||
self.check_node_info()
|
||||
if not check_all:
|
||||
self.check_for_user(user)
|
||||
else:
|
||||
for u in self.user_manager.users:
|
||||
self.check_for_user(u)
|
||||
|
||||
def check_node_info(self):
|
||||
self._is_configured = self.rpc is not None
|
||||
self._is_running = False
|
||||
if self._is_configured:
|
||||
|
|
@ -187,47 +188,106 @@ class Specter:
|
|||
if not self._is_running:
|
||||
self._info["chain"] = None
|
||||
|
||||
chain = self._info["chain"]
|
||||
if hasattr(user, "is_admin"):
|
||||
user_folder_id = "_" + user.id if user and not user.is_admin else ""
|
||||
else:
|
||||
user_folder_id = ""
|
||||
def get_user_folder_id(self, user=None):
|
||||
"""
|
||||
Returns the suffix for the user wallets and devices.
|
||||
User can be either a flask_login user or a string.
|
||||
"""
|
||||
user = self.user_manager.get_user(user)
|
||||
if not user.is_admin:
|
||||
return "_" + user.id
|
||||
return ""
|
||||
|
||||
if self.config["auth"] != "usernamepassword" or (
|
||||
user and not user.is_anonymous
|
||||
def check_wallet_manager(self, user=None):
|
||||
"""Updates wallet manager for a particular user"""
|
||||
user = self.user_manager.get_user(user)
|
||||
wallets_rpcpath = "specter%s" % self.config["uid"]
|
||||
wallets_folder = os.path.join(self.data_folder, f"wallets{user.folder_id}")
|
||||
# if chain, user or data folder changed
|
||||
wallet_manager = user.wallet_manager
|
||||
if (
|
||||
wallet_manager is None
|
||||
or wallet_manager.data_folder != self.data_folder
|
||||
or wallet_manager.rpc_path != wallets_rpcpath
|
||||
or wallet_manager.chain != self.chain
|
||||
):
|
||||
if self.device_manager is None:
|
||||
self.device_manager = DeviceManager(
|
||||
os.path.join(self.data_folder, "devices{}".format(user_folder_id))
|
||||
)
|
||||
else:
|
||||
self.device_manager.update(
|
||||
data_folder=os.path.join(
|
||||
self.data_folder, "devices{}".format(user_folder_id)
|
||||
)
|
||||
)
|
||||
wallet_manager = WalletManager(
|
||||
wallets_folder,
|
||||
self.rpc,
|
||||
self.chain,
|
||||
self.device_manager,
|
||||
path=wallets_rpcpath,
|
||||
)
|
||||
user.wallet_manager = wallet_manager
|
||||
else:
|
||||
wallet_manager.update(wallets_folder, self.rpc, chain=self.chain)
|
||||
|
||||
wallets_path = "specter%s" % self.config["uid"]
|
||||
# if chain, user or data folder changed
|
||||
if (
|
||||
self.wallet_manager is None
|
||||
or self.wallet_manager.data_folder != self.data_folder
|
||||
or self.wallet_manager.rpc_path != wallets_path
|
||||
or self.wallet_manager.chain != chain
|
||||
):
|
||||
self.wallet_manager = WalletManager(
|
||||
os.path.join(self.data_folder, "wallets{}".format(user_folder_id)),
|
||||
self.rpc,
|
||||
chain,
|
||||
self.device_manager,
|
||||
path=wallets_path,
|
||||
)
|
||||
else:
|
||||
self.wallet_manager.update(
|
||||
os.path.join(self.data_folder, "wallets{}".format(user_folder_id)),
|
||||
self.rpc,
|
||||
chain=chain,
|
||||
def check_device_manager(self, user=None):
|
||||
"""Updates device manager for a particular user"""
|
||||
user = self.user_manager.get_user(user)
|
||||
devices_folder = os.path.join(self.data_folder, f"devices{user.folder_id}")
|
||||
device_manager = user.device_manager
|
||||
if device_manager is None:
|
||||
device_manager = DeviceManager(devices_folder)
|
||||
user.device_manager = device_manager
|
||||
else:
|
||||
device_manager.update(data_folder=devices_folder)
|
||||
|
||||
def check_config(self):
|
||||
"""
|
||||
Updates config if file config have changed.
|
||||
Priority (low to high):
|
||||
- existing / default config
|
||||
- file config from config.json
|
||||
- arg_config passed in constructor
|
||||
"""
|
||||
|
||||
# if config.json file exists - load from it
|
||||
if os.path.isfile(self.config_fname):
|
||||
with self.lock:
|
||||
self.file_config = read_json_file(self.config_fname)
|
||||
deep_update(self.config, self.file_config)
|
||||
# otherwise - create one and assign unique id
|
||||
else:
|
||||
# unique id of specter
|
||||
if self.config["uid"] == "":
|
||||
self.config["uid"] = (
|
||||
random.randint(0, 256 ** 8).to_bytes(8, "big").hex()
|
||||
)
|
||||
self._save()
|
||||
|
||||
# config from constructor overrides file config
|
||||
deep_update(self.config, self.arg_config)
|
||||
|
||||
def check_for_user(self, user=None):
|
||||
"""
|
||||
Performs device and wallet manager check for particular user
|
||||
"""
|
||||
user = self.user_manager.get_user(user)
|
||||
self.check_device_manager(user)
|
||||
self.check_wallet_manager(user)
|
||||
|
||||
def add_user(self, user):
|
||||
if user in self.user_manager.users:
|
||||
return
|
||||
user.wallet_manager = None
|
||||
user.device_manager = None
|
||||
self.user_manager.add_user(user)
|
||||
self.check_for_user(user)
|
||||
|
||||
def delete_user(self, user):
|
||||
if user not in self.user_manager.users:
|
||||
return
|
||||
user = self.user_manager.get_user(user)
|
||||
user.wallet_manager.delete(self)
|
||||
user.device_manager.delete(self)
|
||||
self.user_manager.delete_user(user)
|
||||
|
||||
@property
|
||||
def bitcoin_datadir(self):
|
||||
if "datadir" in self.config["rpc"]:
|
||||
return os.path.expanduser(self.config["rpc"]["datadir"])
|
||||
return get_default_datadir()
|
||||
|
||||
def abortrescanutxo(self):
|
||||
self.rpc.scantxoutset("abort", [])
|
||||
|
|
@ -236,10 +296,6 @@ class Specter:
|
|||
self._info["utxorescan"] = None
|
||||
self.utxorescanwallet = None
|
||||
|
||||
def clear_user_session(self):
|
||||
self.device_manager = None
|
||||
self.wallet_manager = None
|
||||
|
||||
def test_rpc(self, **kwargs):
|
||||
conf = copy.deepcopy(self.config["rpc"])
|
||||
conf.update(kwargs)
|
||||
|
|
@ -297,10 +353,14 @@ class Specter:
|
|||
def _save(self):
|
||||
write_json_file(
|
||||
self.config,
|
||||
os.path.join(self.data_folder, self.CONFIG_FILE_NAME),
|
||||
self.config_fname,
|
||||
lock=self.lock,
|
||||
)
|
||||
|
||||
@property
|
||||
def config_fname(self):
|
||||
return os.path.join(self.data_folder, "config.json")
|
||||
|
||||
def update_rpc(self, **kwargs):
|
||||
need_update = False
|
||||
for k in kwargs:
|
||||
|
|
@ -310,7 +370,7 @@ class Specter:
|
|||
if need_update:
|
||||
self.rpc = get_rpc(self.config["rpc"], None)
|
||||
self._save()
|
||||
self.check()
|
||||
self.check(check_all=True)
|
||||
return self.rpc is not None
|
||||
|
||||
def update_auth(self, auth):
|
||||
|
|
@ -321,6 +381,7 @@ class Specter:
|
|||
|
||||
def update_explorer(self, explorer, user):
|
||||
""" update the block explorers urls """
|
||||
user = self.user_manager.get_user(user)
|
||||
# we don't know what chain to change
|
||||
if not self.chain:
|
||||
return
|
||||
|
|
@ -337,6 +398,7 @@ class Specter:
|
|||
|
||||
def update_hwi_bridge_url(self, url, user):
|
||||
""" update the hwi bridge url to use """
|
||||
user = self.user_manager.get_user(user)
|
||||
if url and not url.endswith("/"):
|
||||
# make sure the urls end with a "/"
|
||||
url += "/"
|
||||
|
|
@ -351,21 +413,21 @@ class Specter:
|
|||
if not url.endswith("/hwi/api/"):
|
||||
url += "hwi/api/"
|
||||
|
||||
if user.id == "admin":
|
||||
if user.is_admin:
|
||||
self.config["hwi_bridge_url"] = url
|
||||
self._save()
|
||||
else:
|
||||
user.set_hwi_bridge_url(self, url)
|
||||
|
||||
def update_unit(self, unit, user):
|
||||
if user.id == "admin":
|
||||
if user.is_admin:
|
||||
self.config["unit"] = unit
|
||||
self._save()
|
||||
else:
|
||||
user.set_unit(self, unit)
|
||||
|
||||
def update_merkleproof_settings(self, validate_bool):
|
||||
if validate_bool is True and self._info.get("pruned") is True:
|
||||
if validate_bool is True and self.info.get("pruned") is True:
|
||||
validate_bool = False
|
||||
logger.warning("Cannot enable merkleproof setting on pruned node.")
|
||||
|
||||
|
|
@ -449,50 +511,39 @@ class Specter:
|
|||
def chain(self):
|
||||
return self._info["chain"]
|
||||
|
||||
@property
|
||||
def user_config(self):
|
||||
return self.config if self.user.is_admin else self.user.config
|
||||
|
||||
@property
|
||||
def explorer(self):
|
||||
# TODO: Unify for user and admin
|
||||
if (not current_user or current_user.is_anonymous) or current_user.is_admin:
|
||||
if "explorers" in self.config and self.chain in self.config["explorers"]:
|
||||
return self.config["explorers"][self.chain]
|
||||
else:
|
||||
return ""
|
||||
else:
|
||||
if (
|
||||
"explorers" in current_user.config
|
||||
and self.chain in current_user.config["explorers"]
|
||||
):
|
||||
return current_user.config["explorers"][self.chain]
|
||||
else:
|
||||
return ""
|
||||
return self.user_config.get("explorers", {}).get(self.chain, "")
|
||||
|
||||
@property
|
||||
def hwi_bridge_url(self):
|
||||
# TODO: Unify for user and admin
|
||||
if (not current_user or current_user.is_anonymous) or current_user.is_admin:
|
||||
if "hwi_bridge_url" in self.config:
|
||||
return self.config["hwi_bridge_url"]
|
||||
else:
|
||||
return ""
|
||||
else:
|
||||
if "hwi_bridge_url" in current_user.config:
|
||||
return current_user.config["hwi_bridge_url"]
|
||||
else:
|
||||
return ""
|
||||
return self.user_config.get("hwi_bridge_url", "")
|
||||
|
||||
@property
|
||||
def unit(self):
|
||||
# TODO: Unify for user and admin
|
||||
if (not current_user or current_user.is_anonymous) or current_user.is_admin:
|
||||
if "unit" in self.config:
|
||||
return self.config["unit"]
|
||||
else:
|
||||
return "btc"
|
||||
else:
|
||||
if "unit" in current_user.config:
|
||||
return current_user.config["unit"]
|
||||
else:
|
||||
return "btc"
|
||||
return self.user_config.get("unit", "btc")
|
||||
|
||||
@property
|
||||
def admin(self):
|
||||
for u in self.users:
|
||||
if u.is_admin:
|
||||
return u
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
return self.user_manager.user
|
||||
|
||||
@property
|
||||
def device_manager(self):
|
||||
return self.user.device_manager
|
||||
|
||||
@property
|
||||
def wallet_manager(self):
|
||||
return self.user.wallet_manager
|
||||
|
||||
def specter_backup_file(self):
|
||||
memory_file = BytesIO()
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
</h1>
|
||||
{% if wallet.rescan_progress or specter.utxorescanwallet == wallet.alias %}
|
||||
<span id="wallet_rescan_data" style="display: contents;">
|
||||
<h2>Rescanning blockchain: <span id="{{ wallet.alias }}_balances_wallet_rescan_percents">{{ "%.2f"|format(specter._info["utxorescan"] if specter.utxorescanwallet == wallet.alias else wallet.rescan_progress * 100) }}</span>%</h2>
|
||||
<h2>Rescanning blockchain: <span id="{{ wallet.alias }}_balances_wallet_rescan_percents">{{ "%.2f"|format(specter.info["utxorescan"] if specter.utxorescanwallet == wallet.alias else wallet.rescan_progress * 100) }}</span>%</h2>
|
||||
<span class="warning">
|
||||
ⓘ<br>
|
||||
Total balance and transactions history may show outdated data during scanning.<br>Please wait until the scanning is complete before you start using the wallet.
|
||||
|
|
|
|||
|
|
@ -33,29 +33,6 @@ def verify_password(stored_password, provided_password):
|
|||
return pwdhash == binascii.a2b_base64(stored_password["pwdhash"])
|
||||
|
||||
|
||||
def get_users_json(specter):
|
||||
users = [
|
||||
{
|
||||
"id": "admin",
|
||||
"username": "admin",
|
||||
"password": hash_password("admin"),
|
||||
"is_admin": True,
|
||||
}
|
||||
]
|
||||
|
||||
# if users.json file exists - load from it
|
||||
if os.path.isfile(os.path.join(specter.data_folder, "users.json")):
|
||||
users = read_json_file(os.path.join(specter.data_folder, "users.json"))
|
||||
# otherwise - create one and assign unique id
|
||||
else:
|
||||
save_users_json(specter, users)
|
||||
return users
|
||||
|
||||
|
||||
def save_users_json(specter, users):
|
||||
write_json_file(users, os.path.join(specter.data_folder, "users.json"))
|
||||
|
||||
|
||||
class User(UserMixin):
|
||||
def __init__(self, id, username, password, config, is_admin=False):
|
||||
self.id = id
|
||||
|
|
@ -63,6 +40,15 @@ class User(UserMixin):
|
|||
self.password = password
|
||||
self.config = config
|
||||
self.is_admin = is_admin
|
||||
self.wallet_manager = None
|
||||
self.device_manager = None
|
||||
self.manager = None
|
||||
|
||||
@property
|
||||
def folder_id(self):
|
||||
if self.is_admin:
|
||||
return ""
|
||||
return f"_{self.id}"
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, user_dict):
|
||||
|
|
@ -86,31 +72,6 @@ class User(UserMixin):
|
|||
except:
|
||||
raise SpecterError("Unable to parse user JSON.")
|
||||
|
||||
@classmethod
|
||||
def get_user(cls, specter, id):
|
||||
users = get_users_json(specter)
|
||||
for user_dict in users:
|
||||
user = User.from_json(user_dict)
|
||||
if user.id == id:
|
||||
return user
|
||||
|
||||
@classmethod
|
||||
def get_user_by_name(cls, specter, username):
|
||||
users = get_users_json(specter)
|
||||
for user_dict in users:
|
||||
user = User.from_json(user_dict)
|
||||
if user.username == username:
|
||||
return user
|
||||
|
||||
@classmethod
|
||||
def get_all_users(cls, specter):
|
||||
users_dicts = get_users_json(specter)
|
||||
users = []
|
||||
for user_dict in users_dicts:
|
||||
user = User.from_json(user_dict)
|
||||
users.append(user)
|
||||
return users
|
||||
|
||||
@property
|
||||
def json(self):
|
||||
user_dict = {
|
||||
|
|
@ -124,20 +85,17 @@ class User(UserMixin):
|
|||
return user_dict
|
||||
|
||||
def save_info(self, specter, delete=False):
|
||||
users = get_users_json(specter)
|
||||
existing = False
|
||||
for i in range(len(users)):
|
||||
if users[i]["id"] == self.id:
|
||||
if not delete:
|
||||
users[i] = self.json
|
||||
existing = True
|
||||
else:
|
||||
del users[i]
|
||||
break
|
||||
if not existing and not delete:
|
||||
users.append(self.json)
|
||||
if self.manager is None:
|
||||
self.manager = specter.user_manager
|
||||
users = self.manager.users
|
||||
existing = self in users
|
||||
|
||||
save_users_json(specter, users)
|
||||
# update specter users
|
||||
if not existing and not delete:
|
||||
specter.add_user(self)
|
||||
if existing and delete:
|
||||
specter.delete_user(self)
|
||||
self.manager.save()
|
||||
|
||||
def set_explorer(self, specter, explorer):
|
||||
self.config["explorers"][specter.chain] = explorer
|
||||
|
|
@ -152,12 +110,23 @@ class User(UserMixin):
|
|||
self.save_info(specter)
|
||||
|
||||
def delete(self, specter):
|
||||
devices_datadir_path = os.path.join(
|
||||
os.path.join(specter.data_folder, "devices_{}".format(self.id))
|
||||
)
|
||||
wallets_datadir_path = os.path.join(
|
||||
os.path.join(specter.data_folder, "wallets_{}".format(self.id))
|
||||
)
|
||||
delete_folder(devices_datadir_path)
|
||||
delete_folder(wallets_datadir_path)
|
||||
# we delete wallet manager and device manager in save_info
|
||||
self.save_info(specter, delete=True)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, str):
|
||||
return self.id == other
|
||||
return self.id == other.id
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self):
|
||||
# to make lookups in dicts by user id
|
||||
return hash(self.id)
|
||||
|
||||
def __str__(self):
|
||||
return self.id
|
||||
|
||||
def __repr__(self):
|
||||
return f"User({self.id})"
|
||||
|
|
|
|||
105
src/cryptoadvance/specter/user_manager.py
Normal file
105
src/cryptoadvance/specter/user_manager.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import os
|
||||
import json
|
||||
import logging
|
||||
from .helpers import alias, load_jsons, fslock
|
||||
from .persistence import (
|
||||
read_json_file,
|
||||
write_json_file,
|
||||
delete_json_file,
|
||||
delete_folder,
|
||||
)
|
||||
from .user import User, hash_password
|
||||
from flask_login import current_user
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UserManager:
|
||||
"""
|
||||
The UserManager can manage users
|
||||
"""
|
||||
|
||||
# of them via json-files in an empty data folder
|
||||
def __init__(self, specter):
|
||||
self.specter = specter
|
||||
self.data_folder = specter.data_folder
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def users_file(self):
|
||||
return os.path.join(self.data_folder, "users.json")
|
||||
|
||||
def load_users(self):
|
||||
# if users.json file exists - load from it
|
||||
if os.path.isfile(self.users_file):
|
||||
users = read_json_file(self.users_file)
|
||||
# otherwise - create one and assign unique id
|
||||
else:
|
||||
users = [
|
||||
{
|
||||
"id": "admin",
|
||||
"username": "admin",
|
||||
"password": hash_password("admin"),
|
||||
"is_admin": True,
|
||||
}
|
||||
]
|
||||
# convert to User instances
|
||||
self.users = [User.from_json(u) for u in users]
|
||||
if not os.path.isfile(self.users_file):
|
||||
self.save()
|
||||
|
||||
def update(self):
|
||||
self.load_users()
|
||||
|
||||
def save(self):
|
||||
users_json = [u.json for u in self.users]
|
||||
write_json_file(users_json, self.users_file)
|
||||
|
||||
def add_user(self, user):
|
||||
if user not in self.users:
|
||||
self.users.append(user)
|
||||
user.manager = self
|
||||
self.save() # save files
|
||||
return self.get_user(user)
|
||||
|
||||
@property
|
||||
def admin(self):
|
||||
"""There is always one admin"""
|
||||
for u in self.users:
|
||||
if u.is_admin:
|
||||
return u
|
||||
|
||||
def get_user(self, user=None):
|
||||
"""
|
||||
Converts from flask_login user to a User in the system.
|
||||
Admin by default.
|
||||
"""
|
||||
# get by string
|
||||
if user is None:
|
||||
user = current_user
|
||||
if isinstance(user, str):
|
||||
return self.get_by_uid(user)
|
||||
if user and not user.is_anonymous:
|
||||
if user in self.users:
|
||||
return self.get_by_uid(user.id)
|
||||
return self.admin
|
||||
|
||||
def get_user_by_username(self, username):
|
||||
for u in self.users:
|
||||
if u.username == username:
|
||||
return u
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
"""User in current context, admin if no context"""
|
||||
return self.get_user(current_user)
|
||||
|
||||
def get_by_uid(self, uid):
|
||||
for u in self.users:
|
||||
if u.id == uid:
|
||||
return u
|
||||
logger.error("Could not find user %s" % uid)
|
||||
|
||||
def delete_user(self, user):
|
||||
self.users.remove(user)
|
||||
self.save()
|
||||
|
|
@ -6,7 +6,7 @@ from .helpers import alias, load_jsons
|
|||
from .rpc import get_default_datadir, RpcError
|
||||
from .specter_error import SpecterError
|
||||
from .wallet import Wallet
|
||||
from .persistence import delete_json_file
|
||||
from .persistence import delete_json_file, delete_folder
|
||||
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
|
@ -287,3 +287,10 @@ Silently ignored!"
|
|||
for tx in txlist:
|
||||
result.append(tx)
|
||||
return list(reversed(sorted(result, key=lambda tx: tx["time"])))
|
||||
|
||||
def delete(self, specter):
|
||||
"""Deletes all the wallets"""
|
||||
for w in self.wallets:
|
||||
wallet = self.wallets[w]
|
||||
self.delete_wallet(wallet, specter.bitcoin_datadir, specter.chain)
|
||||
delete_folder(self.data_folder)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue