@ -143,13 +143,21 @@ class User(flask_login.UserMixin):
def get_id ( self ) :
return str ( self . id )
# Compare given password against JSON store or Env var
def check_password ( self , password ) :
import base64
import hashlib
# Getting the values back out
raw_salt_pass = base64 . b64decode ( datastore . data [ ' settings ' ] [ ' application ' ] [ ' password ' ] )
# Can be stored in env (for deployments) or in the general configs
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
# 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 ' ] )
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 " )
return redirect ( url_for ( ' index ' ) )
@ -227,8 +235,10 @@ def changedetection_app(config=None, datastore_o=None):
@app.before_request
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
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 . base_url . data = datastore . data [ ' settings ' ] [ ' application ' ] [ ' base_url ' ]
# Password unset is a GET
if request . values . get ( ' removepassword ' ) == ' yes ' :
# Password unset is a GET , but we can lock the session to always need the password
if not os . getenv ( " SALTED_PASS " , False ) and request . values . get ( ' removepassword ' ) == ' yes ' :
from pathlib import Path
datastore . data [ ' settings ' ] [ ' application ' ] [ ' password ' ] = False
flash ( " Password protection removed. " , ' notice ' )
@ -606,7 +616,7 @@ def changedetection_app(config=None, datastore_o=None):
else :
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
flash ( " Password protection enabled. " , ' notice ' )
flask_login . logout_user ( )
@ -618,7 +628,10 @@ def changedetection_app(config=None, datastore_o=None):
if request . method == ' POST ' and not form . validate ( ) :
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
@ -999,6 +1012,8 @@ def notification_runner():
except Exception as 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.
def ticker_thread_check_time_launch_checks ( ) :