Ability to use a generated salted password in deployments as env var SALTED_PASS (#397)

* Ability to use a generated salted password in deployments as env var SALTED_PASS
pull/398/merge
dgtlmoon 3 years ago committed by GitHub
parent 03f776ca45
commit 73101a47e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -143,13 +143,21 @@ class User(flask_login.UserMixin):
def get_id(self): def get_id(self):
return str(self.id) return str(self.id)
# Compare given password against JSON store or Env var
def check_password(self, password): def check_password(self, password):
import base64 import base64
import hashlib import hashlib
# Getting the values back out # Can be stored in env (for deployments) or in the general configs
raw_salt_pass = base64.b64decode(datastore.data['settings']['application']['password']) raw_salt_pass = os.getenv("SALTED_PASS", False)
if not raw_salt_pass:
raw_salt_pass = datastore.data['settings']['application']['password']
raw_salt_pass = base64.b64decode(raw_salt_pass)
salt_from_storage = raw_salt_pass[:32] # 32 is the length of the salt salt_from_storage = raw_salt_pass[:32] # 32 is the length of the salt
# Use the exact same setup you used to generate the key, but this time put in the password to check # Use the exact same setup you used to generate the key, but this time put in the password to check
@ -200,7 +208,7 @@ def changedetection_app(config=None, datastore_o=None):
@app.route('/login', methods=['GET', 'POST']) @app.route('/login', methods=['GET', 'POST'])
def login(): def login():
if not datastore.data['settings']['application']['password']: if not datastore.data['settings']['application']['password'] and not os.getenv("SALTED_PASS", False):
flash("Login not required, no password enabled.", "notice") flash("Login not required, no password enabled.", "notice")
return redirect(url_for('index')) return redirect(url_for('index'))
@ -227,8 +235,10 @@ def changedetection_app(config=None, datastore_o=None):
@app.before_request @app.before_request
def do_something_whenever_a_request_comes_in(): def do_something_whenever_a_request_comes_in():
# Disable password loginif there is not one set
app.config['LOGIN_DISABLED'] = datastore.data['settings']['application']['password'] == False # Disable password login if there is not one set
# (No password in settings or env var)
app.config['LOGIN_DISABLED'] = datastore.data['settings']['application']['password'] == False and os.getenv("SALTED_PASS", False) == False
# For the RSS path, allow access via a token # For the RSS path, allow access via a token
if request.path == '/rss' and request.args.get('token'): if request.path == '/rss' and request.args.get('token'):
@ -571,8 +581,8 @@ def changedetection_app(config=None, datastore_o=None):
form.notification_format.data = datastore.data['settings']['application']['notification_format'] form.notification_format.data = datastore.data['settings']['application']['notification_format']
form.base_url.data = datastore.data['settings']['application']['base_url'] form.base_url.data = datastore.data['settings']['application']['base_url']
# Password unset is a GET # Password unset is a GET, but we can lock the session to always need the password
if request.values.get('removepassword') == 'yes': if not os.getenv("SALTED_PASS", False) and request.values.get('removepassword') == 'yes':
from pathlib import Path from pathlib import Path
datastore.data['settings']['application']['password'] = False datastore.data['settings']['application']['password'] = False
flash("Password protection removed.", 'notice') flash("Password protection removed.", 'notice')
@ -606,7 +616,7 @@ def changedetection_app(config=None, datastore_o=None):
else: else:
flash('No notification URLs set, cannot send test.', 'error') flash('No notification URLs set, cannot send test.', 'error')
if form.password.encrypted_password: if not os.getenv("SALTED_PASS", False) and form.password.encrypted_password:
datastore.data['settings']['application']['password'] = form.password.encrypted_password datastore.data['settings']['application']['password'] = form.password.encrypted_password
flash("Password protection enabled.", 'notice') flash("Password protection enabled.", 'notice')
flask_login.logout_user() flask_login.logout_user()
@ -618,7 +628,10 @@ def changedetection_app(config=None, datastore_o=None):
if request.method == 'POST' and not form.validate(): if request.method == 'POST' and not form.validate():
flash("An error occurred, please see below.", "error") flash("An error occurred, please see below.", "error")
output = render_template("settings.html", form=form, current_base_url = datastore.data['settings']['application']['base_url']) output = render_template("settings.html",
form=form,
current_base_url = datastore.data['settings']['application']['base_url'],
hide_remove_pass=os.getenv("SALTED_PASS", False))
return output return output
@ -999,6 +1012,8 @@ def notification_runner():
except Exception as e: except Exception as e:
print("Watch URL: {} Error {}".format(n_object['watch_url'], e)) print("Watch URL: {} Error {}".format(n_object['watch_url'], e))
datastore.update_watch(uuid=n_object['uuid'], update_obj={'last_error': "Notification error: " + str(e)})
# Thread runner to check every minute, look for new watches to feed into the Queue. # Thread runner to check every minute, look for new watches to feed into the Queue.
def ticker_thread_check_time_launch_checks(): def ticker_thread_check_time_launch_checks():

@ -25,12 +25,16 @@
<span class="pure-form-message-inline">Default time for all watches, when the watch does not have a specific time setting.</span> <span class="pure-form-message-inline">Default time for all watches, when the watch does not have a specific time setting.</span>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
{% if current_user.is_authenticated %} {% if not hide_remove_pass %}
<a href="{{url_for('settings_page', removepassword='yes')}}" {% if current_user.is_authenticated %}
class="pure-button pure-button-primary">Remove password</a> <a href="{{url_for('settings_page', removepassword='yes')}}"
class="pure-button pure-button-primary">Remove password</a>
{% else %}
{{ render_field(form.password) }}
<span class="pure-form-message-inline">Password protection for your changedetection.io application.</span>
{% endif %}
{% else %} {% else %}
{{ render_field(form.password) }} <span class="pure-form-message-inline">Password is locked.</span>
<span class="pure-form-message-inline">Password protection for your changedetection.io application.</span>
{% endif %} {% endif %}
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">

Loading…
Cancel
Save