From c901ccbb01bc9e9bea14bb703afb297e2b4e9291 Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Thu, 29 Feb 2024 08:23:18 +0100 Subject: [PATCH] Improved js password strength check Improved check of CJK-Characters --- cps/__init__.py | 28 +++++++--------- cps/admin.py | 1 + cps/config_sql.py | 1 + cps/helper.py | 16 ++++++---- cps/static/js/libs/pwstrength/locales/de.json | 1 + cps/static/js/libs/pwstrength/locales/en.json | 1 + .../libs/pwstrength/pwstrength-bootstrap.js | 32 +++++++++++-------- .../pwstrength/pwstrength-bootstrap.min.js | 4 +-- cps/static/js/password.js | 6 ++-- cps/templates/config_edit.html | 4 +++ cps/templates/user_edit.html | 4 +-- requirements.txt | 1 + 12 files changed, 55 insertions(+), 44 deletions(-) diff --git a/cps/__init__.py b/cps/__init__.py index 27b25e27..621705fd 100755 --- a/cps/__init__.py +++ b/cps/__init__.py @@ -125,13 +125,6 @@ def create_app(): ub.password_change(cli_param.user_credentials) - if not limiter: - log.info('*** "flask-limiter" is needed for calibre-web to run. ' - 'Please install it using pip: "pip install flask-limiter" ***') - print('*** "flask-limiter" is needed for calibre-web to run. ' - 'Please install it using pip: "pip install flask-limiter" ***') - web_server.stop(True) - sys.exit(8) if sys.version_info < (3, 0): log.info( '*** Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2, ' @@ -141,13 +134,6 @@ def create_app(): 'please update your installation to Python3 ***') web_server.stop(True) sys.exit(5) - if not wtf_present: - log.info('*** "flask-WTF" is needed for calibre-web to run. ' - 'Please install it using pip: "pip install flask-WTF" ***') - print('*** "flask-WTF" is needed for calibre-web to run. ' - 'Please install it using pip: "pip install flask-WTF" ***') - web_server.stop(True) - sys.exit(7) lm.login_view = 'web.login' lm.anonymous_user = ub.Anonymous @@ -158,13 +144,21 @@ def create_app(): calibre_db.init_db() updater_thread.init_updater(config, web_server) - # Perform dry run of updater and exit afterwards + # Perform dry run of updater and exit afterward if cli_param.dry_run: updater_thread.dry_run() sys.exit(0) updater_thread.start() - - for res in dependency_check() + dependency_check(True): + requirements = dependency_check() + for res in requirements: + if res['found'] == "not installed": + message = ('Cannot import {name} module, it is needed to run calibre-web, ' + 'please install it using "pip install {name}"').format(name=res["name"]) + log.info(message) + print("*** " + message + " ***") + web_server.stop(True) + sys.exit(8) + for res in requirements + dependency_check(True): log.info('*** "{}" version does not meet the requirements. ' 'Should: {}, Found: {}, please consider installing required version ***' .format(res['name'], diff --git a/cps/admin.py b/cps/admin.py index d10c6290..86e59317 100755 --- a/cps/admin.py +++ b/cps/admin.py @@ -1834,6 +1834,7 @@ def _configuration_update_helper(): _config_checkbox(to_save, "config_password_number") _config_checkbox(to_save, "config_password_lower") _config_checkbox(to_save, "config_password_upper") + _config_checkbox(to_save, "config_password_character") _config_checkbox(to_save, "config_password_special") if 0 < int(to_save.get("config_password_min_length", "0")) < 41: _config_int(to_save, "config_password_min_length") diff --git a/cps/config_sql.py b/cps/config_sql.py index d95d5956..8176bf41 100644 --- a/cps/config_sql.py +++ b/cps/config_sql.py @@ -165,6 +165,7 @@ class _Settings(_Base): config_password_number = Column(Boolean, default=True) config_password_lower = Column(Boolean, default=True) config_password_upper = Column(Boolean, default=True) + config_password_character = Column(Boolean, default=True) config_password_special = Column(Boolean, default=True) config_session = Column(Integer, default=1) config_ratelimiter = Column(Boolean, default=True) diff --git a/cps/helper.py b/cps/helper.py index 5050ffa9..92aa081e 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -22,6 +22,7 @@ import random import io import mimetypes import re +import regex import shutil import socket from datetime import datetime, timedelta @@ -54,7 +55,8 @@ from . import calibre_db, cli_param from .tasks.convert import TaskConvert from . import logger, config, db, ub, fs from . import gdriveutils as gd -from .constants import STATIC_DIR as _STATIC_DIR, CACHE_TYPE_THUMBNAILS, THUMBNAIL_TYPE_COVER, THUMBNAIL_TYPE_SERIES, SUPPORTED_CALIBRE_BINARIES +from .constants import (STATIC_DIR as _STATIC_DIR, CACHE_TYPE_THUMBNAILS, THUMBNAIL_TYPE_COVER, THUMBNAIL_TYPE_SERIES, + SUPPORTED_CALIBRE_BINARIES) from .subproc_wrapper import process_wait from .services.worker import WorkerThread from .tasks.mail import TaskEmail @@ -694,14 +696,16 @@ def valid_password(check_password): if config.config_password_min_length > 0: verify += r"^(?=.{" + str(config.config_password_min_length) + ",}$)" if config.config_password_number: - verify += r"(?=.*?\d)" + verify += "(?=.*?\d)" if config.config_password_lower: - verify += r"(?=.*?[a-z])" + verify += "(?=.*?[\p{Ll}])" if config.config_password_upper: - verify += r"(?=.*?[A-Z])" + verify += "(?=.*?[\p{Lu}])" + if config.config_password_character: + verify += "(?=.*?[\p{Letter}])" if config.config_password_special: - verify += r"(?=.*?[^A-Za-z\s0-9])" - match = re.match(verify, unidecode.unidecode(check_password)) + verify += "(?=.*?[^\p{Letter}\s0-9])" + match = regex.match(verify, check_password) if not match: raise Exception(_("Password doesn't comply with password validation rules")) return check_password diff --git a/cps/static/js/libs/pwstrength/locales/de.json b/cps/static/js/libs/pwstrength/locales/de.json index b7a0f3ef..d7206106 100644 --- a/cps/static/js/libs/pwstrength/locales/de.json +++ b/cps/static/js/libs/pwstrength/locales/de.json @@ -9,6 +9,7 @@ "wordSequences": "Das Passwort enthält Buchstabensequenzen", "wordLowercase": "Bitte mindestens einen Kleinbuchstaben verwenden", "wordUppercase": "Bitte mindestens einen Großbuchstaben verwenden", + "word": "Bitte mindestens einen Buchstaben verwenden", "wordOneNumber": "Bitte mindestens eine Ziffern verwenden", "wordOneSpecialChar": "Bitte mindestens ein Sonderzeichen verwenden", "errorList": "Fehler:", diff --git a/cps/static/js/libs/pwstrength/locales/en.json b/cps/static/js/libs/pwstrength/locales/en.json index 57cd07fc..6440e79c 100644 --- a/cps/static/js/libs/pwstrength/locales/en.json +++ b/cps/static/js/libs/pwstrength/locales/en.json @@ -8,6 +8,7 @@ "wordRepetitions": "Too many repetitions", "wordSequences": "Your password contains sequences", "wordLowercase": "Use at least one lowercase character", + "word": "Use at least one character", "wordUppercase": "Use at least one uppercase character", "wordOneNumber": "Use at least one number", "wordOneSpecialChar": "Use at least one special character", diff --git a/cps/static/js/libs/pwstrength/pwstrength-bootstrap.js b/cps/static/js/libs/pwstrength/pwstrength-bootstrap.js index 040983b0..eb22120f 100644 --- a/cps/static/js/libs/pwstrength/pwstrength-bootstrap.js +++ b/cps/static/js/libs/pwstrength/pwstrength-bootstrap.js @@ -144,13 +144,13 @@ try { validation.wordTwoCharacterClasses = function(options, word, score) { var specialCharRE = new RegExp( - '(.' + options.rules.specialCharClass + ')' + '(.' + options.rules.specialCharClass + ')', 'u' ); if ( - word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) || - (word.match(/([a-zA-Z])/) && word.match(/([0-9])/)) || - (word.match(specialCharRE) && word.match(/[a-zA-Z0-9_]/)) + word.match(/(\p{Ll}.*\p{Lu})|(\p{Lu}.*\p{Ll})/u) || + (word.match(/(\p{Letter})/u) && word.match(/([0-9])/)) || + (word.match(specialCharRE) && word.match(/[\p{Letter}0-9_]/u)) ) { return score; } @@ -202,11 +202,15 @@ try { }; validation.wordLowercase = function(options, word, score) { - return word.match(/[a-z]/) && score; + return word.match(/\p{Ll}/u) && score; }; validation.wordUppercase = function(options, word, score) { - return word.match(/[A-Z]/) && score; + return word.match(/\p{Lu}/u) && score; + }; + + validation.word = function(options, word, score) { + return word.match(/\p{Letter}/u) && score; }; validation.wordOneNumber = function(options, word, score) { @@ -218,7 +222,7 @@ try { }; validation.wordOneSpecialChar = function(options, word, score) { - var specialCharRE = new RegExp(options.rules.specialCharClass); + var specialCharRE = new RegExp(options.rules.specialCharClass, 'u'); return word.match(specialCharRE) && score; }; @@ -228,27 +232,27 @@ try { options.rules.specialCharClass + '.*' + options.rules.specialCharClass + - ')' + ')', 'u' ); return word.match(twoSpecialCharRE) && score; }; validation.wordUpperLowerCombo = function(options, word, score) { - return word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) && score; + return word.match(/(\p{Ll}.*\p{Lu})|(\p{Lu}.*\p{Ll})/u) && score; }; validation.wordLetterNumberCombo = function(options, word, score) { - return word.match(/([a-zA-Z])/) && word.match(/([0-9])/) && score; + return word.match(/([\p{Letter}])/u) && word.match(/([0-9])/) && score; }; validation.wordLetterNumberCharCombo = function(options, word, score) { var letterNumberCharComboRE = new RegExp( - '([a-zA-Z0-9].*' + + '([\p{Letter}0-9].*' + options.rules.specialCharClass + ')|(' + options.rules.specialCharClass + - '.*[a-zA-Z0-9])' + '.*[\p{Letter}0-9])', 'u' ); return word.match(letterNumberCharComboRE) && score; @@ -341,6 +345,7 @@ defaultOptions.rules.scores = { wordTwoCharacterClasses: 2, wordRepetitions: -25, wordLowercase: 1, + word: 1, wordUppercase: 3, wordOneNumber: 3, wordThreeNumbers: 5, @@ -361,6 +366,7 @@ defaultOptions.rules.activated = { wordTwoCharacterClasses: true, wordRepetitions: true, wordLowercase: true, + word: true, wordUppercase: true, wordOneNumber: true, wordThreeNumbers: true, @@ -372,7 +378,7 @@ defaultOptions.rules.activated = { wordIsACommonPassword: true }; defaultOptions.rules.raisePower = 1.4; -defaultOptions.rules.specialCharClass = "(?=.*?[^A-Za-z\s0-9])"; //'[!,@,#,$,%,^,&,*,?,_,~]'; +defaultOptions.rules.specialCharClass = "(?=.*?[^\\p{Letter}\\s0-9])"; //'[!,@,#,$,%,^,&,*,?,_,~]'; // List taken from https://github.com/danielmiessler/SecLists (MIT License) defaultOptions.rules.commonPasswords = [ '123456', diff --git a/cps/static/js/libs/pwstrength/pwstrength-bootstrap.min.js b/cps/static/js/libs/pwstrength/pwstrength-bootstrap.min.js index f850afc9..b70f4f97 100644 --- a/cps/static/js/libs/pwstrength/pwstrength-bootstrap.min.js +++ b/cps/static/js/libs/pwstrength/pwstrength-bootstrap.min.js @@ -1,4 +1,4 @@ /* pwstrength-bootstrap 2021-10-29 - GPLv3 & MIT License */ -!function(s){var t={};!function(o){"use strict";t.fallback={wordMinLength:"Your password is too short",wordMaxLength:"Your password is too long",wordInvalidChar:"Your password contains an invalid character",wordNotEmail:"Do not use your email as your password",wordSimilarToUsername:"Your password cannot contain your username",wordTwoCharacterClasses:"Use different character classes",wordRepetitions:"Too many repetitions",wordSequences:"Your password contains sequences",errorList:"Errors:",veryWeak:"Very Weak",weak:"Weak",normal:"Normal",medium:"Medium",strong:"Strong",veryStrong:"Very Strong"},t.t=function(r){var e="";return(e=o?o.t(r):t.fallback[r])===r?"":e}}(window.i18next);var r,c={};try{!s&&module&&module.exports&&(s=require("jquery"),r=require("jsdom").jsdom,s=s(r().defaultView))}catch(r){}!function(i){"use strict";var r={};c.forbiddenSequences=["0123456789","abcdefghijklmnopqrstuvwxyz","qwertyuiop","asdfghjkl","zxcvbnm","!@#$%^&*()_+"],r.wordNotEmail=function(r,e,o){return e.match(/^([\w!#$%&'*+\-/=?^`{|}~]+\.)*[\w!#$%&'*+\-/=?^`{|}~]+@((((([a-z0-9]{1}[a-z0-9-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(:\d{1,5})?)$/i)?o:0},r.wordMinLength=function(r,e,o){var s=e.length,e=Math.pow(s,r.rules.raisePower);return sr.common.maxChar?o:e},r.wordInvalidChar=function(r,e,o){return r.common.invalidCharsRegExp.test(e)?o:0},r.wordMinLengthStaticScore=function(r,e,o){return e.lengthr.common.maxChar?0:o},r.wordSimilarToUsername=function(r,e,o){r=i(r.common.usernameField).val();return r&&e.toLowerCase().match(r.replace(/[-[\]/{}()*+=?:.\\^$|!,]/g,"\\$&").toLowerCase())?o:0},r.wordTwoCharacterClasses=function(r,e,o){r=new RegExp("(."+r.rules.specialCharClass+")");if(e.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/)||e.match(/([a-zA-Z])/)&&e.match(/([0-9])/)||e.match(r)&&e.match(/[a-zA-Z0-9_]/))return o},r.wordRepetitions=function(r,e,o){return e.match(/(.)\1\1/)?o:0},r.wordSequences=function(r,o,e){var s,t=!1;return 2'+e+"":""},u.ui.popoverError=function(r){"use strict";var e=r.instances.errors,o="
"+r.i18n.t("errorList")+'
    ';return s.each(e,function(r,e){o+="
  • "+e+"
  • "}),o+="
"},u.ui.showVerdicts=!0,u.ui.showVerdictsInsideProgressBar=!1,u.ui.useVerdictCssClass=!1,u.ui.showErrors=!1,u.ui.showScore=!1,u.ui.container=void 0,u.ui.viewports={progress:void 0,verdict:void 0,errors:void 0,score:void 0},u.ui.scores=[0,14,26,38,50],u.i18n={},u.i18n.t=t.t;var d={};!function(n){"use strict";var a=["error","warning","success"],o=["veryWeak","weak","normal","medium","strong","veryStrong"];d.getContainer=function(r,e){r=n(r.ui.container);return r=!r||1!==r.length?e.parent():r},d.findElement=function(r,e,o){return(e?r.find(e):r).find(o)},d.getUIElements=function(r,e){var o;return r.instances.viewports||(o=d.getContainer(r,e),(e={}).$progressbar=d.findElement(o,r.ui.viewports.progress,"div.progress"),r.ui.showVerdictsInsideProgressBar&&(e.$verdict=e.$progressbar.find("span.password-verdict")),r.ui.showPopover||(r.ui.showVerdictsInsideProgressBar||(e.$verdict=d.findElement(o,r.ui.viewports.verdict,"span.password-verdict")),e.$errors=d.findElement(o,r.ui.viewports.errors,"ul.error-list")),e.$score=d.findElement(o,r.ui.viewports.score,"span.password-score"),r.instances.viewports=e)},d.initHelper=function(r,e,o,s){r=d.getContainer(r,e);s?r.find(s).append(o):n(o).insertAfter(e)},d.initVerdict=function(r,e){d.initHelper(r,e,'',r.ui.viewports.verdict)},d.initErrorList=function(r,e){d.initHelper(r,e,'
    ',r.ui.viewports.errors)},d.initScore=function(r,e){d.initHelper(r,e,'',r.ui.viewports.score)},d.initUI=function(r,e){r.ui.showPopover?d.initPopover(r,e):(r.ui.showErrors&&d.initErrorList(r,e),r.ui.showVerdicts&&!r.ui.showVerdictsInsideProgressBar&&d.initVerdict(r,e)),r.ui.showProgressBar&&d.initProgressBar(r,e),r.ui.showScore&&d.initScore(r,e)},d.updateVerdict=function(r,e,o,s){e=d.getUIElements(r,e).$verdict;e.removeClass(r.ui.colorClasses.join(" ")),-1"+e+""}),e.html(s)},d.updateScore=function(r,e,o,s){r=d.getUIElements(r,e).$score,e="";s||(e=o.toFixed(2)),r.html(e)},d.updateFieldStatus=function(o,r,e,s){var t=r;o.ui.bootstrap2?t=r.parents(".control-group").first():o.ui.bootstrap3&&(t=r.parents(".form-group").first()),n.each(a,function(r,e){e=d.cssClassesForBS(o,e),t.removeClass(e)}),s||(e=a[Math.floor(e/2)],e=d.cssClassesForBS(o,e),t.addClass(e))},d.cssClassesForBS=function(r,e){return r.ui.bootstrap3?e="has-"+e:r.ui.bootstrap2||(e="border-"+(e="error"===e?"danger":e)),e},d.getVerdictAndCssClass=function(r,e){return void 0===e?["",0]:(e=e<=r.ui.scores[0]?0:e
    '+o+"",a=!1),r.ui.showErrors&&(0r.common.maxChar?o:e},r.wordInvalidChar=function(r,e,o){return r.common.invalidCharsRegExp.test(e)?o:0},r.wordMinLengthStaticScore=function(r,e,o){return e.lengthr.common.maxChar?0:o},r.wordSimilarToUsername=function(r,e,o){r=i(r.common.usernameField).val();return r&&e.toLowerCase().match(r.replace(/[-[\]/{}()*+=?:.\\^$|!,]/g,"\\$&").toLowerCase())?o:0},r.wordTwoCharacterClasses=function(r,e,o){r=new RegExp("(."+r.rules.specialCharClass+")","u");if(e.match(/(\p{Ll}.*\p{Lu})|(\p{Lu}.*\p{Ll})/u)||e.match(/(\p{Letter})/u)&&e.match(/([0-9])/)||e.match(r)&&e.match(/[\p{Letter}0-9_]/u))return o},r.wordRepetitions=function(r,e,o){return e.match(/(.)\1\1/)?o:0},r.wordSequences=function(r,o,e){var s,t=!1;return 2'+e+"":""},u.ui.popoverError=function(r){"use strict";var e=r.instances.errors,o="
    "+r.i18n.t("errorList")+'
      ';return s.each(e,function(r,e){o+="
    • "+e+"
    • "}),o+="
    "},u.ui.showVerdicts=!0,u.ui.showVerdictsInsideProgressBar=!1,u.ui.useVerdictCssClass=!1,u.ui.showErrors=!1,u.ui.showScore=!1,u.ui.container=void 0,u.ui.viewports={progress:void 0,verdict:void 0,errors:void 0,score:void 0},u.ui.scores=[0,14,26,38,50],u.i18n={},u.i18n.t=t.t;var d={};!function(n){"use strict";var a=["error","warning","success"],o=["veryWeak","weak","normal","medium","strong","veryStrong"];d.getContainer=function(r,e){r=n(r.ui.container);return r=!r||1!==r.length?e.parent():r},d.findElement=function(r,e,o){return(e?r.find(e):r).find(o)},d.getUIElements=function(r,e){var o;return r.instances.viewports||(o=d.getContainer(r,e),(e={}).$progressbar=d.findElement(o,r.ui.viewports.progress,"div.progress"),r.ui.showVerdictsInsideProgressBar&&(e.$verdict=e.$progressbar.find("span.password-verdict")),r.ui.showPopover||(r.ui.showVerdictsInsideProgressBar||(e.$verdict=d.findElement(o,r.ui.viewports.verdict,"span.password-verdict")),e.$errors=d.findElement(o,r.ui.viewports.errors,"ul.error-list")),e.$score=d.findElement(o,r.ui.viewports.score,"span.password-score"),r.instances.viewports=e)},d.initHelper=function(r,e,o,s){r=d.getContainer(r,e);s?r.find(s).append(o):n(o).insertAfter(e)},d.initVerdict=function(r,e){d.initHelper(r,e,'',r.ui.viewports.verdict)},d.initErrorList=function(r,e){d.initHelper(r,e,'
      ',r.ui.viewports.errors)},d.initScore=function(r,e){d.initHelper(r,e,'',r.ui.viewports.score)},d.initUI=function(r,e){r.ui.showPopover?d.initPopover(r,e):(r.ui.showErrors&&d.initErrorList(r,e),r.ui.showVerdicts&&!r.ui.showVerdictsInsideProgressBar&&d.initVerdict(r,e)),r.ui.showProgressBar&&d.initProgressBar(r,e),r.ui.showScore&&d.initScore(r,e)},d.updateVerdict=function(r,e,o,s){e=d.getUIElements(r,e).$verdict;e.removeClass(r.ui.colorClasses.join(" ")),-1"+e+""}),e.html(s)},d.updateScore=function(r,e,o,s){r=d.getUIElements(r,e).$score,e="";s||(e=o.toFixed(2)),r.html(e)},d.updateFieldStatus=function(o,r,e,s){var t=r;o.ui.bootstrap2?t=r.parents(".control-group").first():o.ui.bootstrap3&&(t=r.parents(".form-group").first()),n.each(a,function(r,e){e=d.cssClassesForBS(o,e),t.removeClass(e)}),s||(e=a[Math.floor(e/2)],e=d.cssClassesForBS(o,e),t.addClass(e))},d.cssClassesForBS=function(r,e){return r.ui.bootstrap3?e="has-"+e:r.ui.bootstrap2||(e="border-"+(e="error"===e?"danger":e)),e},d.getVerdictAndCssClass=function(r,e){return void 0===e?["",0]:(e=e<=r.ui.scores[0]?0:e
      '+o+"",a=!1),r.ui.showErrors&&(0
      +
      + + +
      diff --git a/cps/templates/user_edit.html b/cps/templates/user_edit.html index 454fa6c9..a3ead445 100644 --- a/cps/templates/user_edit.html +++ b/cps/templates/user_edit.html @@ -21,7 +21,7 @@ {% endif %}
      - +
      {% endif %}
      @@ -177,7 +177,7 @@ - + {% endblock %} diff --git a/requirements.txt b/requirements.txt index 01bcfaf6..7bb5ff3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,3 +18,4 @@ flask-wtf>=0.14.2,<1.3.0 chardet>=3.0.0,<4.1.0 advocate>=1.0.0,<1.1.0 Flask-Limiter>=2.3.0,<3.6.0 +regex>=2022.3.2,<2024.2.25