Hi Team Wix,
Currently, backend functions are limited to 14 seconds of computing.
Requesting adjustable timeout setting for backend functions, as discussed here .
Keep up the good work.
Hi Brainstorrrm, I messaged you about the TOTP piece on a different thread yesterday it seems to of been removed but you said the JS code should be easy to implement on wix for the TOTP could you give some assistance on this? I look forward to hearing from yo. Si
Simon,
Went down the rabbit hole and got it working.
Fun crossword puzzle for the day.
Download the jsSHA zip file here:
Extract the zip and inside the âsrcâ folder youâll find the file âsha1.jsâ
In the wix editor, create a new file in the public folder with that same name (âsha1.jsâ) and copy the code from the zipped âsha1.jsâ into your wix âsha1.jsâ in the public folder (ignore any errors in the editor).
In the wix editor create a new file in the backend - I called it âsha.jswâ
Code for sha.jsw
export function getSHAobj(key, time) {
var returnvalue = [];
// key applied onto time
var shaObj = new jsSHA("SHA-1", "HEX");
shaObj.setHMACKey(key, "HEX");
shaObj.update(time);
var hmac = shaObj.getHMAC("HEX");
return hmac;
}
The code in the front page âtestâ
import { getSHAobj } from 'backend/sha.jsw';
$w.onReady(function () {
$w("#secretBase32").value = "JBSWY3DPEHPK3PXP";
var myVar = setInterval(timer, 1000);
updateOtp();
});
export function secretBase32_change(event) {
updateOtp();
}
function dec2hex(s) { return (s < 15.5 ? '0' : '') + Math.round(s).toString(16); }
function hex2dec(s) { return parseInt(s, 16); }
function base32tohex(base32) {
var base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
var bits = "";
var hex = "";
for (var i = 0; i < base32.length; i++) {
var val = base32chars.indexOf(base32.charAt(i).toUpperCase());
bits += leftpad(val.toString(2), 5, '0');
}
for (var i = 0; i+4 <= bits.length; i+=4) {
var chunk = bits.substr(i, 4);
hex = hex + parseInt(chunk, 2).toString(16) ;
}
return hex;
}
function leftpad(str, len, pad) {
if (len + 1 >= str.length) {
str = Array(len + 1 - str.length).join(pad) + str;
}
return str;
}
async function updateOtp() {
var key = base32tohex($w("#secretBase32").value);
var epoch = Math.round(new Date().getTime() / 1000.0);
var time = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0');
// updated for jsSHA v2.0.0 - http://caligatio.github.io/jsSHA/
let hmac = await getSHAobj (key, time);
console.log("================================================================");
console.log(" secret key = " + key );
console.log(" string2sign: time = " + time );
console.log(" hmac = " + hmac);
let secretBase32 = $w("#secretBase32").value;
let userID = "simon@wix.com" // this is the ID that Google Authenticator displays
let qrImg_base_url = "https://chart.googleapis.com/chart?chs=200x200&cht=qr&chl=200x200&chld=M|0&cht=qr&chl=otpauth://totp/";
let qrImg_url = qrImg_base_url + userID + "%3Fsecret%3D" + secretBase32;
$w("#image1").src = qrImg_url;
$w("#textSecretHEX").text = "" + key
$w('#textSecretHexLength').text = " " + ((key.length * 4) + ' bits');
$w('#textEpoch').text = "Epoch: " + time;
var offset = hex2dec(hmac.substring(hmac.length - 1));
var part1 = hmac.substr(0, offset * 2);
var part2 = hmac.substr(offset * 2, 8);
var part3 = hmac.substr(offset * 2 + 8, hmac.length - offset);
console.log("part1 = " + part1);
console.log("part2 = " + part2);
console.log("part3 = " + part3);
var displayHMAC = "";
if (part1.length > 0 ) {
displayHMAC = part1;
}
displayHMAC = displayHMAC + " - " + part2;
if (part3.length > 0) {
displayHMAC = displayHMAC + " - " + part3;
}
$w('#texthmac').text = "" + part1 + " - " + part2 + " - " + part3;
console.log("HMAC (1/2/3) = " + displayHMAC);
var otp = (hex2dec(hmac.substr(offset * 2, 8)) & hex2dec('7fffffff')) + '';
otp = (otp).substr(otp.length - 6, 6);
$w('#textOTP30').text = otp;
console.log("OTP = " + otp)
}
function timer()
{
var epoch = Math.round(new Date().getTime() / 1000.0);
var countDown = 30 - (epoch % 30);
if (epoch % 30 == 0) updateOtp();
$w('#textUpdatingIn30').text = "" + countDown;
}
Create the wix UI elements of your choice in the editor - I used these:
Thatâs it for the proof of concept.
To implement properly you should also implement a decent random generator for the secret key, and you should store it safely. Meaning, you may want to hash it before you store it in a collection.
Cheers.
Thank you so much for this, you will change a lot of peopleâs lives with this code and work. Could you by any chance provide an example of hashin before it gets to collection? Si
I am needing some help on understanding why its not working following your steps it says that jsSHA is undefined? do the elements need to be attached to a database or unattached from a database? at present when on a live page I just see the elements without any content? if you could help it would be great. Is
I like crypto-js.
There is a thread here .
Wix supports it: âyou can import crypto-js from NPM repository in the editor. In the editor, click on the backend folder + button, select install NPM package and select crypto-js from the list.
NPM libraries are only available in backend code.â
Then you can use a simple ârequireâ and something like this:
var CryptoJS = require('crypto-js'); // ignore error !
var secret = "Pasw0rd@123";
var string2Sign = secretBase32; // The key for the user in the above sample script
var hashed_key = await hash_keys(string2Sign, secret); // add error handling, etc.
...
export function hash_keys(string2Sign, secret) {
var hash = CryptoJS.HmacSHA256(string2Sign, secret); // object of word arrays
var hash_string = hash.toString(CryptoJS.enc.Hex)) // single string
var signature = CryptoJS.enc.Base64.stringify(hash);
return hash_string;
}
Read the documentation and examples.
@simonadams None of the elements in this example are attached to a database. The code computes the values and changes the attributes of the UI elements (with .value and .text).
" jsSHA is undefined " - did you place the jsSHA routine in the backend?
I am only a Wix customer, not a Wix moderator / coder.
Youâll have to get one of the officials to look at your code and see where you are hanging.
@brainstorrrm Hi I followed the instructions you kindly provided and placed the script from the src folder for sha1 into the backend but it just doesnât seem to work, could you provide a secret shot of what both backend files look like? I also understand youâre a wix customer but you are a very clever and capable individual that has massively helped me and others out, Wix moderators etc only go so far for support, they donât often do what youâve done and for that I am very greatful. Si
@simonadams You may want to carefully review the original instructions. The script from the zipped src folder needs to be placed in the PUBLIC folder.
PUBLIC â âsha1.jsâ
The file holding the routines that use the âsha1.jsâ functions needs to be in the backend.
BACKEND â âsha.jswâ
@brainstorrrm You are an absolute legend, I have finally made it work which is amazing, I just now need to figure out how to utilise it as a verification piece for example checking someone has the right top code plus their password and email to log in, once this has been figured out the world will change massively. Si
Hi @brainstorrrm I have been working on the verification piece for the Authenticator code and have come across this, however I am afraid I have no clue how to make this work in Wix, if you are able to work your magic like on the original piece it would be amazing, I do however understand that you are not a Wix support team member but you are awesome at what you have done.
I provide a link for the authenticator piece: https://authenticator.ppl.family they have the .js element on the fork from git hub link but as I say it is beyond me.
Si
Well, thatâs yet another js API that does the same thing.
You can install it just like jsSHA and use the commands and functions they provide as per the Documentation.
However, I donât find it necessary.
The jsSHA functions just fine.
Validating user provided TOTP is rather trivial.
Just add a few lines of code to the sample I provided, and you can validate any user input of the TOTP.
Take a look at the updated sample here .
Updated code:
import { getSHAobj } from 'backend/sha.jsw';
$w.onReady(function () {
$w("#secretBase32").value = "JBSWY3DPEHPK3PXP";
$w("#inputCompany").value = "ATARI Corp";
$w("#inputAccountName").value = "user@wix.com";
var myVar = setInterval(timer, 1000);
updateOtp();
});
function dec2hex(s) { return (s < 15.5 ? '0' : '') + Math.round(s).toString(16); }
function hex2dec(s) { return parseInt(s, 16); }
function base32tohex(base32) {
var base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
var bits = "";
var hex = "";
for (var i = 0; i < base32.length; i++) {
var val = base32chars.indexOf(base32.charAt(i).toUpperCase());
bits += leftpad(val.toString(2), 5, '0');
}
for (var i = 0; i+4 <= bits.length; i+=4) {
var chunk = bits.substr(i, 4);
hex = hex + parseInt(chunk, 2).toString(16) ;
}
return hex;
}
function leftpad(str, len, pad) {
if (len + 1 >= str.length) {
str = Array(len + 1 - str.length).join(pad) + str;
}
return str;
}
async function updateOtp() {
var key = base32tohex($w("#secretBase32").value);
var epoch = Math.round(new Date().getTime() / 1000.0);
var time = leftpad(dec2hex(Math.floor(epoch / 30)), 16, '0');
// updated for jsSHA v2.0.0 - http://caligatio.github.io/jsSHA/
let hmac = await getSHAobj (key, time);
console.log("================================================================");
console.log(" secret key = " + key );
console.log(" string2sign: time = " + time );
console.log(" hmac = " + hmac);
let secretBase32 = $w("#secretBase32").value;
let company = $w("#inputCompany").value;;
company = company.trim().replace(/ /g, '%20'); // replaces space with %20 for url
let userID = $w("#inputAccountName").value; // this is the ID that Google Authenticator displays
userID = userID.trim().replace(/ /g, '%20');
let qrImg_base_url = "https://chart.googleapis.com/chart?chs=200x200&cht=qr&chl=200x200&chld=M|0&cht=qr&chl=otpauth://totp/";
let qrImg_url = qrImg_base_url + company + "%20(" + userID + "%20)" + "%3Fsecret%3D" + secretBase32;
$w("#image1").src = qrImg_url;
$w("#textSecretHEX").text = "" + key
$w('#textSecretHexLength').text = " " + ((key.length * 4) + ' bits');
$w('#textEpoch').text = "Epoch: " + time;
var offset = hex2dec(hmac.substring(hmac.length - 1));
var part1 = hmac.substr(0, offset * 2);
var part2 = hmac.substr(offset * 2, 8);
var part3 = hmac.substr(offset * 2 + 8, hmac.length - offset);
console.log("part1 = " + part1);
console.log("part2 = " + part2);
console.log("part3 = " + part3);
var displayHMAC = "";
if (part1.length > 0 ) {
displayHMAC = part1;
}
displayHMAC = displayHMAC + " - " + part2;
if (part3.length > 0) {
displayHMAC = displayHMAC + " - " + part3;
}
$w('#texthmac').text = "" + part1 + " - " + part2 + " - " + part3;
console.log("HMAC (1/2/3) = " + displayHMAC);
var otp = (hex2dec(hmac.substr(offset * 2, 8)) & hex2dec('7fffffff')) + '';
otp = (otp).substr(otp.length - 6, 6);
$w('#textOTP30').text = otp.substr( 0, 3) + " " + otp.substr( 3, 3);
console.log("OTP = " + otp)
}
function timer()
{
var epoch = Math.round(new Date().getTime() / 1000.0);
var countDown = 30 - (epoch % 30);
if (epoch % 30 == 0) updateOtp();
$w('#textUpdatingIn30').text = "" + countDown;
}
export function buttonVerify_click(event) {
let otp = $w('#textOTP30').text // "345 789" ... with space
otp = otp.substr( 0, 3) + otp.substr( 4, 3); // remove space
let token = $w('#inputToken').value;
let x = token.length;
// validate token
if( x < 6) {
$w('#textStatus').text = "Token needs 6 digits";
$w('#boxStatus').style.borderColor = "red";
return;
}
if(token === otp) {
$w('#textStatus').text = "Correct - validated!";
$w('#boxStatus').style.borderColor = "green";
} else {
$w('#textStatus').text = "Validation fails!";
$w('#boxStatus').style.borderColor = "red";
}
}
export function inputToken_focus(event) {
$w('#boxStatus').style.borderColor = "grey";
$w('#textStatus').text = "";
}
export function inputToken_keyPress(event) {
// console.log("You pressed" + event.key);
if (event.key === "Enter") {
buttonVerify_click(event);
} else {
//
}
}
export function secretBase32_change(event) {
updateOtp();
}
export function inputCompany_change(event) {
updateOtp();
}
export function inputAccountName_change(event) {
updateOtp();
}
export function buttonUpdateQR_click(event) {
updateOtp();
}
@brainstorrrm I personally do not know how to thank you for taking the time and effort to achieve this not only for myself but also for many others that will either utilise the code or have benefit of utilising it to secure their information further. you are a Star and many people that are skilled as your are are the one that will change this world positively.
Hi @brainstorrrm , I hope you are well, the code seems to have a generator for a random base32 code/secret built in that could be actioned from the buttonUpdateQR function? how would this be implemented within the code? provided as I have tried to alter the code to make it a random generator. any help would be great. Si
function dec2hex(s) { return (s < 15.5 ? â0â : ââ) + Math.round(s).toString(16); }
function hex2dec(s) { return parseInt(s, 16); }
function base32tohex(base32) {
var base32chars = âABCDEFGHIJKLMNOPQRSTUVWXYZ234567â;
var bits = ââ;
var hex = ââ;
for ( var i = 0; i < base32.length; i++) {
var val = base32chars.indexOf(base32.charAt(i).toUpperCase());
bits += leftpad(val.toString(2), 5, â0â);
}
for ( var i = 0; i+4 <= bits.length; i+=4) {
var chunk = bits.substr(i, 4);
hex = hex + parseInt(chunk, 2).toString(16) ;
}
return hex;
}
function leftpad(str, len, pad) {
if (len + 1 >= str.length) {
str = Array(len + 1 - str.length).join(pad) + str;
}
return str;
}
async function updateOtp() {
Simon, I think I understand your question, but Iâm not sure why youâd want randomly generated passwords?
Question: " the code seems to have a generator for a random base32 code/secret built in that could be actioned from the buttonUpdateQR function? "
No, the code does not do that. The function you list takes a password (âsecret32â) and converts that password to HEX with the function: function base32tohex(base32)
The code skipped a step and picks up with provided sample secret password already converted to base32 : JBSWY3DPEHPK3PXP
If you decode the initial base32 password used in the script, you get a starting point of:
Secret: Hello!Ț < -
Secret (base 32): JBSWY3DPEHPK2IB4 FU ======
Plus an error message, telling you that âJBSWY3DPEHPK3PXPâ does not really decode from base32.
The equal signs (â=â) are unused padding.
However, the base32 password used here (JBSWY3DPEHPK3PXP) becomes "48656c6c6f21 deadbeef " when converted to HEX. Perhaps you can see the humor in all of these versions of the secret password.
So, the full sequence for any implementation goes like this:
(using a base32 encoder , leave out the equal signs ( â=â ), thatâs unused padding, per the specs ):
Password (ASCII): My.secret.password.2019
Password(Base32): JV4S443FMNZGK5BOOBQXG43XN5ZGILRSGAYTS
Password(HEX): 4D792E7365637265742E70617373776F72642E32303139
So, usually, for a full implementation on any given website, the user provides her username/password combo as usual on your site.
You take the provided password and convert it to base32.
Then you use the password (converted to base32) and the username (email) and use it like you see in the sample script (company name optional). If you are storing your own passwords, they need to be hashed for added obfuscation.
With Wix, you are provided a Wix users api which lets Wix handle security.
You have options.
If you use the Wix users api and the Wix login screen , you have to be careful how you handle 2fa. To setup this version of 2fa, make a page for your users where they can do that and also where they can change passwords, etc.
Take in the email/password, create the QR. Once your user is logged in with the Wix users api, you handle 2fa on your own page before they can proceed.
Alternatively, you can manage all credentials yourself, manage your own login page and just use the Wix users api to login and logout users from your code. This looks cleaner, is easier, but may be less secure, depending on your code.
Wix does not have an npm package for base32 conversion, but you can google a script that will do that for you. Here is a simple version of ASCII to Base32 , and another .
Second part of your question: why random passwords?
There are lots of resources as far as javascript random password generators .
Cheers and good luck.
Thank you @brainstorrrm I have utilised the 2FA as you have outlined in the first example as in creating a page after login that is like a second barrier before an individual can gain access to their information. if they fail 2FA I am thinking of two options log them out or divert to a non member page, I have generated passwords but just need to convert them to base32 in backend so each individual receives a unique secret when setting up the 2FA no need for randomness on this now. thank you for taking the time to listen and respond to my comments it is greatly appreciated.
I also would need to know how to place this in the backend to utilise the base 32 to convert the pre generated passwords to base32. I do apologise for asking for so much help. Si
I noticed I mentioned randomly generated passwords somewhere.
Thatâs before I considered a full implementation of this particular 2fa with Wix. If you choose to use the Wix user login, you may choose to use an additional âgenerated passwordâ with the 2fa, random or not.
There are many ways to implement this.
As far as placing the base32 function or any other function in the backend, take a look at this.
Wix Code: Calling Server-Side Code from the Front-End with Web Modules
Update:
Switched sample to using ASCII input for secret key instead of Base32.
Note:
-
My clock in the browser was not in sync with the Google Authenticator clock.
Check/Sync the clock of your OS if thatâs the case. After syncing the OS clock, all was in sync. -
Not all ASCII strings lend themselves to a clean Base32 conversion. Validate your ASCII secret keys before you proceed.
-
Not all Base32 strings lend themselves to a clean HEX conversion. Validate your Base32 strings before you proceed.