Loopback IO OAuth not working - javascript

I am trying to get a https loopback server up and running protected by OAuth. I am using the loopback gateway sample project as a reference. But for some reason I can't get the OAuth piece to work. What I mean is, even after adding in the OAuth bits and pieces, the APIs don't seem to be protected. I get a response back even if there is no token in my request. This is what my server.js looks like
var loopback = require('loopback');
var boot = require('loopback-boot');
var https = require('https');
var path = require('path');
var httpsRedirect = require('./middleware/https-redirect');
var site = require('./site');
var sslConfig = require('./ssl-config');
var options = {
key: sslConfig.privateKey,
cert: sslConfig.certificate
};
var app = module.exports = loopback();
// Set up the /favicon.ico
app.middleware('initial', loopback.favicon());
// request pre-processing middleware
app.middleware('initial', loopback.compress());
app.middleware('session', loopback.session({ saveUninitialized: true,
resave: true, secret: 'keyboard cat' }));
// -- Add your pre-processing middleware here --
// boot scripts mount components like REST API
boot(app, __dirname);
// Redirect http requests to https
var httpsPort = app.get('https-port');
app.middleware('routes', httpsRedirect({httpsPort: httpsPort}));
var oauth2 = require('loopback-component-oauth2')(
app, {
// Data source for oAuth2 metadata persistence
dataSource: app.dataSources.pg,
loginPage: '/login', // The login page url
loginPath: '/login' // The login processing url
});
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
// Set up login/logout forms
app.get('/login', site.loginForm);
app.get('/logout', site.logout);
app.get('/account', site.account);
app.get('/callback', site.callbackPage);
var auth = oauth2.authenticate({session: false, scope: 'demo'});
app.use(['/protected', '/api', '/me', '/_internal'], auth);
app.get('/me', function(req, res) {
// req.authInfo is set using the `info` argument supplied by
// `BearerStrategy`. It is typically used to indicate scope of the token,
// and used in access control checks. For illustrative purposes, this
// example simply returns the scope in the response.
res.json({ 'user_id': req.user.id, name: req.user.username,
accessToken: req.authInfo.accessToken });
});
signupTestUserAndApp();
//var rateLimiting = require('./middleware/rate-limiting');
//app.middleware('routes:after', rateLimiting({limit: 100, interval: 60000}));
//var proxy = require('./middleware/proxy');
//var proxyOptions = require('./middleware/proxy/config.json');
//app.middleware('routes:after', proxy(proxyOptions));
app.middleware('files',
loopback.static(path.join(__dirname, '../client/public')));
app.middleware('files', '/admin',
loopback.static(path.join(__dirname, '../client/admin')));
// Requests that get this far won't be handled
// by any middleware. Convert them into a 404 error
// that will be handled later down the chain.
app.middleware('final', loopback.urlNotFound());
// The ultimate error handler.
app.middleware('final', loopback.errorHandler());
app.start = function(httpOnly) {
if(httpOnly === undefined) {
httpOnly = process.env.HTTP;
}
server = https.createServer(options, app);
server.listen(app.get('port'), function() {
var baseUrl = (httpOnly? 'http://' : 'https://') + app.get('host') + ':' + app.get('port');
app.emit('started', baseUrl);
console.log('LoopBack server listening # %s%s', baseUrl, '/');
});
return server;};
// start the server if `$ node server.js`
if (require.main === module) {
app.start();
}
function signupTestUserAndApp() {
// Create a dummy user and client app
app.models.User.create({username: 'bob',
password: 'secret',
email: 'foo#bar.com'}, function(err, user) {
if (!err) {
console.log('User registered: username=%s password=%s',
user.username, 'secret');
}
// Hack to set the app id to a fixed value so that we don't have to change
// the client settings
app.models.Application.beforeSave = function(next) {
this.id = 123;
this.restApiKey = 'secret';
next();
};
app.models.Application.register(
user.username,
'demo-app',
{
publicKey: sslConfig.certificate
},
function(err, demo) {
if (err) {
console.error(err);
} else {
console.log('Client application registered: id=%s key=%s',
demo.id, demo.restApiKey);
}
}
);
});
}
I don't get any errors when the server starts up. Thoughts?

Got it figured. More information here https://github.com/strongloop/loopback-gateway/issues/17, but basically I had my rest-api middleware not configured right.

Related

Check if header value match

Is it possible to check the header value in Node.js? I would like to create a route that can be accessed only if the user supplies a header and its value matches what is coded. For example, suppose that route expect a header like AccessKey: 12345 so it checks if there is such a header containing such value and if it doesn't match it throws an error. I tried to use something like res.hasHeader() like this:
app.route('/rest/api/here').get((req, res) => {
if (res.hasHeader('AccessKey', '12345')){
res.send('test')
} else {
res.send('Header value doesn\'t match')
}
})
but it only checks if the header exists itself and doesn't check if the value match. Application is mostly for educational purposes so this approach is acceptable, if possible.
I would recommend using a library to parse the result, here is a complete example with comments to explain the parts. As the #Heretic Monkey pointed, out the token is on the request object, but thats not the approach i would use.
// this is standard set of imports in app generated by
// express --no-view
// from package npm i -g express-generator
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
// token 'parsing' library
var bearer = require('express-bearer-token');
// more boilerplate
var app = express();
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// look for the key in headers: { Authorization: AccessKey <your key> }
// this library also has options for query, body, etc...
// https://www.npmjs.com/package/express-bearer-token
app.use(bearer({ headerKey: 'AccessKey' }));
// if present and what you wanted, proceed, else, fail
var protect = (req, res, next) => (
(req.token && req.token === '12345')
? next()
: next(new Error('bad token'))
);
// example protected (can protect a whole router with router.use(protect))
app.get('/protected', protect, (r, s) => s.json({ data: 'api' }));
// example not protected
app.get('/example', (r, s) => s.json({ not: 'protected' }));
// make sure to status 500 to make axios client throw
app.use((error, r, s, n) => s
.status(500)
.json({ error: (error + '') }));
// run the server
var server = app.listen(3000);
// client code (axios works in browser same exact api)
var axios = require('axios');
// wait until server started
setTimeout(async function() {
// you will get status 500 without key on protected route
try {
await axios.get('http://localhost:3000/protected');
console.log('nope, wont see me print')
} catch (e) {
console.log('error for protected no token:', e.response.data.error);
}
// non protected works as expected
var example = await axios.get('http://localhost:3000/example');
console.log('got example data fine: ', example.data);
// for protected, need to supply header
var protected = await axios({
method: 'get',
url: 'http://localhost:3000/protected',
headers: { Authorization: 'AccessKey 12345' }
});
console.log('got protected data fine w/tok: ', protected.data);
// wait for server to shut down then exit the program
server.close(() => console.log('bye'))
}, 500);

Error: Converting circular structure to json. Data received from salesforce with access token

Following is code I am using to connect Slack with Salesforce to fetch usreInfo.
Everything works fine until I call 'whoami' function. I am receiving a JSON file from salesforce with access token and other details.
I am trying to put that JSON file inside mappings object and fetch the values inside it in the 'whoami' function.
access_token: '00D2x000001eSKboj9MZ_C1cMLV5N823jCiHXl.9WAu2gRQJQ_mtkbGWj3Yd.GvCWncFMYTuMd4AROItvGW6unk8fsxqO',
signature: '7DGROVVQDt0fqubf6zQdTXSDtFPEezeVsyDk0kg=',
scope: 'full',
id_token: 'eyJraWQiOiIyMjIiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoianJZeVc3RUVkZ0VOQ3pramNHdVBPQSIsInN1YiI6Imh0dHBzOi8vbG9naW4uc2FsZXNmb3JjZS5jb20vaWQvMDBEMngwMDAwMDFlU0tiRUFNLzAwNTJ4MDAwMDAxNlpsREFBVSIsImF1ZCI6IjNNVkc5N3F1QW1GWkpmVnk5JsOFF3aldtck5ZTEJYMzdoenExWXpJcDNEZFNHbVc2YzZiaHc0b3BpSVdQRFVzMVMiLCJpc3MiOiJodHRwczovL2xvZ2luLnNhbGVzZm9yY2UuY29tIiwiZXhwIjoxNTg4MTQzMjMwLCJpYXQiOjE1ODgxNDMxMTB9.WY9ELT5c65p8UeZ3yPRX-Ph8cc-Z2tprY1lCEv1KVos5XfO6qUNQQ_JmTACb-n6fHH3rWZgnceBMD6qlnXeIFvtmVg_MX3oXzfQKmkuo0x0HTp7mSempxcN6vsPIixoOGkOH-v712G3V36t_TU6ZcMn-Pc0xpSKDgEeFL7egqypiNoqXcm_wd_2tNUeLXwiYml80q7MI6SvHn2XVY0xfh6C5ffl2G4bCc8dckEscERa6edeuIrLA4R1x5v4djdK4X2D_TKTTpI4mX_YzuGkZVPaUsgcuk5jMqo2nF8tL37vOvKHI6qlXBUnlUXeJbh6PlfZi7CHi-uwweJQCmOq_69GwThisFHSvuSh9hreVGUmlFDb79qLnAGe5Y5Nm50h-ec6N2v7CN8pjg54tVHe-WfsKKLELkwLe24ZNZTEMLvHGye8iYXkyb-7sRNC9YKYSK_Ubcwev44JlmaShw4NO0GLqgYz5a_1x_Kz6Uc3MdJd7IsmF1AbgNLppmb09wA6s3_Ov871FmdKe8dAFgIr1xJ7LNZHmzNd1b3kHgGleMA8NAwn1GCZV9ItR1vWxZ24X6tZpICTu6pTQVT9PRHJ17zK6RcNfIuW9yXfM-xO2z7nBu1E2zBtm7ulPNSemUY9edeD5Nf1mnsNtTGiOMHkcMoaIHryrkbZ5YTGPJmlBm3c',
instance_url: 'https://ap17.salesforce.com',
id: 'https://login.salesforce.com/id/00D2x00000SKbEAM/0050000016ZlDAAU',
token_type: 'Bearer',
issued_at: '1588143490'
}
Above is the access token string i am receiving from the salesforce.
var express = require('express');
var request = require('request');
var url = require('url');
var clientId = 'hidde ';
var clientSecret = 'hidden';
var SF_LOGIN_URL = "https://login.salesforce.com";
var SF_CLIENT_ID = "hidden";
var SF_CLIENT_SECRET = "hidden";
var SF_AUTH_CODE;
var SF_ACCESS_TOKEN;
var SF_ACCESS_TOKEN_1;
var SF_DOMAIN;
var slackUserId;
var mappings = {};
// We define the port we want to listen to. Logically this has to be the same port than we specified on ngrok.
const PORT=4390;
// Instantiates Express and assigns our app variable to it
var app = express();
app.enable('trust proxy');
//var server = http.createServer(app);
//Lets start our server
app.listen(PORT, function () {
//Callback triggered when server is successfully listening.
console.log("Example app listening on port " + PORT);
});
var bodyParser = require('body-parser');
app.use(bodyParser.json()); // support json encoded bodies
app.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies
// Route the endpoint that our slash command will point to and send back a simple response to indicate that ngrok is working
app.post('/oauth', function(req, res) {
oauth(req, res);
slackUserId = req.body.user_id;
});
app.get('/oauth', function(req, res) {
// console.log(req.query.state);
oauth_access(req, res);
SF_AUTH_CODE = req.query.code;
});
app.post('/whoami', function(req, res){
console.log("Inside whoami");
if(slackUserId === req.body.user_id){
console.log("Crossed user ID");
userInfo();
res.send(req);
}
});
function oauth(req, res){
res.redirect(200, `${SF_LOGIN_URL}/services/oauth2/authorize?response_type=code&client_id=${SF_CLIENT_ID}&redirect_uri=https://hidden.ngrok.io/oauth&display=touch}`);
}
function oauth_access(req, res){
res.redirect(200, `${SF_LOGIN_URL}/services/oauth2/authorize?response_type=code&client_id=${SF_CLIENT_ID}&redirect_uri=https://hidden.ngrok.io/oauth&display=touch}`);
}
function oauth_access(req, res){
let options = {
url: `${SF_LOGIN_URL}/services/oauth2/token`,
qs: {
grant_type: "authorization_code",
code: req.query.code,
client_id: SF_CLIENT_ID,
client_secret: SF_CLIENT_SECRET,
redirect_uri: `https://hidden.ngrok.io/oauth`
}
};
request.get(options, function (error, response, body) {
if (error) {
console.log(error);
return res.send("error");
}
//Storing the body consisting of access token to the mappings
// SF_ACCESS_TOKEN = mappings[slackUserId].access_token;
//Accessing the access token supplied by the SF
// console.log(mappings[slackUserId].access_token);
let html = `
<html>
<body style="text-align:center;padding-top:100px">
<img src="/images/linked.png"/>
<div style="font-family:'Helvetica Neue';font-weight:300;color:#444">
<h2 style="font-weight: normal">Authentication completed</h2>
Your Slack User Id is now linked to your Salesforce User Id.<br/>
You can now go back to Slack and execute authenticated Salesforce commands.
</h2>
</body>
</html>
`;
res.send(html);
//body is in JSON format so we will try to store it inside global JSON object and access it through out the script!
// slackUserId_value = slackUserId.toString();
// console.log(slackUserId_value);
console.log(typeof(body));
// mappings[slackUserId].body = undefined;
mappings[slackUserId] = JSON.parse(body);
console.log(typeof(mappings));
console.log(mappings[slackUserId]);
// console.log(mappings);
// access_token_string = "access_token";
// console.log(mappings[slackUserId]);
console.log(mappings[slackUserId].access_token);
SF_ACCESS_TOKEN = mappings[slackUserId].access_token;
//SF_ACCESS_TOKEN_1 = SF_ACCESS_TOKEN;
});
};
function userInfo(){
console.log("User Info Function");
// SF_ACCESS_TOKEN = mappings[slackUserId].access_token;
mappings[slackUserId].access_token = undefined;
//console.log(mappings[slackUserId].access_token);
let options = {
url: `https://login.salesforce.com/services/oauth2/userinfo`,
qs: {
oauth_token: SF_ACCESS_TOKEN,
redirect_uri: `https://hidden.ngrok.io/whoami`
}
};
request.post(options, function(error, response, body){
if(error){
console.log(error);
return res.send("error");
}
});
};
Following is the output:
string
object
{
access_token: '00D2x000001eSKb!ARwAv1AWvtCd1.xew2dqgscgNfryydnpPlWbTrC7eQEw0ViEb1v7SWbwcMDHOHiyWQ6a0y9DFJ56.ldKDGfoG',
signature: 'lXynNIOCEF6Qz5m4qsG8dCfucwNmEkx5YeEKRH0=',
scope: 'full',
id_token: 'eyJraWQiOiIyMjIiLCJ0eXAiOiciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoiR1dPdi0xTzJnZl91WnFidkFVOEtYZyIsInN1YiI6Imh0dHBzOi8vbG9naW4uc2FsZXNmb3JjZS5jb20vaWQvMDBEMngwMDAwMDFlU0tiRUFNLzAwNTJ4MDAwMDAxNlpsREFBVSIsImF1ZCI6IjNNVkc5N3F1QW1GWkpmVnk5Mjk5MEgxaS5uaW1nRkRyREZockJsOFF3aldtck5ZTEJYMzdoenExWXpJcDNEZFNHbVc2YzZiaHc0b3BpSVdQRFVzMVMiLCJpc3MiOiJodHRwczovL2xvZ2luLnNhbGVzZm9yY2UuY29tIiwiZXhwIjoxNTg4MTQzNzEyLCJpYXQiOjE1ODgxNDM1OTJ9.aI-wxVy2KLo9GDQDjxffdaZv8BmdftUQkWtI8kpXby8h-iLvNRDLSY4KrDQwJgAQAB2HUUaabftc2u-FeLh99h5Zuu4SGiyGZVPsPZ0Fkf0SjAcLiR4Q9caaYCwvlCkK_PM_v0IqWO413kXkcw2wEqvko-ekOM0bss26Qpiti0pcJntcMBn7XyIiAHhA7Fx5AhF9VnNyJIbI9cIIOFHtY_CoQsBgl-HmM7aYY5zXCUSdjbm4a4hNzV1veE4DL5Oxp5gto3vhRLd198koIaDA3xaIf_nyal6PEcafHfdQ3QlrNnSpigrKkB5Y5-vwY1xLf0EwkQ2bcOzT7l7fPsV1CN4PY3vjUyj1_tgpR_RJMTApM2etTbB1nMFxwTAnTwctVuZfK5fZeTp04XlZOpAyysAUaS9_1uhb6fLn-8CHW3tcPTT0cMqmJ0OgB5Tio_TGNRHb-xbWXxkFHXuatX2Lwj94xo_RfbT4CPYfK9JGQvsyPy8clclwsn-KLW8lx7tYYl2pEQthz9UX14WmgAQ2PzrVgEcTvf-kG4oORZsBvNraBcZVrYSdGUxaicjfoYA3Ue1sXIhyW_TvvRqsgWcXzhO-Qz2DuT6TTxb0tC6zlfvjeShhvnlLcukUfeyZPbuSksALD-ApLEQwAx8AzKZlmQ6N0dj7-CDcmGoG_4f493k',
instance_url: 'https://ap17.salesforce.com',
id: 'https://login.salesforce.com/id/00D2x01eSKbEAM/0052x0000lDAAU',
token_type: 'Bearer',
issued_at: '15881435266'
}
00D2x000001eSKb!ARwAQMguUkqgscgNfryydnpPlWbTrC7eQEw0ViEb1v7SWbwcMDHOHiyWQ6a0y9DFJ56.ldKDGfoG
Inside whoami
Crossed user ID
User Info Function
TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Socket'
| property 'parser' -> object with constructor 'HTTPParser'
--- property 'socket' closes the circle
at JSON.stringify (<anonymous>)
at stringify (/Users/sonagrasumit/Desktop/salckapp/node_modules/express/lib/response.js:1123:12)
at ServerResponse.json (/Users/sonagrasumit/Desktop/salckapp/node_modules/express/lib/response.js:260:14)
at ServerResponse.send (/Users/sonagrasumit/Desktop/salckapp/node_modules/express/lib/response.js:158:21)
at /Users/sonagrasumit/Desktop/salckapp/index.js:51:17
at Layer.handle [as handle_request] (/Users/sonagrasumit/Desktop/salckapp/node_modules/express/lib/router/layer.js:95:5)
at next (/Users/sonagrasumit/Desktop/salckapp/node_modules/express/lib/router/route.js:137:13)
at Route.dispatch (/Users/sonagrasumit/Desktop/salckapp/node_modules/express/lib/router/route.js:112:3)
at Layer.handle [as handle_request] (/Users/sonagrasumit/Desktop/salckapp/node_modules/express/lib/router/layer.js:95:5)
at /Users/sonagrasumit/Desktop/salckapp/node_modules/express/lib/router/index.js:281:22
I have modified credentials for security reasons.

How to set um a Node.JS proxy on AWS Lambda

I was trying to make a Polyfill.io server as a microservice on AWS Lambda, it was supposed to run a JavaScript file on a GET request.
When I run the service locally the call goes through but it returns an undefined object instead of JS file.
I'm running it locally using serverless, my code is based on polyfill.io's github repo
I've modified the service/index.js to be like so:
'use strict';
const express = require('express');
const path = require('path');
const Raven = require('raven');
const morgan = require('morgan');
const shrinkRay = require('./shrink-ray');
const app = express().enable("strict routing");
const one_day = 60 * 60 * 24;
const one_week = one_day * 7;
const one_year = one_day * 365;
app.use(shrinkRay({
brotli: {quality: 11}
}));
let ravenClient;
// Log requests
if (process.env.ENABLE_ACCESS_LOG) {
app.use(morgan('method=:method path=":url" request_id=:req[X-Request-ID] status=:status service=:response-time bytes=:res[content-length]'));
}
process.on('uncaughtException', (err) => {
console.log('Caught exception', err);
});
// Set up Sentry (getsentry.com) to collect JS errors.
if (process.env.SENTRY_DSN) {
const about = require(path.join(__dirname, '../about.json'));
ravenClient = new Raven.Client(process.env.SENTRY_DSN, {
release: about.appVersion || process.env.SENTRY_RELEASE || 'unknown'
});
ravenClient.patchGlobal();
app.use(Raven.middleware.express.requestHandler(ravenClient));
}
// Do not send the X-Powered-By header.
app.disable("x-powered-by");
// Default response headers
app.use((req, res, next) => {
// Ensure our site is only served over TLS and reduce the chances of someone performing a MITM attack.
res.set('Strict-Transport-Security', `max-age=${one_year}; includeSubdomains; preload`);
// Enables the cross-site scripting filter built into most modern web browsers.
res.set('X-XSS-Protection', `1; mode=block`);
// Prevents MIME-sniffing a response away from the declared content type.
res.set('X-Content-Type-Options', `nosniff`);
// Sets content-type
res.set('Content-Type', `application/javascript`);
// Prevents clickjacking by prohibiting our site from being included on other domains in an iframe.
res.set('X-Frame-Options', `sameorigin`);
res.set('Cache-Control', 'public, s-maxage=' + one_year + ', max-age=' + one_week + ', stale-while-revalidate=' + one_week + ', stale-if-error=' + one_week);
res.set('Surrogate-Key', process.env.SURROGATE_KEY || 'polyfill-service');
res.set('Timing-Allow-Origin', '*');
return next();
});
/* Routes */
app.use(require('./routes/api.js'));
app.use(require('./routes/meta.js'));
app.use('/test', require('./routes/test.js'));
if (process.env.RUM_MYSQL_DSN) {
app.use(require('./routes/rum.js'));
}
app.use(/^\/v[12]\/assets/, express.static(__dirname + '/../docs/assets'));
if (process.env.SENTRY_DSN) {
app.use(Raven.middleware.express.errorHandler(ravenClient));
}
module.exports.node = (event, context, callback) => {
const response = {
statusCode: 200,
body: JSON.stringify({
message: 'Go Serverless v1.0! Your function executed successfully!',
input: event,
}),
};
callback(event, app);
};
This is my serverless.yml:
service: serverless-node
provider:
name: aws
region: us-east-1
stage: dev
runtime: nodejs6.10
functions:
node:
handler: service/index.node
events:
- http:
path: node
method: get
I think you need to pass back the response object in the callback in the handler e.g.
callback(null, response);

Firebase Authentication Failure on Node JS: NotAuthorizedError

I'm trying to send a push notification through Firebase/Node JS. I currently have this code running on Heroku.
var Firebase = require('firebase');
var request = require('request');
var express = require('express');
var FCM = require('fcm-node');
var app = express();
app.set('port', (process.env.PORT || 5000));
app.use(express.static(__dirname + '/public'));
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.get('/push', function(request, response) {
response.send("running");
});
app.listen(app.get('port'), function() {
console.log('Node app is running on port', app.get('port'));
});
// Planet Firebase API Key
var API_KEY = "AIz..."; // found at Project Settings > General > Web API Key
var fcm = new FCM(API_KEY);
var config = {
apiKey: "AIz...",
authDomain: "XXX.firebaseapp.com", // Authentication > Sign-In Method > OAuth Redirect Domains
databaseURL: "https://XXX.firebaseio.com/", // Database > Url at the top
storageBucket: "gs://XXX.appspot.com", // Storage > Url at the top
};
var firebase = Firebase.initializeApp(config);
var ref = firebase.database().ref();
function listenForNotificationRequests() {
var requests = ref.child('notifications');
requests.on('child_changed', function(requestSnapshot) {
var objectAdded = requestSnapshot.val();
var uid = receiver["uid"]
var notificationMessage = objectAdded["message"]
sendNotificationToUser(uid, notificationMessage);
}, function(error) {
console.error(error);
});
};
function sendNotificationToUser(receiverID, notificationMessage) {
var message = {
to: '/notifications/'+receiverID,
notification: {
title: "App Title",
body: notificationMessage,
badge: 1
}
};
fcm.send(message, function(err, response){
if (response) {
console.log("Successfully sent with response: ", response);
} else {
console.log("Something has gone wrong! Error: " + err);
}
});
}
// start listening
listenForNotificationRequests();
Whenever fcm.send() is called, I get back:
Something has gone wrong! Error: NotAuthorizedError
This leads be to believe Firebase isn't initializing correctly, but I've checked the links and keys multiple times. What have I done incorrectly?
In your code, I noticed this part (specially the comment):
// Planet Firebase API Key
var API_KEY = "AIz..."; // found at Project Settings > General > Web API Key
var fcm = new FCM(API_KEY);
You're probably receiving an 401 - Authentication Error.
When using FCM, you should always use the Server Key and not the Web API Key. This is also visible in the Firebase Console:
Project Settings > Cloud Messaging > Server Key.
See my answer here for an idea about the different keys.

Jawbone API OAuth access_token handling with node.js (express & passport)

Has anyone successfully navigated Jawbone's OAuth2.0 authentication for their REST API?
I am unable to figure out how to access and send the authorization_code in order to obtain the access_token (steps 4 & 5 in the Jawbone API Authorization Documentation). I want to reuse the access_token for subsequent (AJAX-style) calls and avoid asking the user to reauthorize each time.
Each call of the API (get.sleeps) requires a full round trip of the auth process including this reauthorization to get an authorization_token (screen shot). Both the Jawbone and Passport Documentation is vague on this point.
My stack involves, node.js, the jawbone-up NPM, express.js and passport.js. The Passport Strategy for Jawbone appears to work correctly as I get valid data back.
The jawbone-up NPM explicitly does not help maintain the session (access_token), saying "This library does not assist in getting an access_token through OAuth..."
QUESTION: how do I actually use the OAUTH access_token in the API call? Can someone show me some code to do this?
Thanks
var dotenv = require('dotenv').load(),
express = require('express'),
app = express(),
ejs = require('ejs'),
https = require('https'),
fs = require('fs'),
bodyParser = require('body-parser'),
passport = require('passport'),
JawboneStrategy = require('passport-oauth').OAuth2Strategy,
port = 5000,
jawboneAuth = {
clientID: process.env.JAWBONE_CLIENT_ID,
clientSecret: process.env.JAWBONE_CLIENT_SECRET,
authorizationURL: process.env.JAWBONE_AUTH_URL,
tokenURL: process.env.JAWBONE_AUTH_TOKEN_URL,
callbackURL: process.env.JAWBONE_CALLBACK_URL
},
sslOptions = {
key: fs.readFileSync('./server.key'),
cert: fs.readFileSync('./server.crt')
};
app.use(bodyParser.json());
app.use(express.static(__dirname + '/public'));
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views');
// ----- Passport set up ----- //
app.use(passport.initialize());
app.get('/',
passport.authorize('jawbone', {
scope: ['basic_read','sleep_read'],
failureRedirect: '/'
})
);
app.get('/done',
passport.authorize('jawbone', {
scope: ['basic_read','sleep_read'],
failureRedirect: '/'
}), function(req, res) {
res.render('userdata', req.account);
}
);
passport.use('jawbone', new JawboneStrategy({
clientID: jawboneAuth.clientID,
clientSecret: jawboneAuth.clientSecret,
authorizationURL: jawboneAuth.authorizationURL,
tokenURL: jawboneAuth.tokenURL,
callbackURL: jawboneAuth.callbackURL
}, function(token, refreshToken, profile, done) {
var options = {
access_token: token,
client_id: jawboneAuth.clientID,
client_secret: jawboneAuth.clientSecret
},
up = require('jawbone-up')(options);
up.sleeps.get({}, function(err, body) {
if (err) {
console.log('Error receiving Jawbone UP data');
} else {
var jawboneData = JSON.parse(body).data;
console.log(jawboneData);
return done(null, jawboneData, console.log('Jawbone UP data ready to be displayed.'));
}
});
}));
// HTTPS
var secureServer = https.createServer(sslOptions, app).listen(port, function(){
console.log('UP server listening on ' + port);
});
You weren't too far off, you were already getting the token. To make your code work a few steps are needed:
Add the concept of a "session", data that exists from request to request as a global variable. When you do a full web app use express-sessions and passport-sessions and implement user management. But for now we just add a global for a single user state.
var demoSession = {
accessToken: '',
refreshToken: ''
};
Pass in a user object in the done() of JawboneStrategy. This is because the "authorize" feature of passport is expecting a user to exist in the session. It attaches the authorize results to this user. Since we are just testing the API just pass in an empty user.
// Setup the passport jawbone authorization strategy
passport.use('jawbone', new JawboneStrategy({
clientID: jawboneAuth.clientID,
clientSecret: jawboneAuth.clientSecret,
authorizationURL: jawboneAuth.authorizationURL,
tokenURL: jawboneAuth.tokenURL,
callbackURL: jawboneAuth.callbackURL
}, function(accessToken, refreshToken, profile, done) {
// we got the access token, store it in our temp session
demoSession.accessToken = accessToken;
demoSession.refreshToken = refreshToken;
var user = {}; // <-- need empty user
done(null, user);
console.dir(demoSession);
}));
Use a special page to show the data "/data". Add a route to separate the auth from the display of service.
app.get('/done', passport.authorize('jawbone', {
scope: ['basic_read','sleep_read'],
failureRedirect: '/'
}), function(req, res) {
res.redirect('/data');
}
);
Lastly the Jawbone Up sleeps API is a little tricky. you have to add a YYYYMMDD string to the request:
app.get('/data', function(req, res) {
var options = {
access_token: demoSession.accessToken,
client_id: jawboneAuth.clientID,
client_secret: jawboneAuth.clientSecret
};
var up = require('jawbone-up')(options);
// we need to add date or sleep call fails
var yyyymmdd = (new Date()).toISOString().slice(0, 10).replace(/-/g, "");
console.log('Getting sleep for day ' + yyyymmdd);
up.sleeps.get({date:yyyymmdd}, function(err, body) {
if (err) {
console.log('Error receiving Jawbone UP data');
} else {
try {
var result = JSON.parse(body);
console.log(result);
res.render('userdata', {
requestTime: result.meta.time,
jawboneData: JSON.stringify(result.data)
});
}
catch(err) {
res.render('userdata', {
requestTime: 0,
jawboneData: 'Unknown result'
});
}
}
});
});
I have created a gist that works for me here thats based on your code: https://gist.github.com/longplay/65056061b68f730f1421
The Jawbone access token expires in 1 year so you definitely don't need to re-authenticate the user each time. Also you are provided with a refresh_token as well, so you can refresh the access token when needed.
Once you have the access_token you have to store it somewhere, preferably in some sort of a database or a file storage for later use, then you use that token for each request made to the Jawbone REST API.
The jawbone-up module uses request internally, so I'm going to show you how to make a request with it (it should be pretty much the same with any other module).
Here is how you can get the user's profile (the most basic API call):
var request = require('request')
request.get({
uri:'https://jawbone.com/nudge/api/v.1.1/users/#me',
auth:{bearer:'[ACCESS_TOKEN]'},
json:true
}, function (err, res, body) {
// body is a parsed JSON object containing the response data
})
There is another module called Purest which also uses request internally, but hides some of the complexity around using a REST API. Here is how the same request would look like using that module:
var Purest = require('purest')
var jawbone = new Purest({provider:'jawbone'})
jawbone.get('users/#me', {
auth:{bearer:'[ACCESS_TOKEN]'}
}, function (err, res, body) {
// body is a parsed JSON object containing the response data
})
Alternatively for authenticating the user (getting the access_token) you can use another module called Grant which I personally use, but either one should work.

Categories

Resources