Transfer files to dropbox from node js without browser based oauth authentication - node.js

I am running a nodejs + express based api server from heroku and using the dropbox-js library. Here's what I'd like to do:
A user hits a specific api endpoint and kicks off the process.
Generate some text files via a node process and save them on the server
Transfer these files to a dropbox that I own using my own credentials (user and dropbox app).
There will never be a case when a random user needs to do this.. it's a team account and this is an internal tool.
The part that is tripping me up is that dropbox wants to open a browser window and get permission from me to connect to the app. The issue is that I obviously can't click the button when the process is running on the heroku instance.
Is there any way for me to authorize access to the app totally in node?
I feel like I could potentially use a phantomJS process to click the button - but it seems too complicated and I'd like to avoid it if possible.
Here is my authentication code:
// Libraries
var Dropbox = require('dropbox');
var DROPBOX_APP_KEY = "key";
var DROPBOX_APP_SECRET = "secret";
var dbClient = new Dropbox.Client({
key: DROPBOX_APP_KEY, secret: DROPBOX_APP_SECRET, sandbox: false
});
dbClient.authDriver(new Dropbox.Drivers.NodeServer(8191));
dbClient.authenticate(function(error, client) {
if (error) {
console.log("Some shit happened trying to authenticate with dropbox");
console.log(error);
return;
}
client.writeFile("test.txt", "sometext", function (error, stat) {
if (error) {
console.log(error);
return;
}
console.log("file saved!");
console.log(stat);
});
});

Took me a bit of testing, but it's possible.
First, you need to authenticate through the browser and save the token and token secret that are returned by Dropbox:
dbClient.authenticate(function(error, client) {
console.log('connected...');
console.log('token ', client.oauth.token); // THE_TOKEN
console.log('secret', client.oauth.tokenSecret); // THE_TOKEN_SECRET
...
});
Once you have the token and the secret, you can use them in the Dropbox.Client constructor:
var dbClient = new Dropbox.Client({
key : DROPBOX_APP_KEY,
secret : DROPBOX_APP_SECRET,
sandbox : false,
token : THE_TOKEN,
tokenSecret : THE_TOKEN_SECRET
});
After that, you won't get bothered with having to authenticate through a browser anymore (or at least not until someone runs the code again without the token and the secret, which will make Dropbox generate a new token/secret pair and invalidate the old ones, or the apps credentials are revoked).

Or you can just use the Implicit grant and get the oauth token.
var client = new Dropbox.Client({
key: "xxxxx",
secret: "xxxxx",
token:"asssdsadadsadasdasdasdasdaddadadadsdsa", //got from implicit grant
sandbox:false
});
No need to get to the browser at all.This line is no longer required!
client.authDriver(new Dropbox.AuthDriver.NodeServer(8191));

Related

Trouble Connecting to Google Cloud IoT via MQTT with Node.js

I'm trying to create a MQTT client that'll connect to the Google Cloud IoT Core, but for some reason, it won't connect at all. Here's what I have so far
mqtt = require("mqtt")
fs = require("fs")
var jwt = require('jsonwebtoken');
const projectId = "my-project"
const deviceId = "my-device"
const registryId = "my-degistry"
const region = "us-central1"
const algorithm = "RS256"
const privateKeyFile = "./rsa_private.pem"
const mqttBridgeHostname = "mqtt.googleapis.com"
const mqttBridgePort = 8883
const messageType = "events"
//The mqttClientId is a unique string that identifies a particular device.
//For Google Cloud IoT Core, it must be the format below
const mqttClientId = `projects/${projectId}/locations/${region}/registries/${registryId}/devices/${deviceId}`
const mqttTopic = `/devices/${deviceId}/${messageType}`;
const createJwt = (projectId, privateKeyFile, algorithm) => {
// Create a JWT to authenticate this device. The device will be disconnected
// after the token expires, and will have to reconnect with a new token. The
// audience field should always be set to the GCP project id.
const token = {
iat: parseInt(Date.now() / 1000),
exp: parseInt(Date.now() / 1000) + 20 * 60, // 20 minutes
aud: projectId,
};
const privateKey = fs.readFileSync(privateKeyFile);
return jwt.sign(token, privateKey, {algorithm: algorithm});
};
//Username field is ignored in Cloud IoT Core, but it must be set to something
//Password field sends a JWT (javascript web token) to authorize the device
//mqtts protocol causes library to connecti using SSL, which is required for IoT Core
const connectionArgs = {
host: mqttBridgeHostname,
port: mqttBridgePort,
clientId: mqttClientId,
username: "unused",
password: createJwt(projectId, privateKeyFile, algorithm),
protocol: "mqtts",
secureProtocol: "TLSv1_2_method"
}
const client = mqtt.connect(connectionArgs)
client.on("connect", (connected)=>{
console.log("Attempting to connect")
if (!connected) {
console.log("Client failed to connect")
} else {
console.log("Client is connected!")
}
})
client.on("error", err => {
console.log(err)
setTimeout(( ()=> {
console.log('Terminating process')
return process.kill(process.pid);
}), 1000);
})
client.on("packetsend", (payload) => {
console.log("Payload has been sent")
return process.kill(process.pid)
})
client.on("packetreceive", packet => {
console.log("Killing")
//return process.kill(process.pid)
})
client.on("reconnect", ()=>{
console.log("Attempting a reconnect")
//return process.kill(process.pid)
})
client.on("close", ()=>{
console.log("A disconnect occurred")
// return process.kill(process.pid)
})
client.on("offline", () => {
console.log("Client is offline")
//return process.kill(process.pid)
})
I'm not getting any errors when I try to connect to the server. In other words, everything seems to be authenticating properly and I get no error messages, but the client never connects to the Cloud and instead repeatedly tries to reconnect in an endless cycle (which is why I included code to kill the script). I tried going through the Google Cloud troubleshooting page but nothing there really seemed to help. I don't get any sort of errors messages or helpful tidbits of information when using the Cloud SDK like the guide suggested.
I've opened up the port 8883 through my firewall just in case that was the issue but it doesn't appear to be.
I based this code off some of Google's guides and based on this guide here. I have a registry, project, and device all set up with a proper RSA key.
So I'm not really sure how to proceed! If there's any additional information that would help, please let me know.
Thank you.
I realized that when I was creating the project and registry on the Google Console, I actually mistyped the name I was intending (I thought it was "testmqtt" but it was actually "tesmqtt").
So if you're having an issue similar to this, I'd suggest trying the follwing:
Make sure your you've spelled everything right. Make sure the project title is correct, the registry title, etc. It sounds dumb but these types of mistakes happen, so it doesn't hurt to check them first. Otherwise you'll overthink things like I did.
Check out this this page for troubleshooting. There's two parts of this troubleshooting page that might really help you. The first is trying to see if you can actually connect to the cloud at all. You can test if you're able to make a connection by issuing a command like openssl s_client -connect mqtt.googleapis.com:8883 on the command line. You'll need to have openssl downloaded in order to issue that command, however. You can see the page I just linked for more details about testing your connection. The second thing you can do is check to see if you have authentication by running a gcloud command using Google's sdk. The troubleshooting page I linked also has more details in this regard.
This quickstart guide is also particularly helpful. It might be confusing to navigate at first but understanding it will be your best bet.
Google's github repository also has some good examples of how to make an mqtt connection to the cloud IoT core.
DavidC in the comments below helped me find some of these helpful links, so credit should go to him.
Apart from the links I provided in the comment section and as additional to what you have found out, some users use the Project Number instead of the Project ID which leads to a similar concern which you have encountered. It really pays to double check everything in your configuration as you troubleshoot.
If you need to have a refresher about the authentication, you may also refer to this link.

How to implement this in nodejs?

I am new to nodejs and working on a proof of concept just for fun.
Background:
I have a cloud directory of user information (like username, password and other info). This cloud directory can be used to authenticate a user only via restful API (i.e. no direct connectivity using LDAP or JDBC etc.).
Aim:
To build an LDAP interface for this cloud directory. To start with I am interested only in authentication (LDAP bind).
Intended Flow:
LDAPClient initiates a standard LDAP simple BIND request:
Host: host where my nodejs app will run
Port: 1389 (port that my nodejs app will be bound to)
Username: a user from cloud directory
Password: user's password
This request is received by my NodeJS app (I am using ldapjs module).
// process ldap bind operation
myLdapServer.bind(searchBase, function (bindReq, bindRes, next) {
// bind creds
var userDn = req.dn.toString();
var userPw = req.credentials;
console.log('bind DN: ' + req.dn.toString());
...
...
}
Within the above callback, I must use http.request to fire a restful API (POST) to the cloud directory with the details I received from the BIND request (i.e. username, password).
If restful api response status is 200 (auth success), then I must return success to the LDAPClient, else I must return invalid credentials error.
Success:
bindRes.end();
return next();
Failure:
Console.log("returning error");
return next(new ldap.InvalidCredentialsError());
Questions:
Is this possible using NodeJS? Asking because of the nesting involved as evident above (calling of REST API from within a callback). Also since this is an authentication operation, this is meant to be a blocking operation(?)
Thanks,
Jatin
UPDATE:
Thanks Klvs, my solution is more or less like the one you posted. Please have a look at the snippet below:
// do the POST call from within callback
var postRequest = https.request(postOptions, function(postResponse) {
console.log("statusCode: ", postResponse.statusCode);
if(postResponse.statusCode!=200) {
console.log("cloud authentication failed: "+postResponse.statusCode);
return next(ldapModule.InvalidCredentialsError());
} else {
postResponse.on('data', function(d) {
console.info('POST result:\n');
process.stdout.write(d);
console.info('\n\nPOST completed');
});
res.end();
return next();
}
});
// write json data
postRequest.write(postData);
postRequest.end();
postRequest.on('error', function(e) {
console.error("postRequest error occured: "+e);
});
Successful authentication works fine, however, failed authentication does not send any response back to the LDAPClient at all. My client just times out instead of showing authentication failure error. I do see the "cloud authentication failed: " log message on the Node console, which means the below statement is not doing what I intend it do:
return next(ldapModule.InvalidCredentialsError());
Note that the above statement works when I remove the rest call etc, and just return the error back to the client.
Am I missing something?
Thanks,
Jatin
Of course it's possible in nodejs. If I understand you want to make an authenticating request to a server and have it either fail or succeed.
const request = require('request')
// process ldap bind operation
myLdapServer.bind(searchBase, function (bindReq, bindRes, next) {
// bind creds
var userDn = req.dn.toString();
var userPw = req.credentials;
console.log('bind DN: ' + req.dn.toString());
request.post({username: userDn, password: userPw}, (err, res, body)=>{
if(err) {
console.log("returning error");
next(new ldap.InvalidCredentialsError());
} else {
bindRes.end();
next();
}
})
}
Is that what you're looking for? If so, you just need to get accustom to callbacks.

Using JWT tokens. Is there a better approach?

I'm using JWT tokens via nJWT package to authenticate my users to my Socket.io using socket.io-jwt package.
More or less, the code looks like this. User sends a POST reques to play/login via HTML form to generate a JWT token. Then, socket.io client initializes using that token.
/**
* Create Express server.
*/
const app = express();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const socketioJwt = require('socketio-jwt');
app.set('jwt.secret', secureRandom(256, {
type: 'Buffer'
}));
app.post('/play/login', (req, res) => {
// validate user's req.body.email and req.body.password
const claims = {
iss: "http://app.dev", // The URL of your service
sub: "user-1", // The UID of the user in your system
scope: "game"
};
const jwt = nJwt.create(claims, app.get("jwt.secret"));
const token = jwt.compact();
new Cookies(req,res).set('access_token', token, {
httpOnly: true,
secure: process.env.ENVIRONMENT === "production"
});
tokenUserRelations[token] = req.body.email;
res.json({
code: 200,
token: token
});
});
/**
* Add Socket IO auth middleware
*/
io.set('authorization', socketioJwt.authorize({
secret: app.get("jwt.secret"),
handshake: true
}));
io.sockets.on('connection', function (socket) {
socket.on('chat message', function (req) {
io.emit("chat message emit", {
email: tokenUserRelations[socket.handshake.query.token],
msg: req.msg
});
});
socket.on('debug', function (req) {
io.emit("debug emit", {
playersOnline: Object.keys(tokenUserRelations).length
});
});
socket.on('disconnect', function (req) {
delete tokenUserRelations[socket.handshake.query.token];
});
});
io.listen(app.get('socket.port'), () => {
console.log('Started! Socket server listening on port %d in %s mode', app.get('socket.port'), app.get('env'));
});
Right now, it works properly, but in order to track emails from tokens, I had to do this:
tokenUserRelations[token] = req.body.email;
so I can relate which user the token points to.
I have a feeling that keeping token<->email relations in a global object is going to cause me headaches in the future, especially when tokens/cookies expires.
Is there any better way about this? I need to know which user that JWT token points to so I can do some business logic with them.
Thank you.
A token can contain information about anything you want, this information is encrypted along the token.
What you can do is encrypt a user id in the token, when you receive a request, decrypt the token (which is anyway done when you verify it), and use the user id as normal.
This way, if the token expire, the new token will have the same user id, and your code will not be impacted.
This is what I did in one of my web app, and it worked fine. However, I was using the official jwt module
You don't show anything in your code about how tokenUserRelations is created or maintained, but as soon as I hear "global" a red flag goes up in my head.
The JWT standard includes the concept of embedding 'claims' in the token itself; you're already doing so with your claims constant. That data format is arbitrary and can be trusted by your app so long as the overall JWT gets validated. Note that you'll want to verify JWT on every request. So, stuffing email into that claims object is not just fine, it's what most folks do.
As a sidenote, you should be careful about how you're setting your 'jwt.secret' right now. What you have now will generate a new one every time the app starts up, which means that a) all your users will be logged out and have to re-login every time the app restarts, and b) you can't make use of multiple processes or multiple servers if you need to in the future.
Better to pull that from the environment (e.g. an env var) than to generate it on app start, unless you're just doing so for debugging purposes.
Adding to the excellent answers above, it is also important that if you decide to store your jwt.secret in a file and pull that in when the code loads that you do not add that to your git repository (or whatever other VCS you are using). Make sure you include a path to 'jwt.secret' in your .gitignore file. Then when you are ready to deploy your production code you can then set that key as an environment variable as suggested. And you will have a record of that key in your local environment if you ever need to reset it.
Using JWTs is an excellent and convenient way of securing your api, but it is essential to follow best practice.

Chrome Web Store 'You don't have access to licensing data for App ID:'

I have an offline Chrome Web App I want to sell through the web store.
It has a server backend. I'd like the server to check license data. How do I do that?
Here's the API. Here's a Java HowTo. Here's a OAuth-JWT lib for nodejs. I made a new client-id as described in the lib's readme.
I get this response:
{ error:
{ errors: [ [Object] ],
code: 403,
message: 'You don\'t have access to licensing data for App ID: aaaaaaaaaaaaaaaaaaaaaaaaaaaa'
}
}
How do I access license data for my app, in my server?
Mike
Here's the code:
var appId = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
var userId = '1111111111111111111111';
// obtain a JWT-enabled version of request
var request = require('google-oauth-jwt').requestWithJWT();
request({
url: 'https://www.googleapis.com/chromewebstore/v1.1/licenses/'+appId+'/'+userId,
jwt: {
// use the email address of the service account, as seen in the API console
email: '11111111111-aaaaaaaaaaaaaaaaaaaaaaaa#developer.gserviceaccount.com',
// use the PEM file we generated from the downloaded key
keyFile: 'config/keys/app-11111111111111.pem',
// specify the scopes you wish to access - each application has different scopes
scopes: ['https://www.googleapis.com/auth/chromewebstore.readonly']
}
}, function (err, res, body) {
console.log(JSON.parse(body));
});
I've spent way to much time on this and other small problems with the Chrome Webstore. Going with Stripe.com, Braintree, or similar.

Performing simple authentication in Angular and Node

I have been struggling with performing simple authentication in my angular node application. I am well aware that there are ready to use angular-express yeoman kits out there. But I want to understand this fuly and hence the attempt.
What I have been able to do so far is create a login form which connects to node server. It sens login name and password and receives a response back.
Where I am stuck is how to convert this simple interaction into a authentication process.
My Directory structure is as below
--ParentDirectory/
-client/
--css/
--lib/ /*all angular and jquery library files*/
--src/ /* All other angular modules, directives etc */
--app.js
--index.html /* default page associated with app.js
--login.js /*module login is independent of app.js module */
--login.html
-server/
--server.js /*restify code sits here */
app.js is where the main app resides.
So far it looks like :
angular.module('app',['']);
angular.module('app').controller('mainCtrl',function($scope){
$scope.hello = "Hello World";
});
Now First things first.. when a user visits my website i.e index.html page.. they will end up in this app and I would like for them to be re-directed to login.html page if they are not authenticated.
Any clues on how to achieve that ?
Moving on..
Login.html simply asks for a username and password (not showing the code here to keep things compact)
Login.js looks like this:
angular.module('loginApp',['common.webservice'])
.controller('loginCtrl',['$scope','WSLogin','$location','$window','Authen',function($scope,WSLogin,$location,$window,Authen){
$scope.message;
$scope.submit = function(){
var temp = {logonID: $scope.username,password: $scope.password};
WSLogin.save(temp,function(result){
Authen.isLogged = true;
$window.sessionStorage.token = result.token;
$scope.message = result.token;
$location.path("/main");
},function(err){
$scope.message = "Authentication failed. Pls retry";
});
};
$scope.logout = function(){
if (AuthenticationService.isLogged){
Authen.isLogged = false;
delete $window.sessionStorage.token;
$location.path("/");
}
}
}])
.factory('Authen', function() {
var auth = { isLogged :false };
return auth;
});
WSLogin is a resource that connects to the node serve on the path user/authenticate. This webservice is working fine so far.
[not showing the code here to keep things compact]
The server.js file looks like this:
var restify = require('restify');
var server = restify.createServer({
log: log,
name: 'demo'
});
server.listen(12345, function(){
console.log('%s listening at %s', server.name,server.url);
});
server.post('/user/authenticate',function(req,res,next){
if (!(req.params.logonID === "test" && req.params.password === "test")) {
res.send(401, 'Wrong user or password');
return;
}
var profile = {
first_name: 'John',
last_name: 'Doe',
email: 'john#doe.com',
id: 123
};
// we are sending the profile inside the token
res.json({token:profile);
});
How can I patch things up so that this somewhat resembles a authentication system.
I have picked up tips from various blogs, sites etc.. but no luck implementing them.
I understand you want to know how things are built, but I highly recommend picking up Passport to use on the Node/server side for authentication. With that said, in your implementation of login, it might be easier to include a cookie with the token in the response, rather than returning the token in the response. The code can then return a 200 (or 201) response code to indicate the login was successful, but by moving the token to a cookie, the client code doesn't have to deal with sending the token on future requests -- it's sent automatically in a cookie.
Moving on to your client side questions, keep in mind that you would enable security to protect resources on the server side. Anything you put in your client side JavaScript code can be read by anyone. So in the end these APIs that return protected data are the ones that need to be protected. Again, Passport provides an easy way to protect an API, but you could validate that each API request contains this token (or cookie) prior to providing the data.
In the case that the request is not authorized (it doesn't contain the token, or the token is invalid), you can return a 401 (Unauthorized) response. In the client side code, you can check for this and automatically route the user to the Login page.
Using Angular, one pattern to accomplish this is to use httpProvider Interceptors which allow you to hook into each HTTP request. You could check for a responseError with a status of 401, and route them to the Login page.

Resources