Server: Allow logged in users to change their passwords by clicking the user icon in the top right corner of the home screen. Fixes #1700631 [FR - Change PW via Web in content server](https://bugs.launchpad.net/calibre/+bug/1700631)

This commit is contained in:
Kovid Goyal 2017-06-28 18:18:08 +05:30
parent af48f8907b
commit 9b118f35d2
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 74 additions and 6 deletions

View File

@ -4,6 +4,8 @@
from __future__ import absolute_import, division, print_function, unicode_literals from __future__ import absolute_import, division, print_function, unicode_literals
import json
from calibre import as_unicode from calibre import as_unicode
from calibre.srv.errors import HTTPBadRequest, HTTPForbidden from calibre.srv.errors import HTTPBadRequest, HTTPForbidden
from calibre.srv.routes import endpoint from calibre.srv.routes import endpoint
@ -16,14 +18,17 @@ def change_pw(ctx, rd):
if user is None: if user is None:
raise HTTPForbidden('Anonymous users are not allowed to change passwords') raise HTTPForbidden('Anonymous users are not allowed to change passwords')
try: try:
pw = rd.request_body_file.read().decode('utf-8') pw = json.loads(rd.request_body_file.read())
oldpw, newpw = pw['oldpw'], pw['newpw']
except Exception: except Exception:
raise HTTPBadRequest('No decodable password found') raise HTTPBadRequest('No decodable password found')
err = validate_password(pw) if oldpw != ctx.user_manager.get(user):
raise HTTPBadRequest(_('Existing password is incorrect'))
err = validate_password(newpw)
if err: if err:
raise HTTPBadRequest(err) raise HTTPBadRequest(err)
try: try:
ctx.user_manager.change_password(user, pw) ctx.user_manager.change_password(user, newpw)
except Exception as err: except Exception as err:
raise HTTPBadRequest(as_unicode(err)) raise HTTPBadRequest(as_unicode(err))
ctx.log.warn('Changed password for user', user) ctx.log.warn('Changed password for user', user)

View File

@ -5,16 +5,17 @@ from __python__ import bound_methods, hash_literals
from elementmaker import E from elementmaker import E
from gettext import gettext as _ from gettext import gettext as _
from ajax import ajax_send
from book_list.cover_grid import BORDER_RADIUS from book_list.cover_grid import BORDER_RADIUS
from book_list.globals import get_db from book_list.globals import get_db
from book_list.library_data import last_virtual_library_for, sync_library_books from book_list.library_data import last_virtual_library_for, sync_library_books
from book_list.router import open_book, update_window_title from book_list.router import open_book, update_window_title
from book_list.top_bar import add_button, create_top_bar from book_list.top_bar import add_button, create_top_bar
from book_list.ui import set_default_panel_handler, show_panel from book_list.ui import set_default_panel_handler, show_panel
from dom import add_extra_css, build_rule, ensure_id from dom import add_extra_css, build_rule, clear, ensure_id, unique_id, set_css
from modals import create_custom_dialog from modals import create_custom_dialog
from session import get_device_uuid, get_interface_data from session import get_device_uuid, get_interface_data
from utils import conditional_timeout, username_key, safe_set_inner_html from utils import conditional_timeout, safe_set_inner_html, username_key
from widgets import create_button from widgets import create_button
CLASS_NAME = 'home-page' CLASS_NAME = 'home-page'
@ -138,6 +139,64 @@ def show_recent():
else: else:
print(db.initialize_error_msg) print(db.initialize_error_msg)
# User account {{{
def change_password():
interface_data = get_interface_data()
create_custom_dialog(_('Change password for: {}').format(interface_data.username), def(parent, close_modal):
ids = unique_id(), unique_id(), unique_id()
parent.appendChild(E.table(
E.tr(
E.td(E.label(_('Current password:') + '\xa0', for_=ids[0])), set_css(E.td(E.input(type='password'), id=ids[0]), padding_bottom='1.5ex')
),
E.tr(
E.td(E.label(_('New password:') + '\xa0', for_=ids[1])), set_css(E.td(E.input(type='password'), id=ids[1]), padding_bottom='1.5ex')
),
E.tr(
E.td(E.label(_('New password again:') + '\xa0', for_=ids[2])), set_css(E.td(E.input(type='password'), id=ids[2]), padding_bottom='1.5ex')
)
))
parent.appendChild(E.div())
def show_msg(html, is_info):
msg = parent.firstChild.nextSibling
safe_set_inner_html(msg, html)
msg.style.color = 'red' if not is_info else 'currentColor'
def on_complete(end_type, xhr, ev):
if end_type is 'load':
clear(parent)
parent.appendChild(E.div(_(
'Password successfully changed, you will be asked for the new password'
' the next time the browser has to contact the calibre server.')))
parent.appendChild(
E.div(class_='button-box',
create_button(_('OK'), None, close_modal),
))
return
show_msg(_('Failed to change password, with error: {}').format(xhr.error_html))
def ok():
pws = parent.firstChild.getElementsByTagName('input')
oldpw, pw1, pw2 = pws[0].value, pws[1].value, pws[2].value
if pw1 is not pw2:
show_msg(_('The two new passwords do not match'))
return
if not pw1 or not oldpw:
show_msg(_('Empty passwords are not allowed'))
return
ajax_send('users/change-pw', {'oldpw': oldpw, 'newpw': pw1}, on_complete)
show_msg(_('Contacting server, please wait...'), True)
parent.lastChild.display = 'none'
parent.appendChild(
E.div(class_='button-box',
create_button(_('OK'), None, ok), '\xa0',
create_button(_('Cancel'), None, close_modal),
)
)
)
def show_user_details(): def show_user_details():
interface_data = get_interface_data() interface_data = get_interface_data()
@ -149,11 +208,15 @@ def show_user_details():
parent.appendChild(msg) parent.appendChild(msg)
parent.appendChild( parent.appendChild(
E.div(class_='button-box', E.div(class_='button-box',
create_button(_('Change password'), None, def():
setTimeout(change_password, 0)
close_modal()
), '\xa0',
create_button(_('Close'), None, close_modal), create_button(_('Close'), None, close_modal),
) )
) )
) )
# }}}