Skip to content

Commit

Permalink
Enhance password reset experience
Browse files Browse the repository at this point in the history
  • Loading branch information
Robert Putt authored and Robert Putt committed Jan 7, 2023
1 parent 87d818c commit b1e2d54
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 16 deletions.
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
include hw_diag/templates/template.html
include hw_diag/templates/password_reset.html
include hw_diag/templates/password_change_form.html
include hw_diag/templates/login_form.html
include hw_diag/templates/diagnostics_page_light_miner.html
Expand Down
3 changes: 2 additions & 1 deletion hw_diag/templates/login_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ <h3>
{% endif %}
<form method="POST" action="/login">
<div class="form-group">
<input type="password" class="form-control" id="txtPassword" name="txtPassword" placeholder="Password" style="width: 90%" />
<input type="password" class="form-control" id="txtPassword" name="txtPassword" placeholder="Password" />
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
<br/>
<p style="color: blue;">Hint: The default password is the ethernet MAC address of your device without any colons which may be found on your device's identification sticker.</p>
<p style="text-align: center;"><a href="/reset_password">Forgotten Password?</a></p>
</div>
<div class="col-1"></div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions hw_diag/templates/password_change_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ <h3 class="text-center mb-4">Change Password</h3>
<div class="col-10">
<form method="POST" action="/change_password">
<div class="form-group">
<input type="password" class="form-control" id="txtOriginalPassword" name="txtOriginalPassword" placeholder="Current Password" style="width: 90%" />
<input type="password" class="form-control" id="txtOriginalPassword" name="txtOriginalPassword" placeholder="Current Password" />
<br />
<input type="password" class="form-control" id="txtNewPassword" name="txtNewPassword" placeholder="New Password" style="width: 90%" />
<input type="password" class="form-control" id="txtNewPassword" name="txtNewPassword" placeholder="New Password" />
<br />
<input type="password" class="form-control" id="txtConfirmPassword" name="txtConfirmPassword" placeholder="Confirm New Password" style="width: 90%" />
<input type="password" class="form-control" id="txtConfirmPassword" name="txtConfirmPassword" placeholder="Confirm New Password" />
<br />
<span style="text-align: center;"><button type="submit" class="btn btn-primary">Change Password</button></span>
</div>
Expand Down
25 changes: 21 additions & 4 deletions hw_diag/templates/password_reset.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ <h3>
<div class="row">
<div class="col-1"></div>
<div class="col-10">
<br />
<p>
Press and hold your miner's button for at-least 5 seconds to reset device's password.
You must complete this within 60 seconds, if you require more time please refresh the page.
Expand All @@ -43,14 +44,30 @@ <h3>
<script>
var count = 60
timer = setInterval(function() {
count--
document.getElementById("counter").innerHTML = count;
console.log(count)
if(count == 0) clearInterval(timer);
var requestOptions = {
method: 'GET',
redirect: 'follow'
};

fetch("/password_reset", requestOptions)
.then(response => response.text())
.then(result => {
const jsonResult = JSON.parse(result);
if (jsonResult.password_updated) {
clearInterval(timer);
document.getElementById("counter").innerHTML = "Password Reset!";
}
})
.catch(error => console.log('error', error));
count--
document.getElementById("counter").innerHTML = count;
console.log(count)
if(count == 0) clearInterval(timer);
}, 1000
);
</script>
</p>
<p><a href="/login">Cancel</a> | <a href="/reset_password">Refresh</a></p>
</div>
<div class="col-1"></div>
</div>
Expand Down
56 changes: 48 additions & 8 deletions hw_diag/utilities/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,22 +145,62 @@ def update_password_reset_expiry():
g.db.commit()


def check_password_reset_expiry():
def set_last_password_reset():
now = datetime.datetime.utcnow()
expiry = now + datetime.timedelta(minutes=1)

try:
reset_expiry_row = g.db.query(AuthKeyValue). \
filter(AuthKeyValue.key == 'password_reset_expiry'). \
filter(AuthKeyValue.key == 'password_last_reset'). \
one()
reset_expiry_row.value = expiry.isoformat()
reset_expiry_row.value = now.isoformat()
g.db.commit()
except NoResultFound:
reset_expiry_row = AuthKeyValue(
key='password_reset_expiry',
value=expiry.isoformat()
key='password_last_reset',
value=now.isoformat()
)
g.db.add(reset_expiry_row)
g.db.commit()

return expiry

def password_updated_in_last_minute():
now = datetime.datetime.utcnow()
one_min_ago = now - datetime.timedelta(minutes=1)
try:
last_reset_row = g.db.query(AuthKeyValue). \
filter(AuthKeyValue.key == 'password_last_reset'). \
one()
last_reset = datetime.datetime.fromisoformat(last_reset_row.value)

if last_reset > one_min_ago:
valid = True
else:
valid = False
except Exception:
valid = False

return valid


def perform_password_reset():
now = datetime.datetime.utcnow()

try:
reset_expiry_row = g.db.query(AuthKeyValue). \
filter(AuthKeyValue.key == 'password_reset_expiry'). \
one()
expiry = datetime.datetime.fromisoformat(reset_expiry_row.value)

if expiry > now:
diagnostics = read_diagnostics_file()
eth_mac = diagnostics.get('E0')
default_password = eth_mac.replace(':', '')
write_password(default_password)
set_last_password_reset()
valid = True
else:
valid = False

except Exception:
valid = False

return valid
20 changes: 20 additions & 0 deletions hw_diag/views/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from flask import request
from flask import redirect
from flask import session
from flask import jsonify
from hm_pyhelper.logger import get_logger

from hw_diag.utilities.diagnostics import read_diagnostics_file
Expand All @@ -17,6 +18,8 @@
from hw_diag.utilities.auth import count_recent_auth_failures
from hw_diag.utilities.auth import add_login_failure
from hw_diag.utilities.auth import update_password_reset_expiry
from hw_diag.utilities.auth import perform_password_reset
from hw_diag.utilities.auth import password_updated_in_last_minute


logging.basicConfig(level=os.environ.get("LOGLEVEL", "DEBUG"))
Expand Down Expand Up @@ -136,6 +139,9 @@ def handle_login():

@AUTH.route('/reset_password')
def display_password_reset_page():
if session.get('logged_in'):
return redirect('/')

diagnostics = read_diagnostics_file()
display_lte = should_display_lte(diagnostics)
now = datetime.datetime.utcnow()
Expand All @@ -148,3 +154,17 @@ def display_password_reset_page():
display_lte=display_lte,
now=now
)


@AUTH.route('/password_reset', methods=['POST'])
def handle_reset_password():
# Check this originates from the docker private subnet, only
# internal containers should be privileged to reset the password.
password_reset = perform_password_reset()
return jsonify({'password_updated': password_reset})


@AUTH.route('/password_reset', methods=['GET'])
def validate_password_reset():
result = password_updated_in_last_minute()
return jsonify({'password_updated': result})

0 comments on commit b1e2d54

Please sign in to comment.