/**
* @license Pass*Field | (c) 2013 Antelle | https://github.com/antelle/passfield/blob/master/MIT-LICENSE.txt
*/
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
/**
* Entry point
* Initializes PassField
* If jQuery is present, sets jQuery plugin ($.passField)
*/
(function($, document, window, undefined) {
"use strict";
// ========================== definitions ==========================
var PassField = window.PassField = window.PassField || {};
/**
* Password char types
* @readonly
* @enum {string}
*/
PassField.CharTypes = {
DIGIT: "digits",
LETTER: "letters",
LETTER_UP: "letters_up",
SYMBOL: "symbols",
UNKNOWN: "unknown"
};
/**
* Password checking mode
* @readonly
* @enum {number}
*/
PassField.CheckModes = {
/** more user friendly: if a password is better than the pattern (e.g. longer), its strength is increased and it could match even not containing all char types */
MODERATE: 0,
/** more strict: it a password is longer than expected length, this makes no difference; all rules must be satisfied */
STRICT: 1
};
// ========================== defaults ==========================
PassField.Config = {
defaults: {
pattern: "abcdef12", // pattern for password (for strength calculation)
acceptRate: 0.8, // threshold (of strength conformity with pattern) under which the password is considered weak
allowEmpty: true, // allow empty password (will show validation errors if not)
isMasked: true, // is the password masked by default
showToggle: true, // show toggle password masking button
showGenerate: true, // show generation button
showWarn: true, // show short password validation warning
showTip: true, // show password validation tooltips
tipPopoverStyle: {}, // if tooltip is shown and Twitter Bootstrap is present, this style will be used. null = use own tips, {} = bootstrap tip options
strengthCheckTimeout: 500, // timeout before automatic strength checking if no key is pressed (in ms; 0 = disable this feature)
validationCallback: null, // function which will be called when password strength is validated
blackList: [], // well-known bad passwords (very weak), e.g. qwerty or 12345
locale: "", // selected locale (null to auto-detect)
localeMsg: {}, // overriden locale messages
warnMsgClassName: "help-inline form-control-static", // class name added to the waring control (empty or null to disable the feature)
errorWrapClassName: "error", // class name added to wrapping control when validation fails (empty or null to disable the feature)
allowAnyChars: true, // suppress validation errors if password contains characters not from list (chars param)
checkMode: PassField.CheckModes.MODERATE, // password checking mode (how the strength is calculated)
chars: {
// symbol sequences for generation and checking
digits: "1234567890",
letters: "abcdefghijklmnopqrstuvwxyzßабвгедёжзийклмнопрстуфхцчшщъыьэюяґєåäâáàãéèêëíìîїóòôõöüúùûýñçøåæþðαβγδεζηθικλμνξοπρσςτυφχψω",
letters_up: "ABCDEFGHIJKLMNOPQRSTUVWXYZАБВГЕДЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯҐЄÅÄÂÁÀÃÉÈÊËÍÌÎЇÓÒÔÕÖÜÚÙÛÝÑÇØÅÆÞÐΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ",
symbols: "@#$%^&*()-_=+[]{};:<>/?!"
},
events: {
generated: null,
switched: null
},
nonMatchField: null, // login field (to compare password with - should not be equal)
length: { // strict length checking rules
min: null,
max: null
},
maskBtn : {
textMasked : "abc",
textUnmasked: "•••",
className: false,
classMasked: false,
classUnmasked: false
}
},
locales: PassField.Config ? PassField.Config.locales : {}, // locales are defined in locales.js
blackList: [
"password", "123456", "12345678", "abc123", "qwerty", "monkey", "letmein", "dragon", "111111", "baseball",
"iloveyou", "trustno1", "1234567", "sunshine", "master", "123123", "welcome", "shadow", "ashley", "football",
"jesus", "michael", "ninja", "mustang", "password1", "p@ssw0rd", "miss", "root", "secret"
],
generationChars: {
digits: "1234567890",
letters: "abcdefghijklmnopqrstuvwxyz",
letters_up: "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
},
dataAttr: "PassField.Field"
};
// ========================== initialization ==========================
/**
* Encapsulates PassField logic
* @param {HTMLInputElement|string} el - input for which the field is initizlized (or ID of the input)
* @param {object} [opts] - options to override defaults
*/
PassField.Field = function(el, opts) {
var _conf = PassField.Config;
var _dom = {};
var _opts = utils.extend({}, _conf.defaults, opts);
var _isMasked = true;
var _locale;
var _id;
var _validateTimer;
var _blurTimer;
var _blurMaskTimer;
var _warningShown = false;
var _validationResult = null;
var _features;
var _is_input_group = false;
var _isInputHover = false;
var _isInputFocused = false;
var _passHidesGenBtn = false;
var _passHidesMaskBtn = false;
var _bootstrapPopoverShownText = false;
var _tipHtml = null;
var ELEMENTS_PREFIX = "a_pf-";
var JQUERY_EVENT_PREFIX = "pass:";
var BUTTONS_PADDING_RIGHT = 5;
var KEY_DELETE = 46;
var KEY_BACKSPACE = 8;
var BOOTSTRAP_INPUT_GROUP_CLASS = "input-group";
// exports
this.toggleMasking = function(isMasked) { toggleMasking(isMasked); };
this.setPass = setPass;
this.validatePass = validatePassword;
this.getPassValidationMessage = getPassValidationMessage;
this.getPassStrength = getPassStrength;
init.call(this);
/**
* Initizlizes the password field
*/
function init() {
fixErrorsAndFillOptions();
if (!setMainEl())
return;
setLocale();
defineId();
detectFeatures();
createNodes();
bindEvents();
toggleMasking(_opts.isMasked, false);
doAutofocus();
assignDataObject(PassField.Config.dataAttr, this);
}
// ========================== logic ==========================
/**
* Corrects user errors in options
* Fills blackList
*/
function fixErrorsAndFillOptions() {
_opts.blackList = (_opts.blackList || []).concat(PassField.Config.blackList);
}
/**
* Sets mainEl to the actual element (it can be string)
*/
function setMainEl() {
if (typeof el == "string") {
//noinspection JSValidateTypes
el = document.getElementById(el);
}
_dom.mainInput = el;
if(hasClass(el.parentNode, BOOTSTRAP_INPUT_GROUP_CLASS, true)) {
_is_input_group = true;
}
return !!_dom.mainInput;
}
/**
* Fills _locale from setting defined in _opts
* Locale will be merged from default locale and user-defined messages
*/
function setLocale() {
var neutralLocale = "en";
var loc = _opts.locale;
if (!loc && navigator.language)
loc = navigator.language.replace(/\-.*/g, "");
if (loc)
_locale = _conf.locales[loc];
if (_locale)
_locale = utils.extend({}, _conf.locales[neutralLocale], _locale);
if (!_locale)
_locale = utils.extend({}, _conf.locales[neutralLocale]);
if (_opts.localeMsg)
utils.extend(_locale.msg, _opts.localeMsg);
}
/**
* Sets password value in field
* @param {String} val - value to set
*/
function setPass(val) {
_dom.mainInput.value = val;
if (_dom.clearInput) {
_dom.clearInput.value = val;
}
handleInputKeyup();
}
/**
* Defines _id
* This will be id of the main input or random id (if main input doesn't have id)
*/
function defineId() {
_id = _dom.mainInput.id;
if (!_id) {
_id = ("i" + Math.round(Math.random() * 100000));
_dom.mainInput.id = _id;
}
}
/**
* Inserts DOM nodes to the tree
* Fills _dom with created objects
*/
function createNodes() {
var mainInputRect = getRect(_dom.mainInput);
mainInputRect.top += cssFloat(_dom.mainInput, "marginTop");
setupWrapper();
setInputAttrs();
createClearInput();
createWarnLabel();
createMaskBtn();
createGenBtn();
createTip(mainInputRect);
createFakePlaceholder(mainInputRect);
createPassLengthChecker();
setTimeout(resizeControls, 0);
}
/**
* Assigns necessary properties to wrapper element
*/
function setupWrapper() {
_dom.wrapper = _dom.mainInput.parentNode;
addClass(_dom.wrapper, "wrap");
if (!_features.hasInlineBlock) {
addClass(_dom.wrapper, "wrap-no-ib");
}
if (css(_dom.wrapper, "position") == "static") {
_dom.wrapper.style.position = "relative";
}
}
/**
* Assigns attributes (from options) to the input field
*/
function setInputAttrs() {
if (_opts.length && _opts.length.max)
_dom.mainInput.setAttribute("maxLength", _opts.length.max.toString());
}
/**
* Creates cleartext password field
*/
function createClearInput() {
if (!_features.changeType) {
// IE < 9 don't support input type changing
_dom.clearInput = newEl("input", { type: "text", id: "txt-clear", className: "txt-clear", value: _dom.mainInput.value },
{ display: "none" });
var cls = _dom.mainInput.className;
if (cls) {
addClass(_dom.clearInput, cls, true);
}
var inputStyle = _dom.mainInput.style.cssText;
if (inputStyle) {
_dom.clearInput.style.cssText = inputStyle;
}
utils.each(["maxLength", "size", "placeholder"], function(attr) {
var value = _dom.mainInput.getAttribute(attr);
if (value) {
_dom.clearInput.setAttribute(attr, value);
}
});
insertAfter(_dom.mainInput, _dom.clearInput);
}
addClass(_dom.mainInput, "txt-pass");
}
/**
* Creates password warning label
*/
function createWarnLabel() {
if (_opts.showWarn) {
_dom.warnMsg = newEl("p", { id: "warn", className: "warn" },
{ margin: "0 0 0 3px" });
addClass(_dom.warnMsg, "empty");
if (_opts.warnMsgClassName)
addClass(_dom.warnMsg, _opts.warnMsgClassName, true);
var insertAfterNode = _dom.clearInput || _dom.mainInput;
if (hasClass(el.parentNode, BOOTSTRAP_INPUT_GROUP_CLASS, true)) {
insertAfterNode = insertAfterNode.parentNode;
}
insertAfter(insertAfterNode, _dom.warnMsg);
}
}
/**
* Creates toggle password masking button
*/
function createMaskBtn() {
if (_opts.showToggle) {
var zIndex = css(_dom.mainInput, 'z-index');
_dom.maskBtn = newEl("div", { id: "btn-mask", className: "btn-mask", title: _locale.msg.showPass },
{ position: "absolute", margin: "0", padding: "0", 'z-index': zIndex ? zIndex + 1 : null });
addClass(_dom.maskBtn, "btn");
if (_opts.maskBtn.className) {
addClass(_dom.maskBtn, _opts.maskBtn.className, true);
}
if (_opts.maskBtn.classMasked) {
addClass(_dom.maskBtn, _opts.maskBtn.classMasked, true);
}
setHtml(_dom.maskBtn, _opts.maskBtn.textMasked);
insertBefore(_dom.mainInput, _dom.maskBtn);
}
}
/**
* Creates password generation button
*/
function createGenBtn() {
if (_opts.showGenerate) {
var zIndex = css(_dom.mainInput, "z-index");
_dom.genBtn = newEl("div", { id: "btn-gen", className: "btn-gen", title: _locale.msg.genPass },
{ position: "absolute", margin: "0", padding: "0", "z-index": zIndex ? zIndex + 1 : null });
addClass(_dom.genBtn, "btn");
insertBefore(_dom.mainInput, _dom.genBtn);
_dom.genBtnInner = newEl("div", { id: "btn-gen-i", className: "btn-gen-i", title: _locale.msg.genPass });
_dom.genBtn.appendChild(_dom.genBtnInner);
}
}
/**
* Creates password tooltip
* If Twitter Bootstrap is present, this will be popover; fallback to own popover else
* @param {Object} mainInputRect - password input element rect
*/
function createTip(mainInputRect) {
if (_opts.showTip) {
if (_opts.tipPopoverStyle && $ && typeof $.fn.popover === "function") {
// using Twitter Bootstrap
$(_dom.mainInput).popover(utils.extend({
// popover defaults (overridable)
title: '',
placement: _opts.tipPopoverStyle.placement || function(pop, el) {
//noinspection JSValidateTypes
var top = $(el).position().top - $(window).scrollTop();
var spaceBelow = $(window).height() - top;
return spaceBelow > 300 || spaceBelow > top ? "bottom" : "top";
},
animation: false
}, _opts.tipPopoverStyle, {
// popovers properties (non-overridable)
trigger: "manual",
html: true,
content: function() { return _tipHtml; }
}));
} else {
// not using Twitter Bootstrap
_dom.tip = newEl("div", { id: "tip", className: "tip" },
{ position: "absolute", margin: "0", padding: "0", width: mainInputRect.width + "px" });
insertBefore(_dom.mainInput, _dom.tip);
var arrWrap = newEl("div", { id: "tip-arr-wrap", className: "tip-arr-wrap" });
_dom.tip.appendChild(arrWrap);
arrWrap.appendChild(newEl("div", { id: "tip-arr", className: "tip-arr" }));
arrWrap.appendChild(newEl("div", { id: "tip-arr-in", className: "tip-arr-in" }));
_dom.tipBody = newEl("div", { id: "tip-body", className: "tip-body" });
_dom.tip.appendChild(_dom.tipBody);
}
}
}
/**
* Creates fake placeholders (if there's no support in browser)
* @param {Object} mainInputRect - password input element rect
*/
function createFakePlaceholder(mainInputRect) {
if (!_features.placeholders) {
var placeholderText = _dom.mainInput.getAttribute("placeholder") || _dom.mainInput.getAttribute("data-placeholder");
if (placeholderText) {
_dom.placeholder = newEl("div", { id: "placeholder", className: "placeholder" },
{ position: "absolute", margin: "0", padding: "0", height: mainInputRect.height + "px", lineHeight: mainInputRect.height + "px" });
setHtml(_dom.placeholder, placeholderText);
insertBefore(_dom.mainInput, _dom.placeholder);
}
} else if (!_dom.mainInput.getAttribute("placeholder") && _dom.mainInput.getAttribute("data-placeholder")) {
_dom.mainInput.setAttribute("placeholder", _dom.mainInput.getAttribute("data-placeholder"));
}
}
/**
* Creates invisible div for measuring password test rect
*/
function createPassLengthChecker() {
if (_features.passSymbol) {
_dom.passLengthChecker = newEl("div", { id: "len" },
{ position: "absolute", height: css(_dom.mainInput, "height"),
top: "-10000px", left: "-10000px", display: "block", color: "transparent", border: "none" });
insertBefore(_dom.mainInput, _dom.passLengthChecker);
setTimeout(function() {
utils.each(["marginLeft", "fontFamily", "fontSize", "fontWeight", "fontStyle", "fontVariant"], function(attr) {
var value = css(_dom.mainInput, attr);
if (value) {
_dom.passLengthChecker.style[attr] = value;
}
});
}, 50);
}
}
/**
* Resizes controls (for window.onresize event)
*/
function resizeControls() {
toggleButtons();
toggleTip();
var rect = getRect(getActiveInput());
var left = getRightBtnPadding();
if (_dom.maskBtn && _dom.maskBtn.style.display != "none") {
left += cssFloat(_dom.maskBtn, "width");
setRect(_dom.maskBtn, { top: rect.top, left: rect.left + rect.width - left, height: rect.height });
}
if (_dom.genBtn && _dom.genBtn.style.display != "none") {
left += cssFloat(_dom.genBtn, "width");
setRect(_dom.genBtn, { top: rect.top, left: rect.left + rect.width - left, height: rect.height });
_dom.genBtnInner.style.marginTop = Math.max(0, Math.round((rect.height - 19) / 2)) + "px";
}
if (_dom.placeholder && _dom.placeholder.style.display != "none") {
setRect(_dom.placeholder, { top: rect.top, left: rect.left + 7, height: rect.height });
}
if (_dom.tip && _dom.tip.style.display != "none") {
setRect(_dom.tip, { left: rect.left, top: rect.top + rect.height, width: rect.width });
}
}
/**
* Gets padding after right button
* @returns {number} - padding in px.
*/
function getRightBtnPadding() {
var paddingRight = cssFloat(getActiveInput(), "paddingRight");
return Math.max(BUTTONS_PADDING_RIGHT, paddingRight);
}
/**
* Shows or hides buttons depending on the controls state
*/
function toggleButtons() {
if (_dom.genBtn) {
_dom.genBtn.style.display = _isInputHover || _isInputFocused && !_passHidesGenBtn ? "block" : "none";
}
if (_dom.maskBtn) {
_dom.maskBtn.style.display = _isInputHover || _isInputFocused && !_passHidesMaskBtn ? "block" : "none";
}
}
/**
* Toggles tip visibility
*/
function toggleTip() {
if (!_opts.showTip) {
return;
}
if (_dom.tip) {
_dom.tip.style.display = (_warningShown && _isInputFocused) ? "block" : "none";
} else {
if (_warningShown && _isInputFocused) {
if (!_bootstrapPopoverShownText || (_tipHtml != _bootstrapPopoverShownText)) {
var data = $(_dom.mainInput).data("popover") || $(_dom.mainInput).data("bs.popover");
var opts = data.options;
var animationBackup = opts.animation;
if (_bootstrapPopoverShownText)
opts.animation = false;
// set popover width (Bootstrap popovers doesn't support width setting, so we'll apply a hack)
var width = getActiveInput().offsetWidth - 2;
var el = data.$tip;
if (el) {
el.width(width);
} else if (data.options.template) {
data.options.template = data.options.template.replace("class=\"popover\"", "class=\"popover\" style=\"width: " + width + "px\"");
}
if (_dom.clearInput) {
data.$element = $(getActiveInput());
}
$(_dom.mainInput).popover("show");
_bootstrapPopoverShownText = _tipHtml;
opts.animation = animationBackup;
}
} else {
if (_bootstrapPopoverShownText) {
_bootstrapPopoverShownText = null;
$(_dom.mainInput).popover("hide");
}
}
}
}
/**
* Detects browser features support
* Fills in _features variable
*/
function detectFeatures() {
var supportsPlaceholder = true;
var changeType = true;
var test = document.createElement("input");
if (!("placeholder" in test)) {
supportsPlaceholder = false;
}
test.setAttribute("style", "position:absolute;left:-10000px;top:-10000px;");
document.body.appendChild(test);
try {
test.setAttribute("type", "password");
} catch (err) {
changeType = false;
}
document.body.removeChild(test);
var box = document.createElement("div");
box.setAttribute("style", "display:inline-block");
box.style.paddingLeft = box.style.width = "1px";
document.body.appendChild(box);
var isBoxModel = box.offsetWidth == 2;
var hasInlineBlock = css(box, "display") === "inline-block";
document.body.removeChild(box);
var passSymbol = navigator.userAgent.indexOf("AppleWebKit") >= 0 || navigator.userAgent.indexOf("Opera") >= 0 || navigator.userAgent.indexOf("Firefox") >= 0 && navigator.platform.indexOf("Mac") >= 0 ? /*BULLET*/"\u2022" : /*BLACK CIRCLE*/"\u25cf";
_features = {
placeholders: supportsPlaceholder,
changeType: changeType,
boxModel: isBoxModel,
hasInlineBlock: hasInlineBlock,
passSymbol: passSymbol
};
}
/**
* Gets currently active input
* @return {HTMLInputElement} - currently active input.
*/
function getActiveInput() {
return _isMasked ? _dom.mainInput : _dom.clearInput || _dom.mainInput;
}
/**
* Binds handler events to DOM nodes
*/
function bindEvents() {
utils.each(_dom.clearInput ? [_dom.mainInput, _dom.clearInput] : [_dom.mainInput], function (el) {
utils.attachEvent(el, "onkeyup", handleInputKeyup);
utils.attachEvent(el, "onfocus", handleInputFocus);
utils.attachEvent(el, "onblur", handleInputBlur);
utils.attachEvent(el, "onmouseover", handleMouseEvent);
utils.attachEvent(el, "onmouseout", handleMouseEvent);
if (_dom.placeholder) {
utils.attachEvent(el, "onkeydown", handleInputKeydown);
}
});
utils.attachEvent(window, "onresize", resizeControls);
if (_dom.maskBtn) {
utils.attachEvent(_dom.maskBtn, "onclick", function() { toggleMasking(); });
utils.attachEvent(_dom.maskBtn, "onmouseover", handleMouseEvent);
utils.attachEvent(_dom.maskBtn, "onmouseout", handleMouseEvent);
}
if (_dom.genBtn) {
utils.attachEvent(_dom.genBtn, "onclick", function() { generatePassword(); });
utils.attachEvent(_dom.genBtn, "onmouseover", handleMouseEvent);
utils.attachEvent(_dom.genBtn, "onmouseout", handleMouseEvent);
}
if (_dom.placeholder) {
utils.attachEvent(_dom.placeholder, "onclick", handlePlaceholderClicked);
}
if (_opts.nonMatchField) {
var el = getEl(_opts.nonMatchField);
if (el) {
utils.attachEvent(el, "onkeyup", handleLoginChanged);
}
}
}
/**
* Handles fake placeholder click event
*/
function handlePlaceholderClicked() {
getActiveInput().focus();
}
/**
* Handles MouseOver and MouseOut events
* sets _inputHover state
* @param {object} e - event
*/
function handleMouseEvent(e) {
var isInside = e.type === "mouseover";
var el = e.relatedTarget ? e.relatedTarget : isInside ? e.fromElement : e.toElement;
if (el && el.id && (el.id.indexOf(ELEMENTS_PREFIX + "btn") === 0 || el === _dom.mainInput || el === _dom.clearInput))
return;
_isInputHover = isInside;
resizeControls();
}
/**
* Input received keyup
* @param {Event} [e] - event
*/
function handleInputKeyup(e) {
var keyCode = e ? e.which || e.keyCode : null;
var isDelete = keyCode === KEY_BACKSPACE || keyCode === KEY_DELETE;
var val;
if (_dom.clearInput) {
if (_isMasked) {
val = _dom.clearInput.value = _dom.mainInput.value;
} else {
val = _dom.mainInput.value = _dom.clearInput.value;
}
} else {
val = _dom.mainInput.value;
}
if (_opts.strengthCheckTimeout > 0 && !_warningShown && !isDelete) {
if (_validateTimer) {
clearTimeout(_validateTimer);
}
_validateTimer = setTimeout(validatePassword, _opts.strengthCheckTimeout);
} else {
validatePassword();
}
if (_dom.placeholder && !val) {
_dom.placeholder.style.display = "block";
}
hideButtonsByPassLength();
}
/**
* Hides buttons if the password is too long
*/
function hideButtonsByPassLength() {
if (!_dom.passLengthChecker)
return;
var pass = getActiveInput().value;
if (_isMasked)
pass = pass.replace(/./g, _features.passSymbol);
setHtml(_dom.passLengthChecker, pass);
var passWidth = _dom.passLengthChecker.offsetWidth;
passWidth += cssFloat(_dom.mainInput, "paddingLeft");
var maskBtnWidth = 0, genBtnWidth = 0;
var fieldBounds = getBounds(getActiveInput());
var fieldWidth = fieldBounds.width;
var changed = false;
var btnPadding = getRightBtnPadding();
if (_dom.maskBtn) {
maskBtnWidth = cssFloat(_dom.maskBtn, "width");
var maskBtnLeft = fieldWidth - maskBtnWidth - btnPadding;
var passHidesMaskBtn = passWidth > maskBtnLeft;
if (_passHidesMaskBtn != passHidesMaskBtn) {
changed = true;
_passHidesMaskBtn = passHidesMaskBtn;
}
}
if (_dom.genBtn) {
genBtnWidth = cssFloat(_dom.genBtn, "width");
var genBtnLeft = fieldWidth - maskBtnWidth - genBtnWidth - btnPadding;
var passHidesGenBtn = passWidth > genBtnLeft;
if (_passHidesGenBtn != passHidesGenBtn) {
changed = true;
_passHidesGenBtn = passHidesGenBtn;
}
}
if (changed) {
resizeControls();
}
}
/**
* Input received KeyDown
*/
function handleInputKeydown() {
// Here we support old browsers and remove placeholder
if (_dom.placeholder) {
_dom.placeholder.style.display = "none";
}
}
/**
* Input is focused
*/
function handleInputFocus() {
if (_blurTimer) {
clearTimeout(_blurTimer);
_blurTimer = null;
}
if (_blurMaskTimer) {
clearTimeout(_blurMaskTimer);
_blurMaskTimer = null;
}
_isInputFocused = true;
resizeControls();
}
/**
* Input is blurred
*/
function handleInputBlur() {
_blurTimer = setTimeout(function() {
_blurTimer = null;
_isInputFocused = false;
resizeControls();
// if the password has not been masked my default, toggle mode after inactivity
if (_opts.isMasked && !_blurMaskTimer) {
_blurMaskTimer = setTimeout(function() {
_blurMaskTimer = null;
toggleMasking(true, false);
}, 1500);
}
}, 100);
}
/**
* Login changed in login field
*/
function handleLoginChanged() {
if (_warningShown) {
validatePassword();
}
}
/**
* Toggles masking state
* @param {Boolean} [isMasked] - should we display the password masked (undefined or null = change masking)
* @param {Boolean} [needFocus] - should we focus the field after changing
*/
function toggleMasking(isMasked, needFocus) {
if (needFocus === undefined)
needFocus = true;
var eventHappened = isMasked != _isMasked;
if (isMasked === undefined)
isMasked = !_isMasked;
else
isMasked = !!isMasked;
if (_features.changeType) {
var el = getActiveInput();
var sel = getSelection(el);
el.setAttribute("type", isMasked ? "password" : "text");
if (needFocus) {
setSelection(el, sel);
el.focus();
}
} else {
var currentDisplayMode = css(getActiveInput(), "display") || "block";
var currentInput = isMasked ? _dom.clearInput : _dom.mainInput;
var nextInput = isMasked ? _dom.mainInput : _dom.clearInput;
if (_isMasked != isMasked) {
// LastPass could insert style attributes here: we'll copy them to clear input (if any)
utils.each(["paddingRight", "width", "backgroundImage", "backgroundPosition", "backgroundRepeat", "backgroundAttachment", "border"], function (prop) {
var cur = currentInput.style[prop];
if (cur) {
nextInput.style[prop] = cur;
}
});
}
var selection = getSelection(currentInput);
nextInput.style.display = currentDisplayMode;
currentInput.style.display = "none";
nextInput.value = currentInput.value;
if (needFocus) {
setSelection(nextInput, selection);
nextInput.focus();
}
// jQuery.validation can insert error label right after our input, so we'll handle it here
if (_dom.mainInput.nextSibling != _dom.clearInput) {
insertAfter(_dom.mainInput, _dom.clearInput);
}
}
if (_dom.maskBtn) {
setHtml(_dom.maskBtn, isMasked ? _opts.maskBtn.textMasked : _opts.maskBtn.textUnmasked);
if (isMasked) {
if (_opts.maskBtn.classUnmasked)
removeClass(_dom.maskBtn, _opts.maskBtn.classUnmasked, true);
if (_opts.maskBtn.classMasked)
addClass(_dom.maskBtn, _opts.maskBtn.classMasked, true);
} else {
if (_opts.maskBtn.classMasked)
removeClass(_dom.maskBtn, _opts.maskBtn.classMasked, true);
if (_opts.maskBtn.classUnmasked)
addClass(_dom.maskBtn, _opts.maskBtn.classUnmasked, true);
}
_dom.maskBtn.title = isMasked ? _locale.msg.showPass : _locale.msg.hidePass;
}
_isMasked = isMasked;
hideButtonsByPassLength();
resizeControls();
if (eventHappened) {
triggerEvent("switched", _isMasked);
}
}
/**
* Gets selected text indices in input
* @param {HTMLInputElement} el - element to get the selection
* @returns {{ start: Number, end: Number }} - selection.
*/
function getSelection(el) {
if (typeof el.selectionStart === "number" && typeof el.selectionEnd === "number") {
return { start: el.selectionStart, end: el.selectionEnd };
}
return null;
}
/**
* Sets selection in input
* @param {HTMLInputElement} el - element to set the selection
* @param {{ start: Number, end: Number }} selection - selection.
*/
function setSelection(el, selection) {
if (!selection)
return;
if (typeof el.selectionStart === "number" && typeof el.selectionEnd === "number") {
el.selectionStart = selection.start;
el.selectionEnd = selection.end;
}
}
/**
* If the element has autofocus attribute, we'll check it and focus if necessary
*/
function doAutofocus() {
if (typeof _dom.mainInput.hasAttribute === "function" && _dom.mainInput.hasAttribute("autofocus") || _dom.mainInput.getAttribute("autofocus")) {
_dom.mainInput.focus();
handleInputFocus();
}
}
/**
* Generates random password and inserts it to the field
*/
function generatePassword() {
var pass = createRandomPassword();
_dom.mainInput.value = pass;
if (_dom.clearInput) {
_dom.clearInput.value = pass;
}
triggerEvent("generated", pass);
toggleMasking(false);
if (_validateTimer) {
clearTimeout(_validateTimer);
_validateTimer = null;
}
validatePassword();
if (_dom.placeholder) {
_dom.placeholder.style.display = "none";
}
}
/**
* Validates the password and shows an alert if need
* @return {Boolean} - is the password valid.
*/
function validatePassword() {
if (_validateTimer) {
clearTimeout(_validateTimer);
_validateTimer = null;
}
var pass = getActiveInput().value;
var checkResult = calculateStrength(pass);
if (pass.length === 0) {
checkResult = { strength: _opts.allowEmpty ? 0 : null, messages: [_locale.msg.passRequired] };
} else {
// check: contains bad chars
if (!_opts.allowAnyChars && checkResult.charTypes[PassField.CharTypes.UNKNOWN]) {
checkResult = { strength: null, messages: [_locale.msg.badChars.replace("{}", checkResult.charTypes[PassField.CharTypes.UNKNOWN])] };
}
delete checkResult.charTypes;
// check: blacklist
var isInBlackList = false;
utils.each(_opts.blackList, function(el) {
if (el == pass) {
isInBlackList = true;
return false;
}
return true;
});
if (isInBlackList) {
checkResult = { strength: 0, messages: [_locale.msg.inBlackList] };
}
// check equal to login
if (pass && pass === getNonMatchingFieldValue()) {
checkResult = { strength: 0, messages: [_locale.msg.equalTo] };
}
}
// check: external (if present)
if (typeof _opts.validationCallback === "function") {
var externalResult = _opts.validationCallback(_dom.mainInput, checkResult);
var returnedMessages;
var returnedStrength;
if (externalResult && externalResult.messages && utils.isArray(externalResult.messages)) {
returnedMessages = externalResult.messages;
}
if (externalResult && Object.prototype.hasOwnProperty.call(externalResult, "strength") && ((typeof externalResult.strength === "number") || (externalResult.strength === null))) {
returnedStrength = externalResult.strength;
}
if (returnedMessages && returnedMessages.length) {
// both messages and strength are replaced with custom messages
checkResult.messages = returnedMessages;
checkResult.strength = returnedStrength;
} else {
// no message is provided; strength can only be increased
if (returnedStrength && returnedStrength > checkResult.strength) {
checkResult.strength = returnedStrength;
}
// else:
// We have been told: "the password is invalid/weak".
// But why? There's no explanation given (in messages) and we can't guess the reason.
// We'll not show such unknown errors to the user and disregard returned strength.
}
}
if (pass.length === 0 && _opts.allowEmpty) {
// empty but ok
hidePasswordWarning();
_validationResult = { strength: 0 };
return true;
} else if (checkResult.strength === null || checkResult.strength < _opts.acceptRate) {
showPasswordWarning(checkResult.strength, checkResult.messages);
return false;
} else { // ok
hidePasswordWarning();
_validationResult = { strength: checkResult.strength };
return true;
}
}
/**
* Calculates password strength according to pattern
* @param {string} pass - password
* @return {object} - { strength: 0..1 (null for non-valid pass), messages: ["please add numbers"] }.
*/
function calculateStrength(pass) {
var charTypesPattern = splitToCharTypes(_opts.pattern, PassField.CharTypes.SYMBOL);
var charTypesPass = splitToCharTypes(pass, _opts.allowAnyChars ? PassField.CharTypes.SYMBOL : PassField.CharTypes.UNKNOWN);
var messages = [];
var charTypesPatternCount = 0;
utils.each(charTypesPattern, function(charType) {
charTypesPatternCount++;
if (!charTypesPass[charType]) {
var msg = _locale.msg[charType];
if (charType == PassField.CharTypes.SYMBOL) {
// we should give example of symbols; for other types this is not required
var symbolsCount = 4;
var charsExample = _opts.chars[charType];
if (charsExample.length > symbolsCount)
charsExample = charsExample.substring(0, symbolsCount);
msg = msg + " (" + charsExample + ")";
}
messages.push(msg);
}
});
var strength = 1 - messages.length / charTypesPatternCount;
if (messages.length) {
messages = [joinMessagesForCharTypes(messages)];
}
if (_opts.checkMode == PassField.CheckModes.MODERATE) {
var extraCharTypesCount = 0;
utils.each(charTypesPass, function(charType) {
if (!charTypesPattern[charType]) {
// cool: the user entered char of type which was not in pattern; +strength!
extraCharTypesCount++;
}
});
strength += extraCharTypesCount / charTypesPatternCount;
}
var minPassLength = _opts.pattern.length;
var lengthRatio = pass.length / minPassLength - 1;
if (_opts.length && _opts.length.min && pass.length < _opts.length.min) {
lengthRatio = -10;
if (_opts.length.min > minPassLength)
minPassLength = _opts.length.min;
}
if (lengthRatio < 0) {
strength += lengthRatio;
messages.push(_locale.msg.passTooShort.replace("{}", minPassLength.toString()));
} else {
if (_opts.checkMode == PassField.CheckModes.MODERATE) {
strength += lengthRatio / charTypesPatternCount;
}
}
if (pass.length > 2) {
var firstChar = pass.charAt(0);
var allEqual = true;
for (var i = 0; i < pass.length; i++) {
if (pass.charAt(i) != firstChar) {
allEqual = false;
break;
}
}
if (allEqual) {
strength = 0;
messages = [_locale.msg.repeat];
}
}
if (strength < 0) {
strength = 0;
}
// MODERATE checking mode could produce positive results for extra long passwords
if (strength > 1) {
strength = 1;
}
return { strength: strength, messages: messages, charTypes: charTypesPass };
}
/**
* Joins messages about absesnse of different char types in one message like:
* "there are no XXX, YYY and ZZZ in your password"
* @param messages {string[]} - messages to join.
* @return {string} - single joined message
*/
function joinMessagesForCharTypes(messages) {
var replacement = messages[0];
for (var i = 1; i < messages.length; i++) {
if (i == messages.length - 1)
replacement += " " + _locale.msg.and + " ";
else
replacement += ", ";
replacement += messages[i];
}
return _locale.msg.noCharType.replace("{}", replacement);
}
/**
* Shows password warning
* @param {Number} strength - password strength (null if the password is not valid)
* @param {String[]} messages - validation messages
*/
function showPasswordWarning(strength, messages) {
var shortErrorText = "";
var errorText = "";
if (strength === null) {
shortErrorText = _locale.msg.invalidPassWarn;
errorText = messages[0].charAt(0).toUpperCase() + messages[0].substring(1);
} else {
shortErrorText = _locale.msg.weakWarn;
errorText = "";
if (messages) {
for (var i = 0; i < messages.length; i++) {
var firstLetter = messages[i].charAt(0);
if (i === 0) {
errorText += _locale.msg.weakTitle + ": ";
if (_locale.lower)
firstLetter = firstLetter.toLowerCase();
} else {
errorText += "
";
firstLetter = firstLetter.toUpperCase();
}
errorText += firstLetter + messages[i].substring(1);
if (errorText && (errorText.charAt(errorText.length - 1) != "."))
errorText += ".";
}
}
}
if (errorText && (errorText.charAt(errorText.length - 1) != "."))
errorText += ".";
_validationResult = { strength: strength, message: errorText };
if (_dom.warnMsg) {
setHtml(_dom.warnMsg, shortErrorText);
_dom.warnMsg.title = errorText;
if (_opts.errorWrapClassName) {
addClass(_dom.wrapper, _opts.errorWrapClassName, true);
}
if (shortErrorText)
removeClass(_dom.warnMsg, "empty");
else
addClass(_dom.warnMsg, "empty");
}
if (_opts.showTip) {
var html = errorText;
if (_dom.genBtn) {
html += "
" + _locale.msg.generateMsg.replace("{}", "