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:
kdmukai 2021-10-10 04:41:18 -05:00 committed by GitHub
parent d05140d55a
commit 10b312ef4e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 186 additions and 16 deletions

3
.gitignore vendored
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 = []

View file

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

View file

@ -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 %}

View file

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

View file

@ -11,6 +11,35 @@
{% include "includes/language/language_select.jinja" %}<br/>
<br><br>
<h1>{{ _("Privacy") }}</h1>
</label>&nbsp;{{ _("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>&nbsp;{{ _("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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</label>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<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>

View file

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

View file

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