Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ask for new passwords twice, ask for current password on change #180

Merged
merged 7 commits into from
Nov 2, 2021
107 changes: 75 additions & 32 deletions nativeauthenticator/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ async def get(self):
)
self.finish(html)

def get_result_message(self, user, taken, human=True):
def get_result_message(self, user, taken, confirmation_matches, human=True):
alert = "alert-info"
message = "Your information has been sent to the admin"
if user and user.login_email_sent:
Expand All @@ -72,6 +72,9 @@ def get_result_message(self, user, taken, human=True):
"username is already in use. Please try again "
"with a different username."
)
elif not confirmation_matches:
alert = "alert-danger"
message = "Your password did not match the confirmation. Please try again."
else:
# Error if user creation was not successful.
if not user:
Expand Down Expand Up @@ -133,7 +136,7 @@ async def post(self):
if assume_human:
user_info = {
"username": self.get_body_argument("username", strip=False),
"pw": self.get_body_argument("pw", strip=False),
"password": self.get_body_argument("signup_password", strip=False),
lambdaTotoro marked this conversation as resolved.
Show resolved Hide resolved
"email": self.get_body_argument("email", "", strip=False),
"has_2fa": bool(self.get_body_argument("2fa", "", strip=False)),
}
Expand All @@ -143,7 +146,15 @@ async def post(self):
user = False
taken = False

alert, message = self.get_result_message(user, taken, assume_human)
password = self.get_body_argument("signup_password", strip=False)
confirmation = self.get_body_argument(
"signup_password_confirmation", strip=False
)
confirmation_matches = password == confirmation

alert, message = self.get_result_message(
user, taken, confirmation_matches, assume_human
)

otp_secret, user_2fa = "", ""
if user:
Expand Down Expand Up @@ -187,7 +198,7 @@ async def get(self, slug):
class AuthorizeHandler(LocalBase):
async def get(self, slug):
must_stop = True
msg = "Invalid URL"
message = "Invalid URL"
if self.authenticator.allow_self_approval_for:
try:
data = AuthorizeHandler.validate_slug(
Expand All @@ -199,17 +210,17 @@ async def get(self, slug):

if not must_stop:
username = data["username"]
msg = f"{username} was already authorized"
message = f"{username} was already authorized"
usr = UserInfo.find(self.db, username)
if not usr.is_authorized:
UserInfo.change_authorization(self.db, username)
msg = f"{username} has been authorized"
message = f"{username} has been authorized"

# add POSIX user!!

html = await self.render_template(
"my_message.html",
message=msg,
message=message,
)
self.finish(html)

Expand Down Expand Up @@ -266,23 +277,43 @@ async def get(self):
@web.authenticated
async def post(self):
user = await self.get_current_user()
new_password = self.get_body_argument("password", strip=False)
success = self.authenticator.change_password(user.name, new_password)
old_password = self.get_body_argument("old_password", strip=False)
new_password = self.get_body_argument("new_password", strip=False)
confirmation = self.get_body_argument("new_password_confirmation", strip=False)

if success:
alert = "alert-success"
msg = "Your password has been changed successfully!"
else:
correct_password_provided = self.authenticator.get_user(
user.name
).is_valid_password(old_password)

new_password_matches_confirmation = new_password == confirmation

if not correct_password_provided:
alert = "alert-danger"
pw_len = self.authenticator.minimum_password_length
msg = (
"Something went wrong! Be sure your new "
f"password has at least {pw_len} characters and is "
"not too common."
message = "Your current password was incorrect. Please try again."
elif not new_password_matches_confirmation:
alert = "alert-danger"
message = (
"Your new password didn't match the confirmation. Please try again."
)
else:
success = self.authenticator.change_password(user.name, new_password)
if success:
alert = "alert-success"
message = "Your password has been changed successfully!"
else:
alert = "alert-danger"
pw_len = self.authenticator.minimum_password_length
message = (
"Something went wrong! Be sure your new "
f"password has at least {pw_len} characters and is "
"not too common."
)

html = await self.render_template(
"change-password.html", user_name=user.name, result_message=msg, alert=alert
"change-password.html",
user_name=user.name,
result_message=message,
alert=alert,
)
self.finish(html)

Expand All @@ -295,30 +326,42 @@ async def get(self, user_name):
if not self.authenticator.user_exists(user_name):
raise web.HTTPError(404)
html = await self.render_template(
"change-password.html",
"change-password-admin.html",
user_name=user_name,
)
self.finish(html)

@admin_users_scope
async def post(self, user_name):
new_password = self.get_body_argument("password", strip=False)
success = self.authenticator.change_password(user_name, new_password)
new_password = self.get_body_argument("new_password", strip=False)
confirmation = self.get_body_argument("new_password_confirmation", strip=False)
lambdaTotoro marked this conversation as resolved.
Show resolved Hide resolved

if success:
alert = "alert-success"
msg = f"The password for {user_name} has been changed successfully"
else:
new_password_matches_confirmation = new_password == confirmation

if not new_password_matches_confirmation:
alert = "alert-danger"
pw_len = self.authenticator.minimum_password_length
msg = (
"Something went wrong! Be sure the new password "
f"for {user_name} has at least {pw_len} characters and is "
"not too common."
message = (
"The new password didn't match the confirmation. Please try again."
)
else:
success = self.authenticator.change_password(user_name, new_password)
if success:
alert = "alert-success"
message = f"The password for {user_name} has been changed successfully"
else:
alert = "alert-danger"
pw_len = self.authenticator.minimum_password_length
message = (
"Something went wrong! Be sure the new password "
f"for {user_name} has at least {pw_len} characters and is "
"not too common."
)

html = await self.render_template(
"change-password.html", user_name=user_name, result_message=msg, alert=alert
"change-password-admin.html",
user_name=user_name,
result_message=message,
alert=alert,
)
self.finish(html)

Expand Down
17 changes: 10 additions & 7 deletions nativeauthenticator/nativeauthenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,20 +295,20 @@ def get_authed_users(self):
def user_exists(self, username):
return self.get_user(username) is not None

def create_user(self, username, pw, **kwargs):
def create_user(self, username, password, **kwargs):
username = self.normalize_username(username)

if self.user_exists(username):
if self.user_exists(username) or not self.validate_username(username):
return

if not self.is_password_strong(pw) or not self.validate_username(username):
if not self.is_password_strong(password):
return

if not self.enable_signup:
return

encoded_pw = bcrypt.hashpw(pw.encode(), bcrypt.gensalt())
infos = {"username": username, "password": encoded_pw}
encoded_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
infos = {"username": username, "password": encoded_password}
infos.update(kwargs)

if self.open_signup or username in self.get_authed_users():
Expand Down Expand Up @@ -358,7 +358,7 @@ def send_approval_email(self, dest, url):
raise web.HTTPError(
503,
reason="Self-authorization email could not "
+ "be sent. Please contact the jupyterhub "
+ "be sent. Please contact the JupyterHub "
+ "admin about this.",
)

Expand All @@ -375,7 +375,10 @@ def get_unauthed_amount(self):
def change_password(self, username, new_password):
user = self.get_user(username)

criteria = [user is not None, self.is_password_strong(new_password)]
criteria = [
user is not None,
self.is_password_strong(new_password),
]
if not all(criteria):
return

Expand Down
58 changes: 58 additions & 0 deletions nativeauthenticator/templates/change-password-admin.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{% extends "page.html" %}

{% block script %}
{{ super() }}
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
let button = document.getElementById('eye');
button.addEventListener("click", function(e) {
let npwd = document.getElementById("new_password_input");
let cpwd = document.getElementById("new_password_confirmation_input");
if (npwd.getAttribute("type") === "password") {
npwd.setAttribute("type", "text");
cpwd.setAttribute("type", "text");
button.textContent = "🔑";
} else {
npwd.setAttribute("type", "password");
cpwd.setAttribute("type", "password");
button.textContent = "👁";
}
});
});
</script>
{% endblock script %}

{% block main %}
<div class="container">
<form action="{{post_url}}" method="post" role="form">
<h1>
Change password for {{user_name}}
</h1>

<p>Please enter the new password you want to set for {{user_name}}.</p>

<div class="form-group">
<label for="new_password_input">New password:</label>
<div class="input-group col-xs-6">
<input id="new_password_input" type="password" name="new_password" val="" autocapitalize="off" autocorrect="off" class="form-control" />
<span class="input-group-addon">
<button id="eye" type="button" style="border:0;">👁</button>
</span>
</div>
<p></p>

<label for="new_password_confirmation_input">Confirm new password:</label>
<div class="input-group col-xs-6">
<input id="new_password_confirmation_input" type="password" name="new_password_confirmation" val="" autocapitalize="off" autocorrect="off" class="form-control" />
</div>
<p></p>

<input id="signup_submit" type="submit" value='Change Password' class='btn btn-jupyter' />
</div>
</form>

{% if result_message %}
<div class="alert {{alert}} col-xs-6" role="alert">{{result_message}}</div>
{% endif %}
</div>
{% endblock %}
37 changes: 29 additions & 8 deletions nativeauthenticator/templates/change-password.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@
document.addEventListener('DOMContentLoaded', function() {
let button = document.getElementById('eye');
button.addEventListener("click", function(e) {
let pwd = document.getElementById("password_input");
if (pwd.getAttribute("type") === "password") {
pwd.setAttribute("type", "text");
let opwd = document.getElementById("old_password_input");
let npwd = document.getElementById("new_password_input");
let cpwd = document.getElementById("new_password_confirmation_input");
if (opwd.getAttribute("type") === "password") {
opwd.setAttribute("type", "text");
npwd.setAttribute("type", "text");
cpwd.setAttribute("type", "text");
button.textContent = "🔑";
} else {
pwd.setAttribute("type", "password");
opwd.setAttribute("type", "password");
npwd.setAttribute("type", "password");
cpwd.setAttribute("type", "password");
button.textContent = "👁";
}
});
Expand All @@ -25,23 +31,38 @@
<h1>
Change password for {{user_name}}
</h1>

<p>Please enter your current password and the new password you want to set it to. If you have forgotten your password, an admin can reset it for you.</p>

<div class="form-group">
<label for="password_input">New password:</label>
<div class="input-group">
<input id="password_input" type="password" name="password" val="" autocapitalize="off" autocorrect="off" class="form-control" />
<label for="old_password_input">Old password:</label>
<div class="input-group col-xs-6">
<input id="old_password_input" type="password" name="old_password" val="" autocapitalize="off" autocorrect="off" class="form-control" />
<span class="input-group-addon">
<button id="eye" type="button" style="border:0;">👁</button>
</span>
</div>
<p></p>

<label for="new_password_input">New password:</label>
<div class="input-group col-xs-6">
<input id="new_password_input" type="password" name="new_password" val="" autocapitalize="off" autocorrect="off" class="form-control" />
</div>
<p></p>

<label for="new_password_confirmation_input">Confirm new password:</label>
<div class="input-group col-xs-6">
<input id="new_password_confirmation_input" type="password" name="new_password_confirmation" val="" autocapitalize="off" autocorrect="off" class="form-control" />
<span></span>
</div>
<p></p>

<input id="signup_submit" type="submit" value='Change Password' class='btn btn-jupyter' />
</div>
</form>

{% if result_message %}
<div class="alert {{alert}}" role="alert">{{result_message}}</div>
<div class="alert {{alert}} col-xs-6" role="alert">{{result_message}}</div>
{% endif %}
</div>
{% endblock %}
12 changes: 11 additions & 1 deletion nativeauthenticator/templates/signup.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@
let button = document.getElementById('eye');
button.addEventListener("click", function(e) {
let pwd = document.getElementById("password_input");
let pwdc = document.getElementById("password_confirmation_input");

if (pwd.getAttribute("type") === "password") {
pwd.setAttribute("type", "text");
pwdc.setAttribute("type", "text");
button.textContent = "🔑";
} else {
pwd.setAttribute("type", "password");
pwdc.setAttribute("type", "password");
button.textContent = "👁";
}
});
Expand Down Expand Up @@ -80,12 +84,18 @@

<label for="password_input">Password:</label>
<div class="input-group">
<input id="password_input" type="password" name="pw" val="" autocapitalize="off" autocorrect="off" class="form-control" />
<input id="password_input" type="password" name="signup_password" val="" autocapitalize="off" autocorrect="off" class="form-control" />
<span class="input-group-addon">
<button id="eye" type="button" style="border:0;">👁</button>
</span>
</div>
<p></p>

<label for="password_confirm">Confirm password:</label>
<div class="input-group">
<input id="password_confirmation_input" type="password" name="signup_password_confirmation" val="" autocapitalize="off" autocorrect="off" class="form-control" />
</div>
<p></p>

{% if two_factor_auth %}
<input type="checkbox" id="2fa" name="2fa" value="2fa">
Expand Down