How does this API endpoint validate the request is legit? - javascript

I ran across a list of open API endpoints a while back, and took notice of this one because of the way it is called.
https://developer.marvel.com/documentation/authorization
Here are the rules for sending a request:
Authentication for Server-Side Applications
Server-side applications must pass two parameters in addition to the apikey parameter:
ts - a timestamp (or other long string which can change on a request-by-request basis)
hash - a md5 digest of the ts parameter, your private key and your public key (e.g. md5(ts+privateKey+publicKey)
For example, a user with a public key of "1234" and a private key of "abcd" could construct a valid call as follows:
http://gateway.marvel.com/v1/public/comics?ts=1&apikey=1234&hash=ffd275c5130566a2916217b101f26150
(the hash value is the md5 digest of 1abcd1234)
My Question
How does their server validate such a request? I am using a TIMESTAMP, but it is being hashed - how do they know my request is legit?
So, for fun and games, let's assume we are responsible for validating that call, in - let's say, Javascript (PsuedoCode):
/* Grab and Parse Querystring */
let ts = "1";
let publicKey = "1234"; // Public Key
let hash = "ffd275c5130566a2916217b101f26150"; //md5 ts+ privateKey + publicKey
// Ok. now what???

In this case, there is a "shared secret", and that is what they're calling the "private key".
Basically, they use the apikey parameter to look up what the secret is in their database. Then, they re-hash to ensure that the hash matches.
The reason for the timestamp is to prevent replay attacks. However, from what information you're saying, it doesn't appear that they're actually validating the request data itself (URL, parameters, etc.). So, someone who gets a request URL could make other requests with that same hash if they do so quickly. This is not a very good practice.

The timestamp and public key are both in the query parameters along with the hash. The server can look up the private key associated with the public key in the request, generate a hash, and compare that with the value you provided.
(The public key and private key are issued by the server as a pair, so it already knows the private key.)

Related

How to use bcrypt to encrypt a date and decrypt it for comparison?

I am sending a token to user email like below and using bcrypt for this as an encrypt/decrypt mechanism.
const token = await bcrypt.hash(joinDate, 10);
When the user clicks on the link in email, I get the above token back as that token is a part of
/api/unsubscribe?userId="abcd"&token="token_that_was_generated_using_bcrypt_and_sent_to_user"
const {userId, token} = req.query;
In the api, I am comparing joinDate obtained from database vs token sent by req.query but it never matches.
const joinDate = user.joinDate.toString();
const tokenValidated = await bcrypt.compare(joinDate, token)//this is always false although us generated from same joinDate field
Why is tokenValidated always false although it was generated using the same field joinDate?
Your use of bcrypt is not secure. Anyone can brute-force a few hundred or a few thousand dates and cause any user to become unsubscribed.
I assume your motivation is to avoid having to create a new table in your database and store a random token for every email you've sent out.
If so, the proper tool to use is HMAC.
Your URL should be of the form: /api/unsubscribe?userId=abcd&mac=....
Come up with a secret key known only to your server. This secret key will be used to create and authenticate all unsubscribe requests. Only use this key for authenticating unsubscribe requests.
Perform HMAC-SHA512 on the user id, with the HMAC output truncated to 128 bits. Then base64-encode the 128 bits and set it as the mac parameter in the URL.
HMAC means to create a hash-based message authentication code, which will confirm to your server that it must have created and emailed out that 'mac'.
Now, your server can authenticate each response, because only someone with knowledge of the server's secret key can produce a valid unsubscribe link.
Your logic of using bcrypt for hash and verifying on the other end should work. The only possible thing that can prevent it from working is either:
1 - the joinDate here: const token = await bcrypt.hash(joinDate, 10);
is not ===
to joinDate here:const tokenValidated = await bcrypt.compare(joinDate, token)
( maybe differes in ", ' or different format orsomething )
2 - or the token is kinda different in ' or " when passing through / read from queryparam; (e.g. you token is going "'token_that_was_generated_using_bcrypt_and_sent_to_user'" for comparison.)

How do I convert the address returned from my smart contract into a readable string?

I have simple get function that returns an address. On the front end in JS, I want to convert this address into some sort of readable function, namely a string.
After migrating my contract, I use web3 to use the function to return an address. However, I'm having troubles reading it. I'm hoping to avoid converting it into a string in the .sol file as to avoid unnecessary gas usage.
This is the function in the smart contract
function getBookAccounts() public returns(address){
return bookAccount;
}
Here is the JS file trying to console log the address
async showAccounts() {
const contract = require('truffle-contract')
const simpleStorage = contract(SimpleStorageContract)
simpleStorage.setProvider(this.state.web3.currentProvider)
var currAccount = await this.simpleStorageInstance.getBookAccounts();
console.log('The address is ', currAccount)
}
Unfortunately, I can't print this address. I'm guessing I need to convert it into a string rather than a UTF8 as is used in solidity.
Make sure your Solidity function is marked as a view. Otherwise the default behavior of web3.js is to send a transaction, and you're probably getting back a transaction hash. (Transactions don't have return values.)
function getBookAccounts() public view returns (address) {
If you change the function to a view, web3.js should make a call instead of sending a transaction. This is faster, requires no gas, and can return a value.

Reply to an email in Gmail with AppScript with changed recipients ends up in a new thread

I have an email in my mailbox and I want the AppScript program to reply to it with just me and a special google group as the recipients. The purpose of this is communication of the program with me as the program replies to the message once it has processed it with necessary details about the processing in the reply body. There might also be other recipients apart from me in the original message and I don't want the program to send the reply to them.
So I need to reply with a changed set of recipients. When I do it in the Gmail GUI it works just fine, I hit reply, change the recipients, send the message and the reply ends up in the original thread. However when I do it in the script the reply always ends up in a new thread. Originally I thought Gmail decides based on the subject of the email but it seems there's more to it (perhaps it has recently changed as I think it used to work that way).
I tried multitude of slightly different approached, one of them being:
var messageBody = "foo";
var newRecipients = "me#gmail.com, my-group#gmail.com";
var messageToReplyTo = ...;
var advancedParams = {from : "my-alias#gmail.com"};
var replyDraft = messageToReplyTo.createDraftReply(messageBody);
var replySubject = replyDraft.getMessage().getSubject();
var replyBody = replyDraft.getMessage().getBody();
replyDraft.update(newRecipients, replySubject, replyBody, advancedParams);
replyDraft.send();
There are a couple fun things you need to do in order to achieve this, but you can do it without too much trouble. You should definitely review the guide to Drafts.
Per the API spec:
In order to be part of a thread, a message or draft must meet the following criteria:
The requested threadId must be specified on the Message or Draft.Message you supply with your request.
The References and In-Reply-To headers must be set in compliance with the RFC 2822 standard.
The Subject headers must match.
To start, you need to get a reference to the draft you want to update. This is probably simplest by using GmailApp:
const thread = /** get the thread somehow */;
const newBody = /** your plaintext here */;
const reply = thread.createDraftReply(newBody);
The primary issue with Gmail & Drafts is that a Draft is an immutable message to server resources. If you change any of it, you change all of it. Thus, to change a header value such as the recipient address, you need to completely rebuild the message. This is why using the GmailApp methods to update a draft fail to maintain the existing thread information - you can't specify it as one of the advanced options for building the new message. Thus, you must use the Gmail REST API for this task:
const rawMsg = Gmail.Users.Drafts.get("me", reply.getId(), {format: "raw"}).message;
To update a draft, you need to supply an RFC 2822 formatted message encoded in base64. If you are comfortable converting the rich format message parts into such a valid string, by all means work with the non-raw format, as you have direct access to the headers in the message.payload.
To work with the raw message, know that Apps Script casts the described base64 encoded string to a byte array in the above call. The leap is then to treat that byte array as string bytes, specifically, charCodes:
const msg_string = rawMsg.raw.reduce(function (acc, b) { return acc + String.fromCharCode(b); }, "");
console.log({message: "Converted byte[] to str", bytes: rawMsg.raw, str: msg_string});
Once you have the message as a string, you can use regular expressions to update your desired headers:
const pattern = /^To: .+$/m;
var new_msg_string = msg_string.replace(pattern, "To: <....>");
// new_msg_string += ....
Since the Gmail API endpoint to update a Draft expects a base64 web-safe encoded string, you can compute that:
const encoded_msg = Utilities.base64EncodeWebSafe(new_msg_string);
And the only remaining bit is to perform the call (and/or send the updated draft).
const resource = {
id: <draft id>, // e.g. reply.getId()
message: {
threadId: <thread id>, // e.g. thread.getId()
raw: encoded_msg
}
}
const resp = Gmail.Users.Drafts.update(resource, "me", reply.getId());
const sent_msg = Gmail.Users.Drafts.send({id: resp.id}, "me");
console.log({message: "Sent the draft", msg: sent_msg});
I don't claim that the handling of the Byte array returned from the Message.raw property is 100% correct, only that it seems correct and didn't result in any errors in the test message I sent. There may also be an easier approach, as the Apps Script service has a Drafts.update endpoint which accepts a Blob input and I have not investigated how one would use that.

Having trouble grasping how to securely sign JWT with Private Key

I'm looking at this example here which refers to the javascript functionality of JWT
I am trying to use javasrcipt to sign a piece of data. However, it says I have to use a Private RSA Key and it doesn't allow you to use a public key.
My goal was once a form is submitted via PHP, call this javascript function and encrypt the data.
Pardon my ignorance, but how can you use a private RSA key in javascript and keep it private at the same time?
It appears that you have to give it a private key somehow and wouldn't that private key be visible to a user using simple developer tools in the web browser?
function _genJWS() {
var sHead = '{"alg":"RS256"}';
var sPayload = '{"data":"HI","exp":1300819380}';
var sPemPrvKey = document.form1.pemprvkey1.value;
var jws = new KJUR.jws.JWS();
var sResult = null;
try {
sResult = jws.generateJWSByP1PrvKey(sHead, sPayload, sPemPrvKey);
document.form1.jwsgenerated1.value = sResult;
} catch (ex) {
alert("Error: " + ex);
}
}
What your are looking for is not JWS (signed), but JWE (encrypted).
If you want to send secured data to a server using JWE, you must :
get the public key of the server
encrypt your data using this public key and produce a JWE
send your JWE to the server.
As far as I know, there is no javascript library able to produce JWE (I may be wrong, but I found nothing).

Twitter, JavaScript & OAuth: failed to validate oauth signature and token

Im trying to implement OAuth using JavaScript, but when I make my request to http://api.twitter.com/oauth/request_token I am getting the above message in the response ("failed to validate oauth signature and token").
As far as I can tell I'm including all the correct parameters, both in the encoding of the signature base:
basestring: (consumer key removed for security)
POST&http%3A%2F%2Ftwitter.com%2Foauth%2Frequest_token%26oauth_callback
%3Doob%26oauth_consumer_key
%3D11111111111111111111112222222222222%26oauth_nonce
%3DO3cHsSXrfnzT%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp
%3D1275928008%26oauth_version%3D1.0
consumer secret: (removed for security)
11111111111111111111112222222222222&
Signature:
R3eHMuQ04F37+xPJSIsoo0aMzc8
Post Data: (consumer key removed for security)
oauth_callback=oob&oauth_consumer_key=11111111111111111111112222222222222&oauth_signature_method=HMAC-SHA1&oauth_signature=pjDh8jkp89ThBtzzB9dQmxQfcg&oauth_timestamp=1275928413&oauth_nonce=qyq3Jhn8rtTZ&oauth_version=1.0
And I've checked that the clock is correct on my device as that's the only real result I can find for this problem :( The nonce is unique and generated every time it runs...
Unfortunately I don't know where to look now. I can't spot anything obvious. I've re-written the entire request twice - once using the oauth.js library and once completely manually, but in both cases it fails with the same error!
Any suggestions?
Cheers
Perhaps the same as this question - which links to a discussin on twitter: apparently client side javascript with oob is not allowed!?!?
Your signature looks wrong, it should always end with a =. Here is an example of a valid one: "YEBbMFDYmp6DvZ3qW1aCx8q7kTc=". Your base string looks right, so I think you've made a mistake with your signature key.
In C#, here is how I built my signature key,
string signatureKey = Uri.EscapeDataString( consumer_secret ) + "&";
var hmacsha1 = new HMACSHA1( new ASCIIEncoding().GetBytes(signatureKey));
string signatureString = Convert.ToBase64String(
hmacsha1.ComputeHash(
new ASCIIEncoding().GetBytes( signatureBaseString ) ) );
string oauth_signature = signatureString;
More info on this process: https://www.dinochiesa.net/?p=17

Categories