453 lines
19 KiB
JavaScript
453 lines
19 KiB
JavaScript
/**
|
|
* POMjs 1.1.4
|
|
*
|
|
* password-om.js
|
|
*
|
|
* Plain vanilla javascript for the "Öppet Moln" ("Open Cloud") random password
|
|
* generator site (password.oppetmoln.se). For the sake of having a name, it
|
|
* shall be POMjs. There's really nothing fancy going on here :-)
|
|
*
|
|
* Copyright 2022, 2023 Joaquim Homrighausen; All rights reserved.
|
|
* Development sponsored by WebbPlatsen i Sverige AB
|
|
* https://www.webbplatsen.se
|
|
*
|
|
* This file is part of POMjs. POMjs is free software.
|
|
*
|
|
* You may redistribute it and/or modify it under the terms of the
|
|
* GNU General Public License version 2, as published by the Free Software
|
|
* Foundation.
|
|
*
|
|
* POMjs 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 the POMjs package. If not, write to:
|
|
* The Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/* Some globals, to be filled by script */
|
|
|
|
var POM_cfgLanguage = 'noLang?';
|
|
var POM_cfgTitle = 'noTitle?';
|
|
var POM_cfgSlogan = 'noSlogan?';
|
|
var POM_cfgGenPasswordHint = 'noHint?';
|
|
const POM_strUppercase = POM_genAlphabet(true);
|
|
const POM_strLowercase = POM_genAlphabet(false);
|
|
const POM_strDigits = POM_genDigits();
|
|
const POM_strSpecialOne = '-._#$@%!';
|
|
const POM_strSpecialTwo = '"+(){}[]?&,*<>|:;^';
|
|
|
|
/* Easy customization of defaults, etc */
|
|
const POM_strUppercase_Default = true;
|
|
const POM_strLowercase_Default = true;
|
|
const POM_strDigits_Default = true;
|
|
const POM_strSpecialOne_Default = true;
|
|
const POM_strSpecialTwo_Default = false;
|
|
const POM_mkPasswordOnLoad = true;
|
|
const POM_changeIsClick = true;
|
|
const POM_minLength = 8;
|
|
const POM_maxLength = 24;
|
|
const POM_sliderStep = 1;
|
|
const POM_debug = false;
|
|
|
|
/* Generate some strings */
|
|
function POM_genAlphabet(isUpper = false) {
|
|
return [...Array(26)].map((_, i) => String.fromCharCode(i + (isUpper ? 65 : 97))).join('');
|
|
}
|
|
function POM_genDigits() {
|
|
return [...Array(10)].map((_, i) => String.fromCharCode(i + 48)).join('');
|
|
}
|
|
/* Distill string down to unique characters by using a set */
|
|
function POM_distillString(s) {
|
|
s = s.split('');
|
|
s = new Set(s);
|
|
s = [...s].join('+');
|
|
return(s);
|
|
}
|
|
/* Escape special regex characters in string, from MDN */
|
|
function POM_escapeRegExp(s) {
|
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
|
}
|
|
/* Validate "manual input" */
|
|
function POM_validateNumInput() {
|
|
let mkLengthSlider = document.getElementById('mk-password-len-slider');
|
|
if (mkLengthSlider) {
|
|
if (this.value === '') {
|
|
this.value = POM_minLength;
|
|
}
|
|
mkLengthSlider.value = this.value;
|
|
if (mkLengthSlider.value < POM_minLength) {
|
|
mkLengthSlider.value = POM_minLength;
|
|
} else if (mkLengthSlider.value > POM_maxLength) {
|
|
mkLengthSlider.value = POM_maxLength;
|
|
}
|
|
this.value = mkLengthSlider.value;
|
|
}
|
|
if (POM_changeIsClick) {
|
|
document.getElementById('mk-password-btn').click();
|
|
}
|
|
}
|
|
/* Validate "manual input" */
|
|
function POM_generatePassword() {
|
|
let strengthP = document.getElementById('mk-password-strength');
|
|
strengthP.style.background = 'inherit';
|
|
strengthP.style.width = '0';
|
|
strengthP.style.transition = 'none';
|
|
let passwordField = document.getElementById('mk-password-field');
|
|
passwordField.value = '';
|
|
passwordField.classList.remove('mk-password-field-focus');
|
|
let mkLengthField = document.getElementById('mk-length-field');
|
|
if (POM_debug) {
|
|
console.clear();
|
|
}
|
|
if (mkLengthField) {
|
|
if (mkLengthField.value >= POM_minLength && mkLengthField.value <= POM_maxLength) {
|
|
let passwordSource = '';
|
|
if (document.getElementById('mk-uppercase-select').checked) {
|
|
passwordSource += POM_strUppercase;
|
|
}
|
|
if (document.getElementById('mk-special-one-select').checked) {
|
|
passwordSource += (POM_strSpecialOne + POM_strSpecialOne);
|
|
}
|
|
if (document.getElementById('mk-digits-select').checked) {
|
|
passwordSource += POM_strDigits;
|
|
}
|
|
if (document.getElementById('mk-lowercase-select').checked) {
|
|
passwordSource += POM_strLowercase;
|
|
}
|
|
if (document.getElementById('mk-digits-select').checked) {
|
|
passwordSource += POM_strDigits;
|
|
}
|
|
if (document.getElementById('mk-special-two-select').checked) {
|
|
passwordSource += (POM_strSpecialTwo + POM_strSpecialTwo);
|
|
}
|
|
if (passwordSource.length>0) {
|
|
let passwordGen = '';
|
|
let theChar = '';
|
|
for (let i = 0; i < mkLengthField.value; i++) {
|
|
const charPos = Math.floor(Math.random() * passwordSource.length);
|
|
theChar = passwordSource[charPos];
|
|
passwordGen += theChar;
|
|
}
|
|
passwordField.value = passwordGen;
|
|
|
|
let scoreP = 0;/* Password strength score, max is 8 */
|
|
let searchStr;
|
|
let re;
|
|
|
|
/* See if password contains uppercase characters */
|
|
searchStr = '[' + POM_escapeRegExp(POM_strUppercase) + ']';
|
|
if (POM_debug) {
|
|
console.log('Using ' + searchStr + ' to scan password');
|
|
}
|
|
re = new RegExp(searchStr);
|
|
if (re.test(passwordGen)) {
|
|
scoreP++;
|
|
if (POM_debug) {
|
|
console.log('Password contains UPPERCASE');
|
|
}
|
|
} else {
|
|
if (POM_debug) {
|
|
console.log('Password does NOT contain UPPERCASE');
|
|
}
|
|
}
|
|
/* See if password contains lowercase characters */
|
|
searchStr = '[' + POM_escapeRegExp(POM_strLowercase) + ']';
|
|
if (POM_debug) {
|
|
console.log('Using ' + searchStr + ' to scan password');
|
|
}
|
|
re = new RegExp(searchStr);
|
|
if (re.test(passwordGen)) {
|
|
scoreP++;
|
|
if (POM_debug) {
|
|
console.log('Password contains lowercase');
|
|
}
|
|
} else {
|
|
if (POM_debug) {
|
|
console.log('Password does NOT contain lowercase');
|
|
}
|
|
}
|
|
/* See if password contains digits characters */
|
|
searchStr = '[' + POM_escapeRegExp(POM_strDigits) + ']';
|
|
if (POM_debug) {
|
|
console.log('Using ' + searchStr + ' to scan password');
|
|
}
|
|
re = new RegExp(searchStr);
|
|
if (re.test(passwordGen)) {
|
|
scoreP++;
|
|
if (POM_debug) {
|
|
console.log('Password contains digits');
|
|
}
|
|
} else {
|
|
if (POM_debug) {
|
|
console.log('Password does NOT contain digits');
|
|
}
|
|
}
|
|
/* See if password contains specialOne characters */
|
|
searchStr = '[' + POM_escapeRegExp(POM_strSpecialOne) + ']';
|
|
if (POM_debug) {
|
|
console.log('Using ' + searchStr + ' to scan password');
|
|
}
|
|
re = new RegExp(searchStr);
|
|
if (re.test(passwordGen)) {
|
|
scoreP++;
|
|
if (POM_debug) {
|
|
console.log('Password contains specialOne');
|
|
}
|
|
} else {
|
|
if (POM_debug) {
|
|
console.log('Password does NOT contain specialOne');
|
|
}
|
|
}
|
|
/* See if password contains specialtwo characters */
|
|
searchStr = '[' + POM_escapeRegExp(POM_strSpecialTwo) + ']';
|
|
if (POM_debug) {
|
|
console.log('Using ' + searchStr + ' to scan password');
|
|
}
|
|
re = new RegExp(searchStr);
|
|
if (re.test(passwordGen)) {
|
|
scoreP++;
|
|
if (POM_debug) {
|
|
console.log('Password contains specialTwo');
|
|
}
|
|
} else {
|
|
if (POM_debug) {
|
|
console.log('Password does NOT contain specialTwo');
|
|
}
|
|
}
|
|
/* Bump score if password is > 7 characters */
|
|
if (passwordGen.length > 7) {
|
|
scoreP++;
|
|
if (POM_debug) {
|
|
console.log('Password >7 characters, buping score');
|
|
}
|
|
}
|
|
/* Bump score if password is > 15 characters */
|
|
if (passwordGen.length > 15) {
|
|
scoreP++;
|
|
if (POM_debug) {
|
|
console.log('Password >15 characters, buping score');
|
|
}
|
|
}
|
|
/* Bump score if password is > 31 characters */
|
|
if (passwordGen.length > 31) {
|
|
scoreP += 2;
|
|
if (POM_debug) {
|
|
console.log('Password >31 characters, buping score');
|
|
}
|
|
}
|
|
/* Bump score if password is > 63 characters */
|
|
if (passwordGen.length > 63) {
|
|
scoreP += 4;
|
|
if (POM_debug) {
|
|
console.log('Password >63 characters, buping score');
|
|
}
|
|
}
|
|
/* Let's not overflooooow */
|
|
if (scoreP > 8) {
|
|
scoreP = 8;
|
|
}
|
|
if (scoreP > 6) {
|
|
strengthP.style.background = '#27ce60';
|
|
} else if (scoreP > 5) {
|
|
strengthP.style.background = '#27ae60';
|
|
} else if (scoreP > 3) {
|
|
strengthP.style.background = '#FFC300';
|
|
} else {
|
|
strengthP.style.background = '#C0392B';
|
|
}
|
|
strengthP.style.width = (scoreP * 12.5) + '%';
|
|
strengthP.style.transition = 'width 0.3s ease-in';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* Copy content of generated password field, if any, to clipboard */
|
|
function POM_copyPassword() {
|
|
let passwordField = document.getElementById('mk-password-field');
|
|
if (passwordField && passwordField.value.length > 0) {
|
|
if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
|
|
/* Use "modern" method */
|
|
navigator.clipboard.writeText(passwordField.value).then(function() {
|
|
passwordField.classList.add('mk-password-field-focus');
|
|
}, function(e) {
|
|
alert("Could not copy text: " + e);
|
|
});
|
|
} else {
|
|
/* Use legacy method */
|
|
var theText = document.createElement('textarea');
|
|
theText.value = passwordField.value;
|
|
theText.setAttribute('readonly', '');
|
|
theText.style.position = 'absolute';
|
|
theText.style.top = '-6969px';
|
|
theText.style.left = '-6969px';
|
|
document.body.appendChild(theText);
|
|
theText.focus();
|
|
theText.select();
|
|
try {
|
|
var goodCopy = document.execCommand('copy');
|
|
if (goodCopy) {
|
|
passwordField.classList.add('mk-password-field-focus');
|
|
} else {
|
|
alert('That did not work');
|
|
}
|
|
} catch(e) {
|
|
alert('Could not copy text: ' + e.message);
|
|
}
|
|
document.body.removeChild(theText);
|
|
}
|
|
}
|
|
}
|
|
/* Initialize */
|
|
function POM_initialSetup() {
|
|
/* Setup, language strings, etc. Ugly, but it works :-) */
|
|
if (navigator.language) {
|
|
POM_cfgLanguage = navigator.language;
|
|
if (POM_debug) {
|
|
console.log("Language: " + POM_cfgLanguage);
|
|
}
|
|
let dashPos = POM_cfgLanguage.indexOf('-');
|
|
if (dashPos && dashPos>0) {
|
|
POM_cfgLanguage = POM_cfgLanguage.substring(0, dashPos);
|
|
}
|
|
POM_cfgLanguage = POM_cfgLanguage.toLowerCase();
|
|
} else {
|
|
POM_cfgLanguage = '???';
|
|
}
|
|
/* Add your translation here */
|
|
switch(POM_cfgLanguage) {
|
|
case 'sv':
|
|
case 'se':
|
|
POM_cfgTitle = 'Slumpmässigt Lösenord';
|
|
POM_cfgSlogan = 'Inga kakor, <span tyle="display:inline-block">ingen spårning.</span><br/>Bara lösenord.';
|
|
POM_cfgGenPasswordHint = 'Generera lösenord';
|
|
break;
|
|
case 'de':
|
|
/* Thank you Peter Hampf */
|
|
POM_cfgTitle = 'Zufallsgenerator für Passwörter';
|
|
POM_cfgSlogan = 'Keine Cookies, <span style="display:inline-block">keine Tracker.</span><br/>Nur Passwörter.';
|
|
POM_cfgGenPasswordHint = 'Passwort generieren';
|
|
break;
|
|
case 'hu':
|
|
/* Thank you Mihaly Balassy */
|
|
POM_cfgTitle = 'Véletlenszerű jelszógenerátor';
|
|
POM_cfgSlogan = 'Cookie-k nélkül, <span style="display:inline-block">nyomkövetők nélkül.</span><br/>Csak jelszavak.';
|
|
POM_cfgGenPasswordHint = 'Jelszó generálása';
|
|
break;
|
|
case 'nl':
|
|
/* Thank you Jeroen van de Leur */
|
|
POM_cfgTitle = 'Willekeurig Wachtwoord Generator';
|
|
POM_cfgSlogan = 'Geen cookies, <span style="display:inline-block">geen trackers.</span><br/>Uitsluitend wachtwoorden.';
|
|
POM_cfgGenPasswordHint = 'Genereer wachtwoord';
|
|
break;
|
|
case 'lb':
|
|
/* Thank you Alain Fontaine */
|
|
POM_cfgTitle = 'Zoufallspasswuertgenerator';
|
|
POM_cfgSlogan = 'Keng Cookien, <span style="display:inline-block">keng Trackeren.</span><br/>Nemme Passwierder.';
|
|
POM_cfgGenPasswordHint = 'Passwuert genereieren';
|
|
break;
|
|
case 'fr':
|
|
/* Thank you Alain Fontaine */
|
|
POM_cfgTitle = 'Générateur de mot de passe au hazard';
|
|
POM_cfgSlogan = 'Pas de cookies, <span style="display:inline-block">pas de traceurs.</span><br/>Juste des mots de passe.';
|
|
POM_cfgGenPasswordHint = 'Générer mot de passe';
|
|
break;
|
|
case 'fi':
|
|
/* Thank you Thomas Raehalme */
|
|
POM_cfgTitle = 'Salasanageneraattori';
|
|
POM_cfgSlogan = 'Ei evästeitä, <span style="display:inline-block">ei seurantaa.</span><br/>Vain salasanoja.';
|
|
POM_cfgGenPasswordHint = 'Generoi salasana';
|
|
break;
|
|
case 'sl':
|
|
/* Thank you Gregor Godler */
|
|
POM_cfgTitle = 'Generator Naključnih Gesel';
|
|
POM_cfgSlogan = 'Brez piškotkov, <span style="display:inline-block">nič sledilnikov.</span><br/>Samo geslo.';
|
|
POM_cfgGenPasswordHint = 'Generiraj geslo';
|
|
break;
|
|
default:
|
|
POM_cfgTitle = 'Random Password Generator';
|
|
POM_cfgSlogan = 'No cookies, <span style="display:inline-block">no trackers.</span><br/>Just passwords.';
|
|
POM_cfgGenPasswordHint = 'Generate password';
|
|
break;
|
|
}
|
|
/* Paint "HTML" with language strings */
|
|
document.getElementById('cfg-page-title').innerHTML = POM_cfgTitle;
|
|
document.getElementById('cfg-page-slogan').innerHTML = POM_cfgSlogan;
|
|
document.getElementById('cfg-page-lang').innerHTML = '[' + POM_cfgLanguage + ']';
|
|
document.getElementById('mk-password-btn').title = POM_cfgGenPasswordHint;
|
|
/* Set reasonable defaults */
|
|
document.getElementById('mk-uppercase-select').checked = POM_strUppercase_Default;
|
|
document.getElementById('mk-lowercase-select').checked = POM_strLowercase_Default;
|
|
document.getElementById('mk-digits-select').checked = POM_strDigits_Default;
|
|
document.getElementById('mk-special-one-select').checked = POM_strSpecialOne_Default;
|
|
document.getElementById('mk-special-two-select').checked = POM_strSpecialTwo_Default;
|
|
|
|
document.getElementById('mk-password-field').value = '';
|
|
let mkPasswordSlider = document.getElementById('mk-password-len-slider');
|
|
if (mkPasswordSlider) {
|
|
mkPasswordSlider.value = POM_minLength;
|
|
mkPasswordSlider.setAttribute("min", POM_minLength);
|
|
mkPasswordSlider.setAttribute("max", POM_maxLength);
|
|
mkPasswordSlider.step = POM_sliderStep;
|
|
}
|
|
document.getElementById('mk-length-field').value = POM_minLength;
|
|
/* Paint "HTML" with password source */
|
|
document.getElementById("gen-uppercase").innerText = POM_strUppercase;
|
|
document.getElementById("gen-lowercase").innerText = POM_strLowercase;
|
|
document.getElementById("gen-digits").innerText = POM_strDigits;
|
|
document.getElementById("gen-special-one").innerText = POM_strSpecialOne;
|
|
document.getElementById("gen-special-two").innerText = POM_strSpecialTwo;
|
|
/* Add timestamp, for no good reason :) */
|
|
document.getElementById("gen-timestamp").innerText = Date.now();
|
|
/* Add some event handlers */
|
|
document.getElementById('mk-password-len-slider').addEventListener('input', function() {
|
|
let mkLengthField = document.getElementById('mk-length-field');
|
|
if (mkLengthField) {
|
|
mkLengthField.value = this.value;
|
|
if (mkLengthField.value < POM_minLength) {
|
|
mkLengthField.value = POM_minLength;
|
|
} else if (mkLengthField.value > POM_maxLength) {
|
|
mkLengthField.value = POM_maxLength;
|
|
}
|
|
this.value = mkLengthField.value;
|
|
}
|
|
});
|
|
document.getElementById('mk-length-field').addEventListener('click', function() {
|
|
this.select();
|
|
});
|
|
document.getElementById('mk-length-field').addEventListener('blur', POM_validateNumInput);
|
|
document.getElementById('mk-length-field').addEventListener('change', POM_validateNumInput);
|
|
document.getElementById('mk-password-btn').addEventListener('click', POM_generatePassword);
|
|
document.getElementById('mk-password-field').addEventListener('click', POM_copyPassword);
|
|
/* Possibly generate password on load */
|
|
if (POM_mkPasswordOnLoad) {
|
|
document.getElementById('mk-password-btn').click();
|
|
}
|
|
/* Possibly generate password on configuration changes */
|
|
if (POM_changeIsClick) {
|
|
document.getElementById('mk-uppercase-select').addEventListener('click', POM_generatePassword);
|
|
document.getElementById('mk-special-one-select').addEventListener('click', POM_generatePassword);
|
|
document.getElementById('mk-digits-select').addEventListener('click', POM_generatePassword);
|
|
document.getElementById('mk-lowercase-select').addEventListener('click', POM_generatePassword);
|
|
document.getElementById('mk-digits-select').addEventListener('click', POM_generatePassword);
|
|
document.getElementById('mk-special-two-select').addEventListener('click', POM_generatePassword);
|
|
document.getElementById('mk-password-len-slider').addEventListener('change', POM_generatePassword);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* Wait for things to be loaded */
|
|
if (document.readyState === "complete" ||
|
|
(document.readyState !== "loading" && !document.documentElement.doScroll)) {
|
|
POM_initialSetup();
|
|
} else {
|
|
document.addEventListener("DOMContentLoaded", POM_initialSetup);
|
|
}
|
|
|
|
/* end of file "password-om.js" */
|