From fdb3608ddb8193c289c1dbd8b929144890c2e17f Mon Sep 17 00:00:00 2001 From: Leticia Portella Date: Tue, 25 Sep 2018 01:12:43 -0300 Subject: [PATCH 1/7] initial implementation on reset password url --- .../firstuseauthenticator.py | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/firstuseauthenticator/firstuseauthenticator.py b/firstuseauthenticator/firstuseauthenticator.py index 7325c06..dc44382 100644 --- a/firstuseauthenticator/firstuseauthenticator.py +++ b/firstuseauthenticator/firstuseauthenticator.py @@ -7,14 +7,25 @@ locally in a dbm file, and checked next time they log in. """ import dbm from jupyterhub.auth import Authenticator +from jupyterhub.handlers import BaseHandler from jupyterhub.orm import User -from tornado import gen +from tornado import gen, web from traitlets.traitlets import Unicode, Bool import bcrypt +class ResetPasswordHandler(BaseHandler): + """Render the reset password page.""" + + @web.authenticated + async def get(self): + + html = self.render_template('reset.html') + self.finish(html) + + class FirstUseAuthenticator(Authenticator): """ JupyterHub authenticator that lets users set password on first use. @@ -74,3 +85,12 @@ class FirstUseAuthenticator(Authenticator): """ with dbm.open(self.dbm_path, 'c', 0o600) as db: del db[user.name] + + def reset_password(self, data): + username = data['username'] + new_password = data['password'] + db[username] = bcrypt.hashpw(new_password.encode(), bcrypt.gensalt()) + return username + + def get_handlers(self, app): + return super().get_handlers(app) + [(r'/auth/change-password', ResetPasswordHandler)] From 5da66ecd327b1472cfee9a5a5b21454ab87264ed Mon Sep 17 00:00:00 2001 From: Leticia Portella Date: Tue, 25 Sep 2018 19:19:04 -0300 Subject: [PATCH 2/7] add template locally --- .../firstuseauthenticator.py | 23 +++++++- firstuseauthenticator/templates/__init__.py | 0 firstuseauthenticator/templates/reset.html | 54 +++++++++++++++++++ setup.py | 5 +- 4 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 firstuseauthenticator/templates/__init__.py create mode 100644 firstuseauthenticator/templates/reset.html diff --git a/firstuseauthenticator/firstuseauthenticator.py b/firstuseauthenticator/firstuseauthenticator.py index dc44382..a95bcfd 100644 --- a/firstuseauthenticator/firstuseauthenticator.py +++ b/firstuseauthenticator/firstuseauthenticator.py @@ -6,6 +6,8 @@ password for that account. It is hashed with bcrypt & stored locally in a dbm file, and checked next time they log in. """ import dbm +import os +from jinja2 import ChoiceLoader, FileSystemLoader from jupyterhub.auth import Authenticator from jupyterhub.handlers import BaseHandler from jupyterhub.orm import User @@ -16,12 +18,31 @@ from traitlets.traitlets import Unicode, Bool import bcrypt +TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), 'templates') + + class ResetPasswordHandler(BaseHandler): """Render the reset password page.""" + def __init__(self, *args, **kwargs): + self._loaded = False + super().__init__(*args, **kwargs) + + def _register_template_path(self): + if self._loaded: + return + + self.log.debug('Adding %s to template path', TEMPLATE_DIR) + loader = FileSystemLoader([TEMPLATE_DIR]) + + env = self.settings['jinja2_env'] + previous_loader = env.loader + env.loader = ChoiceLoader([previous_loader, loader]) + + self._loaded = True @web.authenticated async def get(self): - + self._register_template_path() html = self.render_template('reset.html') self.finish(html) diff --git a/firstuseauthenticator/templates/__init__.py b/firstuseauthenticator/templates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/firstuseauthenticator/templates/reset.html b/firstuseauthenticator/templates/reset.html new file mode 100644 index 0000000..e0b9988 --- /dev/null +++ b/firstuseauthenticator/templates/reset.html @@ -0,0 +1,54 @@ +{% extends "page.html" %} + +{% block main %} + +
+
+
+ Sign in +
+
+ + + + {% if login_error %} + + {% endif %} + + + + + + +
+
+
+ +{% endblock %} diff --git a/setup.py b/setup.py index a8500ff..fc9bfce 100644 --- a/setup.py +++ b/setup.py @@ -9,5 +9,8 @@ setup( author_email='yuvipanda@gmail.com', license='3 Clause BSD', packages=find_packages(), - install_requires=['bcrypt'] + install_requires=['bcrypt'], + package_data={ + '': ['*.html'], + } ) From 3ab98fa0dc8e13f48bcdf00875e3e2c444fc366d Mon Sep 17 00:00:00 2001 From: Leticia Portella Date: Tue, 25 Sep 2018 20:02:56 -0300 Subject: [PATCH 3/7] add post method for change password --- .../firstuseauthenticator.py | 29 +++++++++++++-- firstuseauthenticator/templates/reset.html | 36 ++++++------------- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/firstuseauthenticator/firstuseauthenticator.py b/firstuseauthenticator/firstuseauthenticator.py index a95bcfd..7836b8f 100644 --- a/firstuseauthenticator/firstuseauthenticator.py +++ b/firstuseauthenticator/firstuseauthenticator.py @@ -46,6 +46,21 @@ class ResetPasswordHandler(BaseHandler): html = self.render_template('reset.html') self.finish(html) + async def post(self): + data = {} + for arg in self.request.arguments: + data[arg] = self.get_argument(arg, strip=False) + user = self.get_current_user() + data['username'] = user.name + self.authenticator.reset_password(data) + + html = self.render_template( + 'reset.html', + result=True, + result_message='password changed successfully', + ) + self.finish(html) + class FirstUseAuthenticator(Authenticator): """ @@ -95,7 +110,8 @@ class FirstUseAuthenticator(Authenticator): if bcrypt.hashpw(password.encode(), stored_pw) != stored_pw: return None else: - db[username] = bcrypt.hashpw(password.encode(), bcrypt.gensalt()) + db[username] = bcrypt.hashpw(password.encode(), + bcrypt.gensalt()) return username def delete_user(self, user): @@ -108,10 +124,17 @@ class FirstUseAuthenticator(Authenticator): del db[user.name] def reset_password(self, data): + """ + This allow to change password of a logged user. + """ username = data['username'] new_password = data['password'] - db[username] = bcrypt.hashpw(new_password.encode(), bcrypt.gensalt()) + + with dbm.open(self.dbm_path, 'c', 0o600) as db: + db[username] = bcrypt.hashpw(new_password.encode(), + bcrypt.gensalt()) return username def get_handlers(self, app): - return super().get_handlers(app) + [(r'/auth/change-password', ResetPasswordHandler)] + return super().get_handlers(app) + [(r'/auth/change-password', + ResetPasswordHandler)] diff --git a/firstuseauthenticator/templates/reset.html b/firstuseauthenticator/templates/reset.html index e0b9988..4f16f3a 100644 --- a/firstuseauthenticator/templates/reset.html +++ b/firstuseauthenticator/templates/reset.html @@ -3,35 +3,14 @@ {% block main %}
-
-
- Sign in -
+ +

+ Change Password +

- - {% if login_error %} - - {% endif %} - - - +
+{% if result %} +

+ {{result_message}} +

+{% endif %}
{% endblock %} From 1e6c7adcbbe18d84bae77c873374b51d552b80fb Mon Sep 17 00:00:00 2001 From: Leticia Portella Date: Wed, 26 Sep 2018 13:26:52 -0300 Subject: [PATCH 4/7] make post on ResetPasswordHandler demand authentication --- firstuseauthenticator/firstuseauthenticator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/firstuseauthenticator/firstuseauthenticator.py b/firstuseauthenticator/firstuseauthenticator.py index 7836b8f..8699d56 100644 --- a/firstuseauthenticator/firstuseauthenticator.py +++ b/firstuseauthenticator/firstuseauthenticator.py @@ -46,6 +46,7 @@ class ResetPasswordHandler(BaseHandler): html = self.render_template('reset.html') self.finish(html) + @web.authenticated async def post(self): data = {} for arg in self.request.arguments: From 49a080f9e8a4296dd8bd04094e1dffc0025368a8 Mon Sep 17 00:00:00 2001 From: Leticia Portella Date: Wed, 26 Sep 2018 18:45:47 -0300 Subject: [PATCH 5/7] change styling on template reset.html --- firstuseauthenticator/templates/reset.html | 66 ++++++++++------------ 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/firstuseauthenticator/templates/reset.html b/firstuseauthenticator/templates/reset.html index 4f16f3a..6f10a89 100644 --- a/firstuseauthenticator/templates/reset.html +++ b/firstuseauthenticator/templates/reset.html @@ -1,38 +1,34 @@ {% extends "page.html" %} - {% block main %} - -
-
-

- Change Password -

-
- - - - - - +
+ +

+ Change Password +

+
+
+ + +
+
+ +
+
+ + {% if result %} + + {% endif %}
- -{% if result %} -

- {{result_message}} -

-{% endif %} -
- -{% endblock %} +{% endblock %} \ No newline at end of file From 64c4f7b886bd093c5f43edcd38abe315171a6385 Mon Sep 17 00:00:00 2001 From: Leticia Portella Date: Wed, 26 Sep 2018 18:46:09 -0300 Subject: [PATCH 6/7] change success message on result message --- firstuseauthenticator/firstuseauthenticator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firstuseauthenticator/firstuseauthenticator.py b/firstuseauthenticator/firstuseauthenticator.py index 8699d56..254bedc 100644 --- a/firstuseauthenticator/firstuseauthenticator.py +++ b/firstuseauthenticator/firstuseauthenticator.py @@ -58,7 +58,7 @@ class ResetPasswordHandler(BaseHandler): html = self.render_template( 'reset.html', result=True, - result_message='password changed successfully', + result_message='your password has been changed successfully', ) self.finish(html) From 12a652976a67e194a2257b94a5d42bcde9f8e1c0 Mon Sep 17 00:00:00 2001 From: Leticia Portella Date: Wed, 26 Sep 2018 19:00:42 -0300 Subject: [PATCH 7/7] change handler to user get_body_argument --- firstuseauthenticator/firstuseauthenticator.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/firstuseauthenticator/firstuseauthenticator.py b/firstuseauthenticator/firstuseauthenticator.py index 254bedc..d35dab2 100644 --- a/firstuseauthenticator/firstuseauthenticator.py +++ b/firstuseauthenticator/firstuseauthenticator.py @@ -48,12 +48,9 @@ class ResetPasswordHandler(BaseHandler): @web.authenticated async def post(self): - data = {} - for arg in self.request.arguments: - data[arg] = self.get_argument(arg, strip=False) user = self.get_current_user() - data['username'] = user.name - self.authenticator.reset_password(data) + new_password = self.get_body_argument('password', strip=False) + self.authenticator.reset_password(user.name, new_password) html = self.render_template( 'reset.html', @@ -124,13 +121,10 @@ class FirstUseAuthenticator(Authenticator): with dbm.open(self.dbm_path, 'c', 0o600) as db: del db[user.name] - def reset_password(self, data): + def reset_password(self, username, new_password): """ This allow to change password of a logged user. """ - username = data['username'] - new_password = data['password'] - with dbm.open(self.dbm_path, 'c', 0o600) as db: db[username] = bcrypt.hashpw(new_password.encode(), bcrypt.gensalt())