Passing CSRF token from Node.js to Django - javascript

I'm working on a simple multiplayer word game app in Django (1.5). Following the example here, I'm using a separate Node.js server and Socket.io to manage the client connections.
My question breaks down into two parts:
The tutorial above uses #csrf_exempt for the API view. As the POST does not come from the client, but from the Node.js server over localhost, what exactly am I exposed to by not using CSRF protection for this view?
As I'm not sure of the above, I would like to use the CSRF protection. I have tried to extract the CSRF token from the cookie supplied by Django (as suggested by the docs) and send it along with the POST, but I still get a 403 response.
game_server.js:
io.configure(function () {
io.set('authorization', function (data, accept) {
if (data.headers.cookie) {
data.cookie = cookie_reader.parse(data.headers.cookie);
return accept(null, true);
}
return accept('error', false);
});
io.set('log level', 1);
});
io.sockets.on('connection', function (socket) {
socket.on('check_word', function (data) {
values = querystring.stringify({
word: data,
sessionid: socket.handshake.cookie['sessionid']
});
var options = {
host: 'localhost',
port: 8000,
path: '/node/check_word',
method: 'POST',
headers: {
'X-CSRFToken': socket.handshake.cookie['csrftoken'],
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': values.length
}
};
var req = http.request(options, function (res) {
res.setEncoding('utf8');
res.on('data', function (message) {
if (message) {
console.log(message);
}
});
});
req.write(values);
req.end();
});
});
game.html (script portion only):
(function ($) {
var socket = io.connect('localhost', { port: 4000 });
socket.on('connect', function () {
console.log("connected");
});
word_el = $('#word-input');
word_el.keypress(function (event) {
if (event.keyCode === 13) {
// Enter key pressed
var word = word_el.attr('value');
if (word) {
socket.emit('check_word', word, function (data) {
console.log(data);
});
}
word_el.attr('value', '');
}
});
})(jQuery);
views.py:
#ensure_csrf_cookie
def check_word(request):
return HttpResponse("MATCH:" + request.POST.get('word'))
Any insight would be greatly appreciated!

After a fair bit of research and experimentation, I've solved the problem.
My findings:
In this particular case, CSRF doesn't expose me to any meaningful attacks. In theory it opens a route for cheating in the game, but that's a lot of difficulty (requiring fabrication of session id and targeting a game currently in progress) for zero reward. However, in other applications such as chat, a CSRF vulnerability here allows for someone to impersonate another user, which is a more important concern. And so we dig deeper...
My original attempt to solve the problem via AJAX headers was a mistake. For one, the request is not actually coming over AJAX. (request.is_ajax() returns False within the view.) Secondly, the error page received from Django cites CSRF cookie not set as the reason for failure.
All of which builds up to the solution:
var options = {
// snip...
headers: {
'Cookie': 'csrftoken=' + socket.handshake.cookie['csrftoken'],
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': values.length
}
};
Add the proper 'Cookie' header, and the request succeeds.

Related

Node.js send post with array parameter

Hello and happy new year!
I'm trying to send a post petition in Node.js. The version I need to replicate in javascript looks like this:
$.post('website.com/api/buy.php', {
action: order,
'items[]': cart_i,
time: localtime,
utb: x //I don't know why that parameter is sent. It's always 0 ._.
} ... //rest of the code of that website
I'm trying to do this in Node.js to replicate that:
var postData = querystring.stringify({
action: 'order',
"items[]": item.i,
time: local_time,
utb: 0
});
var https = require('https');
var options = { method: 'POST', host: 'website.com', port: 443, path: '/api/buy.php', headers: { 'Cache-Control': 'no-cache', 'Cookie': cookies, 'Accept': '/', 'Connection': 'keep-alive' } };
var req = https.request(options, function (res) {
res.setEncoding('utf8');
res.on('data', function (chunk) {
content = content+ chunk;
});
res.on('end', function () {
console.log(content)
});
}
);
req.write(postData);
req.end();
But I always get an error telling me that I didnt send anything. I think the problem could be in items[] because I see it sends "items%5B%5D", but I dont know if I'm doing something wrong. I think is is not about cookies, because if I'm not logged I get other error.
I'm doing the request well? Or I'm missing something that is not about the request? Is there other way to send "post" data easier?
Any help would be appreciated :)

How to use the Watson Text2Speech REST API in a cordova App?

There are many samples: How to use Watson Services in Node.JS, but if you using the REST API with HTTP calls I run into problems with the authorization.
The documentation of the API tells about the commandline curl interface,
but there is no concrete sample for HTTP calls for web or Hybrid Applications using javascript.
In my situation I want to use the Watson Text2Speech in a cordova Mobile APP, for this I going to build a factory.
The http calls I use do work for other APIs, but what I am doing wrong here?
Any missing format?
Can anyone help?
Which looks like this:
.factory('GetSpeech', ['$http','$cordovaDialogs','$cordovaMedia','Base64', function($http,
$cordovaDialogs,
$cordovaMedia,
Base64){
// http://ngcordova.com/docs/plugins/media/
// https://www.ibm.com/watson/developercloud/doc/speech-to-text/input.shtml
// https://www.npmjs.com/package/cordova-plugin-speech-recognition-feat-siri
var watson_url = "https://stream.watsonplatform.net/text-to-speech/api/v1/synthesize";
var watson_token_url = "https://stream.watsonplatform.net/authorization/api/v1/token?url=https://stream.watsonplatform.net/text-to-speech/api";
var watson_token = "";
var username='YOUR_KEY';
var password='YOUR_PW';
var authdata = 'Basic ' + Base64.encode(username + ':' + password);
console.log(">>> Watson - authdata: ",authdata);
var the_get_header = "{'Authorization':'"+ authdata +"','Content-Type':'application/json'}";
var message = "";
var getSpeech_innner = function (){ $http({method: 'GET',
url: watson_token_url,
headers: the_get_header
}).then( function successCallback(response) {
console.log(">>> GetToken Success:",response);
watson_token=response;
var the_post_header = "{'X-Watson-Authorization-Token':'"+ watson_token +"','Content-Type':'application/json','Accept':'audio/wav'}";
var the_post_text = JSON.stringify({ "text":"This is the first sentence of the paragraph. Here is another sentence. Finally, this is the last sentence."
});
$http({
method: 'POST',
url: watson_url,
headers: the_post_header,
data: the_post_text
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
console.log(">>> GetSpeech Success:",response);
message = "Success: " + response;
alert(message);
return true;
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
console.log(">>> GetSpeech Error:",response);
message = "Error: " + response;
alert(message);
return false;
})
}, function errorCallback(response) {
console.log(">>> GetToken Error:",response);
});
};
return {
getSpeech : getSpeech_innner
};
}])
NOTE: By the way in the postman the HTTP calls working. The GET Token and the POST synthesize.
I've tried the same on a similar way:
$http({
method: 'POST',
url: tts_url,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'access-control-allow-headers, Authorization',
'content-type': 'application/json',
'Authorization': 'Basic <base64(uid:password)>',
'Accept': 'audio/wav'
},
data: {'\"text\"': '\"hello world\"' },
output: 'hello_world.wav'
}).then(function successCallback(response) {
console.log(">>> Success:",response.status);
}, function errorCallback(response) {
console.log(">>> Error:", response.status);
});
I'll get this error:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at
https://stream.watsonplatform.net/text-to-speech/api/v1/synthesize.
(Reason: missing token 'access-control-allow-headers' in CORS header
'Access-Control-Allow-Headers' from CORS preflight channel).
The behavior is the same, when I remove the "access-control-allow-headers" entry in the "Access-Control-Allow-Headers" header...
Running the same in postman works fine.
How do I allow my cordova app to call a remote resource?
Have you whitelisted the stream.watsonplatform.net origin in your Cordova application? It looks to me like the domain is being blocked. Details on whitelisting here: https://cordova.apache.org/docs/en/latest/guide/appdev/whitelist/
I my situation I wanted to get a token directly from the service broker.
At the current situation it seems for the communication with the text2speech service , I need a extra server app, for example running on Node.JS Server, which provides the token for the cordova app client.
Sample code for the server token app is available in the documentation.
This is documented in the chapter programming models in topic using token.
Watson Documentation Link
This is related to the developing models for the watson services.
The reason is that Postman and CURL calls are allowed and other request by Web or Mobile Applications are not valid Origins.
So you can not use the REST API directly, from the Mobile Application or Web Application. The same situation, is by using the Watson IoT.
This is the reason why I used a node framework in a Angular Web App by using Browserify Sample GitHub Project: browserfied-ibmiotf-webapplication
For a better understanding one picture from the Watson API documentation.
This is documented in the chapter programming models in topic using token.
Watson Documentation Link
#Thanks to Rene and Andrew pointing me in this direction.
Now I have a working cordova app with Text2Speech.
I do not use the token in this sample, the speech is provided directly from the Node.JS server to the Mobile App.
#Thanks to Andrii, providing some code to do this.
Node.JS Server:
app.get('/getText2SpeechOutput', function (req, res) {
console.log(' -> text function called');
console.log(' calling watson to synthesize -> ', req.header('synthesize-text'));
var text_to_speech_l = new Text2speech({
username: req.header('service-username'),
password: req.header('service-password'),
});
var params = {
text: req.header('synthesize-text'),
voice: 'en-US_AllisonVoice',
accept: 'audio/wav'
};
var tempaudio = text_to_speech_l.synthesize(params);
console.log(' response received from Watson');
var reader = new wav.Reader();
reader.on('format', function (format) {
console.log(' file read success');
var writer = new wav.FileWriter('output.wav', {
channels: 1,
sampleRate: 22050,
bitDepth: 16
});
reader.pipe(writer);
console.log(' file write success');
writer.pipe(res)
console.log(' <- response provided');
});
tempaudio.pipe(reader);
})
Cordova App:
var getSpeech_innner = function (thetext, callback){
//const fileTransfer = new FileTransfer();
var headers = {
"Authorization": authdata,
"Accept": "audio/wav",
"synthesize-text": thetext,
"service-username": username,
"service-password": password
};
var options = {
headers: headers,
replace: true
};
$cordovaFileTransfer.download(get_speech_url , cordova.file.dataDirectory + 'speech.wav', options, true).then(
function (FileEntry) {
console.debug('>>> Success', FileEntry);
var filePath = cordova.file.dataDirectory + 'speech.wav';
callback(filePath);
},
function (error) {
console.debug('>>> download failure', error);
callback(error);
});
};

Bearer Token in Node.js command line client http.request - Unauthorized

I'm developing a Node.js command line client which calls a REST API.
I have no issues calling non-authenticated gets and posts, but after I authenticate and receive my Bearer token, I'm not able to get an authorized response.
With what little samples are out there, I've simplified my call for test purposes with the following:
var options =
{
host: "localhost",
port: "62405",
path: "/api/Job",
method: 'GET',
headers:
{
Authorization: "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJVbml0VGVzdGVyQG12cHNpLmNvbSIsInJvbGUiOiJ1c2VyIiwiSkFNU1RlbmVudElkIjoiNDIiLCJXaW5kb3dzSWRlbnRpdHkiOiJVbml0VGVzdGVyQG12cHNpLmNvbSIsImlzcyI6Ik1WUFNJIiwiYXVkIjoiSkFNU0FEQ2xpZW50SWQiLCJleHAiOjE0MzEzOTY4NzEsIm5iZiI6MTQzMTM1MzY3MX0.iYYX3TjcJONKhbNDqxG2hNGFwxGV0zG2m3rJ1wqmzIw"
}
};
var callback = function (res) {
console.log(res.statusCode);
console.log(res.headers);
var str = "";
res.on("data", function(chunk) {
str += chunk;
});
res.on("end", function() {
console.log(str);
});
};
var request = require('http').request(options, callback);
request.end();
Instead of receiving the reply, I'm getting a 401 with a response message of "Authorization has been denied for this request."
Again, this is a simplified version of my extensible code... but any suggestions would be greatly appreciated.
Clarification, when I use Postman and manually put in the token into the Authentication header, I receive an authorized (and the expected) response.

How to correctly format outbound GET requests that contain data in Node.js

I am working on the backend of my iOS game and I want to validate Facebook logins on my server before I send any data back to the client. I have a valid app_access token provided by Facebook and I am able to successfully enter the following link in my browser to debug/validate access tokens:
https://graph.facebook.com/debug_token?input_token=users_access_token&access_token=apps_access_token
Ofcourse the correct access tokens are placed in the placeholders. From that request I receive a response that looks something like this:
{
"data": {
"app_id": app_id,
"is_valid": true,
"application": "My App",
"user_id": user_id,
"expires_at": 1382468400,
"scopes": [
"email",
"publish_actions",
"user_birthday",
"user_location"
]
}
}
From which I am able to determine if the user has a correct access token. However, trying to implement this from the server side has been fruitless. Here is how I am trying now
var http = require('http');
var https = require('https');
var querystring = require('querystring');
var data = querystring.stringify({
'access_token': app_access_token,
'input_token': user_access_token
});
console.log('' + data); //Debug to see if data is correctly formatted
var options = {
host: 'graph.facebook.com',
port: 443,
path: '/debug_token',
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': Buffer.byteLength(data)
}
};
var req = https.request(options, function(res) {
res.setEncoding('utf8');
res.on('data', function (chunk) {
console.log("body: " + chunk);
});
});
req.write(data);
req.end();
Which always returns me the following response:
body: {"error":{"message":"(#100) You must provide an app access token or a user access token that is an owner or developer of the app","type":"OAuthException","code":100}}
I have noticed that the querystring does tend to format the "|" into "%7C" but I have manually replaced the characters and the data string to no avail. Is there something I am doing wrong? The tokens are correct, I just cant seem to format the data correctly.
Edit
Finally got it working. Michaels solution is right. I tried the same solution earlier and it didnt work because I performed a http.request() instead of an https.request() and I forgot to try it again. Such a silly mistake. Thanks!
With GET parameters are passed as a query string, part of path /path?param1=val1&param2=val2. In your case
path: '/debug_token?'+data,
instead of req.write(data).

How to post to a request using node.js

I am trying to post some json to a URL. I saw various other questions about this on stackoverflow but none of them seemed to be clear or work. This is how far I got, I modified the example on the api docs:
var http = require('http');
var google = http.createClient(80, 'server');
var request = google.request('POST', '/get_stuff',
{'host': 'sever', 'content-type': 'application/json'});
request.write(JSON.stringify(some_json),encoding='utf8'); //possibly need to escape as well?
request.end();
request.on('response', function (response) {
console.log('STATUS: ' + response.statusCode);
console.log('HEADERS: ' + JSON.stringify(response.headers));
response.setEncoding('utf8');
response.on('data', function (chunk) {
console.log('BODY: ' + chunk);
});
});
When I post this to the server I get an error telling me that it's not of the json format or that it's not utf8, which they should be. I tried to pull the request url but it is null. I am just starting with nodejs so please be nice.
The issue is that you are setting Content-Type in the wrong place. It is part of the request headers, which have their own key in the options object, the first parameter of the request() method. Here's an implementation using ClientRequest() for a one-time transaction (you can keep createClient() if you need to make multiple connections to the same server):
var http = require('http')
var body = JSON.stringify({
foo: "bar"
})
var request = new http.ClientRequest({
hostname: "SERVER_NAME",
port: 80,
path: "/get_stuff",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(body)
}
})
request.end(body)
The rest of the code in the question is correct (request.on() and below).
Jammus got this right. If the Content-Length header is not set, then the body will contain some kind of length at the start and a 0 at the end.
So when I was sending from Node:
{"email":"joe#bloggs.com","passwd":"123456"}
my rails server was receiving:
"2b {"email":"joe#bloggs.com","passwd":"123456"} 0 "
Rails didn't understand the 2b, so it wouldn't interpret the results.
So, for passing params via JSON, set the Content-Type to application/json, and always give the Content-Length.
To send JSON as POST to an external API with NodeJS... (and "http" module)
var http = require('http');
var post_req = null,
post_data = '{"login":"toto","password":"okay","duration":"9999"}';
var post_options = {
hostname: '192.168.1.1',
port : '8080',
path : '/web/authenticate',
method : 'POST',
headers : {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'Content-Length': post_data.length
}
};
post_req = http.request(post_options, function (res) {
console.log('STATUS: ' + res.statusCode);
console.log('HEADERS: ' + JSON.stringify(res.headers));
res.setEncoding('utf8');
res.on('data', function (chunk) {
console.log('Response: ', chunk);
});
});
post_req.on('error', function(e) {
console.log('problem with request: ' + e.message);
});
post_req.write(post_data);
post_req.end();
There is a very good library that support sending POST request in Nodejs:
Link: https://github.com/mikeal/request
Sample code:
var request = require('request');
//test data
var USER_DATA = {
"email": "email#mail.com",
"password": "a075d17f3d453073853f813838c15b8023b8c487038436354fe599c3942e1f95"
}
var options = {
method: 'POST',
url: 'URL:PORT/PATH',
headers: {
'Content-Type': 'application/json'
},
json: USER_DATA
};
function callback(error, response, body) {
if (!error) {
var info = JSON.parse(JSON.stringify(body));
console.log(info);
}
else {
console.log('Error happened: '+ error);
}
}
//send request
request(options, callback);
Try including the content length.
var body = JSON.stringify(some_json);
var request = google.request('POST', '/get_stuff', {
host: 'server',
'Content-Length': Buffer.byteLength(body),
'Content-Type': 'application/json'
});
request.write(body);
request.end();
This might not solve your problem, but javascript doesn't support named arguments, so where you say:
request.write(JSON.stringify(some_json),encoding='utf8');
You should be saying:
request.write(JSON.stringify(some_json),'utf8');
The encoding= is assigning to a global variable, so it's valid syntax but probably not doing what you intend.
Probably non-existent at the time this question was asked, you could use nowadays a higher level library for handling http requests, such as https://github.com/mikeal/request. Node's built-in http module is too low level for beginners to start with.
Mikeal's request module has built-in support for directly handling JSON (see the documentation, especially https://github.com/mikeal/request#requestoptions-callback).
var request = google.request(
'POST',
'/get_stuff',
{
'host': 'sever',
**'headers'**:
{
'content-type': 'application/json'
}
}
);

Categories