I am signing an arbitrary string by
const ts = Date.now();
const hash = await web3.utils.sha3(anystring+ts);
const sign = await web3.eth.personal.sign(hash, account);
and check the address by
const address = await web3.eth.accounts.recover(hash, sign);
The problem is, that this signature is valid forever.
How is it possible to introduce and expiry date/time?
The signature expiration must be checked at the application level.
The message itself must contain a payload for how long the message is valid.
Then the application checks the timestamp after checking the signature.
Related
I'm looking forward to have a faster SHA256 hash calculation.
My current implementation looks like this where url points to the uploaded file on telegram server
var response = UrlFetchApp.fetch(url);
var fileText = response.getContent();
var bytes = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, fileText);
var hexstr = bytes.map(byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
return hexstr;
which takes 4-5 seconds for a 2MB file.
For the performance comparison, I took #filehashing_bot telegram bot, and it calculates hashes way faster than my implementation.
Kindly suggest a better and faster solution. How can I improve my implementation? What could be the possible ways?
I was reading about crypto.subtle.digest() and implemented something for local files and it's working way faster
async function myFunction(){
const finput = document.getElementById('fileinput');
const file = finput.files[0];
const arrayBuffer = await file.arrayBuffer();
const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer); // hash the message
console.log(hashBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
document.getElementById("hash").innerHTML = hashHex;
}
Can it be used in app script?
For reference: Here is my Google Form.
Here is a Google PE Blank Sheet which has been tied to the output of my Google Form (answers will output on tab Form Responses 2).
I've built up a Google Form capable of collecting a good bit of data. It is meant to be filled out by a manager requesting SAP permissions for a new hire.
They proceed through the Form and enter their primary department (SAP Stream) which leads them to the next page/section that allows the manager to check off what the employee's required permissions will be.
Sometimes, an employee will interface with multiple departments, so the option for a supporting/secondary SAP Stream exists on the second page. This can be continued as needed until all of an employee's requisite roles/permissions are collected in a Google Sheet.
In the Sheet, you will see that:
Columns A-F include basic information
Columns H-T are for the Job Responsibilities
Columns U-AG are a range for the information of one's Supporting SAP Stream
Columns AH-AT are the Supporting SAP Stream's Job Responsibilities
Columns AU-BG collect the Secondary Supporting SAP Stream
I had built Google Script language to collect each of these bits of information in variables which would then get declared as global variables so they could be passed along to an HTML page in the App Script.
Finally, an email consisting of this HTML page would be sent out to the manager in charge of designating SAP permissions.
Everything is working fine since my last update. The email gets sent out and the variables are collecting the information from the range of cells I have targeted. But the output is full of commas separating the returned values.
Screenshot of email result
Is it that the cells which have nothing are being included and separated with commas? Or is there something else I've done wrong with my variables?
Edit:
Here is a sample of some of the relevant portions of my code:
function sendEmail_v3() {
// get the spreadsheet information
const ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
//const responseSheet = ss.getSheetByName('Form Responses 1');
const data = ss.getDataRange().getValues();
//console.log(data);
// Loop over the rows
data.forEach((row,i) => {
// Identify whether notification has been sent
if (row[59] === '') {
// Get the Form info
var emailTo = "xxxxxx#gmail.com"
var subject = 'SAP Role Request Subject';
const Timestamp = row[0];
var emailAddress = row[1];
var name = row[2];
var department = row[3];
var userID = row[4];
var SAP_Stream = row[5];
var valuesA = ss.getRange(i,8,1,13).getValues();
var jobResponsibilities = valuesA;
var valuesB = ss.getRange(i,21,1,13).getValues();
var supportingSAP = valuesB;
var valuesC = ss.getRange(i,34,1,13).getValues();
var secondaryJob = valuesC;
var valuesD = ss.getRange(i,47,1,13).getValues();
var secondarySAP = valuesD;
formTime = Timestamp;
formEmail = emailAddress;
formName = name;
formUserID = userID;
formSAP_Stream = SAP_Stream;
formDepartment = department;
formResponsibilities = jobResponsibilities;
formSupportingSAP = supportingSAP + "," + secondarySAP;
formSecondaryJob = secondaryJob;
let body = '';
// Write the email in email.html
var html = HtmlService.createTemplateFromFile("email.html");
var htmlText = html.evaluate().getContent();
var options = {htmlBody: htmlText};
// Send the email
GmailApp.sendEmail(emailTo,subject,'',{htmlBody: htmlText});
// Mark as Notified
const g = 'Notification sent';
ss.getRange(i + 1,60).setValue(g);
}
});
}
The global variables get pulled into a separate part of the Google Script in an HTML file which gets sent out in the email. The email generated looks like this:
Screenshot of email result
I imagine there is another function or some code I can add to the lines with the values variables which will help to avoid the NULL results.
var valuesA = ss.getRange(i,8,1,13).getValues();
You can use filter, concat and join instead. In your case, since the data is also 2D array, you additionally need to do flat on them before using concat. Check the code below. Use concat instead of adding 2 arrays directly using +. I have compared yours and the code below on Logs and Output section:
Code:
formSupportingSAP = supportingSAP.flat().concat(secondarySAP.flat()).filter(Boolean).join(", ");
Logs and Output:
References/Notes:
flat() to get the 1 dimensional array equivalent
concat() to combine the 2 arrays
filter() to filter out blank cells
join() to combine into a string with a delimiter ", "
I am creating a Google Script that fills a Google Doc template based on the fields of a Google Forms and sends the generated PDF via email to the user.
All of the steps that I followed are explained in detail here: Hacking it: Generate PDFs from Google Forms
The script (obtained from the article) is:
function onSubmit(e) {
const rg = e.range;
const sh = rg.getSheet();
//Get all the form submitted data
//Note: This data is dependent on the headers. If headers, are changed update these as well.
const cName = e.namedValues['Client Name'][0];
const cEmail = e.namedValues['Client Email'][0];
const cAddress = e.namedValues['Client Address'][0];
const cMobile = e.namedValues['Client Mobile'][0];
const sendCopy = e.namedValues['Send client a copy?'][0];
const paymentType = e.namedValues['What is your agreed upon payment schedule?'][0];
const fixedCost = e.namedValues['What was your agreed upon cost for the project?'][0];
const hourlyRate = e.namedValues['Hourly Rate'][0];
const manHours = e.namedValues['Total man hours'][0];
const services = e.namedValues['Select the services'][0];
//Consequential Data
const tax = 18.5
var subtotal = 0;
var taxAmt = 0;
var payableAmt = 0;
//if the user has selected hourly payment model
//Note: Be careful that the responses match the elements on the actual form
switch (paymentType ){
case 'Hourly Rate':
subtotal = hourlyRate*manHours;
taxAmt = subtotal * (tax/100);
payableAmt = +subtotal + +taxAmt;
break;
case 'Fixed Cost':
subtotal = fixedCost;
taxAmt = fixedCost * (tax/100)
payableAmt = +fixedCost + +taxAmt;
break;
}
const invoiceID = 'IN' + Math.random().toString().substr(2, 9);
var formattedDate = Utilities.formatDate(new Date(), "IST", "dd-MMM-yyyy");
//Set the consequential data in the columns of the spreadsheet for record keeping
//Note: These variable are dependent on the sheet's columns so if that changes, please update.
const row = rg.getRow();
const payableAmtCol = 2; //B
const invoiceIDCol = 3; //C
sh.getRange(row,payableAmtCol).setValue(payableAmt);
sh.getRange(row,invoiceIDCol).setValue(invoiceID);
//Build a new invoice from the file
//Folder and file IDs
const invoiceFolderID = '<invoice-folder-id>';
const invoiceFolder = DriveApp.getFolderById(invoiceFolderID);
const templateFileID = '<template-id>';
const newFilename = 'Invoice_' + invoiceID;
//Make a copy of the template file
const newInvoiceFileID = DriveApp.getFileById(templateFileID).makeCopy(newFilename, invoiceFolder).getId();;
//Get the invoice body into a variable
var document = DocumentApp.openById(newInvoiceFileID);
var body = document.getBody();
//Replace all the {{ }} text in the invoice body
body.replaceText('{{Invoice num}}', invoiceID);
body.replaceText('{{Date}}', formattedDate);
body.replaceText('{{Client Name}}', cName);
body.replaceText('{{Client Address}}', cAddress);
body.replaceText('{{Client Mobile}}', cMobile);
body.replaceText('{{Client Email}}', cEmail);
body.replaceText('{{Services}}', services.split(', ').join('\n'));
body.replaceText('{{Subtotal}}', subtotal);
body.replaceText('{{Tax Value}}', taxAmt);
body.replaceText('{{Total}}', payableAmt);
//In the case of hourly rate payment type, let's add an additional message giving the rate and the man hours.
if(paymentType.includes('Hourly Rate')){
//It should look something like this on the invoice
//Hourly Rate
//Rate of Rs.1200/hour
//Completed 50 man hours
const message = paymentType + '\nRate of Rs.' + hourlyRate + '/hour\nCompleted ' + manHours + ' man hours';
body.replaceText('{{Payment Type}}', message);
} else {
body.replaceText('{{Payment Type}}', paymentType);
}
document.saveAndClose();
//send email with the file
var attachment = DriveApp.getFileById(newInvoiceFileID);
GmailApp.sendEmail(cEmail, '<subject>,
'<body>',
{attachments: [attachment.getAs(MimeType.PDF)]});
}
The code works fine. Now I need that the user can edit its response after he press "Send Form" on Google Forms. So I decided to check "Respondents can edit after submit". Then I need to send the document again via GmailApp with the edited fields. So I created a new trigger: Edit (from a Spreadsheet). The other trigger is Form submit.
However I have a problem. When the user edits a field and press, again, "Send Form", the trigger "Edit" is activated with the following error: Failed to send email: no recipient.
If I go to the Spreadsheet responses I can see the edited row (because the cell has a comment "The person who responded has updated this value"), and the column mail is not edited but it stills throwing the exception.
How can we solve this problem if cEmail was never edited?
Searchs
I could find some interesting answers:
Failed to send email: no recipient
Google scripts “Failed to send email: no recipient” but email arrives
Failed to send email: no recipient, what is wrong with my code?
They seem to describe that a blank row can be generated when the trigger "Edit" is activated. However I don't see why this could happen, and how I can solve it since the Spreadsheet Responses is automatically edited after a new user submit an answer.
When a form response is edited the on form submit event object properties values and namedValues only include values for those questions that were edited.
To fix the error Failed to send email: no recipient, replace
GmailApp.sendEmail(cEmail, '<subject>,
'<body>',
{attachments: [attachment.getAs(MimeType.PDF)]});
by
const recipientIdx = 1; // This is the 0 based index of the column having the recipient email address
const recipient = cEmail ? cEmail : e.range.getValues().flat()[recipientIdx]
GmailApp.sendEmail(recipient , '<subject>',
'<body>',
{attachments: [attachment.getAs(MimeType.PDF)]});
P.S. Instead of hardcoding the value assigned to recipientIdx you might use some code to get it based on the column headers.
NOTE: The above only will prevent the error mentioned in the question. In order to make the script work you will have to apply the same idea for all the fields: Read the missing values from the spreadsheet by using the e.range.getValues().flat().
Related
How to check if current submission is editing response or a new response
How to check if current form submission is editing response
I have some code that interacts with an existing smart contract on Ropsten. I have run it multiple times in the past and had no issue.
I'll post the full code and the two errors that I get when I try some alterations to the code.
var Tx = require("ethereumjs-tx");
const Web3 = require("web3");
const web3 = new Web3(
"https://ropsten.infura.io/v3/d55489f8ea264a1484c293b05ed7eb85"
);
const abi = [...];
const contractAddress = "0x15E1ff7d97CB0D7C054D19bCF579e3147FC9009b";
const myAccount = "0x59f568176e21EF86017EfED3660625F4397A2ecE";
const privateKey1 = new Buffer(
"__PrivateKey__",
"hex"
);
hashValue = "newly updated value";
const contract = new web3.eth.Contract(abi, contractAddress, {
from: myAccount
});
web3.eth.getTransactionCount(myAccount, (err, txCount) => {
//Smart contract data
const data = contract.methods.setHashValue(hashValue).encodeABI();
// Build the transaction
const txObject = {
nonce: web3.utils.toHex(txCount),
gasLimit: web3.utils.toHex(1000000),
gasPrice: 100000,
data: data,
from: myAccount,
to: contractAddress
};
// Sign the transaction
const tx = new Tx(txObject);
tx.sign(privateKey1);
const serializedTx = tx.serialize();
// Broadcast the transaction
web3.eth
.sendSignedTransaction("0x" + serializedTx.toString("hex"))
.on("receipt", console.log);
});
If I deploy this code, it creates a transaction that stays pending indefinitely.
If I run it again I get
Returned error: replacement transaction underpriced
If I alter the code to add to the nonce like this nonce: web3.utils.toHex(txCount + 1) I am returned an error that it is a known transaction (the pending transaction).
Very frustrating is that this code worked fine a few days ago!
I'm wondering if I maybe changed my Web3 module on accident...and the version change broke something. Or is there a problem with Ropsten? Last night it was not showing any blocks or transactions.
The two pending transactions are
0xc57316782bb34608b16c7f5ebd1cfb4404a0c8f1b0d5b3e6db6a2f973c527bc3
0xe0d4c513c03c3dba5e853ac0511ee6cf06be6728ba0e054b703cdc49086aa5f7
I manually replaced the nonce field with the pending nonces, and gave the transaction 20000000000 gas price. This replaced the pending transactions. Suddenly the original code is working again...
I am trying to create a flow in Node-RED to sign for Etherem transactions (in this primary case, just messages) off-line and am struggling.
I have a Python code working fine (meaning doing well this off-line signing) and posting via an API but the Node-RED is not happy doing what I want (it returns a different signature than the one I get on the Web3.py and thus the EVM reverts the transaction).
Can anyone help? I tried several approaches, including the ones commented out, but none will match the signature generated at the Python code...
PS - the Python code implements it with signed_hash = self.web3.eth.account.signHash(hash, self._fetch_signer_key())
Thanks!
var Web3 = global.get('web3');
var web3 = new Web3();
var account = "0x9EfEe29e8fDf2cXXXXXXXde5502ABDf3f13ADac9a";
var privateKey = "0xbde16f62dadb43dad898e182e4e798baXXXXae46458d4939361d2494e11ce9e4";
var password = "12XXX8";
var message = "SMX1 10 1527596375";
var _hash = web3.utils.sha3(message);
//var signedObject = web3.eth.accounts.sign(_hash, privateKey);
var signedObject = web3.eth.accounts.sign(message, account);
//var signedObject = web3.eth.personal.sign(message, account, password);
//const signature = web3.eth.sign(message,account);
var msg = {};
msg.payload = {
//"message": message,
"message": signedObject.message,
"hash": _hash,
//"hash": signedObject.messageHash,
"signature": web3.utils.toHex(signedObject.signature),
//"signature": web3.eth.personal.sign(message, account, password),
"identity": identity,
};
/*
//msg.payload = signedObject.message;
msg.payload.message = signedObject.message;
msg.payload.hash = signedObject.messageHash;
msg.payload.signature = signedObject.signature;
msg.payload.identity = identity;
*/
//return signedObject.messageHash;
//return msg;
return signedObject;