mirror of
https://github.com/nuxsmin/sysPass.git
synced 2026-03-02 14:44:09 +01:00
627 lines
18 KiB
JavaScript
627 lines
18 KiB
JavaScript
/*
|
|
* sysPass
|
|
*
|
|
* @author nuxsmin
|
|
* @link https://syspass.org
|
|
* @copyright 2012-2020, Rubén Domínguez nuxsmin@$syspass.org
|
|
*
|
|
* This file is part of sysPass.
|
|
*
|
|
* sysPass is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* sysPass is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
sysPass.Util = function (log) {
|
|
"use strict";
|
|
|
|
/**
|
|
* @author https://stackoverflow.com/users/82548/david-thomas
|
|
* @link http://stackoverflow.com/questions/5796718/html-entity-decode
|
|
*/
|
|
const decodeEntities = function (str) {
|
|
return $('<textarea />').html(str).text();
|
|
};
|
|
|
|
/**
|
|
* Resizes an image to viewport size
|
|
*
|
|
* @param $obj
|
|
*/
|
|
const resizeImage = function ($obj) {
|
|
const viewport = {
|
|
width: $(window).width() * 0.90,
|
|
height: $(window).height() * 0.90
|
|
};
|
|
|
|
const image = {
|
|
width: $obj.width(),
|
|
height: $obj.height()
|
|
};
|
|
|
|
const dimension = {
|
|
calc: 0,
|
|
main: 0,
|
|
secondary: 0,
|
|
factor: 0.90,
|
|
rel: image.width / image.height
|
|
};
|
|
|
|
/**
|
|
* Fits the image aspect ratio
|
|
*
|
|
* It takes into account the maximum dimension in the opposite axis
|
|
*
|
|
* @param dimension
|
|
* @returns {*}
|
|
*/
|
|
const adjustRel = function (dimension) {
|
|
if (dimension.main > dimension.secondary) {
|
|
dimension.calc = dimension.main / dimension.rel;
|
|
} else if (dimension.main < dimension.secondary) {
|
|
dimension.calc = dimension.main * dimension.rel;
|
|
}
|
|
|
|
if (dimension.calc > dimension.secondary) {
|
|
dimension.main *= dimension.factor;
|
|
|
|
adjustRel(dimension);
|
|
}
|
|
|
|
return dimension;
|
|
};
|
|
|
|
/**
|
|
* Resize from width
|
|
*/
|
|
const resizeWidth = function () {
|
|
dimension.main = viewport.width;
|
|
dimension.secondary = viewport.height;
|
|
|
|
const adjust = adjustRel(dimension);
|
|
|
|
$obj.css({
|
|
"width": adjust.main,
|
|
"height": adjust.calc
|
|
});
|
|
|
|
image.width = adjust.main;
|
|
image.height = adjust.calc;
|
|
};
|
|
|
|
/**
|
|
* Resize from height
|
|
*/
|
|
const resizeHeight = function () {
|
|
dimension.main = viewport.height;
|
|
dimension.secondary = viewport.width;
|
|
|
|
const adjust = adjustRel(dimension);
|
|
|
|
$obj.css({
|
|
"width": adjust.calc,
|
|
"height": adjust.main
|
|
});
|
|
|
|
image.width = adjust.calc;
|
|
image.height = adjust.main;
|
|
};
|
|
|
|
if (image.width > viewport.width) {
|
|
resizeWidth();
|
|
} else if (image.height > viewport.height) {
|
|
resizeHeight();
|
|
}
|
|
|
|
return image;
|
|
};
|
|
|
|
/**
|
|
* Function to enable file uploading through a drag&drop or form
|
|
* @param $obj
|
|
* @returns {{requestDoneAction: string, setRequestData: setRequestData, getRequestData: function(): {actionId: *, itemId: *, sk: *}, beforeSendAction: string, url: string, allowedExts: Array}}
|
|
*/
|
|
const fileUpload = function ($obj) {
|
|
|
|
/**
|
|
* Initializes the files form in legacy mode
|
|
*
|
|
* @param display
|
|
* @returns {*}
|
|
*/
|
|
const initForm = function (display) {
|
|
const $form = $("#fileUploadForm");
|
|
|
|
if (display === false) {
|
|
$form.hide();
|
|
}
|
|
|
|
const $input = $form.find("input[type='file']");
|
|
|
|
$input.on("change", function () {
|
|
if (typeof options.beforeSendAction === "function") {
|
|
options.beforeSendAction();
|
|
}
|
|
|
|
handleFiles(this.files);
|
|
});
|
|
|
|
return $input;
|
|
};
|
|
|
|
const requestData = {
|
|
actionId: $obj.data("action-id"),
|
|
itemId: $obj.data("item-id"),
|
|
sk: sysPassApp.sk.get()
|
|
};
|
|
|
|
const options = {
|
|
requestDoneAction: "",
|
|
setRequestData: function (data) {
|
|
$.extend(requestData, data);
|
|
},
|
|
getRequestData: function () {
|
|
return requestData;
|
|
},
|
|
beforeSendAction: "",
|
|
url: "",
|
|
allowedMime: []
|
|
};
|
|
|
|
/**
|
|
* Uploads a file
|
|
* @param file
|
|
* @returns {boolean}
|
|
*/
|
|
const sendFile = function (file) {
|
|
if (options.url === undefined || options.url === "") {
|
|
return false;
|
|
}
|
|
|
|
// Objeto FormData para crear datos de un formulario
|
|
const fd = new FormData();
|
|
fd.append("inFile", file);
|
|
fd.append("isAjax", 1);
|
|
|
|
requestData.sk = sysPassApp.sk.get();
|
|
|
|
Object.keys(requestData).forEach(function (key) {
|
|
fd.append(key, requestData[key]);
|
|
});
|
|
|
|
const opts = sysPassApp.requests.getRequestOpts();
|
|
opts.url = options.url;
|
|
opts.processData = false;
|
|
opts.contentType = false;
|
|
opts.data = fd;
|
|
|
|
sysPassApp.requests.getActionCall(opts, function (json) {
|
|
const status = json.status;
|
|
const description = json.description;
|
|
|
|
if (status === 0) {
|
|
if (typeof options.requestDoneAction === "function") {
|
|
options.requestDoneAction();
|
|
}
|
|
|
|
sysPassApp.msg.ok(description);
|
|
} else if (status === 10) {
|
|
sysPassApp.appActions().main.logout();
|
|
} else {
|
|
sysPassApp.msg.error(description);
|
|
}
|
|
});
|
|
|
|
};
|
|
|
|
const checkFileSize = function (size) {
|
|
return (size / 1000 > sysPassApp.config.FILES.MAX_SIZE);
|
|
};
|
|
|
|
const checkFileMimeType = function (mimeType) {
|
|
if (mimeType === '') {
|
|
return true;
|
|
}
|
|
|
|
for (let mime in options.allowedMime) {
|
|
if (mimeType.indexOf(options.allowedMime[mime]) !== -1) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Checks the files and upload them
|
|
*/
|
|
const handleFiles = function (filesArray) {
|
|
if (filesArray.length > 5) {
|
|
sysPassApp.msg.error(sysPassApp.config.LANG[17] + " (Max: 5)");
|
|
return;
|
|
}
|
|
|
|
for (let i = 0; i < filesArray.length; i++) {
|
|
const file = filesArray[i];
|
|
|
|
if (checkFileSize(file.size)) {
|
|
sysPassApp.msg.error(sysPassApp.config.LANG[18] + "<br>" + file.name + " (Max: " + sysPassApp.config.FILES.MAX_SIZE + ")");
|
|
} else if (!checkFileMimeType(file.type)) {
|
|
sysPassApp.msg.error(sysPassApp.config.LANG[19] + "<br>" + file.type);
|
|
} else {
|
|
sendFile(filesArray[i]);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Initializes the Drag&Drop zone
|
|
*/
|
|
const init = function () {
|
|
log.info("fileUpload:init");
|
|
|
|
const fallback = initForm(false);
|
|
|
|
$obj.on("dragover dragenter", function (e) {
|
|
log.info("fileUpload:drag");
|
|
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
});
|
|
|
|
$obj.on("drop", function (e) {
|
|
log.info("fileUpload:drop");
|
|
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
|
|
if (typeof options.beforeSendAction === "function") {
|
|
options.beforeSendAction();
|
|
}
|
|
|
|
handleFiles(e.originalEvent.dataTransfer.files);
|
|
});
|
|
|
|
$obj.on("click", function () {
|
|
fallback.click();
|
|
});
|
|
};
|
|
|
|
|
|
if (window.File && window.FileList && window.FileReader) {
|
|
init();
|
|
} else {
|
|
initForm(true);
|
|
}
|
|
|
|
return options;
|
|
};
|
|
|
|
/**
|
|
*
|
|
* @type {{md5: function(*=): String}}
|
|
*/
|
|
const hash = {
|
|
md5: function (data) {
|
|
return SparkMD5.hash(data, false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Scrolls to the top of the viewport
|
|
*/
|
|
const scrollUp = function () {
|
|
$("html, body").animate({scrollTop: 0}, "slow");
|
|
};
|
|
|
|
// Función para establecer la altura del contenedor ajax
|
|
const setContentSize = function () {
|
|
const $container = $("#container");
|
|
|
|
if ($container.hasClass("content-no-auto-resize")) {
|
|
return;
|
|
}
|
|
|
|
//console.info($("#content").height());
|
|
|
|
// Calculate total height for full body resize
|
|
$container.css("height", $("#content").height() + 200);
|
|
};
|
|
|
|
// Función para obtener el tiempo actual en milisegundos
|
|
const getTime = function () {
|
|
const t = new Date();
|
|
return t.getTime();
|
|
};
|
|
|
|
/**
|
|
*
|
|
* @type {{config: {passLength: number, minPasswordLength: number, complexity: {chars: boolean, numbers: boolean, symbols: boolean, uppercase: boolean, numlength: number}}, random: random, output: output, checkLevel: checkLevel}}
|
|
*/
|
|
const password = {
|
|
config: {
|
|
passLength: 0,
|
|
minPasswordLength: 12,
|
|
complexity: {
|
|
chars: true,
|
|
numbers: true,
|
|
symbols: true,
|
|
uppercase: true,
|
|
numlength: 12
|
|
},
|
|
charset: {
|
|
special: "!\"#$%&\'*+,.\/:;=?@\\^`|~[]{}()<>",
|
|
number: "1234567890",
|
|
char: "abcdefghijklmnopqrstuvwxyz"
|
|
}
|
|
},
|
|
/**
|
|
* Function to generate random password and call a callback sending the generated string
|
|
* and a zxcvbn object
|
|
*
|
|
* @param callback
|
|
*/
|
|
random: function (callback) {
|
|
log.info("password:random");
|
|
|
|
let chars = "";
|
|
|
|
if (this.config.complexity.symbols) {
|
|
chars += this.config.charset.special;
|
|
}
|
|
|
|
if (this.config.complexity.numbers) {
|
|
chars += this.config.charset.number;
|
|
}
|
|
|
|
if (this.config.complexity.chars) {
|
|
chars += this.config.charset.char;
|
|
|
|
if (this.config.complexity.uppercase) {
|
|
chars += this.config.charset.char.toUpperCase();
|
|
}
|
|
}
|
|
|
|
const getRandomChar = function (min, max) {
|
|
return chars.charAt(Math.floor((Math.random() * max) + min));
|
|
};
|
|
|
|
const generateRandom = function () {
|
|
let out = "";
|
|
let i = 0;
|
|
|
|
for (; i++ < password.config.complexity.numlength;) {
|
|
out += getRandomChar(0, chars.length - 1);
|
|
}
|
|
|
|
return out;
|
|
};
|
|
|
|
const checkComplexity = function (inPass) {
|
|
log.info("password:random:checkComplexity");
|
|
|
|
const inPassArray = inPass.split("");
|
|
|
|
if (password.config.complexity.symbols) {
|
|
const res = inPassArray.some(
|
|
function (el) {
|
|
return password.config.charset.special.indexOf(el) > 0;
|
|
});
|
|
|
|
if (res === false) {
|
|
return res;
|
|
}
|
|
}
|
|
|
|
if (password.config.complexity.numbers) {
|
|
const res = inPassArray.some(
|
|
function (el) {
|
|
return password.config.charset.number.indexOf(el) > 0;
|
|
});
|
|
|
|
if (res === false) {
|
|
return res;
|
|
}
|
|
}
|
|
|
|
if (password.config.complexity.chars) {
|
|
const res = inPassArray.some(
|
|
function (el) {
|
|
return password.config.charset.char.indexOf(el) > 0;
|
|
});
|
|
|
|
if (res === false) {
|
|
return res;
|
|
}
|
|
}
|
|
|
|
if (password.config.complexity.chars
|
|
&& password.config.complexity.uppercase
|
|
) {
|
|
const chars = password.config.charset.char.toUpperCase();
|
|
const res = inPassArray.some(
|
|
function (el) {
|
|
return chars.indexOf(el) > 0;
|
|
});
|
|
|
|
if (res === false) {
|
|
return res;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
let outPassword = "";
|
|
|
|
do {
|
|
outPassword = generateRandom();
|
|
} while (!checkComplexity(outPassword));
|
|
|
|
this.config.passLength = outPassword.length;
|
|
|
|
if (typeof callback === "function") {
|
|
callback(outPassword, zxcvbn(outPassword));
|
|
}
|
|
},
|
|
output: function (level, $target) {
|
|
log.info("password:outputResult");
|
|
|
|
const $passLevel = $("#password-level-" + $target.attr("id"));
|
|
const score = level.score;
|
|
|
|
$passLevel.removeClass("weak good strong strongest");
|
|
|
|
if (this.config.passLength === 0) {
|
|
$passLevel.attr("data-level-msg", "");
|
|
} else if (this.config.passLength < this.config.minPasswordLength) {
|
|
$passLevel.attr("data-level-msg", sysPassApp.config.LANG[11]).addClass("weak");
|
|
} else if (score === 0) {
|
|
$passLevel.attr("data-level-msg", sysPassApp.config.LANG[9] + " - " + level.feedback.warning).addClass("weak");
|
|
} else if (score === 1 || score === 2) {
|
|
$passLevel.attr("data-level-msg", sysPassApp.config.LANG[8] + " - " + level.feedback.warning).addClass("good");
|
|
} else if (score === 3) {
|
|
$passLevel.attr("data-level-msg", sysPassApp.config.LANG[7]).addClass("strong");
|
|
} else if (score === 4) {
|
|
$passLevel.attr("data-level-msg", sysPassApp.config.LANG[10]).addClass("strongest");
|
|
}
|
|
},
|
|
checkLevel: function ($target) {
|
|
log.info("password:checkPassLevel");
|
|
|
|
this.config.passLength = $target.val().length;
|
|
|
|
password.output(zxcvbn($target.val()), $target);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Redirect to a given URL
|
|
*
|
|
* @param url
|
|
*/
|
|
const redirect = function (url) {
|
|
window.location.replace(url);
|
|
};
|
|
|
|
/**
|
|
* Generates an unique id
|
|
*
|
|
* @see https://stackoverflow.com/questions/3231459/create-unique-id-with-javascript
|
|
* @returns {string}
|
|
*/
|
|
const uniqueId = function () {
|
|
// always start with a letter (for DOM friendlyness)
|
|
let idstr = String.fromCharCode(Math.floor((Math.random() * 25) + 65));
|
|
|
|
do {
|
|
// between numbers and characters (48 is 0 and 90 is Z (42-48 = 90)
|
|
const ascicode = Math.floor((Math.random() * 42) + 48);
|
|
if (ascicode < 58 || ascicode > 64) {
|
|
// exclude all chars between : (58) and @ (64)
|
|
idstr += String.fromCharCode(ascicode);
|
|
}
|
|
} while (idstr.length < 32);
|
|
|
|
return idstr.toLowerCase();
|
|
};
|
|
|
|
/**
|
|
* Sends a browser notification
|
|
*/
|
|
const notifications = {
|
|
state: {
|
|
lastHash: ''
|
|
},
|
|
send: function (title, message, id) {
|
|
log.info("sendNotification");
|
|
|
|
if (!("Notification" in window)) {
|
|
log.info("Notifications not supported");
|
|
return;
|
|
}
|
|
|
|
if (id === notifications.state.lastHash) {
|
|
return;
|
|
}
|
|
|
|
const fireMessage = function () {
|
|
log.info("sendNotification:fireMessage");
|
|
|
|
notifications.state.lastHash = id;
|
|
|
|
const options = {};
|
|
|
|
if (message !== undefined) {
|
|
options.body = message;
|
|
}
|
|
|
|
const notification = new Notification(title, options);
|
|
};
|
|
|
|
|
|
if (Notification.permission === "granted") {
|
|
fireMessage();
|
|
} else if (Notification.permission !== "denied") {
|
|
Notification.requestPermission().then(function (result) {
|
|
if (result === "granted") {
|
|
fireMessage();
|
|
} else {
|
|
log.info("Notifications disabled");
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns a serialized URL for a GET request
|
|
* @param base
|
|
* @param parts
|
|
* @returns {string}
|
|
*/
|
|
const getUrl = function (base, parts) {
|
|
return base + "?" + Object.keys(parts).map(function (key) {
|
|
if (Array.isArray(parts[key])) {
|
|
return key + "=" + parts[key].join("/");
|
|
}
|
|
|
|
return key + "=" + parts[key];
|
|
}).join("&");
|
|
};
|
|
|
|
/**
|
|
*
|
|
* @param $container
|
|
*/
|
|
const focus = function ($container) {
|
|
log.debug("focus");
|
|
|
|
$container.find("input:not([id*=selectized]):visible:first").focus();
|
|
};
|
|
|
|
return {
|
|
decodeEntities: decodeEntities,
|
|
resizeImage: resizeImage,
|
|
fileUpload: fileUpload,
|
|
scrollUp: scrollUp,
|
|
setContentSize: setContentSize,
|
|
redirect: redirect,
|
|
uniqueId: uniqueId,
|
|
getUrl: getUrl,
|
|
focus: focus,
|
|
sendNotification: notifications.send,
|
|
password: password,
|
|
hash: hash
|
|
};
|
|
};
|