diff --git a/.gitignore b/.gitignore index 1d473adf..ff08f874 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ settings.yaml gdrive_credentials client_secrets.json gmail.json +/.key diff --git a/cps.py b/cps.py index e4f9c520..f97d0f5a 100755 --- a/cps.py +++ b/cps.py @@ -21,7 +21,7 @@ import os import sys -# Add local path to sys.path so we can import cps +# Add local path to sys.path, so we can import cps path = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, path) diff --git a/cps/__init__.py b/cps/__init__.py index 9d8cfa92..269e4aca 100644 --- a/cps/__init__.py +++ b/cps/__init__.py @@ -41,6 +41,11 @@ from . import config_sql from . import cache_buster from . import ub, db +try: + from flask_limiter import Limiter + limiter_present = True +except ImportError: + limiter_present = False try: from flask_wtf.csrf import CSRFProtect wtf_present = True @@ -81,7 +86,7 @@ app.config.update( lm = MyLoginManager() -config = config_sql._ConfigSQL() +config = config_sql.ConfigSQL() cli_param = CliParameter() @@ -96,33 +101,36 @@ web_server = WebServer() updater_thread = Updater() +if limiter_present: + limiter = Limiter(key_func=True, headers_enabled=True, auto_check=False, swallow_errors=True) +else: + limiter = None def create_app(): - lm.login_view = 'web.login' - lm.anonymous_user = ub.Anonymous - lm.session_protection = 'strong' - if csrf: csrf.init_app(app) cli_param.init() - ub.init_db(cli_param.settings_path, cli_param.user_credentials) - + ub.init_db(cli_param.settings_path) # pylint: disable=no-member - config_sql.load_configuration(config, ub.session, cli_param) + encrypt_key, error = config_sql.get_encryption_key(os.path.dirname(cli_param.settings_path)) - db.CalibreDB.update_config(config) - db.CalibreDB.setup_db(config.config_calibre_dir, cli_param.settings_path) - calibre_db.init_db() + config_sql.load_configuration(ub.session, encrypt_key) + config.init_config(ub.session, encrypt_key, cli_param) - updater_thread.init_updater(config, web_server) - # Perform dry run of updater and exit afterwards - if cli_param.dry_run: - updater_thread.dry_run() - sys.exit(0) - updater_thread.start() + if error: + log.error(error) + 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, ' @@ -139,6 +147,22 @@ def create_app(): '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 + lm.session_protection = 'strong' if config.config_session == 1 else "basic" + + db.CalibreDB.update_config(config) + db.CalibreDB.setup_db(config.config_calibre_dir, cli_param.settings_path) + calibre_db.init_db() + + updater_thread.init_updater(config, web_server) + # Perform dry run of updater and exit afterwards + if cli_param.dry_run: + updater_thread.dry_run() + sys.exit(0) + updater_thread.start() + for res in dependency_check() + dependency_check(True): log.info('*** "{}" version does not meet the requirements. ' 'Should: {}, Found: {}, please consider installing required version ***' @@ -150,7 +174,6 @@ def create_app(): if os.environ.get('FLASK_DEBUG'): cache_buster.init_cache_busting(app) log.info('Starting Calibre Web...') - Principal(app) lm.init_app(app) app.secret_key = os.getenv('SECRET_KEY', config_sql.get_flask_session_key(ub.session)) @@ -168,9 +191,13 @@ def create_app(): services.ldap.init_app(app, config) if services.goodreads_support: services.goodreads_support.connect(config.config_goodreads_api_key, - config.config_goodreads_api_secret, + config.config_goodreads_api_secret_e, config.config_use_goodreads) config.store_calibre_uuid(calibre_db, db.Library_Id) + # Configure rate limiter + app.config.update(RATELIMIT_ENABLED=config.config_ratelimiter) + limiter.init_app(app) + # Register scheduled tasks from .schedule import register_scheduled_tasks, register_startup_tasks register_scheduled_tasks(config.schedule_reconnect) diff --git a/cps/admin.py b/cps/admin.py index 9cc568cd..0cf087ef 100644 --- a/cps/admin.py +++ b/cps/admin.py @@ -22,7 +22,6 @@ import os import re -import base64 import json import operator import time @@ -104,7 +103,6 @@ def before_request(): if not ub.check_user_session(current_user.id, flask_session.get('_id')) and 'opds' not in request.path: logout_user() g.constants = constants - # g.user = current_user g.google_site_verification = os.getenv('GOOGLE_SITE_VERIFICATION','') g.allow_registration = config.config_public_reg g.allow_anonymous = config.config_anonbrowse @@ -116,6 +114,7 @@ def before_request(): 'admin.simulatedbchange', 'admin.db_configuration', 'web.login', + 'web.login_post', 'web.logout', 'admin.load_dialogtexts', 'admin.ajax_pathchooser'): @@ -214,12 +213,12 @@ def admin(): commit = version['version'] all_user = ub.session.query(ub.User).all() - email_settings = config.get_mail_settings() + # email_settings = mail_config.get_mail_settings() schedule_time = format_time(datetime_time(hour=config.schedule_start_time), format="short") t = timedelta(hours=config.schedule_duration // 60, minutes=config.schedule_duration % 60) schedule_duration = format_timedelta(t, threshold=.99) - return render_title_template("admin.html", allUser=all_user, email=email_settings, config=config, commit=commit, + return render_title_template("admin.html", allUser=all_user, config=config, commit=commit, feature_support=feature_support, schedule_time=schedule_time, schedule_duration=schedule_duration, title=_("Admin page"), page="admin") @@ -1084,7 +1083,7 @@ def _config_checkbox_int(to_save, x): def _config_string(to_save, x): - return config.set_from_dictionary(to_save, x, lambda y: y.strip() if y else y) + return config.set_from_dictionary(to_save, x, lambda y: y.strip().strip(u'\u200B\u200C\u200D\ufeff') if y else y) def _configuration_gdrive_helper(to_save): @@ -1173,9 +1172,9 @@ def _configuration_ldap_helper(to_save): reboot_required |= _config_string(to_save, "config_ldap_cert_path") reboot_required |= _config_string(to_save, "config_ldap_key_path") _config_string(to_save, "config_ldap_group_name") - if to_save.get("config_ldap_serv_password", "") != "": + if to_save.get("config_ldap_serv_password_e", "") != "": reboot_required |= 1 - config.set_from_dictionary(to_save, "config_ldap_serv_password", base64.b64encode, encode='UTF-8') + config.set_from_dictionary(to_save, "config_ldap_serv_password_e") config.save() if not config.config_ldap_provider_url \ @@ -1187,7 +1186,7 @@ def _configuration_ldap_helper(to_save): if config.config_ldap_authentication > constants.LDAP_AUTH_ANONYMOUS: if config.config_ldap_authentication > constants.LDAP_AUTH_UNAUTHENTICATE: - if not config.config_ldap_serv_username or not bool(config.config_ldap_serv_password): + if not config.config_ldap_serv_username or not bool(config.config_ldap_serv_password_e): return reboot_required, _configuration_result(_('Please Enter a LDAP Service Account and Password')) else: if not config.config_ldap_serv_username: @@ -1255,7 +1254,7 @@ def new_user(): kobo_support=kobo_support, registered_oauth=oauth_check) -@admi.route("/admin/mailsettings") +@admi.route("/admin/mailsettings", methods=["GET"]) @login_required @admin_required def edit_mailsettings(): @@ -1288,7 +1287,7 @@ def update_mailsettings(): else: _config_int(to_save, "mail_port") _config_int(to_save, "mail_use_ssl") - _config_string(to_save, "mail_password") + _config_string(to_save, "mail_password_e") _config_int(to_save, "mail_size", lambda y: int(y) * 1024 * 1024) config.mail_server = to_save.get('mail_server', "").strip() config.mail_from = to_save.get('mail_from', "").strip() @@ -1348,7 +1347,7 @@ def update_scheduledtasks(): error = False to_save = request.form.to_dict() if 0 <= int(to_save.get("schedule_start_time")) <= 23: - _config_int(to_save, "schedule_start_time") + _config_int( to_save, "schedule_start_time") else: flash(_("Invalid start time for task specified"), category="error") error = True @@ -1771,10 +1770,10 @@ def _configuration_update_helper(): # Goodreads configuration _config_checkbox(to_save, "config_use_goodreads") _config_string(to_save, "config_goodreads_api_key") - _config_string(to_save, "config_goodreads_api_secret") + _config_string(to_save, "config_goodreads_api_secret_e") if services.goodreads_support: services.goodreads_support.connect(config.config_goodreads_api_key, - config.config_goodreads_api_secret, + config.config_goodreads_api_secret_e, config.config_use_goodreads) _config_int(to_save, "config_updatechannel") @@ -1787,10 +1786,25 @@ def _configuration_update_helper(): if config.config_login_type == constants.LOGIN_OAUTH: reboot_required |= _configuration_oauth_helper(to_save) + # logfile configuration reboot, message = _configuration_logfile_helper(to_save) if message: return message reboot_required |= reboot + + # security configuration + _config_checkbox(to_save, "config_password_policy") + _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_special") + if 0 < int(to_save.get("config_password_min_length", "0")) < 41: + _config_int(to_save, "config_password_min_length") + else: + return _configuration_result(_('Password length has to be between 1 and 40')) + reboot_required |= _config_int(to_save, "config_session") + reboot_required |= _config_checkbox(to_save, "config_ratelimiter") + # Rarfile Content configuration _config_string(to_save, "config_rarfile_location") if "config_rarfile_location" in to_save: @@ -1859,11 +1873,11 @@ def _handle_new_user(to_save, content, languages, translations, kobo_support): content.sidebar_view |= constants.DETAIL_RANDOM content.role = constants.selected_roles(to_save) - content.password = generate_password_hash(to_save["password"]) try: if not to_save["name"] or not to_save["email"] or not to_save["password"]: log.info("Missing entries on new user") raise Exception(_("Oops! Please complete all fields.")) + content.password = generate_password_hash(helper.valid_password(to_save.get("password", ""))) content.email = check_email(to_save["email"]) # Query username, if not existing, change content.name = check_username(to_save["name"]) @@ -1947,14 +1961,6 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support): log.warning("No admin user remaining, can't remove admin role from {}".format(content.name)) flash(_("No admin user remaining, can't remove admin role"), category="error") return redirect(url_for('admin.admin')) - if to_save.get("password"): - content.password = generate_password_hash(to_save["password"]) - anonymous = content.is_anonymous - content.role = constants.selected_roles(to_save) - if anonymous: - content.role |= constants.ROLE_ANONYMOUS - else: - content.role &= ~constants.ROLE_ANONYMOUS val = [int(k[5:]) for k in to_save if k.startswith('show_')] sidebar, __ = get_sidebar_config() @@ -1982,6 +1988,15 @@ def _handle_edit_user(to_save, content, languages, translations, kobo_support): if to_save.get("locale"): content.locale = to_save["locale"] try: + anonymous = content.is_anonymous + content.role = constants.selected_roles(to_save) + if anonymous: + content.role |= constants.ROLE_ANONYMOUS + else: + content.role &= ~constants.ROLE_ANONYMOUS + if to_save.get("password", ""): + content.password = generate_password_hash(helper.valid_password(to_save.get["password"])) + new_email = valid_email(to_save.get("email", content.email)) if not new_email: raise Exception(_("Email can't be empty and has to be a valid Email")) diff --git a/cps/config_sql.py b/cps/config_sql.py index b6419a84..771b353c 100644 --- a/cps/config_sql.py +++ b/cps/config_sql.py @@ -23,6 +23,10 @@ import json from sqlalchemy import Column, String, Integer, SmallInteger, Boolean, BLOB, JSON from sqlalchemy.exc import OperationalError from sqlalchemy.sql.expression import text +from sqlalchemy import exists +from cryptography.fernet import Fernet +import cryptography.exceptions +from base64 import urlsafe_b64decode try: # Compatibility with sqlalchemy 2.0 from sqlalchemy.orm import declarative_base @@ -56,7 +60,8 @@ class _Settings(_Base): mail_port = Column(Integer, default=25) mail_use_ssl = Column(SmallInteger, default=0) mail_login = Column(String, default='mail@example.com') - mail_password = Column(String, default='mypassword') + mail_password_e = Column(String) + mail_password = Column(String) mail_from = Column(String, default='automailer ') mail_size = Column(Integer, default=25*1024*1024) mail_server_type = Column(SmallInteger, default=0) @@ -74,8 +79,7 @@ class _Settings(_Base): config_random_books = Column(Integer, default=4) config_authors_max = Column(Integer, default=0) config_read_column = Column(Integer, default=0) - config_title_regex = Column(String, default=r'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines|Le|La|Les|L\'|Un|Une)\s+') - # config_mature_content_tags = Column(String, default='') + config_title_regex = Column(String, default=r'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines|Le|La|Les|L\'|Un|Une)\s+') config_theme = Column(Integer, default=0) config_log_level = Column(SmallInteger, default=logger.DEFAULT_LOG_LEVEL) @@ -107,6 +111,7 @@ class _Settings(_Base): config_use_goodreads = Column(Boolean, default=False) config_goodreads_api_key = Column(String) + config_goodreads_api_secret_e = Column(String) config_goodreads_api_secret = Column(String) config_register_email = Column(Boolean, default=False) config_login_type = Column(Integer, default=0) @@ -117,7 +122,8 @@ class _Settings(_Base): config_ldap_port = Column(SmallInteger, default=389) config_ldap_authentication = Column(SmallInteger, default=constants.LDAP_AUTH_SIMPLE) config_ldap_serv_username = Column(String, default='cn=admin,dc=example,dc=org') - config_ldap_serv_password = Column(String, default="") + config_ldap_serv_password_e = Column(String) + config_ldap_serv_password = Column(String) config_ldap_encryption = Column(SmallInteger, default=0) config_ldap_cacert_path = Column(String, default="") config_ldap_cert_path = Column(String, default="") @@ -148,23 +154,33 @@ class _Settings(_Base): schedule_generate_series_covers = Column(Boolean, default=False) schedule_reconnect = Column(Boolean, default=False) + config_password_policy = Column(Boolean, default=True) + config_password_min_length = Column(Integer, default=8) + config_password_number = Column(Boolean, default=True) + config_password_lower = Column(Boolean, default=True) + config_password_upper = Column(Boolean, default=True) + config_password_special = Column(Boolean, default=True) + config_session = Column(Integer, default=1) + config_ratelimiter = Column(Boolean, default=True) + def __repr__(self): return self.__class__.__name__ # Class holds all application specific settings in calibre-web -class _ConfigSQL(object): +class ConfigSQL(object): # pylint: disable=no-member def __init__(self): - pass + self.__dict__["dirty"] = list() - def init_config(self, session, cli): + def init_config(self, session, secret_key, cli): self._session = session self._settings = None self.db_configured = None self.config_calibre_dir = None - self.load() + self._fernet = Fernet(secret_key) self.cli = cli + self.load() change = False if self.config_converterpath == None: # pylint: disable=access-member-before-definition @@ -293,10 +309,10 @@ class _ConfigSQL(object): setattr(self, field, new_value) return True - def toDict(self): + def to_dict(self): storage = {} for k, v in self.__dict__.items(): - if k[0] != '_' and not k.endswith("password") and not k.endswith("secret") and not k == "cli": + if k[0] != '_' and not k.endswith("_e") and not k == "cli": storage[k] = v return storage @@ -310,7 +326,13 @@ class _ConfigSQL(object): column = s.__class__.__dict__.get(k) if column.default is not None: v = column.default.arg - setattr(self, k, v) + if k.endswith("_e") and v is not None: + try: + setattr(self, k, self._fernet.decrypt(v).decode()) + except cryptography.fernet.InvalidToken: + setattr(self, k, "") + else: + setattr(self, k, v) have_metadata_db = bool(self.config_calibre_dir) if have_metadata_db: @@ -332,16 +354,20 @@ class _ConfigSQL(object): except OperationalError as e: log.error('Database error: %s', e) self._session.rollback() + self.__dict__["dirty"] = list() def save(self): """Apply all configuration values to the underlying storage.""" s = self._read_from_storage() # type: _Settings - for k, v in self.__dict__.items(): + for k in self.dirty: if k[0] == '_': continue if hasattr(s, k): - setattr(s, k, v) + if k.endswith("_e"): + setattr(s, k, self._fernet.encrypt(self.__dict__[k].encode())) + else: + setattr(s, k, self.__dict__[k]) log.debug("_ConfigSQL updating storage") self._session.merge(s) @@ -357,7 +383,6 @@ class _ConfigSQL(object): log.error(error) log.warning("invalidating configuration") self.db_configured = False - # self.config_calibre_dir = None self.save() def store_calibre_uuid(self, calibre_db, Library_table): @@ -369,8 +394,40 @@ class _ConfigSQL(object): except AttributeError: pass + def __setattr__(self, attr_name, attr_value): + super().__setattr__(attr_name, attr_value) + self.__dict__["dirty"].append(attr_name) -def _migrate_table(session, orm_class): + +def _encrypt_fields(session, secret_key): + try: + session.query(exists().where(_Settings.mail_password_e)).scalar() + except OperationalError: + with session.bind.connect() as conn: + conn.execute("ALTER TABLE settings ADD column 'mail_password_e' String") + conn.execute("ALTER TABLE settings ADD column 'config_goodreads_api_secret_e' String") + conn.execute("ALTER TABLE settings ADD column 'config_ldap_serv_password_e' String") + session.commit() + crypter = Fernet(secret_key) + settings = session.query(_Settings.mail_password, _Settings.config_goodreads_api_secret, + _Settings.config_ldap_serv_password).first() + if settings.mail_password: + session.query(_Settings).update( + {_Settings.mail_password_e: crypter.encrypt(settings.mail_password.encode())}) + if settings.config_goodreads_api_secret: + session.query(_Settings).update( + {_Settings.config_goodreads_api_secret_e: + crypter.encrypt(settings.config_goodreads_api_secret.encode())}) + if settings.config_ldap_serv_password: + session.query(_Settings).update( + {_Settings.config_ldap_serv_password_e: + crypter.encrypt(settings.config_ldap_serv_password.encode())}) + session.commit() + + +def _migrate_table(session, orm_class, secret_key=None): + if secret_key: + _encrypt_fields(session, secret_key) changed = False for column_name, column in orm_class.__dict__.items(): @@ -446,22 +503,18 @@ def autodetect_kepubify_binary(): return "" -def _migrate_database(session): +def _migrate_database(session, secret_key): # make sure the table is created, if it does not exist _Base.metadata.create_all(session.bind) - _migrate_table(session, _Settings) + _migrate_table(session, _Settings, secret_key) _migrate_table(session, _Flask_Settings) -def load_configuration(conf, session, cli): - _migrate_database(session) - +def load_configuration(session, secret_key): + _migrate_database(session, secret_key) if not session.query(_Settings).count(): session.add(_Settings()) session.commit() - # conf = _ConfigSQL() - conf.init_config(session, cli) - # return conf def get_flask_session_key(_session): @@ -471,3 +524,25 @@ def get_flask_session_key(_session): _session.add(flask_settings) _session.commit() return flask_settings.flask_session_key + + +def get_encryption_key(key_path): + key_file = os.path.join(key_path, ".key") + generate = True + error = "" + if os.path.exists(key_file) and os.path.getsize(key_file) > 32: + with open(key_file, "rb") as f: + key = f.read() + try: + urlsafe_b64decode(key) + generate = False + except ValueError: + pass + if generate: + key = Fernet.generate_key() + try: + with open(key_file, "wb") as f: + f.write(key) + except PermissionError as e: + error = e + return key, error diff --git a/cps/debug_info.py b/cps/debug_info.py index 6cb30edb..82ca8ca6 100644 --- a/cps/debug_info.py +++ b/cps/debug_info.py @@ -65,7 +65,7 @@ def send_debug(): file_list.remove(element) memory_zip = BytesIO() with zipfile.ZipFile(memory_zip, 'w', compression=zipfile.ZIP_DEFLATED) as zf: - zf.writestr('settings.txt', json.dumps(config.toDict(), sort_keys=True, indent=2)) + zf.writestr('settings.txt', json.dumps(config.to_dict(), sort_keys=True, indent=2)) zf.writestr('libs.txt', json.dumps(collect_stats(), sort_keys=True, indent=2, cls=lazyEncoder)) for fp in file_list: zf.write(fp, os.path.basename(fp)) diff --git a/cps/helper.py b/cps/helper.py index 7fc2edc9..85d71122 100644 --- a/cps/helper.py +++ b/cps/helper.py @@ -18,6 +18,7 @@ # along with this program. If not, see . import os +import random import io import mimetypes import re @@ -612,7 +613,7 @@ def reset_password(user_id): if not config.get_mail_server_configured(): return 2, None try: - password = generate_random_password() + password = generate_random_password(config.config_password_min_length) existing_user.password = generate_password_hash(password) ub.session.commit() send_registration_mail(existing_user.email, existing_user.name, password, True) @@ -621,11 +622,35 @@ def reset_password(user_id): ub.session.rollback() return 0, None +def generate_random_password(min_length): + min_length = max(8, min_length) - 4 + random_source = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&*()?" + # select 1 lowercase + s = "abcdefghijklmnopqrstuvwxyz" + password = [s[c % len(s)] for c in os.urandom(1)] + # select 1 uppercase + s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + password.extend([s[c % len(s)] for c in os.urandom(1)]) + # select 1 digit + s = "01234567890" + password.extend([s[c % len(s)] for c in os.urandom(1)]) + # select 1 special symbol + s = "!@#$%&*()?" + password.extend([s[c % len(s)] for c in os.urandom(1)]) -def generate_random_password(): + # generate other characters + password.extend([random_source[c % len(random_source)] for c in os.urandom(min_length)]) + + # password_list = list(password) + # shuffle all characters + random.SystemRandom().shuffle(password) + return ''.join(password) + + +'''def generate_random_password(min_length): s = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&*()?" - passlen = 8 - return "".join(s[c % len(s)] for c in os.urandom(passlen)) + passlen = min_length + return "".join(s[c % len(s)] for c in os.urandom(passlen))''' def uniq(inpt): @@ -664,6 +689,23 @@ def valid_email(email): raise Exception(_("Invalid Email address format")) return email +def valid_password(check_password): + if config.config_password_policy: + verify = "" + if config.config_password_min_length > 0: + verify += "^(?=.{" + str(config.config_password_min_length) + ",}$)" + if config.config_password_number: + verify += "(?=.*?\d)" + if config.config_password_lower: + verify += "(?=.*?[a-z])" + if config.config_password_upper: + verify += "(?=.*?[A-Z])" + if config.config_password_special: + verify += "(?=.*?[^A-Za-z\s0-9])" + match = re.match(verify, check_password) + if not match: + raise Exception(_("Password doesn't comply with password validation rules")) + return check_password # ################################# External interface ################################# diff --git a/cps/kobo_auth.py b/cps/kobo_auth.py index 82c140ff..3736e4e1 100644 --- a/cps/kobo_auth.py +++ b/cps/kobo_auth.py @@ -64,11 +64,12 @@ from datetime import datetime from os import urandom from functools import wraps -from flask import g, Blueprint, url_for, abort, request +from flask import g, Blueprint, abort, request from flask_login import login_user, current_user, login_required from flask_babel import gettext as _ +from flask_limiter import RateLimitExceeded -from . import logger, config, calibre_db, db, helper, ub, lm +from . import logger, config, calibre_db, db, helper, ub, lm, limiter from .render_template import render_title_template log = logger.create() @@ -151,6 +152,10 @@ def requires_kobo_auth(f): def inner(*args, **kwargs): auth_token = get_auth_token() if auth_token is not None: + try: + limiter.check() + except RateLimitExceeded: + return abort(429) user = ( ub.session.query(ub.User) .join(ub.RemoteAuthToken) @@ -159,7 +164,8 @@ def requires_kobo_auth(f): ) if user is not None: login_user(user) + [limiter.limiter.storage.clear(k.key) for k in limiter.current_limits] return f(*args, **kwargs) - log.debug("Received Kobo request without a recognizable auth token.") - return abort(401) + log.debug("Received Kobo request without a recognizable auth token.") + return abort(401) return inner diff --git a/cps/main.py b/cps/main.py index d3591c06..286b2b27 100644 --- a/cps/main.py +++ b/cps/main.py @@ -18,9 +18,14 @@ import sys -from . import create_app +from . import create_app, limiter from .jinjia import jinjia from .remotelogin import remotelogin +from flask import request + + +def request_username(): + return request.authorization.username def main(): app = create_app() @@ -39,6 +44,7 @@ def main(): try: from .kobo import kobo, get_kobo_activated from .kobo_auth import kobo_auth + from flask_limiter.util import get_remote_address kobo_available = get_kobo_activated() except (ImportError, AttributeError): # Catch also error for not installed flask-WTF (missing csrf decorator) kobo_available = False @@ -56,6 +62,7 @@ def main(): app.register_blueprint(tasks) app.register_blueprint(web) app.register_blueprint(opds) + limiter.limit("3/minute",key_func=request_username)(opds) app.register_blueprint(jinjia) app.register_blueprint(about) app.register_blueprint(shelf) @@ -67,6 +74,7 @@ def main(): if kobo_available: app.register_blueprint(kobo) app.register_blueprint(kobo_auth) + limiter.limit("3/minute", key_func=get_remote_address)(kobo) if oauth_available: app.register_blueprint(oauth) success = web_server.start() diff --git a/cps/schedule.py b/cps/schedule.py index 707e9bb3..9235f0d9 100644 --- a/cps/schedule.py +++ b/cps/schedule.py @@ -27,8 +27,7 @@ from .tasks.metadata_backup import TaskBackupMetadata def get_scheduled_tasks(reconnect=True): tasks = list() - # config.schedule_reconnect or - # Reconnect Calibre database (metadata.db) + # Reconnect Calibre database (metadata.db) based on config.schedule_reconnect if reconnect: tasks.append([lambda: TaskReconnectDatabase(), 'reconnect', False]) diff --git a/cps/server.py b/cps/server.py index 15a1ef7b..ed3b7716 100644 --- a/cps/server.py +++ b/cps/server.py @@ -22,7 +22,6 @@ import errno import signal import socket import subprocess # nosec -from .services.background_scheduler import BackgroundScheduler try: from gevent.pywsgi import WSGIServer @@ -270,6 +269,7 @@ class WebServer(object): @staticmethod def shutdown_scheduler(): + from .services.background_scheduler import BackgroundScheduler scheduler = BackgroundScheduler() if scheduler: scheduler.scheduler.shutdown() diff --git a/cps/services/simpleldap.py b/cps/services/simpleldap.py index 1ca7e5bf..872538d1 100644 --- a/cps/services/simpleldap.py +++ b/cps/services/simpleldap.py @@ -44,15 +44,15 @@ def init_app(app, config): app.config['LDAP_SCHEMA'] = 'ldap' if config.config_ldap_authentication > constants.LDAP_AUTH_ANONYMOUS: if config.config_ldap_authentication > constants.LDAP_AUTH_UNAUTHENTICATE: - if config.config_ldap_serv_password is None: - config.config_ldap_serv_password = '' - app.config['LDAP_PASSWORD'] = base64.b64decode(config.config_ldap_serv_password) + if config.config_ldap_serv_password_e is None: + config.config_ldap_serv_password_e = '' + app.config['LDAP_PASSWORD'] = config.config_ldap_serv_password_e else: - app.config['LDAP_PASSWORD'] = base64.b64decode("") + app.config['LDAP_PASSWORD'] = "" app.config['LDAP_USERNAME'] = config.config_ldap_serv_username else: app.config['LDAP_USERNAME'] = "" - app.config['LDAP_PASSWORD'] = base64.b64decode("") + app.config['LDAP_PASSWORD'] = "" if bool(config.config_ldap_cert_path): app.config['LDAP_CUSTOM_OPTIONS'].update({ pyLDAP.OPT_X_TLS_REQUIRE_CERT: pyLDAP.OPT_X_TLS_DEMAND, diff --git a/cps/static/css/style.css b/cps/static/css/style.css index 59bcd2c0..0bab5b53 100644 --- a/cps/static/css/style.css +++ b/cps/static/css/style.css @@ -434,3 +434,7 @@ div.log { #detailcover:-moz-full-screen { cursor:zoom-out; border: 0; } #detailcover:-ms-fullscreen { cursor:zoom-out; border: 0; } #detailcover:fullscreen { cursor:zoom-out; border: 0; } + +.error-list { + margin-top: 5px; + } diff --git a/cps/static/js/libs/pwstrength/i18next.min.js b/cps/static/js/libs/pwstrength/i18next.min.js new file mode 100644 index 00000000..0b1c1222 --- /dev/null +++ b/cps/static/js/libs/pwstrength/i18next.min.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).i18next=t()}(this,function(){"use strict";function e(t){return(e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(t)}function t(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function n(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:{};t(this,e),this.init(n,r)}return r(e,[{key:"init",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};this.prefix=t.prefix||"i18next:",this.logger=e||p,this.options=t,this.debug=t.debug}},{key:"setDebug",value:function(e){this.debug=e}},{key:"log",value:function(){for(var e=arguments.length,t=new Array(e),n=0;n1?t-1:0),r=1;r-1?e.replace(/###/g,"."):e}function o(){return!e||"string"==typeof e}for(var i="string"!=typeof t?[].concat(t):t.split(".");i.length>1;){if(o())return{};var a=r(i.shift());!e[a]&&n&&(e[a]=new n),e=Object.prototype.hasOwnProperty.call(e,a)?e[a]:{}}return o()?{}:{obj:e,k:r(i.shift())}}function m(e,t,n){var r=y(e,t,Object);r.obj[r.k]=n}function b(e,t){var n=y(e,t),r=n.obj,o=n.k;if(r)return r[o]}function O(e,t,n){var r=b(e,n);return void 0!==r?r:b(t,n)}function k(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}var w={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};function x(e){return"string"==typeof e?e.replace(/[&<>"'\/]/g,function(e){return w[e]}):e}var S="undefined"!=typeof window&&window.navigator&&void 0===window.navigator.userAgentData&&window.navigator.userAgent&&window.navigator.userAgent.indexOf("MSIE")>-1,j=[" ",",","?","!",";"];function P(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function L(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:{ns:["translation"],defaultNS:"translation"};return t(this,i),r=n.call(this),S&&h.call(o(r)),r.data=e||{},r.options=a,void 0===r.options.keySeparator&&(r.options.keySeparator="."),void 0===r.options.ignoreJSONStructure&&(r.options.ignoreJSONStructure=!0),r}return r(i,[{key:"addNamespaces",value:function(e){this.options.ns.indexOf(e)<0&&this.options.ns.push(e)}},{key:"removeNamespaces",value:function(e){var t=this.options.ns.indexOf(e);t>-1&&this.options.ns.splice(t,1)}},{key:"getResource",value:function(e,t,n){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},o=void 0!==r.keySeparator?r.keySeparator:this.options.keySeparator,i=void 0!==r.ignoreJSONStructure?r.ignoreJSONStructure:this.options.ignoreJSONStructure,a=[e,t];n&&"string"!=typeof n&&(a=a.concat(n)),n&&"string"==typeof n&&(a=a.concat(o?n.split(o):n)),e.indexOf(".")>-1&&(a=e.split("."));var s=b(this.data,a);return s||!i||"string"!=typeof n?s:function e(t,n){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:".";if(t){if(t[n])return t[n];for(var o=n.split(r),i=t,a=0;aa+s;)s++,c=i[u=o.slice(a,a+s).join(r)];if(void 0===c)return;if(null===c)return null;if(n.endsWith(u)){if("string"==typeof c)return c;if(u&&"string"==typeof c[u])return c[u]}var l=o.slice(a+s).join(r);return l?e(c,l,r):void 0}i=i[o[a]]}return i}}(this.data&&this.data[e]&&this.data[e][t],n,o)}},{key:"addResource",value:function(e,t,n,r){var o=arguments.length>4&&void 0!==arguments[4]?arguments[4]:{silent:!1},i=this.options.keySeparator;void 0===i&&(i=".");var a=[e,t];n&&(a=a.concat(i?n.split(i):n)),e.indexOf(".")>-1&&(r=t,t=(a=e.split("."))[1]),this.addNamespaces(t),m(this.data,a,r),o.silent||this.emit("added",e,t,n,r)}},{key:"addResources",value:function(e,t,n){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{silent:!1};for(var o in n)"string"!=typeof n[o]&&"[object Array]"!==Object.prototype.toString.apply(n[o])||this.addResource(e,t,o,n[o],{silent:!0});r.silent||this.emit("added",e,t,n)}},{key:"addResourceBundle",value:function(e,t,n,r,o){var i=arguments.length>5&&void 0!==arguments[5]?arguments[5]:{silent:!1},a=[e,t];e.indexOf(".")>-1&&(r=n,n=t,t=(a=e.split("."))[1]),this.addNamespaces(t);var s=b(this.data,a)||{};r?function e(t,n,r){for(var o in n)"__proto__"!==o&&"constructor"!==o&&(o in t?"string"==typeof t[o]||t[o]instanceof String||"string"==typeof n[o]||n[o]instanceof String?r&&(t[o]=n[o]):e(t[o],n[o],r):t[o]=n[o]);return t}(s,n,o):s=L(L({},s),n),m(this.data,a,s),i.silent||this.emit("added",e,t,n)}},{key:"removeResourceBundle",value:function(e,t){this.hasResourceBundle(e,t)&&delete this.data[e][t],this.removeNamespaces(t),this.emit("removed",e,t)}},{key:"hasResourceBundle",value:function(e,t){return void 0!==this.getResource(e,t)}},{key:"getResourceBundle",value:function(e,t){return t||(t=this.options.defaultNS),"v1"===this.options.compatibilityAPI?L(L({},{}),this.getResource(e,t)):this.getResource(e,t)}},{key:"getDataByLanguage",value:function(e){return this.data[e]}},{key:"hasLanguageSomeTranslations",value:function(e){var t=this.getDataByLanguage(e);return!!(t&&Object.keys(t)||[]).find(function(e){return t[e]&&Object.keys(t[e]).length>0})}},{key:"toJSON",value:function(){return this.data}}]),i}(),C={processors:{},addPostProcessor:function(e){this.processors[e.name]=e},handle:function(e,t,n,r,o){var i=this;return e.forEach(function(e){i.processors[e]&&(t=i.processors[e].process(t,n,r,o))}),t}};function E(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function D(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:{};return t(this,s),n=i.call(this),S&&h.call(o(n)),r=["resourceStore","languageUtils","pluralResolver","interpolator","backendConnector","i18nFormat","utils"],a=e,u=o(n),r.forEach(function(e){a[e]&&(u[e]=a[e])}),n.options=c,void 0===n.options.keySeparator&&(n.options.keySeparator="."),n.logger=g.create("translator"),n}return r(s,[{key:"changeLanguage",value:function(e){e&&(this.language=e)}},{key:"exists",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{interpolation:{}};if(null==e)return!1;var n=this.resolve(e,t);return n&&void 0!==n.res}},{key:"extractFromKey",value:function(e,t){var n=void 0!==t.nsSeparator?t.nsSeparator:this.options.nsSeparator;void 0===n&&(n=":");var r=void 0!==t.keySeparator?t.keySeparator:this.options.keySeparator,o=t.ns||this.options.defaultNS||[],i=n&&e.indexOf(n)>-1,a=!(this.options.userDefinedKeySeparator||t.keySeparator||this.options.userDefinedNsSeparator||t.nsSeparator||function(e,t,n){t=t||"",n=n||"";var r=j.filter(function(e){return t.indexOf(e)<0&&n.indexOf(e)<0});if(0===r.length)return!0;var o=new RegExp("(".concat(r.map(function(e){return"?"===e?"\\?":e}).join("|"),")")),i=!o.test(e);if(!i){var a=e.indexOf(n);a>0&&!o.test(e.substring(0,a))&&(i=!0)}return i}(e,n,r));if(i&&!a){var s=e.match(this.interpolator.nestingRegexp);if(s&&s.length>0)return{key:e,namespaces:o};var u=e.split(n);(n!==r||n===r&&this.options.ns.indexOf(u[0])>-1)&&(o=u.shift()),e=u.join(r)}return"string"==typeof o&&(o=[o]),{key:e,namespaces:o}}},{key:"translate",value:function(t,n,r){var o=this;if("object"!==e(n)&&this.options.overloadTranslationOptionHandler&&(n=this.options.overloadTranslationOptionHandler(arguments)),n||(n={}),null==t)return"";Array.isArray(t)||(t=[String(t)]);var i=void 0!==n.returnDetails?n.returnDetails:this.options.returnDetails,a=void 0!==n.keySeparator?n.keySeparator:this.options.keySeparator,u=this.extractFromKey(t[t.length-1],n),c=u.key,l=u.namespaces,f=l[l.length-1],p=n.lng||this.language,g=n.appendNamespaceToCIMode||this.options.appendNamespaceToCIMode;if(p&&"cimode"===p.toLowerCase()){if(g){var h=n.nsSeparator||this.options.nsSeparator;return i?(d.res="".concat(f).concat(h).concat(c),d):"".concat(f).concat(h).concat(c)}return i?(d.res=c,d):c}var d=this.resolve(t,n),v=d&&d.res,y=d&&d.usedKey||c,m=d&&d.exactUsedKey||c,b=Object.prototype.toString.apply(v),O=void 0!==n.joinArrays?n.joinArrays:this.options.joinArrays,k=!this.i18nFormat||this.i18nFormat.handleAsObject;if(k&&v&&("string"!=typeof v&&"boolean"!=typeof v&&"number"!=typeof v)&&["[object Number]","[object Function]","[object RegExp]"].indexOf(b)<0&&("string"!=typeof O||"[object Array]"!==b)){if(!n.returnObjects&&!this.options.returnObjects){this.options.returnedObjectHandler||this.logger.warn("accessing an object - but returnObjects options is not enabled!");var w=this.options.returnedObjectHandler?this.options.returnedObjectHandler(y,v,D(D({},n),{},{ns:l})):"key '".concat(c," (").concat(this.language,")' returned an object instead of string.");return i?(d.res=w,d):w}if(a){var x="[object Array]"===b,S=x?[]:{},j=x?m:y;for(var P in v)if(Object.prototype.hasOwnProperty.call(v,P)){var L="".concat(j).concat(a).concat(P);S[P]=this.translate(L,D(D({},n),{joinArrays:!1,ns:l})),S[P]===L&&(S[P]=v[P])}v=S}}else if(k&&"string"==typeof O&&"[object Array]"===b)(v=v.join(O))&&(v=this.extendTranslation(v,t,n,r));else{var R=!1,N=!1,C=void 0!==n.count&&"string"!=typeof n.count,E=s.hasDefaultValue(n),F=C?this.pluralResolver.getSuffix(p,n.count,n):"",I=n["defaultValue".concat(F)]||n.defaultValue;!this.isValidLookup(v)&&E&&(R=!0,v=I),this.isValidLookup(v)||(N=!0,v=c);var A=(n.missingKeyNoValueFallbackToKey||this.options.missingKeyNoValueFallbackToKey)&&N?void 0:v,T=E&&I!==v&&this.options.updateMissing;if(N||R||T){if(this.logger.log(T?"updateKey":"missingKey",p,f,c,T?I:v),a){var V=this.resolve(c,D(D({},n),{},{keySeparator:!1}));V&&V.res&&this.logger.warn("Seems the loaded translations were in flat JSON format instead of nested. Either set keySeparator: false on init or make sure your translations are published in nested format.")}var U=[],K=this.languageUtils.getFallbackCodes(this.options.fallbackLng,n.lng||this.language);if("fallback"===this.options.saveMissingTo&&K&&K[0])for(var B=0;B1&&void 0!==arguments[1]?arguments[1]:{};return"string"==typeof e&&(e=[e]),e.forEach(function(e){if(!a.isValidLookup(t)){var u=a.extractFromKey(e,s),c=u.key;n=c;var l=u.namespaces;a.options.fallbackNS&&(l=l.concat(a.options.fallbackNS));var f=void 0!==s.count&&"string"!=typeof s.count,p=f&&!s.ordinal&&0===s.count&&a.pluralResolver.shouldUseIntlApi(),g=void 0!==s.context&&("string"==typeof s.context||"number"==typeof s.context)&&""!==s.context,h=s.lngs?s.lngs:a.languageUtils.toResolveHierarchy(s.lng||a.language,s.fallbackLng);l.forEach(function(e){a.isValidLookup(t)||(i=e,!I["".concat(h[0],"-").concat(e)]&&a.utils&&a.utils.hasLoadedNamespace&&!a.utils.hasLoadedNamespace(i)&&(I["".concat(h[0],"-").concat(e)]=!0,a.logger.warn('key "'.concat(n,'" for languages "').concat(h.join(", "),'" won\'t get resolved as namespace "').concat(i,'" was not yet loaded'),"This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!")),h.forEach(function(n){if(!a.isValidLookup(t)){o=n;var i,u=[c];if(a.i18nFormat&&a.i18nFormat.addLookupKeys)a.i18nFormat.addLookupKeys(u,c,n,e,s);else{var l;f&&(l=a.pluralResolver.getSuffix(n,s.count,s));if(f&&(u.push(c+l),p&&u.push(c+"_zero")),g){var h="".concat(c).concat(a.options.contextSeparator).concat(s.context);u.push(h),f&&(u.push(h+l),p&&u.push(h+"_zero"))}}for(;i=u.pop();)a.isValidLookup(t)||(r=i,t=a.getResource(n,e,i,s))}}))})}}),{res:t,usedKey:n,exactUsedKey:r,usedLng:o,usedNS:i}}},{key:"isValidLookup",value:function(e){return!(void 0===e||!this.options.returnNull&&null===e||!this.options.returnEmptyString&&""===e)}},{key:"getResource",value:function(e,t,n){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};return this.i18nFormat&&this.i18nFormat.getResource?this.i18nFormat.getResource(e,t,n,r):this.resourceStore.getResource(e,t,n,r)}}],[{key:"hasDefaultValue",value:function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t)&&"defaultValue"===t.substring(0,"defaultValue".length)&&void 0!==e[t])return!0;return!1}}]),s}();function T(e){return e.charAt(0).toUpperCase()+e.slice(1)}var V=function(){function e(n){t(this,e),this.options=n,this.supportedLngs=this.options.supportedLngs||!1,this.logger=g.create("languageUtils")}return r(e,[{key:"getScriptPartFromCode",value:function(e){if(!e||e.indexOf("-")<0)return null;var t=e.split("-");return 2===t.length?null:(t.pop(),"x"===t[t.length-1].toLowerCase()?null:this.formatLanguageCode(t.join("-")))}},{key:"getLanguagePartFromCode",value:function(e){if(!e||e.indexOf("-")<0)return e;var t=e.split("-");return this.formatLanguageCode(t[0])}},{key:"formatLanguageCode",value:function(e){if("string"==typeof e&&e.indexOf("-")>-1){var t=["hans","hant","latn","cyrl","cans","mong","arab"],n=e.split("-");return this.options.lowerCaseLng?n=n.map(function(e){return e.toLowerCase()}):2===n.length?(n[0]=n[0].toLowerCase(),n[1]=n[1].toUpperCase(),t.indexOf(n[1].toLowerCase())>-1&&(n[1]=T(n[1].toLowerCase()))):3===n.length&&(n[0]=n[0].toLowerCase(),2===n[1].length&&(n[1]=n[1].toUpperCase()),"sgn"!==n[0]&&2===n[2].length&&(n[2]=n[2].toUpperCase()),t.indexOf(n[1].toLowerCase())>-1&&(n[1]=T(n[1].toLowerCase())),t.indexOf(n[2].toLowerCase())>-1&&(n[2]=T(n[2].toLowerCase()))),n.join("-")}return this.options.cleanCode||this.options.lowerCaseLng?e.toLowerCase():e}},{key:"isSupportedCode",value:function(e){return("languageOnly"===this.options.load||this.options.nonExplicitSupportedLngs)&&(e=this.getLanguagePartFromCode(e)),!this.supportedLngs||!this.supportedLngs.length||this.supportedLngs.indexOf(e)>-1}},{key:"getBestMatchFromCodes",value:function(e){var t,n=this;return e?(e.forEach(function(e){if(!t){var r=n.formatLanguageCode(e);n.options.supportedLngs&&!n.isSupportedCode(r)||(t=r)}}),!t&&this.options.supportedLngs&&e.forEach(function(e){if(!t){var r=n.getLanguagePartFromCode(e);if(n.isSupportedCode(r))return t=r;t=n.options.supportedLngs.find(function(e){if(0===e.indexOf(r))return e})}}),t||(t=this.getFallbackCodes(this.options.fallbackLng)[0]),t):null}},{key:"getFallbackCodes",value:function(e,t){if(!e)return[];if("function"==typeof e&&(e=e(t)),"string"==typeof e&&(e=[e]),"[object Array]"===Object.prototype.toString.apply(e))return e;if(!t)return e.default||[];var n=e[t];return n||(n=e[this.getScriptPartFromCode(t)]),n||(n=e[this.formatLanguageCode(t)]),n||(n=e[this.getLanguagePartFromCode(t)]),n||(n=e.default),n||[]}},{key:"toResolveHierarchy",value:function(e,t){var n=this,r=this.getFallbackCodes(t||this.options.fallbackLng||[],e),o=[],i=function(e){e&&(n.isSupportedCode(e)?o.push(e):n.logger.warn("rejecting language code not found in supportedLngs: ".concat(e)))};return"string"==typeof e&&e.indexOf("-")>-1?("languageOnly"!==this.options.load&&i(this.formatLanguageCode(e)),"languageOnly"!==this.options.load&&"currentOnly"!==this.options.load&&i(this.getScriptPartFromCode(e)),"currentOnly"!==this.options.load&&i(this.getLanguagePartFromCode(e))):"string"==typeof e&&i(this.formatLanguageCode(e)),r.forEach(function(e){o.indexOf(e)<0&&i(n.formatLanguageCode(e))}),o}}]),e}(),U=[{lngs:["ach","ak","am","arn","br","fil","gun","ln","mfe","mg","mi","oc","pt","pt-BR","tg","tl","ti","tr","uz","wa"],nr:[1,2],fc:1},{lngs:["af","an","ast","az","bg","bn","ca","da","de","dev","el","en","eo","es","et","eu","fi","fo","fur","fy","gl","gu","ha","hi","hu","hy","ia","it","kk","kn","ku","lb","mai","ml","mn","mr","nah","nap","nb","ne","nl","nn","no","nso","pa","pap","pms","ps","pt-PT","rm","sco","se","si","so","son","sq","sv","sw","ta","te","tk","ur","yo"],nr:[1,2],fc:2},{lngs:["ay","bo","cgg","fa","ht","id","ja","jbo","ka","km","ko","ky","lo","ms","sah","su","th","tt","ug","vi","wo","zh"],nr:[1],fc:3},{lngs:["be","bs","cnr","dz","hr","ru","sr","uk"],nr:[1,2,5],fc:4},{lngs:["ar"],nr:[0,1,2,3,11,100],fc:5},{lngs:["cs","sk"],nr:[1,2,5],fc:6},{lngs:["csb","pl"],nr:[1,2,5],fc:7},{lngs:["cy"],nr:[1,2,3,8],fc:8},{lngs:["fr"],nr:[1,2],fc:9},{lngs:["ga"],nr:[1,2,3,7,11],fc:10},{lngs:["gd"],nr:[1,2,3,20],fc:11},{lngs:["is"],nr:[1,2],fc:12},{lngs:["jv"],nr:[0,1],fc:13},{lngs:["kw"],nr:[1,2,3,4],fc:14},{lngs:["lt"],nr:[1,2,10],fc:15},{lngs:["lv"],nr:[1,2,0],fc:16},{lngs:["mk"],nr:[1,2],fc:17},{lngs:["mnk"],nr:[0,1,2],fc:18},{lngs:["mt"],nr:[1,2,11,20],fc:19},{lngs:["or"],nr:[2,1],fc:2},{lngs:["ro"],nr:[1,2,20],fc:20},{lngs:["sl"],nr:[5,1,2,3],fc:21},{lngs:["he","iw"],nr:[1,2,20,21],fc:22}],K={1:function(e){return Number(e>1)},2:function(e){return Number(1!=e)},3:function(e){return 0},4:function(e){return Number(e%10==1&&e%100!=11?0:e%10>=2&&e%10<=4&&(e%100<10||e%100>=20)?1:2)},5:function(e){return Number(0==e?0:1==e?1:2==e?2:e%100>=3&&e%100<=10?3:e%100>=11?4:5)},6:function(e){return Number(1==e?0:e>=2&&e<=4?1:2)},7:function(e){return Number(1==e?0:e%10>=2&&e%10<=4&&(e%100<10||e%100>=20)?1:2)},8:function(e){return Number(1==e?0:2==e?1:8!=e&&11!=e?2:3)},9:function(e){return Number(e>=2)},10:function(e){return Number(1==e?0:2==e?1:e<7?2:e<11?3:4)},11:function(e){return Number(1==e||11==e?0:2==e||12==e?1:e>2&&e<20?2:3)},12:function(e){return Number(e%10!=1||e%100==11)},13:function(e){return Number(0!==e)},14:function(e){return Number(1==e?0:2==e?1:3==e?2:3)},15:function(e){return Number(e%10==1&&e%100!=11?0:e%10>=2&&(e%100<10||e%100>=20)?1:2)},16:function(e){return Number(e%10==1&&e%100!=11?0:0!==e?1:2)},17:function(e){return Number(1==e||e%10==1&&e%100!=11?0:1)},18:function(e){return Number(0==e?0:1==e?1:2)},19:function(e){return Number(1==e?0:0==e||e%100>1&&e%100<11?1:e%100>10&&e%100<20?2:3)},20:function(e){return Number(1==e?0:0==e||e%100>0&&e%100<20?1:2)},21:function(e){return Number(e%100==1?1:e%100==2?2:e%100==3||e%100==4?3:0)},22:function(e){return Number(1==e?0:2==e?1:(e<0||e>10)&&e%10==0?2:3)}},B=["v1","v2","v3"],M={zero:0,one:1,two:2,few:3,many:4,other:5};var H=function(){function e(n){var r,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};t(this,e),this.languageUtils=n,this.options=o,this.logger=g.create("pluralResolver"),this.options.compatibilityJSON&&"v4"!==this.options.compatibilityJSON||"undefined"!=typeof Intl&&Intl.PluralRules||(this.options.compatibilityJSON="v3",this.logger.error("Your environment seems not to be Intl API compatible, use an Intl.PluralRules polyfill. Will fallback to the compatibilityJSON v3 format handling.")),this.rules=(r={},U.forEach(function(e){e.lngs.forEach(function(t){r[t]={numbers:e.nr,plurals:K[e.fc]}})}),r)}return r(e,[{key:"addRule",value:function(e,t){this.rules[e]=t}},{key:"getRule",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(this.shouldUseIntlApi())try{return new Intl.PluralRules(e,{type:t.ordinal?"ordinal":"cardinal"})}catch(e){return}return this.rules[e]||this.rules[this.languageUtils.getLanguagePartFromCode(e)]}},{key:"needsPlural",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=this.getRule(e,t);return this.shouldUseIntlApi()?n&&n.resolvedOptions().pluralCategories.length>1:n&&n.numbers.length>1}},{key:"getPluralFormsOfKey",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return this.getSuffixes(e,n).map(function(e){return"".concat(t).concat(e)})}},{key:"getSuffixes",value:function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=this.getRule(e,n);return r?this.shouldUseIntlApi()?r.resolvedOptions().pluralCategories.sort(function(e,t){return M[e]-M[t]}).map(function(e){return"".concat(t.options.prepend).concat(e)}):r.numbers.map(function(r){return t.getSuffix(e,r,n)}):[]}},{key:"getSuffix",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=this.getRule(e,n);return r?this.shouldUseIntlApi()?"".concat(this.options.prepend).concat(r.select(t)):this.getSuffixRetroCompatible(r,t):(this.logger.warn("no plural rule found for: ".concat(e)),"")}},{key:"getSuffixRetroCompatible",value:function(e,t){var n=this,r=e.noAbs?e.plurals(t):e.plurals(Math.abs(t)),o=e.numbers[r];this.options.simplifyPluralSuffix&&2===e.numbers.length&&1===e.numbers[0]&&(2===o?o="plural":1===o&&(o=""));var i=function(){return n.options.prepend&&o.toString()?n.options.prepend+o.toString():o.toString()};return"v1"===this.options.compatibilityJSON?1===o?"":"number"==typeof o?"_plural_".concat(o.toString()):i():"v2"===this.options.compatibilityJSON?i():this.options.simplifyPluralSuffix&&2===e.numbers.length&&1===e.numbers[0]?i():this.options.prepend&&r.toString()?this.options.prepend+r.toString():r.toString()}},{key:"shouldUseIntlApi",value:function(){return!B.includes(this.options.compatibilityJSON)}}]),e}();function z(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function J(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:{};t(this,e),this.logger=g.create("interpolator"),this.options=n,this.format=n.interpolation&&n.interpolation.format||function(e){return e},this.init(n)}return r(e,[{key:"init",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};e.interpolation||(e.interpolation={escapeValue:!0});var t=e.interpolation;this.escape=void 0!==t.escape?t.escape:x,this.escapeValue=void 0===t.escapeValue||t.escapeValue,this.useRawValueToEscape=void 0!==t.useRawValueToEscape&&t.useRawValueToEscape,this.prefix=t.prefix?k(t.prefix):t.prefixEscaped||"{{",this.suffix=t.suffix?k(t.suffix):t.suffixEscaped||"}}",this.formatSeparator=t.formatSeparator?t.formatSeparator:t.formatSeparator||",",this.unescapePrefix=t.unescapeSuffix?"":t.unescapePrefix||"-",this.unescapeSuffix=this.unescapePrefix?"":t.unescapeSuffix||"",this.nestingPrefix=t.nestingPrefix?k(t.nestingPrefix):t.nestingPrefixEscaped||k("$t("),this.nestingSuffix=t.nestingSuffix?k(t.nestingSuffix):t.nestingSuffixEscaped||k(")"),this.nestingOptionsSeparator=t.nestingOptionsSeparator?t.nestingOptionsSeparator:t.nestingOptionsSeparator||",",this.maxReplaces=t.maxReplaces?t.maxReplaces:1e3,this.alwaysFormat=void 0!==t.alwaysFormat&&t.alwaysFormat,this.resetRegExp()}},{key:"reset",value:function(){this.options&&this.init(this.options)}},{key:"resetRegExp",value:function(){var e="".concat(this.prefix,"(.+?)").concat(this.suffix);this.regexp=new RegExp(e,"g");var t="".concat(this.prefix).concat(this.unescapePrefix,"(.+?)").concat(this.unescapeSuffix).concat(this.suffix);this.regexpUnescape=new RegExp(t,"g");var n="".concat(this.nestingPrefix,"(.+?)").concat(this.nestingSuffix);this.nestingRegexp=new RegExp(n,"g")}},{key:"interpolate",value:function(e,t,n,r){var o,i,a,s=this,u=this.options&&this.options.interpolation&&this.options.interpolation.defaultVariables||{};function c(e){return e.replace(/\$/g,"$$$$")}var l=function(e){if(e.indexOf(s.formatSeparator)<0){var o=O(t,u,e);return s.alwaysFormat?s.format(o,void 0,n,J(J(J({},r),t),{},{interpolationkey:e})):o}var i=e.split(s.formatSeparator),a=i.shift().trim(),c=i.join(s.formatSeparator).trim();return s.format(O(t,u,a),c,n,J(J(J({},r),t),{},{interpolationkey:a}))};this.resetRegExp();var f=r&&r.missingInterpolationHandler||this.options.missingInterpolationHandler,p=r&&r.interpolation&&void 0!==r.interpolation.skipOnVariables?r.interpolation.skipOnVariables:this.options.interpolation.skipOnVariables;return[{regex:this.regexpUnescape,safeValue:function(e){return c(e)}},{regex:this.regexp,safeValue:function(e){return s.escapeValue?c(s.escape(e)):c(e)}}].forEach(function(t){for(a=0;o=t.regex.exec(e);){var n=o[1].trim();if(void 0===(i=l(n)))if("function"==typeof f){var u=f(e,o,r);i="string"==typeof u?u:""}else if(r&&r.hasOwnProperty(n))i="";else{if(p){i=o[0];continue}s.logger.warn("missed to pass in variable ".concat(n," for interpolating ").concat(e)),i=""}else"string"==typeof i||s.useRawValueToEscape||(i=v(i));var c=t.safeValue(i);if(e=e.replace(o[0],c),p?(t.regex.lastIndex+=i.length,t.regex.lastIndex-=o[0].length):t.regex.lastIndex=0,++a>=s.maxReplaces)break}}),e}},{key:"nest",value:function(e,t){var n,r,o=this,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},a=J({},i);function s(e,t){var n=this.nestingOptionsSeparator;if(e.indexOf(n)<0)return e;var r=e.split(new RegExp("".concat(n,"[ ]*{"))),o="{".concat(r[1]);e=r[0],o=(o=this.interpolate(o,a)).replace(/'/g,'"');try{a=JSON.parse(o),t&&(a=J(J({},t),a))}catch(t){return this.logger.warn("failed parsing options string in nesting for key ".concat(e),t),"".concat(e).concat(n).concat(o)}return delete a.defaultValue,e}for(a.applyPostProcessor=!1,delete a.defaultValue;n=this.nestingRegexp.exec(e);){var u=[],c=!1;if(-1!==n[0].indexOf(this.formatSeparator)&&!/{.*}/.test(n[1])){var l=n[1].split(this.formatSeparator).map(function(e){return e.trim()});n[1]=l.shift(),u=l,c=!0}if((r=t(s.call(this,n[1].trim(),a),a))&&n[0]===e&&"string"!=typeof r)return r;"string"!=typeof r&&(r=v(r)),r||(this.logger.warn("missed to resolve ".concat(n[1]," for nesting ").concat(e)),r=""),c&&(r=u.reduce(function(e,t){return o.format(e,t,i.lng,J(J({},i),{},{interpolationkey:n[1].trim()}))},r.trim())),e=e.replace(n[0],r),this.regexp.lastIndex=0}return e}}]),e}();function q(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&void 0!==arguments[0]?arguments[0]:{};t(this,e),this.logger=g.create("formatter"),this.options=n,this.formats={number:function(e,t,n){return new Intl.NumberFormat(t,n).format(e)},currency:function(e,t,n){return new Intl.NumberFormat(t,Y(Y({},n),{},{style:"currency"})).format(e)},datetime:function(e,t,n){return new Intl.DateTimeFormat(t,Y({},n)).format(e)},relativetime:function(e,t,n){return new Intl.RelativeTimeFormat(t,Y({},n)).format(e,n.range||"day")},list:function(e,t,n){return new Intl.ListFormat(t,Y({},n)).format(e)}},this.init(n)}return r(e,[{key:"init",value:function(e){var t=(arguments.length>1&&void 0!==arguments[1]?arguments[1]:{interpolation:{}}).interpolation;this.formatSeparator=t.formatSeparator?t.formatSeparator:t.formatSeparator||","}},{key:"add",value:function(e,t){this.formats[e.toLowerCase().trim()]=t}},{key:"format",value:function(e,t,n,r){var o=this;return t.split(this.formatSeparator).reduce(function(e,t){var i=function(e){var t=e.toLowerCase().trim(),n={};if(e.indexOf("(")>-1){var r=e.split("(");t=r[0].toLowerCase().trim();var o=r[1].substring(0,r[1].length-1);"currency"===t&&o.indexOf(":")<0?n.currency||(n.currency=o.trim()):"relativetime"===t&&o.indexOf(":")<0?n.range||(n.range=o.trim()):o.split(";").forEach(function(e){if(e){var t=$(e.split(":")),r=t[0],o=t.slice(1).join(":").trim().replace(/^'+|'+$/g,"");n[r.trim()]||(n[r.trim()]=o),"false"===o&&(n[r.trim()]=!1),"true"===o&&(n[r.trim()]=!0),isNaN(o)||(n[r.trim()]=parseInt(o,10))}})}return{formatName:t,formatOptions:n}}(t),a=i.formatName,s=i.formatOptions;if(o.formats[a]){var u=e;try{var c=r&&r.formatParams&&r.formatParams[r.interpolationkey]||{},l=c.locale||c.lng||r.locale||r.lng||n;u=o.formats[a](e,l,Y(Y(Y({},s),r),c))}catch(e){o.logger.warn(e)}return u}return o.logger.warn("there was no format function for ".concat(a)),e},e)}}]),e}();function Q(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function X(e){for(var t=1;t3&&void 0!==arguments[3]?arguments[3]:{};return t(this,i),s=n.call(this),S&&h.call(o(s)),s.backend=e,s.store=r,s.services=a,s.languageUtils=a.languageUtils,s.options=u,s.logger=g.create("backendConnector"),s.waitingReads=[],s.maxParallelReads=u.maxParallelReads||10,s.readingCalls=0,s.state={},s.queue=[],s.backend&&s.backend.init&&s.backend.init(a,u.backend,u),s}return r(i,[{key:"queueLoad",value:function(e,t,n,r){var o=this,i={},a={},s={},u={};return e.forEach(function(e){var r=!0;t.forEach(function(t){var s="".concat(e,"|").concat(t);!n.reload&&o.store.hasResourceBundle(e,t)?o.state[s]=2:o.state[s]<0||(1===o.state[s]?void 0===a[s]&&(a[s]=!0):(o.state[s]=1,r=!1,void 0===a[s]&&(a[s]=!0),void 0===i[s]&&(i[s]=!0),void 0===u[t]&&(u[t]=!0)))}),r||(s[e]=!0)}),(Object.keys(i).length||Object.keys(a).length)&&this.queue.push({pending:a,pendingCount:Object.keys(a).length,loaded:{},errors:[],callback:r}),{toLoad:Object.keys(i),pending:Object.keys(a),toLoadLanguages:Object.keys(s),toLoadNamespaces:Object.keys(u)}}},{key:"loaded",value:function(e,t,n){var r=e.split("|"),o=r[0],i=r[1];t&&this.emit("failedLoading",o,i,t),n&&this.store.addResourceBundle(o,i,n),this.state[e]=t?-1:2;var a={};this.queue.forEach(function(n){var r,s,u,c,l,f;r=n.loaded,s=i,c=y(r,[o],Object),l=c.obj,f=c.k,l[f]=l[f]||[],u&&(l[f]=l[f].concat(s)),u||l[f].push(s),function(e,t){void 0!==e.pending[t]&&(delete e.pending[t],e.pendingCount--)}(n,e),t&&n.errors.push(t),0!==n.pendingCount||n.done||(Object.keys(n.loaded).forEach(function(e){a[e]||(a[e]={});var t=n.loaded[e];t.length&&t.forEach(function(t){void 0===a[e][t]&&(a[e][t]=!0)})}),n.done=!0,n.errors.length?n.callback(n.errors):n.callback())}),this.emit("loaded",a),this.queue=this.queue.filter(function(e){return!e.done})}},{key:"read",value:function(e,t,n){var r=this,o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0,i=arguments.length>4&&void 0!==arguments[4]?arguments[4]:350,a=arguments.length>5?arguments[5]:void 0;return e.length?this.readingCalls>=this.maxParallelReads?void this.waitingReads.push({lng:e,ns:t,fcName:n,tried:o,wait:i,callback:a}):(this.readingCalls++,this.backend[n](e,t,function(s,u){if(s&&u&&o<5)setTimeout(function(){r.read.call(r,e,t,n,o+1,2*i,a)},i);else{if(r.readingCalls--,r.waitingReads.length>0){var c=r.waitingReads.shift();r.read(c.lng,c.ns,c.fcName,c.tried,c.wait,c.callback)}a(s,u)}})):a(null,{})}},{key:"prepareLoading",value:function(e,t){var n=this,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},o=arguments.length>3?arguments[3]:void 0;if(!this.backend)return this.logger.warn("No backend was added via i18next.use. Will not load resources."),o&&o();"string"==typeof e&&(e=this.languageUtils.toResolveHierarchy(e)),"string"==typeof t&&(t=[t]);var i=this.queueLoad(e,t,r,o);if(!i.toLoad.length)return i.pending.length||o(),null;i.toLoad.forEach(function(e){n.loadOne(e)})}},{key:"load",value:function(e,t,n){this.prepareLoading(e,t,{},n)}},{key:"reload",value:function(e,t,n){this.prepareLoading(e,t,{reload:!0},n)}},{key:"loadOne",value:function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",r=e.split("|"),o=r[0],i=r[1];this.read(o,i,"read",void 0,void 0,function(r,a){r&&t.logger.warn("".concat(n,"loading namespace ").concat(i," for language ").concat(o," failed"),r),!r&&a&&t.logger.log("".concat(n,"loaded namespace ").concat(i," for language ").concat(o),a),t.loaded(e,r,a)})}},{key:"saveMissing",value:function(e,t,n,r,o){var i=arguments.length>5&&void 0!==arguments[5]?arguments[5]:{};this.services.utils&&this.services.utils.hasLoadedNamespace&&!this.services.utils.hasLoadedNamespace(t)?this.logger.warn('did not save key "'.concat(n,'" as the namespace "').concat(t,'" was not yet loaded'),"This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!"):null!=n&&""!==n&&(this.backend&&this.backend.create&&this.backend.create(e,t,n,r,null,X(X({},i),{},{isUpdate:o})),e&&e[0]&&this.store.addResource(e[0],t,n,r))}}]),i}();function te(e){return"string"==typeof e.ns&&(e.ns=[e.ns]),"string"==typeof e.fallbackLng&&(e.fallbackLng=[e.fallbackLng]),"string"==typeof e.fallbackNS&&(e.fallbackNS=[e.fallbackNS]),e.supportedLngs&&e.supportedLngs.indexOf("cimode")<0&&(e.supportedLngs=e.supportedLngs.concat(["cimode"])),e}function ne(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function re(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:{},a=arguments.length>1?arguments[1]:void 0;if(t(this,u),e=i.call(this),S&&h.call(o(e)),e.options=te(r),e.services={},e.logger=g,e.modules={external:[]},n=o(e),Object.getOwnPropertyNames(Object.getPrototypeOf(n)).forEach(function(e){"function"==typeof n[e]&&(n[e]=n[e].bind(n))}),a&&!e.isInitialized&&!r.isClone){if(!e.options.initImmediate)return e.init(r,a),s(e,o(e));setTimeout(function(){e.init(r,a)},0)}return e}return r(u,[{key:"init",value:function(){var t=this,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=arguments.length>1?arguments[1]:void 0;"function"==typeof n&&(r=n,n={}),!n.defaultNS&&n.ns&&("string"==typeof n.ns?n.defaultNS=n.ns:n.ns.indexOf("translation")<0&&(n.defaultNS=n.ns[0]));var o={debug:!1,initImmediate:!0,ns:["translation"],defaultNS:["translation"],fallbackLng:["dev"],fallbackNS:!1,supportedLngs:!1,nonExplicitSupportedLngs:!1,load:"all",preload:!1,simplifyPluralSuffix:!0,keySeparator:".",nsSeparator:":",pluralSeparator:"_",contextSeparator:"_",partialBundledLanguages:!1,saveMissing:!1,updateMissing:!1,saveMissingTo:"fallback",saveMissingPlurals:!0,missingKeyHandler:!1,missingInterpolationHandler:!1,postProcess:!1,postProcessPassResolved:!1,returnNull:!0,returnEmptyString:!0,returnObjects:!1,joinArrays:!1,returnedObjectHandler:!1,parseMissingKeyHandler:!1,appendNamespaceToMissingKey:!1,appendNamespaceToCIMode:!1,overloadTranslationOptionHandler:function(t){var n={};if("object"===e(t[1])&&(n=t[1]),"string"==typeof t[1]&&(n.defaultValue=t[1]),"string"==typeof t[2]&&(n.tDescription=t[2]),"object"===e(t[2])||"object"===e(t[3])){var r=t[3]||t[2];Object.keys(r).forEach(function(e){n[e]=r[e]})}return n},interpolation:{escapeValue:!0,format:function(e,t,n,r){return e},prefix:"{{",suffix:"}}",formatSeparator:",",unescapePrefix:"-",nestingPrefix:"$t(",nestingSuffix:")",nestingOptionsSeparator:",",maxReplaces:1e3,skipOnVariables:!0}};function i(e){return e?"function"==typeof e?new e:e:null}if(this.options=re(re(re({},o),this.options),te(n)),"v1"!==this.options.compatibilityAPI&&(this.options.interpolation=re(re({},o.interpolation),this.options.interpolation)),void 0!==n.keySeparator&&(this.options.userDefinedKeySeparator=n.keySeparator),void 0!==n.nsSeparator&&(this.options.userDefinedNsSeparator=n.nsSeparator),!this.options.isClone){var a;this.modules.logger?g.init(i(this.modules.logger),this.options):g.init(null,this.options),this.modules.formatter?a=this.modules.formatter:"undefined"!=typeof Intl&&(a=G);var s=new V(this.options);this.store=new N(this.options.resources,this.options);var u=this.services;u.logger=g,u.resourceStore=this.store,u.languageUtils=s,u.pluralResolver=new H(s,{prepend:this.options.pluralSeparator,compatibilityJSON:this.options.compatibilityJSON,simplifyPluralSuffix:this.options.simplifyPluralSuffix}),!a||this.options.interpolation.format&&this.options.interpolation.format!==o.interpolation.format||(u.formatter=i(a),u.formatter.init(u,this.options),this.options.interpolation.format=u.formatter.format.bind(u.formatter)),u.interpolator=new _(this.options),u.utils={hasLoadedNamespace:this.hasLoadedNamespace.bind(this)},u.backendConnector=new ee(i(this.modules.backend),u.resourceStore,u,this.options),u.backendConnector.on("*",function(e){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o1?n-1:0),o=1;o0&&"dev"!==c[0]&&(this.options.lng=c[0])}this.services.languageDetector||this.options.lng||this.logger.warn("init: no languageDetector is used and no lng is defined");["getResource","hasResourceBundle","getResourceBundle","getDataByLanguage"].forEach(function(e){t[e]=function(){var n;return(n=t.store)[e].apply(n,arguments)}});["addResource","addResources","addResourceBundle","removeResourceBundle"].forEach(function(e){t[e]=function(){var n;return(n=t.store)[e].apply(n,arguments),t}});var l=d(),f=function(){var e=function(e,n){t.isInitialized&&!t.initializedStoreOnce&&t.logger.warn("init: i18next is already initialized. You should call init just once!"),t.isInitialized=!0,t.options.isClone||t.logger.log("initialized",t.options),t.emit("initialized",t.options),l.resolve(n),r(e,n)};if(t.languages&&"v1"!==t.options.compatibilityAPI&&!t.isInitialized)return e(null,t.t.bind(t));t.changeLanguage(t.options.lng,e)};return this.options.resources||!this.options.initImmediate?f():setTimeout(f,0),l}},{key:"loadResources",value:function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:ie,r="string"==typeof e?e:this.language;if("function"==typeof e&&(n=e),!this.options.resources||this.options.partialBundledLanguages){if(r&&"cimode"===r.toLowerCase())return n();var o=[],i=function(e){e&&t.services.languageUtils.toResolveHierarchy(e).forEach(function(e){o.indexOf(e)<0&&o.push(e)})};if(r)i(r);else this.services.languageUtils.getFallbackCodes(this.options.fallbackLng).forEach(function(e){return i(e)});this.options.preload&&this.options.preload.forEach(function(e){return i(e)}),this.services.backendConnector.load(o,this.options.ns,function(e){e||t.resolvedLanguage||!t.language||t.setResolvedLanguage(t.language),n(e)})}else n(null)}},{key:"reloadResources",value:function(e,t,n){var r=d();return e||(e=this.languages),t||(t=this.options.ns),n||(n=ie),this.services.backendConnector.reload(e,t,function(e){r.resolve(),n(e)}),r}},{key:"use",value:function(e){if(!e)throw new Error("You are passing an undefined module! Please check the object you are passing to i18next.use()");if(!e.type)throw new Error("You are passing a wrong module! Please check the object you are passing to i18next.use()");return"backend"===e.type&&(this.modules.backend=e),("logger"===e.type||e.log&&e.warn&&e.error)&&(this.modules.logger=e),"languageDetector"===e.type&&(this.modules.languageDetector=e),"i18nFormat"===e.type&&(this.modules.i18nFormat=e),"postProcessor"===e.type&&C.addPostProcessor(e),"formatter"===e.type&&(this.modules.formatter=e),"3rdParty"===e.type&&this.modules.external.push(e),this}},{key:"setResolvedLanguage",value:function(e){if(e&&this.languages&&!(["cimode","dev"].indexOf(e)>-1))for(var t=0;t-1)&&this.store.hasLanguageSomeTranslations(n)){this.resolvedLanguage=n;break}}}},{key:"changeLanguage",value:function(e,t){var n=this;this.isLanguageChangingTo=e;var r=d();this.emit("languageChanging",e);var o=function(e){n.language=e,n.languages=n.services.languageUtils.toResolveHierarchy(e),n.resolvedLanguage=void 0,n.setResolvedLanguage(e)},i=function(i){e||i||!n.services.languageDetector||(i=[]);var a="string"==typeof i?i:n.services.languageUtils.getBestMatchFromCodes(i);a&&(n.language||o(a),n.translator.language||n.translator.changeLanguage(a),n.services.languageDetector&&n.services.languageDetector.cacheUserLanguage(a)),n.loadResources(a,function(e){!function(e,i){i?(o(i),n.translator.changeLanguage(i),n.isLanguageChangingTo=void 0,n.emit("languageChanged",i),n.logger.log("languageChanged",i)):n.isLanguageChangingTo=void 0,r.resolve(function(){return n.t.apply(n,arguments)}),t&&t(e,function(){return n.t.apply(n,arguments)})}(e,a)})};return e||!this.services.languageDetector||this.services.languageDetector.async?!e&&this.services.languageDetector&&this.services.languageDetector.async?this.services.languageDetector.detect(i):i(e):i(this.services.languageDetector.detect()),r}},{key:"getFixedT",value:function(t,n,r){var o=this,i=function t(n,i){var a;if("object"!==e(i)){for(var s=arguments.length,u=new Array(s>2?s-2:0),c=2;c1&&void 0!==arguments[1]?arguments[1]:{};if(!this.isInitialized)return this.logger.warn("hasLoadedNamespace: i18next was not initialized",this.languages),!1;if(!this.languages||!this.languages.length)return this.logger.warn("hasLoadedNamespace: i18n.languages were undefined or empty",this.languages),!1;var r=this.resolvedLanguage||this.languages[0],o=!!this.options&&this.options.fallbackLng,i=this.languages[this.languages.length-1];if("cimode"===r.toLowerCase())return!0;var a=function(e,n){var r=t.services.backendConnector.state["".concat(e,"|").concat(n)];return-1===r||2===r};if(n.precheck){var s=n.precheck(this,a);if(void 0!==s)return s}return!!this.hasResourceBundle(r,e)||(!(this.services.backendConnector.backend&&(!this.options.resources||this.options.partialBundledLanguages))||!(!a(r,e)||o&&!a(i,e)))}},{key:"loadNamespaces",value:function(e,t){var n=this,r=d();return this.options.ns?("string"==typeof e&&(e=[e]),e.forEach(function(e){n.options.ns.indexOf(e)<0&&n.options.ns.push(e)}),this.loadResources(function(e){r.resolve(),t&&t(e)}),r):(t&&t(),Promise.resolve())}},{key:"loadLanguages",value:function(e,t){var n=d();"string"==typeof e&&(e=[e]);var r=this.options.preload||[],o=e.filter(function(e){return r.indexOf(e)<0});return o.length?(this.options.preload=r.concat(o),this.loadResources(function(e){n.resolve(),t&&t(e)}),n):(t&&t(),Promise.resolve())}},{key:"dir",value:function(e){if(e||(e=this.resolvedLanguage||(this.languages&&this.languages.length>0?this.languages[0]:this.language)),!e)return"rtl";return["ar","shu","sqr","ssh","xaa","yhd","yud","aao","abh","abv","acm","acq","acw","acx","acy","adf","ads","aeb","aec","afb","ajp","apc","apd","arb","arq","ars","ary","arz","auz","avl","ayh","ayl","ayn","ayp","bbz","pga","he","iw","ps","pbt","pbu","pst","prp","prd","ug","ur","ydd","yds","yih","ji","yi","hbo","men","xmn","fa","jpr","peo","pes","prs","dv","sam","ckb"].indexOf(this.services.languageUtils.getLanguagePartFromCode(e))>-1||e.toLowerCase().indexOf("-arab")>1?"rtl":"ltr"}},{key:"cloneInstance",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:ie,r=re(re(re({},this.options),t),{isClone:!0}),o=new u(r);return["store","services","language"].forEach(function(t){o[t]=e[t]}),o.services=re({},this.services),o.services.utils={hasLoadedNamespace:o.hasLoadedNamespace.bind(o)},o.translator=new A(o.services,o.options),o.translator.on("*",function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1?arguments[1]:void 0;return new ae(e,t)});var se=ae.createInstance();return se.createInstance=ae.createInstance,se}); diff --git a/cps/static/js/libs/pwstrength/i18nextHttpBackend.min.js b/cps/static/js/libs/pwstrength/i18nextHttpBackend.min.js new file mode 100644 index 00000000..05c56c9d --- /dev/null +++ b/cps/static/js/libs/pwstrength/i18nextHttpBackend.min.js @@ -0,0 +1 @@ +!function(e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).i18nextHttpBackend=e()}(function(){return function o(i,r,a){function s(t,e){if(!r[t]){if(!i[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(u)return u(t,!0);throw(n=new Error("Cannot find module '"+t+"'")).code="MODULE_NOT_FOUND",n}n=r[t]={exports:{}},i[t][0].call(n.exports,function(e){return s(i[t][1][e]||e)},n,n.exports,o,i,r,a)}return r[t].exports}for(var u="function"==typeof require&&require,e=0;e options.common.maxChar) { + return score; + } + return lenScore; + }; + + validation.wordInvalidChar = function(options, word, score) { + if (options.common.invalidCharsRegExp.test(word)) { + return score; + } + return 0; + }; + + validation.wordMinLengthStaticScore = function(options, word, score) { + return word.length < options.common.minChar ? 0 : score; + }; + + validation.wordMaxLengthStaticScore = function(options, word, score) { + return word.length > options.common.maxChar ? 0 : score; + }; + + validation.wordSimilarToUsername = function(options, word, score) { + var username = $(options.common.usernameField).val(); + if ( + username && + word + .toLowerCase() + .match( + username + .replace(/[-[\]/{}()*+=?:.\\^$|!,]/g, '\\$&') + .toLowerCase() + ) + ) { + return score; + } + return 0; + }; + + validation.wordTwoCharacterClasses = function(options, word, score) { + var specialCharRE = new RegExp( + '(.' + options.rules.specialCharClass + ')' + ); + + 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_]/)) + ) { + return score; + } + return; + }; + + validation.wordRepetitions = function(options, word, score) { + if (word.match(/(.)\1\1/)) { + return score; + } + return 0; + }; + + validation.wordSequences = function(options, word, score) { + var found = false, + j; + + if (word.length > 2) { + $.each(rulesEngine.forbiddenSequences, function(idx, seq) { + var sequences; + if (found) { + return; + } + sequences = [ + seq, + seq + .split('') + .reverse() + .join('') + ]; + $.each(sequences, function(ignore, sequence) { + for (j = 0; j < word.length - 2; j += 1) { + // iterate the word trough a sliding window of size 3: + if ( + sequence.indexOf( + word.toLowerCase().substring(j, j + 3) + ) > -1 + ) { + found = true; + } + } + }); + }); + if (found) { + return score; + } + } + return 0; + }; + + validation.wordLowercase = function(options, word, score) { + return word.match(/[a-z]/) && score; + }; + + validation.wordUppercase = function(options, word, score) { + return word.match(/[A-Z]/) && score; + }; + + validation.wordOneNumber = function(options, word, score) { + return word.match(/\d+/) && score; + }; + + validation.wordThreeNumbers = function(options, word, score) { + return word.match(/(.*[0-9].*[0-9].*[0-9])/) && score; + }; + + validation.wordOneSpecialChar = function(options, word, score) { + var specialCharRE = new RegExp(options.rules.specialCharClass); + return word.match(specialCharRE) && score; + }; + + validation.wordTwoSpecialChar = function(options, word, score) { + var twoSpecialCharRE = new RegExp( + '(.*' + + options.rules.specialCharClass + + '.*' + + options.rules.specialCharClass + + ')' + ); + + return word.match(twoSpecialCharRE) && score; + }; + + validation.wordUpperLowerCombo = function(options, word, score) { + return word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) && score; + }; + + validation.wordLetterNumberCombo = function(options, word, score) { + return word.match(/([a-zA-Z])/) && word.match(/([0-9])/) && score; + }; + + validation.wordLetterNumberCharCombo = function(options, word, score) { + var letterNumberCharComboRE = new RegExp( + '([a-zA-Z0-9].*' + + options.rules.specialCharClass + + ')|(' + + options.rules.specialCharClass + + '.*[a-zA-Z0-9])' + ); + + return word.match(letterNumberCharComboRE) && score; + }; + + validation.wordIsACommonPassword = function(options, word, score) { + if ($.inArray(word, options.rules.commonPasswords) >= 0) { + return score; + } + return 0; + }; + + rulesEngine.validation = validation; + + rulesEngine.executeRules = function(options, word) { + var totalScore = 0; + + $.each(options.rules.activated, function(rule, active) { + var score, funct, result, errorMessage; + + if (active) { + score = options.rules.scores[rule]; + funct = rulesEngine.validation[rule]; + + if (typeof funct !== 'function') { + funct = options.rules.extra[rule]; + } + + if (typeof funct === 'function') { + result = funct(options, word, score); + if (result) { + totalScore += result; + } + if (result < 0 || (!$.isNumeric(result) && !result)) { + errorMessage = options.ui.spanError(options, rule); + if (errorMessage.length > 0) { + options.instances.errors.push(errorMessage); + } + } + } + } + }); + + return totalScore; + }; +})(jQuery); + +try { + if (module && module.exports) { + module.exports = rulesEngine; + } +} catch (ignore) { + // Nothing to do +} + +// Source: src/options.js + + + +// eslint-disable-next-line no-implicit-globals +var defaultOptions = {}; + +defaultOptions.common = {}; +defaultOptions.common.minChar = 6; +defaultOptions.common.maxChar = 20; +defaultOptions.common.usernameField = '#username'; +defaultOptions.common.invalidCharsRegExp = new RegExp(/[\s,'"]/); +defaultOptions.common.userInputs = [ + // Selectors for input fields with user input +]; +defaultOptions.common.onLoad = undefined; +defaultOptions.common.onKeyUp = undefined; +defaultOptions.common.onScore = undefined; +defaultOptions.common.zxcvbn = false; +defaultOptions.common.zxcvbnTerms = [ + // List of disrecommended words +]; +defaultOptions.common.events = ['keyup', 'change', 'paste']; +defaultOptions.common.debug = false; + +defaultOptions.rules = {}; +defaultOptions.rules.extra = {}; +defaultOptions.rules.scores = { + wordNotEmail: -100, + wordMinLength: -50, + wordMaxLength: -50, + wordInvalidChar: -100, + wordSimilarToUsername: -100, + wordSequences: -20, + wordTwoCharacterClasses: 2, + wordRepetitions: -25, + wordLowercase: 1, + wordUppercase: 3, + wordOneNumber: 3, + wordThreeNumbers: 5, + wordOneSpecialChar: 3, + wordTwoSpecialChar: 5, + wordUpperLowerCombo: 2, + wordLetterNumberCombo: 2, + wordLetterNumberCharCombo: 2, + wordIsACommonPassword: -100 +}; +defaultOptions.rules.activated = { + wordNotEmail: true, + wordMinLength: true, + wordMaxLength: false, + wordInvalidChar: false, + wordSimilarToUsername: true, + wordSequences: true, + wordTwoCharacterClasses: true, + wordRepetitions: true, + wordLowercase: true, + wordUppercase: true, + wordOneNumber: true, + wordThreeNumbers: true, + wordOneSpecialChar: true, + wordTwoSpecialChar: true, + wordUpperLowerCombo: true, + wordLetterNumberCombo: true, + wordLetterNumberCharCombo: true, + wordIsACommonPassword: true +}; +defaultOptions.rules.raisePower = 1.4; +defaultOptions.rules.specialCharClass = "(?=.*?[^A-Za-z\s0-9])"; //'[!,@,#,$,%,^,&,*,?,_,~]'; +// List taken from https://github.com/danielmiessler/SecLists (MIT License) +defaultOptions.rules.commonPasswords = [ + '123456', + 'password', + '12345678', + 'qwerty', + '123456789', + '12345', + '1234', + '111111', + '1234567', + 'dragon', + '123123', + 'baseball', + 'abc123', + 'football', + 'monkey', + 'letmein', + '696969', + 'shadow', + 'master', + '666666', + 'qwertyuiop', + '123321', + 'mustang', + '1234567890', + 'michael', + '654321', + 'pussy', + 'superman', + '1qaz2wsx', + '7777777', + 'fuckyou', + '121212', + '000000', + 'qazwsx', + '123qwe', + 'killer', + 'trustno1', + 'jordan', + 'jennifer', + 'zxcvbnm', + 'asdfgh', + 'hunter', + 'buster', + 'soccer', + 'harley', + 'batman', + 'andrew', + 'tigger', + 'sunshine', + 'iloveyou', + 'fuckme', + '2000', + 'charlie', + 'robert', + 'thomas', + 'hockey', + 'ranger', + 'daniel', + 'starwars', + 'klaster', + '112233', + 'george', + 'asshole', + 'computer', + 'michelle', + 'jessica', + 'pepper', + '1111', + 'zxcvbn', + '555555', + '11111111', + '131313', + 'freedom', + '777777', + 'pass', + 'fuck', + 'maggie', + '159753', + 'aaaaaa', + 'ginger', + 'princess', + 'joshua', + 'cheese', + 'amanda', + 'summer', + 'love', + 'ashley', + '6969', + 'nicole', + 'chelsea', + 'biteme', + 'matthew', + 'access', + 'yankees', + '987654321', + 'dallas', + 'austin', + 'thunder', + 'taylor', + 'matrix' +]; + +defaultOptions.ui = {}; +defaultOptions.ui.bootstrap2 = false; +defaultOptions.ui.bootstrap3 = false; +defaultOptions.ui.colorClasses = [ + 'danger', + 'danger', + 'danger', + 'warning', + 'warning', + 'success' +]; +defaultOptions.ui.showProgressBar = true; +defaultOptions.ui.progressBarEmptyPercentage = 1; +defaultOptions.ui.progressBarMinWidth = 1; +defaultOptions.ui.progressBarMinPercentage = 1; +defaultOptions.ui.progressExtraCssClasses = ''; +defaultOptions.ui.progressBarExtraCssClasses = ''; +defaultOptions.ui.showPopover = false; +defaultOptions.ui.popoverPlacement = 'bottom'; +defaultOptions.ui.showStatus = false; +defaultOptions.ui.spanError = function(options, key) { + 'use strict'; + var text = options.i18n.t(key); + if (!text) { + return ''; + } + return '' + text + ''; +}; +defaultOptions.ui.popoverError = function(options) { + 'use strict'; + var errors = options.instances.errors, + errorsTitle = options.i18n.t('errorList'), + message = + '
' + + errorsTitle + + '
    '; + + jQuery.each(errors, function(idx, err) { + message += '
  • ' + err + '
  • '; + }); + message += '
'; + return message; +}; +defaultOptions.ui.showVerdicts = true; +defaultOptions.ui.showVerdictsInsideProgressBar = false; +defaultOptions.ui.useVerdictCssClass = false; +defaultOptions.ui.showErrors = false; +defaultOptions.ui.showScore = false; +defaultOptions.ui.container = undefined; +defaultOptions.ui.viewports = { + progress: undefined, + verdict: undefined, + errors: undefined, + score: undefined +}; +defaultOptions.ui.scores = [0, 14, 26, 38, 50]; + +defaultOptions.i18n = {}; +defaultOptions.i18n.t = i18n.t; + +// Source: src/ui.js + +// eslint-disable-next-line no-implicit-globals +var ui = {}; + +(function($) { + 'use strict'; + + var statusClasses = ['error', 'warning', 'success'], + verdictKeys = [ + 'veryWeak', + 'weak', + 'normal', + 'medium', + 'strong', + 'veryStrong' + ]; + + ui.getContainer = function(options, $el) { + var $container; + + $container = $(options.ui.container); + if (!($container && $container.length === 1)) { + $container = $el.parent(); + } + return $container; + }; + + ui.findElement = function($container, viewport, cssSelector) { + if (viewport) { + return $container.find(viewport).find(cssSelector); + } + return $container.find(cssSelector); + }; + + ui.getUIElements = function(options, $el) { + var $container, result; + + if (options.instances.viewports) { + return options.instances.viewports; + } + + $container = ui.getContainer(options, $el); + + result = {}; + result.$progressbar = ui.findElement( + $container, + options.ui.viewports.progress, + 'div.progress' + ); + if (options.ui.showVerdictsInsideProgressBar) { + result.$verdict = result.$progressbar.find('span.password-verdict'); + } + + if (!options.ui.showPopover) { + if (!options.ui.showVerdictsInsideProgressBar) { + result.$verdict = ui.findElement( + $container, + options.ui.viewports.verdict, + 'span.password-verdict' + ); + } + result.$errors = ui.findElement( + $container, + options.ui.viewports.errors, + 'ul.error-list' + ); + } + result.$score = ui.findElement( + $container, + options.ui.viewports.score, + 'span.password-score' + ); + + options.instances.viewports = result; + return result; + }; + + ui.initHelper = function(options, $el, html, viewport) { + var $container = ui.getContainer(options, $el); + if (viewport) { + $container.find(viewport).append(html); + } else { + $(html).insertAfter($el); + } + }; + + ui.initVerdict = function(options, $el) { + ui.initHelper( + options, + $el, + '', + options.ui.viewports.verdict + ); + }; + + ui.initErrorList = function(options, $el) { + ui.initHelper( + options, + $el, + '
    ', + options.ui.viewports.errors + ); + }; + + ui.initScore = function(options, $el) { + ui.initHelper( + options, + $el, + '', + options.ui.viewports.score + ); + }; + + ui.initUI = function(options, $el) { + if (options.ui.showPopover) { + ui.initPopover(options, $el); + } else { + if (options.ui.showErrors) { + ui.initErrorList(options, $el); + } + if ( + options.ui.showVerdicts && + !options.ui.showVerdictsInsideProgressBar + ) { + ui.initVerdict(options, $el); + } + } + if (options.ui.showProgressBar) { + ui.initProgressBar(options, $el); + } + if (options.ui.showScore) { + ui.initScore(options, $el); + } + }; + + ui.updateVerdict = function(options, $el, cssClass, text) { + var $verdict = ui.getUIElements(options, $el).$verdict; + $verdict.removeClass(options.ui.colorClasses.join(' ')); + if (cssClass > -1) { + $verdict.addClass(options.ui.colorClasses[cssClass]); + } + if (options.ui.showVerdictsInsideProgressBar) { + $verdict.css('white-space', 'nowrap'); + } + $verdict.html(text); + }; + + ui.updateErrors = function(options, $el, remove) { + var $errors = ui.getUIElements(options, $el).$errors, + html = ''; + + if (!remove) { + $.each(options.instances.errors, function(idx, err) { + html += '
  • ' + err + '
  • '; + }); + } + $errors.html(html); + }; + + ui.updateScore = function(options, $el, score, remove) { + var $score = ui.getUIElements(options, $el).$score, + html = ''; + + if (!remove) { + html = score.toFixed(2); + } + $score.html(html); + }; + + ui.updateFieldStatus = function(options, $el, cssClass, remove) { + var $target = $el; + + if (options.ui.bootstrap2) { + $target = $el.parents('.control-group').first(); + } else if (options.ui.bootstrap3) { + $target = $el.parents('.form-group').first(); + } + + $.each(statusClasses, function(idx, css) { + css = ui.cssClassesForBS(options, css); + $target.removeClass(css); + }); + + if (remove) { + return; + } + + cssClass = statusClasses[Math.floor(cssClass / 2)]; + cssClass = ui.cssClassesForBS(options, cssClass); + $target.addClass(cssClass); + }; + + ui.cssClassesForBS = function(options, css) { + if (options.ui.bootstrap3) { + css = 'has-' + css; + } else if (!options.ui.bootstrap2) { + // BS4 + if (css === 'error') { + css = 'danger'; + } + css = 'border-' + css; + } + return css; + }; + + ui.getVerdictAndCssClass = function(options, score) { + var level, verdict; + + if (score === undefined) { + return ['', 0]; + } + + if (score <= options.ui.scores[0]) { + level = 0; + } else if (score < options.ui.scores[1]) { + level = 1; + } else if (score < options.ui.scores[2]) { + level = 2; + } else if (score < options.ui.scores[3]) { + level = 3; + } else if (score < options.ui.scores[4]) { + level = 4; + } else { + level = 5; + } + + verdict = verdictKeys[level]; + + return [options.i18n.t(verdict), level]; + }; + + ui.updateUI = function(options, $el, score) { + var cssClass, verdictText, verdictCssClass; + + cssClass = ui.getVerdictAndCssClass(options, score); + verdictText = score === 0 ? '' : cssClass[0]; + cssClass = cssClass[1]; + verdictCssClass = options.ui.useVerdictCssClass ? cssClass : -1; + + if (options.ui.showProgressBar) { + ui.showProgressBar( + options, + $el, + score, + cssClass, + verdictCssClass, + verdictText + ); + } + + if (options.ui.showStatus) { + ui.updateFieldStatus(options, $el, cssClass, score === undefined); + } + + if (options.ui.showPopover) { + ui.updatePopover(options, $el, verdictText, score === undefined); + } else { + if ( + options.ui.showVerdicts && + !options.ui.showVerdictsInsideProgressBar + ) { + ui.updateVerdict(options, $el, verdictCssClass, verdictText); + } + if (options.ui.showErrors) { + ui.updateErrors(options, $el, score === undefined); + } + } + + if (options.ui.showScore) { + ui.updateScore(options, $el, score, score === undefined); + } + }; +})(jQuery); + +// Source: src/ui.progressbar.js + + + +(function($) { + 'use strict'; + + ui.percentage = function(options, score, maximun) { + var result = Math.floor((100 * score) / maximun), + min = options.ui.progressBarMinPercentage; + + result = result <= min ? min : result; + result = result > 100 ? 100 : result; + return result; + }; + + ui.initProgressBar = function(options, $el) { + var $container = ui.getContainer(options, $el), + progressbar = '
    '; + + if (options.ui.showVerdictsInsideProgressBar) { + progressbar += ''; + } + + progressbar += '
    '; + + if (options.ui.viewports.progress) { + $container.find(options.ui.viewports.progress).append(progressbar); + } else { + $(progressbar).insertAfter($el); + } + }; + + ui.showProgressBar = function( + options, + $el, + score, + cssClass, + verdictCssClass, + verdictText + ) { + var barPercentage; + + if (score === undefined) { + barPercentage = options.ui.progressBarEmptyPercentage; + } else { + barPercentage = ui.percentage(options, score, options.ui.scores[4]); + } + ui.updateProgressBar(options, $el, cssClass, barPercentage); + if (options.ui.showVerdictsInsideProgressBar) { + ui.updateVerdict(options, $el, verdictCssClass, verdictText); + } + }; + + ui.updateProgressBar = function(options, $el, cssClass, percentage) { + var $progressbar = ui.getUIElements(options, $el).$progressbar, + $bar = $progressbar.find('.progress-bar'), + cssPrefix = 'progress-'; + + if (options.ui.bootstrap2) { + $bar = $progressbar.find('.bar'); + cssPrefix = ''; + } + + $.each(options.ui.colorClasses, function(idx, value) { + if (options.ui.bootstrap2 || options.ui.bootstrap3) { + $bar.removeClass(cssPrefix + 'bar-' + value); + } else { + $bar.removeClass('bg-' + value); + } + }); + if (options.ui.bootstrap2 || options.ui.bootstrap3) { + $bar.addClass( + cssPrefix + 'bar-' + options.ui.colorClasses[cssClass] + ); + } else { + $bar.addClass('bg-' + options.ui.colorClasses[cssClass]); + } + if (percentage > 0) { + $bar.css('min-width', options.ui.progressBarMinWidth + 'px'); + } else { + $bar.css('min-width', ''); + } + $bar.css('width', percentage + '%'); + }; +})(jQuery); + +// Source: src/ui.popover.js + + + +(function() { + 'use strict'; + + ui.initPopover = function(options, $el) { + try { + $el.popover('destroy'); + } catch (error) { + // Bootstrap 4.2.X onwards + $el.popover('dispose'); + } + $el.popover({ + html: true, + placement: options.ui.popoverPlacement, + trigger: 'manual', + content: ' ' + }); + }; + + ui.updatePopover = function(options, $el, verdictText, remove) { + var popover = $el.data('bs.popover'), + html = '', + hide = true, + bootstrap5 = false, + itsVisible = false; + + if ( + options.ui.showVerdicts && + !options.ui.showVerdictsInsideProgressBar && + verdictText.length > 0 + ) { + html = + '
    ' + + verdictText + + '
    '; + hide = false; + } + if (options.ui.showErrors) { + if (options.instances.errors.length > 0) { + hide = false; + } + html += options.ui.popoverError(options); + } + + if (hide || remove) { + $el.popover('hide'); + return; + } + + if (options.ui.bootstrap2) { + popover = $el.data('popover'); + } else if (!popover) { + // Bootstrap 5 + popover = bootstrap.Popover.getInstance($el[0]); + bootstrap5 = true; + } + + if (bootstrap5) { + itsVisible = $(popover.tip).is(':visible'); + } else { + itsVisible = popover.$arrow && popover.$arrow.parents('body').length > 0; + } + + if (itsVisible) { + if (bootstrap5) { + $(popover.tip).find('.popover-body').html(html); + } else { + $el.find('+ .popover .popover-content').html(html); + } + } else { + // It's hidden + if (options.ui.bootstrap2 || options.ui.bootstrap3) { + popover.options.content = html; + } else if (bootstrap5) { + popover._config.content = html; + } else { + popover.config.content = html; + } + $el.popover('show'); + } + }; +})(); + +// Source: src/methods.js + + + +// eslint-disable-next-line no-implicit-globals +var methods = {}; + +(function($) { + 'use strict'; + var onKeyUp, onPaste, applyToAll; + + onKeyUp = function(event) { + var $el = $(event.target), + options = $el.data('pwstrength-bootstrap'), + word = $el.val(), + userInputs, + verdictText, + verdictLevel, + score; + + if (options === undefined) { + return; + } + + options.instances.errors = []; + if (word.length === 0) { + score = undefined; + } else { + if (options.common.zxcvbn) { + userInputs = []; + $.each( + options.common.userInputs.concat([ + options.common.usernameField + ]), + function(idx, selector) { + var value = $(selector).val(); + if (value) { + userInputs.push(value); + } + } + ); + userInputs = userInputs.concat(options.common.zxcvbnTerms); + score = zxcvbn(word, userInputs).guesses; + score = Math.log(score) * Math.LOG2E; + } else { + score = rulesEngine.executeRules(options, word); + } + if (typeof options.common.onScore === 'function') { + score = options.common.onScore(options, word, score); + } + } + ui.updateUI(options, $el, score); + verdictText = ui.getVerdictAndCssClass(options, score); + verdictLevel = verdictText[1]; + verdictText = verdictText[0]; + + if (options.common.debug) { + console.log(score + ' - ' + verdictText); + } + + if (typeof options.common.onKeyUp === 'function') { + options.common.onKeyUp(event, { + score: score, + verdictText: verdictText, + verdictLevel: verdictLevel + }); + } + }; + + onPaste = function(event) { + // This handler is necessary because the paste event fires before the + // content is actually in the input, so we cannot read its value right + // away. Therefore, the timeouts. + var $el = $(event.target), + word = $el.val(), + tries = 0, + callback; + + callback = function() { + var newWord = $el.val(); + + if (newWord !== word) { + onKeyUp(event); + } else if (tries < 3) { + tries += 1; + setTimeout(callback, 100); + } + }; + + setTimeout(callback, 100); + }; + + methods.init = function(settings) { + this.each(function(idx, el) { + // Make it deep extend (first param) so it extends also the + // rules and other inside objects + var clonedDefaults = $.extend(true, {}, defaultOptions), + localOptions = $.extend(true, clonedDefaults, settings), + $el = $(el); + + localOptions.instances = {}; + $el.data('pwstrength-bootstrap', localOptions); + + $.each(localOptions.common.events, function(ignore, eventName) { + var handler = eventName === 'paste' ? onPaste : onKeyUp; + $el.on(eventName, handler); + }); + + ui.initUI(localOptions, $el); + $el.trigger('keyup'); + + if (typeof localOptions.common.onLoad === 'function') { + localOptions.common.onLoad(); + } + }); + + return this; + }; + + methods.destroy = function() { + this.each(function(idx, el) { + var $el = $(el), + options = $el.data('pwstrength-bootstrap'), + elements = ui.getUIElements(options, $el); + elements.$progressbar.remove(); + elements.$verdict.remove(); + elements.$errors.remove(); + $el.removeData('pwstrength-bootstrap'); + }); + + return this; + }; + + methods.forceUpdate = function() { + this.each(function(idx, el) { + var event = { target: el }; + onKeyUp(event); + }); + + return this; + }; + + methods.addRule = function(name, method, score, active) { + this.each(function(idx, el) { + var options = $(el).data('pwstrength-bootstrap'); + + options.rules.activated[name] = active; + options.rules.scores[name] = score; + options.rules.extra[name] = method; + }); + + return this; + }; + + applyToAll = function(rule, prop, value) { + this.each(function(idx, el) { + $(el).data('pwstrength-bootstrap').rules[prop][rule] = value; + }); + }; + + methods.changeScore = function(rule, score) { + applyToAll.call(this, rule, 'scores', score); + + return this; + }; + + methods.ruleActive = function(rule, active) { + applyToAll.call(this, rule, 'activated', active); + + return this; + }; + + methods.ruleIsMet = function(rule) { + var rulesMetCnt = 0; + + if (rule === 'wordMinLength') { + rule = 'wordMinLengthStaticScore'; + } else if (rule === 'wordMaxLength') { + rule = 'wordMaxLengthStaticScore'; + } + + this.each(function(idx, el) { + var options = $(el).data('pwstrength-bootstrap'), + ruleFunction = rulesEngine.validation[rule], + result; + + if (typeof ruleFunction !== 'function') { + ruleFunction = options.rules.extra[rule]; + } + if (typeof ruleFunction === 'function') { + result = ruleFunction(options, $(el).val(), 1); + if ($.isNumeric(result)) { + rulesMetCnt += result; + } + } + }); + + return rulesMetCnt === this.length; + }; + + $.fn.pwstrength = function(method) { + var result; + + if (methods[method]) { + result = methods[method].apply( + this, + Array.prototype.slice.call(arguments, 1) + ); + } else if (typeof method === 'object' || !method) { + result = methods.init.apply(this, arguments); + } else { + $.error( + 'Method ' + + method + + ' does not exist on jQuery.pwstrength-bootstrap' + ); + } + + return result; + }; +})(jQuery); +}(jQuery)); diff --git a/cps/static/js/libs/pwstrength/pwstrength-bootstrap.min.js b/cps/static/js/libs/pwstrength/pwstrength-bootstrap.min.js new file mode 100644 index 00000000..f850afc9 --- /dev/null +++ b/cps/static/js/libs/pwstrength/pwstrength-bootstrap.min.js @@ -0,0 +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&&(0. + */ +$(document).ready(function() { + i18next.use(i18nextHttpBackend).init({ + lng: $('#password').data("lang"), + debug: false, + fallbackLng: 'en', + backend: { + loadPath: getPath() + "/static/js/libs/pwstrength/locales/{{lng}}.json", + }, + + }, function () { + if ($('#password').data("verify")) { + // Initialized and ready to go + var options = {}; + options.common = { + minChar: $('#password').data("min"), + maxChar: -1 + } + options.ui = { + bootstrap3: true, + showProgressBar: false, + showErrors: true, + showVerdicts: false, + } + options.rules= { + specialCharClass: "(?=.*?[^A-Za-z\\s0-9])", + activated: { + wordNotEmail: false, + wordMinLength: $('#password').data("min"), + // wordMaxLength: false, + // wordInvalidChar: true, + wordSimilarToUsername: false, + wordSequences: false, + wordTwoCharacterClasses: false, + wordRepetitions: false, + wordLowercase: $('#password').data("lower") === "True" ? true : false, + wordUppercase: $('#password').data("upper") === "True" ? true : false, + wordOneNumber: $('#password').data("number") === "True" ? true : false, + wordThreeNumbers: false, + wordOneSpecialChar: $('#password').data("special") === "True" ? true : false, + // wordTwoSpecialChar: true, + wordUpperLowerCombo: false, + wordLetterNumberCombo: false, + wordLetterNumberCharCombo: false + } + } + $('#password').pwstrength(options); + } + }); +}); diff --git a/cps/static/js/table.js b/cps/static/js/table.js index 33c22a88..833f1a13 100644 --- a/cps/static/js/table.js +++ b/cps/static/js/table.js @@ -634,7 +634,7 @@ function UserActions (value, row) { /* Function for cancelling tasks */ function TaskActions (value, row) { - var cancellableStats = [0, 1, 2]; + var cancellableStats = [0, 2]; if (row.task_id && row.is_cancellable && cancellableStats.includes(row.stat)) { return [ "
      ", diff --git a/cps/tasks/mail.py b/cps/tasks/mail.py index a7f0c2ec..0deb82a2 100755 --- a/cps/tasks/mail.py +++ b/cps/tasks/mail.py @@ -202,8 +202,8 @@ class TaskEmail(CalibreTask): self.asyncSMTP.set_debuglevel(1) if use_ssl == 1: self.asyncSMTP.starttls() - if self.settings["mail_password"]: - self.asyncSMTP.login(str(self.settings["mail_login"]), str(self.settings["mail_password"])) + if self.settings["mail_password_e"]: + self.asyncSMTP.login(str(self.settings["mail_login"]), str(self.settings["mail_password_e"])) # Convert message to something to send fp = StringIO() diff --git a/cps/tasks/metadata_backup.py b/cps/tasks/metadata_backup.py index 0bcdf6c1..b700b22a 100644 --- a/cps/tasks/metadata_backup.py +++ b/cps/tasks/metadata_backup.py @@ -22,8 +22,8 @@ from urllib.request import urlopen from lxml import etree from html import escape -from cps import config, db, fs, gdriveutils, logger, ub -from cps.services.worker import CalibreTask, STAT_CANCELLED, STAT_ENDED +from cps import config, db, gdriveutils, logger +from cps.services.worker import CalibreTask from flask_babel import lazy_gettext as N_ OPF_NAMESPACE = "http://www.idpf.org/2007/opf" @@ -74,7 +74,10 @@ class TaskBackupMetadata(CalibreTask): def backup_metadata(self): try: metadata_backup = self.calibre_db.session.query(db.Metadata_Dirtied).all() - custom_columns = self.calibre_db.session.query(db.CustomColumns).order_by(db.CustomColumns.label).all() + custom_columns = (self.calibre_db.session.query(db.CustomColumns) + .filter(db.CustomColumns.mark_for_delete == 0) + .filter(db.CustomColumns.datatype.notin_(db.cc_exceptions)) + .order_by(db.CustomColumns.label).all()) count = len(metadata_backup) i = 0 for backup in metadata_backup: @@ -149,7 +152,7 @@ class TaskBackupMetadata(CalibreTask): package.set("unique-identifier", "uuid_id") package.set("version", "2.0") - # generate metadata element and all subelements of it + # generate metadata element and all sub elements of it metadata = etree.SubElement(package, "metadata", nsmap=NSMAP) identifier = etree.SubElement(metadata, PURL + "identifier", id="calibre_id", nsmap=NSMAP) identifier.set(OPF + "scheme", "calibre") @@ -226,11 +229,11 @@ class TaskBackupMetadata(CalibreTask): # doc = etree.tostring(package, xml_declaration=True, encoding='utf-8', pretty_print=True) # .replace(b"&quot;", b""") try: with open(book_metadata_filepath, 'wb') as f: - # f.write(doc) doc.write(f, xml_declaration=True, encoding='utf-8', pretty_print=True) except Exception: # ToDo: Folder not writeable error pass + @property def name(self): return "Metadata backup" diff --git a/cps/templates/admin.html b/cps/templates/admin.html index 1199b21f..9460fa83 100644 --- a/cps/templates/admin.html +++ b/cps/templates/admin.html @@ -61,27 +61,27 @@

      {{_('Email Server Settings')}}

      {% if config.get_mail_server_configured() %} - {% if email.mail_server_type == 0 %} + {% if config.mail_server_type == 0 %}
      {{_('SMTP Hostname')}}
      -
      {{email.mail_server}}
      +
      {{config.mail_server}}
      {{_('SMTP Port')}}
      -
      {{email.mail_port}}
      +
      {{config.mail_port}}
      {{_('Encryption')}}
      -
      {{ display_bool_setting(email.mail_use_ssl) }}
      +
      {{ display_bool_setting(config.mail_use_ssl) }}
      {{_('SMTP Login')}}
      -
      {{email.mail_login}}
      +
      {{config.mail_login}}
      {{_('From Email')}}
      -
      {{email.mail_from}}
      +
      {{config.mail_from}}
      {% else %} @@ -92,7 +92,7 @@
      {{_('From Email')}}
      -
      {{email.mail_gmail_token['email']}}
      +
      {{config.mail_gmail_token['email']}}
      {% endif %} diff --git a/cps/templates/config_edit.html b/cps/templates/config_edit.html index 815fb162..265ceff3 100644 --- a/cps/templates/config_edit.html +++ b/cps/templates/config_edit.html @@ -159,8 +159,8 @@
      - - + +
      {% endif %} @@ -245,8 +245,8 @@
      - - + +
      @@ -353,6 +353,57 @@
      +
      + +
      +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      + + +
      +
      +
      +
      +
      diff --git a/cps/templates/email_edit.html b/cps/templates/email_edit.html index 73096f4b..ffd0dea4 100644 --- a/cps/templates/email_edit.html +++ b/cps/templates/email_edit.html @@ -48,8 +48,8 @@
      - - + +
      diff --git a/cps/templates/locales/en/translation.json b/cps/templates/locales/en/translation.json new file mode 100644 index 00000000..655669ee --- /dev/null +++ b/cps/templates/locales/en/translation.json @@ -0,0 +1,10 @@ +{ + "input": { + "placeholder": "a placeholder" + }, + "nav": { + "home": "Home", + "page1": "Page One", + "page2": "Page Two" + } +} \ No newline at end of file diff --git a/cps/templates/login.html b/cps/templates/login.html index 36471dc6..94886528 100644 --- a/cps/templates/login.html +++ b/cps/templates/login.html @@ -7,11 +7,11 @@
      - +
      - +
      Traceback (most recent call last):
      -  File "/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py", line 230, in test_edit_title
      -    self.assertEqual(ele.text, u'Very long extra super turbo cool title without any issue of displaying including ö utf-8 characters')
      -AttributeError: 'bool' object has no attribute 'text'
      + File "/home/ozzie/Development/calibre-web-test/test/test_edit_ebooks_gdrive.py", line 226, in test_edit_title + self.edit_book(content={'book_title': u'Very long extra super turbo cool title without any issue of displaying including ö utf-8 characters'}) + File "/home/ozzie/Development/calibre-web-test/test/helper_ui.py", line 1761, in edit_book + ele.send_keys(Keys.DELETE) + File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/selenium/webdriver/remote/webelement.py", line 230, in send_keys + self._execute( + File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/selenium/webdriver/remote/webelement.py", line 403, in _execute + return self._parent.execute(command, params) + File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/selenium/webdriver/remote/webdriver.py", line 440, in execute + self.error_handler.check_response(response) + File "/home/ozzie/Development/calibre-web-test/venv/lib/python3.10/site-packages/selenium/webdriver/remote/errorhandler.py", line 245, in check_response + raise exception_class(message, screen, stacktrace) +selenium.common.exceptions.StaleElementReferenceException: Message: The element reference of <input id="book_title" class="form-control" name="book_title" type="text"> is stale; either the element is no longer attached to the DOM, it is not in the current frame context, or the document has been refreshed +Stacktrace: +RemoteError@chrome://remote/content/shared/RemoteError.sys.mjs:8:8 +WebDriverError@chrome://remote/content/shared/webdriver/Errors.sys.mjs:180:5 +StaleElementReferenceError@chrome://remote/content/shared/webdriver/Errors.sys.mjs:461:5 +element.resolveElement@chrome://remote/content/marionette/element.sys.mjs:674:11 +evaluate.fromJSON@chrome://remote/content/marionette/evaluate.sys.mjs:255:31 +evaluate.fromJSON@chrome://remote/content/marionette/evaluate.sys.mjs:263:29 +receiveMessage@chrome://remote/content/marionette/actors/MarionetteCommandsChild.sys.mjs:74:34
      @@ -2156,13 +2903,13 @@ AttributeError: 'bool' object has no attribute 'text' TestKoboSync - 11 - 11 + 12 + 12 0 0 0 - Detail + Detail @@ -2188,7 +2935,7 @@ AttributeError: 'bool' object has no attribute 'text' -
      TestKoboSync - test_kobo_sync_selected_shelfs
      +
      TestKoboSync - test_kobo_limit
      PASS @@ -2197,7 +2944,7 @@ AttributeError: 'bool' object has no attribute 'text' -
      TestKoboSync - test_kobo_upload_book
      +
      TestKoboSync - test_kobo_sync_selected_shelfs
      PASS @@ -2206,7 +2953,7 @@ AttributeError: 'bool' object has no attribute 'text' -
      TestKoboSync - test_shelves_add_remove_books
      +
      TestKoboSync - test_kobo_upload_book
      PASS @@ -2215,7 +2962,7 @@ AttributeError: 'bool' object has no attribute 'text' -
      TestKoboSync - test_sync_changed_book
      +
      TestKoboSync - test_shelves_add_remove_books
      PASS @@ -2224,7 +2971,7 @@ AttributeError: 'bool' object has no attribute 'text' -
      TestKoboSync - test_sync_invalid
      +
      TestKoboSync - test_sync_changed_book
      PASS @@ -2233,7 +2980,7 @@ AttributeError: 'bool' object has no attribute 'text' -
      TestKoboSync - test_sync_reading_state
      +
      TestKoboSync - test_sync_invalid
      PASS @@ -2242,7 +2989,7 @@ AttributeError: 'bool' object has no attribute 'text' -
      TestKoboSync - test_sync_shelf
      +
      TestKoboSync - test_sync_reading_state
      PASS @@ -2251,7 +2998,7 @@ AttributeError: 'bool' object has no attribute 'text' -
      TestKoboSync - test_sync_unchanged
      +
      TestKoboSync - test_sync_shelf
      PASS @@ -2259,6 +3006,15 @@ AttributeError: 'bool' object has no attribute 'text' + +
      TestKoboSync - test_sync_unchanged
      + + PASS + + + + +
      TestKoboSync - test_sync_upload
      @@ -2268,13 +3024,13 @@ AttributeError: 'bool' object has no attribute 'text' - + TestKoboSyncBig 6 - 0 6 0 0 + 0 Detail @@ -2282,198 +3038,66 @@ AttributeError: 'bool' object has no attribute 'text' - +
      TestKoboSyncBig - test_download_cover
      - -
      - FAIL -
      - - - - + PASS - +
      TestKoboSyncBig - test_kobo_sync_multi_user
      - -
      - FAIL -
      - - - - + PASS - +
      TestKoboSyncBig - test_kobo_sync_selected_shelves
      - -
      - FAIL -
      - - - - + PASS - +
      TestKoboSyncBig - test_sync_changed_book
      - -
      - FAIL -
      - - - - + PASS - +
      TestKoboSyncBig - test_sync_reading_state
      - -
      - FAIL -
      - - - - + PASS - +
      TestKoboSyncBig - test_sync_shelf
      - -
      - FAIL -
      - - - - + PASS - + TestLdapLogin 13 - 13 - 0 + 12 + 1 0 0 @@ -2591,14 +3215,105 @@ AssertionError: 500 != 200 - +
      TestLdapLogin - test_ldap_opds_download_book
      + +
      + FAIL +
      + + + + + + + + + + + TestSecurity + 4 + 3 + 1 + 0 + 0 + + Detail + + + + + + + +
      TestSecurity - test_login_limit
      + PASS + + + +
      TestSecurity - test_opds_limit
      + + PASS + + + + + + +
      TestSecurity - test_password_strength
      + + PASS + + + + + + +
      TestSecurity - test_register_limit
      + + +
      + FAIL +
      + + + + + + + @@ -2609,13 +3324,13 @@ AssertionError: 500 != 200 0 0 - Detail + Detail - +
      TestCalibreWebListOrders - test_author_sort
      @@ -2624,7 +3339,7 @@ AssertionError: 500 != 200 - +
      TestCalibreWebListOrders - test_download_sort
      @@ -2633,7 +3348,7 @@ AssertionError: 500 != 200 - +
      TestCalibreWebListOrders - test_format_sort
      @@ -2642,7 +3357,7 @@ AssertionError: 500 != 200 - +
      TestCalibreWebListOrders - test_lang_sort
      @@ -2651,7 +3366,7 @@ AssertionError: 500 != 200 - +
      TestCalibreWebListOrders - test_order_authors_all_links
      @@ -2660,7 +3375,7 @@ AssertionError: 500 != 200 - +
      TestCalibreWebListOrders - test_order_series_all_links
      @@ -2669,7 +3384,7 @@ AssertionError: 500 != 200 - +
      TestCalibreWebListOrders - test_publisher_sort
      @@ -2678,7 +3393,7 @@ AssertionError: 500 != 200 - +
      TestCalibreWebListOrders - test_ratings_sort
      @@ -2687,7 +3402,7 @@ AssertionError: 500 != 200 - +
      TestCalibreWebListOrders - test_series_sort
      @@ -2696,7 +3411,7 @@ AssertionError: 500 != 200 - +
      TestCalibreWebListOrders - test_tags_sort
      @@ -2714,13 +3429,13 @@ AssertionError: 500 != 200 0 1 - Detail + Detail - +
      TestLogging - test_access_log_recover
      @@ -2729,7 +3444,7 @@ AssertionError: 500 != 200 - +
      TestLogging - test_debug_log
      @@ -2738,7 +3453,7 @@ AssertionError: 500 != 200 - +
      TestLogging - test_debuginfo_download
      @@ -2747,7 +3462,7 @@ AssertionError: 500 != 200 - +
      TestLogging - test_failed_login
      @@ -2756,19 +3471,19 @@ AssertionError: 500 != 200 - +
      TestLogging - test_failed_register
      - SKIP + SKIP
      -