mirror of
https://github.com/cryptoadvance/specter-desktop.git
synced 2026-08-13 12:33:29 +02:00
Feature: Auto privacy settings (#1415)
* Default bitcoind timeout to 60s for all platforms * Create bitcoind_setup_tasks.py * Missing prop setter bugfix Fixes #1221 * Use jinja's "tojson" to safely escape account_map device labels * Update wallet_pdf.jinja * Babel instruction updates to remove kdmukai fork references * Update README.md * Fix hwi_bridge mode template issues Fixes #1270 * Adds autohide sensitive info and auto logout timeouts * minor js bugfix in `auth_settings.jinja` * minor note in docs * Update base.jinja Removing debugging change. * changed autohide default to 20min; moved int() cast * PEP-8 formatting fix * update dependencies and remove demon-mode * test suite fix for new settings fields * Interim commit * make timeout issues more clear * more clever waiting for mining * extending Cypress test timeout for slow bitcoind operation on old dev machines * JS bugfix when auth type is None * Test patch for pytest * Removing pytest test fix * more robust way to delete file - fix test * fix testcoin_faucet (way too much unnecessary mining) Co-authored-by: Kim Neunert <k9ert@gmx.de>
This commit is contained in:
parent
d05140d55a
commit
10b312ef4e
14 changed files with 186 additions and 16 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -30,4 +30,5 @@ btcd-conn.json
|
|||
elmd-conn.json
|
||||
tests/bitcoin*
|
||||
token.sh
|
||||
src/cryptoadvance/specter/translations/**/messages.mo
|
||||
src/cryptoadvance/specter/translations/**/messages.mo
|
||||
tests/elements
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ Run the server:
|
|||
|
||||
```sh
|
||||
cd specter-desktop
|
||||
python3 -m cryptoadvance.specter server --config DevelopmentConfig
|
||||
export FLASK_ENV=development && python3 -m cryptoadvance.specter server --config DevelopmentConfig
|
||||
```
|
||||
|
||||
#### If `pip install` fails on `cryptography==3.4.x`
|
||||
|
|
@ -175,6 +175,11 @@ pytest tests/test_specter.py::test_specter
|
|||
pytest --setup-show
|
||||
```
|
||||
|
||||
Print the logging output live to the terminal:
|
||||
```
|
||||
pytest --capture=no --log-cli-level=DEBUG
|
||||
```
|
||||
|
||||
Get the log-output of bitcoind side by side with the test-output. For sure you will only see the logs if the test fails.
|
||||
```
|
||||
pytest --bitcoind-log-stdout
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ Cypress.Commands.add("mine2wallet", (chain) => {
|
|||
})
|
||||
, {
|
||||
errorMsg: 'Waited for the funds arriving in the wallet from chain mining but it never did (timeout 30s) ',
|
||||
timeout: 30000,
|
||||
timeout: 60000,
|
||||
interval: 2000
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -66,6 +66,8 @@ class ConfigManager(GenericDataManager):
|
|||
"fee_estimator": "mempool",
|
||||
"fee_estimator_custom_url": "",
|
||||
"hide_sensitive_info": False,
|
||||
"autohide_sensitive_info_timeout_minutes": 20,
|
||||
"autologout_timeout_hours": 4,
|
||||
# TODO: remove
|
||||
"bitcoind": False,
|
||||
}
|
||||
|
|
@ -268,6 +270,24 @@ class ConfigManager(GenericDataManager):
|
|||
else:
|
||||
user.set_hide_sensitive_info(hide_sensitive_info_bool)
|
||||
|
||||
def update_autohide_sensitive_info_timeout(self, timeout_minutes, user):
|
||||
if isinstance(user, str):
|
||||
raise Exception("Please pass a real user, not a string-user")
|
||||
if user.is_admin:
|
||||
self.data["autohide_sensitive_info_timeout_minutes"] = timeout_minutes
|
||||
self._save()
|
||||
else:
|
||||
user.set_autohide_sensitive_info_timeout(timeout_minutes)
|
||||
|
||||
def update_autologout_timeout(self, timeout_hours, user):
|
||||
if isinstance(user, str):
|
||||
raise Exception("Please pass a real user, not a string-user")
|
||||
if user.is_admin:
|
||||
self.data["autologout_timeout_hours"] = timeout_hours
|
||||
self._save()
|
||||
else:
|
||||
user.set_autologout_timeout(timeout_hours)
|
||||
|
||||
def update_price_provider(self, price_provider, user):
|
||||
if isinstance(user, str):
|
||||
raise Exception("Please pass a real user, not a string-user")
|
||||
|
|
|
|||
|
|
@ -90,9 +90,11 @@ def delete_files(paths):
|
|||
"""deletes multiple files and calls storage callback once"""
|
||||
need_callback = False
|
||||
for path in paths:
|
||||
if os.path.exists(path):
|
||||
try:
|
||||
os.remove(path)
|
||||
need_callback = True
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
if need_callback:
|
||||
storage_callback()
|
||||
|
||||
|
|
|
|||
|
|
@ -257,11 +257,12 @@ class NodeController:
|
|||
default_address = default_rpc.getaddressinfo(default_address)[
|
||||
"unconfidential"
|
||||
]
|
||||
while True:
|
||||
btc_balance = default_rpc.getbalance()
|
||||
|
||||
btc_balance = default_rpc.getbalance()
|
||||
while btc_balance <= amount * 5:
|
||||
rpc.generatetoaddress(102, default_address)
|
||||
if btc_balance > amount:
|
||||
break
|
||||
btc_balance = default_rpc.getbalance()
|
||||
|
||||
default_rpc.sendtoaddress(address, amount)
|
||||
if confirm_payment:
|
||||
# confirm it
|
||||
|
|
|
|||
|
|
@ -30,9 +30,10 @@ def login():
|
|||
if request.method == "POST":
|
||||
rate_limit()
|
||||
auth = app.specter.config["auth"]
|
||||
|
||||
if auth["method"] == "none":
|
||||
app.login("admin")
|
||||
app.logger.info("AUDIT: Successfull Login no credentials")
|
||||
app.logger.info("AUDIT: Successful Login no credentials")
|
||||
return redirect_login(request)
|
||||
if auth["method"] == "rpcpasswordaspin":
|
||||
# TODO: check the password via RPC-call
|
||||
|
|
@ -165,13 +166,22 @@ please request a new link from the node operator."
|
|||
@auth_endpoint.route("/logout", methods=["GET", "POST"])
|
||||
def logout():
|
||||
logout_user()
|
||||
flash(_("You were logged out"), "info")
|
||||
if "timeout" in request.args:
|
||||
flash(_("You were automatically logged out"), "info")
|
||||
else:
|
||||
flash(_("You were logged out"), "info")
|
||||
return redirect(url_for("auth_endpoint.login"))
|
||||
|
||||
|
||||
################### Util ######################
|
||||
def redirect_login(request):
|
||||
flash(_("Logged in successfully."), "info")
|
||||
|
||||
# If the user is auto-logged out, hide_sensitive_info will be set. If they're
|
||||
# explicitly logging in now, clear the setting and reveal user's info.
|
||||
if app.specter.hide_sensitive_info:
|
||||
app.specter.update_hide_sensitive_info(False, current_user)
|
||||
|
||||
if request.form.get("next") and request.form.get("next") != "None":
|
||||
response = redirect(request.form["next"])
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -58,6 +58,31 @@ def general():
|
|||
unit = app.specter.unit
|
||||
if request.method == "POST":
|
||||
action = request.form["action"]
|
||||
|
||||
autohide_sensitive_info_timeout = request.form[
|
||||
"autohide_sensitive_info_timeout"
|
||||
]
|
||||
if autohide_sensitive_info_timeout == "NEVER":
|
||||
autohide_sensitive_info_timeout = None
|
||||
elif autohide_sensitive_info_timeout == "CUSTOM":
|
||||
autohide_sensitive_info_timeout = int(
|
||||
request.form["custom_autohide_sensitive_info_timeout"]
|
||||
)
|
||||
else:
|
||||
autohide_sensitive_info_timeout = int(autohide_sensitive_info_timeout)
|
||||
|
||||
if "autologout_timeout" in request.form:
|
||||
# Is only in the form if specter.config.auth.method != "none"
|
||||
autologout_timeout = request.form["autologout_timeout"]
|
||||
if autologout_timeout == "NEVER":
|
||||
autologout_timeout = None
|
||||
elif autologout_timeout == "CUSTOM":
|
||||
autologout_timeout = int(request.form["custom_autologout_timeout"])
|
||||
else:
|
||||
autologout_timeout = int(autologout_timeout)
|
||||
else:
|
||||
autologout_timeout = None
|
||||
|
||||
explorer_id = request.form["explorer"]
|
||||
explorer_data = app.config["EXPLORERS_LIST"][explorer_id]
|
||||
if explorer_id == "CUSTOM":
|
||||
|
|
@ -74,6 +99,13 @@ def general():
|
|||
if current_user.is_admin:
|
||||
set_loglevel(app, loglevel)
|
||||
|
||||
app.specter.config_manager.update_autohide_sensitive_info_timeout(
|
||||
autohide_sensitive_info_timeout, current_user
|
||||
)
|
||||
app.specter.config_manager.update_autologout_timeout(
|
||||
autologout_timeout, current_user
|
||||
)
|
||||
|
||||
app.specter.update_explorer(explorer_id, explorer_data, current_user)
|
||||
app.specter.update_unit(unit, current_user)
|
||||
app.specter.update_merkleproof_settings(
|
||||
|
|
@ -85,6 +117,7 @@ def general():
|
|||
user=current_user,
|
||||
)
|
||||
app.specter.check()
|
||||
|
||||
elif action == "restore":
|
||||
restore_devices = []
|
||||
restore_wallets = []
|
||||
|
|
|
|||
|
|
@ -616,6 +616,14 @@ class Specter:
|
|||
def hide_sensitive_info(self):
|
||||
return self.user_config.get("hide_sensitive_info", False)
|
||||
|
||||
@property
|
||||
def autohide_sensitive_info_timeout(self):
|
||||
return self.user_config.get("autohide_sensitive_info_timeout_minutes", 20)
|
||||
|
||||
@property
|
||||
def autologout_timeout(self):
|
||||
return self.user_config.get("autologout_timeout_hours", 4)
|
||||
|
||||
def requests_session(self, force_tor=False):
|
||||
requests_session = requests.Session()
|
||||
if self.only_tor or force_tor:
|
||||
|
|
|
|||
|
|
@ -294,6 +294,28 @@
|
|||
showError(e);
|
||||
}
|
||||
}
|
||||
|
||||
{% if current_user.is_authenticated and not specter.hide_sensitive_info and specter.autohide_sensitive_info_timeout %}
|
||||
document.addEventListener("DOMContentLoaded", function(){
|
||||
var privacyTimeout = setTimeout(function(){
|
||||
// Automatically trigger the privacy toggle
|
||||
toggleHideSensitiveInfo();
|
||||
},
|
||||
{{ specter.autohide_sensitive_info_timeout }} * 60 * 1000
|
||||
);
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.is_authenticated and specter.config.auth.method != "none" and specter.autologout_timeout %}
|
||||
document.addEventListener("DOMContentLoaded", function(){
|
||||
var logoutTimeout = setTimeout(function(){
|
||||
// Automatically log out the user
|
||||
location.href="{{ url_for('auth_endpoint.logout') }}?timeout=1";
|
||||
},
|
||||
{{ specter.autologout_timeout }} * 60 * 60 * 1000
|
||||
);
|
||||
});
|
||||
{% endif %}
|
||||
</script>
|
||||
{% endif %}
|
||||
{% block scripts %}
|
||||
|
|
|
|||
|
|
@ -79,12 +79,16 @@
|
|||
var generateregistrationlinkDiv = document.getElementById("generateregistrationlink");
|
||||
if (select.options[select.selectedIndex].value === 'usernamepassword'){
|
||||
usernamepasswordDiv.style.display = 'block';
|
||||
generateregistrationlinkDiv.style.display = 'flex';
|
||||
if (generateregistrationlinkDiv !== null) {
|
||||
generateregistrationlinkDiv.style.display = 'flex';
|
||||
}
|
||||
} else {
|
||||
usernamepasswordDiv.style.display = 'none';
|
||||
specterUsername.value = '{{ current_user.username }}';
|
||||
specterPassword.value = '';
|
||||
generateregistrationlinkDiv.style.display = 'none';
|
||||
if (generateregistrationlinkDiv !== null) {
|
||||
generateregistrationlinkDiv.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Password Only mode
|
||||
|
|
|
|||
|
|
@ -11,6 +11,35 @@
|
|||
{% include "includes/language/language_select.jinja" %}<br/>
|
||||
<br><br>
|
||||
|
||||
<h1>{{ _("Privacy") }}</h1>
|
||||
</label> {{ _("Auto-hide sensitive info") }}:<br>
|
||||
<select name="autohide_sensitive_info_timeout" id="autohide_sensitive_info_timeout" onchange="updateAutoHideSensitiveInfoTimeout()">
|
||||
<option value="NEVER" {% if specter.autohide_sensitive_info_timeout %}selected{% endif %}>{{ _("Never") }}</option>
|
||||
<option value="10" {% if specter.autohide_sensitive_info_timeout == 10 %}selected{% endif %}>{{ _("After being idle for 10 minutes") }}</option>
|
||||
<option value="20" {% if specter.autohide_sensitive_info_timeout == 20 %}selected{% endif %}>{{ _("After being idle for 20 minutes") }}</option>
|
||||
<option value="40" {% if specter.autohide_sensitive_info_timeout == 40 %}selected{% endif %}>{{ _("After being idle for 40 minutes") }}</option>
|
||||
<option value="CUSTOM" {% if specter.autohide_sensitive_info_timeout and specter.autohide_sensitive_info_timeout not in [10, 20, 40] %}selected{% endif %}>{{ _("Custom idle time (minutes)") }}</option>
|
||||
</select>
|
||||
<br>
|
||||
<input type="number" min="1" name="custom_autohide_sensitive_info_timeout" id="custom_autohide_sensitive_info_timeout" class="hidden" placeholder='{{ _("minutes") }}' {% if specter.autohide_sensitive_info_timeout and specter.autohide_sensitive_info_timeout not in [10, 20, 40] %}value="{{ specter.autohide_sensitive_info_timeout }}"{% endif %} style="margin-top: 15px; margin-bottom: 30px;"/>
|
||||
<br>
|
||||
|
||||
{% if specter.config.auth.method != "none" %}
|
||||
</label> {{ _("Auto-logout user") }}:<br>
|
||||
<select name="autologout_timeout" id="autologout_timeout" onchange="updateAutoLogoutTimeout()">
|
||||
<option value="NEVER" {% if not specter.autologout_timeout %}selected{% endif %}>{{ _("Never") }}</option>
|
||||
<option value="1" {% if specter.autologout_timeout == 1 %}selected{% endif %}>{{ _("After being idle for 1 hour") }}</option>
|
||||
<option value="4" {% if specter.autologout_timeout == 4 %}selected{% endif %}>{{ _("After being idle for 4 hours") }}</option>
|
||||
<option value="24" {% if specter.autologout_timeout == 24 %}selected{% endif %}>{{ _("After being idle for 24 hours") }}</option>
|
||||
<option value="CUSTOM" {% if specter.autologout_timeout and specter.autologout_timeout not in [1, 4, 24] %}selected{% endif %}>{{ _("Custom idle time (hours)") }}</option>
|
||||
</select>
|
||||
<br>
|
||||
<input type="number" min="1" name="custom_autologout_timeout" id="custom_autologout_timeout" class="hidden" placeholder='{{ _("hours") }}' {% if specter.autologout_timeout and specter.autologout_timeout not in [1, 4, 24] %}value="{{ specter.autologout_timeout }}"{% endif %} style="margin-top: 15px; margin-bottom: 30px;"/>
|
||||
<br>
|
||||
{% endif %}
|
||||
<br/>
|
||||
|
||||
|
||||
<h1>{{ _("Backup and Restore") }}</h1>
|
||||
<div class="tool-tip" style="float: right; margin-bottom: 5px;">
|
||||
<i class="tool-tip__icon">i</i>
|
||||
|
|
@ -24,7 +53,7 @@
|
|||
</div>
|
||||
{{ _("Specter Data Backup") }}:
|
||||
<div class="note">
|
||||
{{ _("Warning: This backup does not include private seed of the hot wallets or, obviously, the private seed of your Hardwarewallets.") }}
|
||||
{{ _("Warning: This backup does not include private seed of the hot wallets or, obviously, the private seed of your Hardwarewallets.") }}
|
||||
</div>
|
||||
<div class="row">
|
||||
<a href="{{ url_for('settings_endpoint.backup_file') }}" class="btn" style="width: 100%; margin-top: -5px;">{{ _("Download Specter backup files") }}</a>
|
||||
|
|
@ -92,13 +121,13 @@
|
|||
|
||||
{{ _("Validate Merkle Proofs") }}:
|
||||
<div class="note">
|
||||
{{ _("Cannot enable when using a pruned bitcoin node") }}
|
||||
{{ _("Cannot enable when using a pruned bitcoin node") }}
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="switch">
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="validatemerkleproof" name="validatemerkleproof" {% if validate_merkle_proofs %}checked{% endif %}>
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</label>
|
||||
<span class="note explorer-link" style="text-decoration: underline;" onclick="toggleHidden('merkle-info-details');">{{ _("What's this?") }}</span>
|
||||
</div>
|
||||
<div class="warning hidden" id="merkle-info-details">
|
||||
|
|
@ -171,6 +200,31 @@
|
|||
}
|
||||
});
|
||||
|
||||
function updateAutoHideSensitiveInfoTimeout() {
|
||||
if (document.getElementById('autohide_sensitive_info_timeout').value == "CUSTOM") {
|
||||
document.getElementById('custom_autohide_sensitive_info_timeout').classList.remove('hidden');
|
||||
} else {
|
||||
document.getElementById('custom_autohide_sensitive_info_timeout').classList.add('hidden');
|
||||
}
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", function(){
|
||||
updateAutoHideSensitiveInfoTimeout();
|
||||
});
|
||||
|
||||
|
||||
{% if specter.config.auth.method != "none" %}
|
||||
function updateAutoLogoutTimeout() {
|
||||
if (document.getElementById('autologout_timeout') !=== null && document.getElementById('autologout_timeout').value == "CUSTOM") {
|
||||
document.getElementById('custom_autologout_timeout').classList.remove('hidden');
|
||||
} else {
|
||||
document.getElementById('custom_autologout_timeout').classList.add('hidden');
|
||||
}
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", function(){
|
||||
updateAutoLogoutTimeout();
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
{% include "includes/language/language_js.jinja" %}
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -208,6 +208,14 @@ class User(UserMixin):
|
|||
self.config["hide_sensitive_info"] = hide_sensitive_info_bool
|
||||
self.save_info()
|
||||
|
||||
def set_autohide_sensitive_info_timeout(self, timeout_minutes):
|
||||
self.config["autohide_sensitive_info_timeout_minutes"] = timeout_minutes
|
||||
self.save_info()
|
||||
|
||||
def set_autologout_timeout(self, timeout_hours):
|
||||
self.config["autologout_timeout_hours"] = timeout_hours
|
||||
self.save_info()
|
||||
|
||||
def set_price_provider(self, price_provider):
|
||||
self.config["price_provider"] = price_provider
|
||||
self.save_info()
|
||||
|
|
|
|||
|
|
@ -66,6 +66,8 @@ def test_settings_general_restore_wallet(bitcoin_regtest, caplog, client):
|
|||
"/settings/general",
|
||||
data=dict(
|
||||
action="restore",
|
||||
autohide_sensitive_info_timeout="NEVER",
|
||||
autologout_timeout="NEVER",
|
||||
explorer="CUSTOM",
|
||||
custom_explorer="",
|
||||
unit="btc",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue