diff --git a/firstuseauthenticator/firstuseauthenticator.py b/firstuseauthenticator/firstuseauthenticator.py index 7325c06..d35dab2 100644 --- a/firstuseauthenticator/firstuseauthenticator.py +++ b/firstuseauthenticator/firstuseauthenticator.py @@ -6,15 +6,60 @@ 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 -from tornado import gen +from tornado import gen, web 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) + + @web.authenticated + async def post(self): + user = self.get_current_user() + new_password = self.get_body_argument('password', strip=False) + self.authenticator.reset_password(user.name, new_password) + + html = self.render_template( + 'reset.html', + result=True, + result_message='your password has been changed successfully', + ) + self.finish(html) + + class FirstUseAuthenticator(Authenticator): """ JupyterHub authenticator that lets users set password on first use. @@ -63,7 +108,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): @@ -74,3 +120,16 @@ class FirstUseAuthenticator(Authenticator): """ with dbm.open(self.dbm_path, 'c', 0o600) as db: del db[user.name] + + def reset_password(self, username, new_password): + """ + This allow to change password of a logged user. + """ + 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)] 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..6f10a89 --- /dev/null +++ b/firstuseauthenticator/templates/reset.html @@ -0,0 +1,34 @@ +{% extends "page.html" %} +{% block main %} +