How to implement oauth2orize in hapijs - node.js

I implemented oauth2orize in hapijs. But when I am calling the api, nothing happen. The function goes inside code.js file of oauth2orize module and hangs in between. Please suggest me how to implement oauth2orize in hapjs. hapi-oauth2orize is also not working as immigration & hapi-oauth2orize plugin throws option error.
const Hapi = require('hapi');
const server = new Hapi.Server();
const oauth2orize = require('oauth2orize');
var oauth = oauth2orize.createServer();
server.connection({
host: 'localhost',
port: 8000
});
server.register([{
register: require('hapi-mongodb'),
options: dbOpts
}], function (err) {
if (err) {
console.error(err);
throw err;
}
server.start();
server.route([
{
method: 'GET',
path: '/oauth/authorizegrant',
config: {
auth: false,
handler: function(request, reply) {
var clientId = request.query.client_id,
redirectUrl = request.query.redirect_uri,
resType = request.query.response_type,
state = request.query.state;
oauth.grant(oauth2orize.grant.code(function(clientId,redirectUrl,resType,state,callback) {
// Create a new authorization code
console.log('client', client);
var db = request.server.plugins['hapi-mongodb'].db;
var code = new Code({
value: uid(16),
clientId: client._id,
redirectUri: redirectUri,
userId: user._id
});
// Save the auth code and check for errors
db.collection('codes').insert(code, function(err) {
if (err) { console.log('err*********', err); return callback(err); }
callback(null, code.value);
});
}));
}
}
},
]);
});

You need to change parameters passed to oauth.grant function, the callback should be removed and replaced by hapi's reply function. A simple snippet would be
if (err) {
return reply(err);
}
return reply(code.value);
I would file an issue in the plugin repo as this is the best way to interface between hapi and oauth2orize.

Related

How can I make my Hapi route wait for data before returning a value?

I am using Hapi.js and have a route that I want to use to fetch data and then return a result.
I have tried to use async/await, but I must be doing something wrong because while the function I am calling eventually prints a result to the console, the route is returning without waiting for that function to return a value.
'use strict';
const Hapi = require('#hapi/hapi');
const HandyStorage = require('handy-storage');
var ethBalance ='';
// Connection to public blockchain via Infura.io
const Web3 = require("web3");
const web3 = new Web3(new Web3.providers.HttpProvider("https://mainnet.infura.io/v3/cf44bc52af3743bcad5f0b66813f8740"));
// Initialize Handy Storage
const storage = new HandyStorage({
beautify: true
});
//Get ETH address from Handy Storage
storage.connect('./preferences.json');
var walletAddress = storage.state.wallet;
// Get wallet balance
const getWalletBalance = async () => {
web3.eth.getBalance(`${walletAddress}`, async function(err, result) {
if (err) {
console.log('There was an error: ' + err);
return ({ error: 'The wallet balance call failed.' });
} else {
ethBalance = await web3.utils.fromWei(result, "ether");
console.log("This should be first: The wallet balance via API call is " + ethBalance + " ETH.");
return ethBalance; // I expect the walletbalance route to wait for this to be returned
}
});
};
// API Server
const init = async () => {
// Connection settings
const server = Hapi.server({
port: 3000,
host: 'localhost'
});
// Get wallet balance
server.route({
method: 'GET',
path: '/walletbalance/',
handler: async (request, h) => {
let result = null;
try {
result = await getWalletBalance();
console.log('This should be second, after the getWalletBalance function has printed to the console.'); // this prints first, so await isn't working as expected
return ({ ethBalance: result });
} catch (err) {
console.log('Error in walletbalance route');
}
}
});
// 404 error handling
server.route({
method: '*',
path: '/{any*}',
handler: function (request, h) {
return ({
message: 'Error!'
});
}
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
process.on('unhandledRejection', (err) => {
console.log(err);
process.exit(1);
});
init();
Any idea where I have gone wrong here? This is the first time I have used async/await.
ETA: My console looks like this:
[nodemon] starting `node index.js`
Server running on http://localhost:3000
This should be second, after the getWalletBalance function has printed to the console.
This should be first: The wallet balance via API call is 4061.894069996147660079 ETH.
And this is the JSON I get back when I use the wallet balance route:
{}
Based on the answer I was given, I was able to get the results I wanted with this:
'use strict';
const Hapi = require('#hapi/hapi');
const HandyStorage = require('handy-storage');
var ethBalance ='';
// Connection to public blockchain via Infura.io
const Web3 = require("web3");
const web3 = new Web3(new Web3.providers.HttpProvider("https://mainnet.infura.io/v3/cf44bc52af3743bcad5f0b66813f8740"));
// Initialize Handy Storage
const storage = new HandyStorage({
beautify: true
});
//Get ETH address from Handy Storage
storage.connect('./preferences.json');
var walletAddress = storage.state.wallet;
// Get wallet balance
async function getWalletBalance(){
let ethBalance = await web3.eth.getBalance(`${walletAddress}`);
if (ethBalance.err) {
console.log('error in the called function');
} else {
return ethBalance;
}
}
// API Server
const init = async () => {
// Connection settings
const server = Hapi.server({
port: 3000,
host: 'localhost'
});
// Get wallet balance
server.route({
method: 'GET',
path: '/walletbalance/',
handler: async (request, h) => {
try {
const result = await getWalletBalance();
const ethBalanceInWei = web3.utils.fromWei(result, "ether");
return ({ balance: ethBalanceInWei });
} catch (err) {
console.log('Error in walletbalance route');
}
}
});
// 404 error handling
server.route({
method: '*',
path: '/{any*}',
handler: function (request, h) {
return ({
message: 'Error!'
});
}
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
process.on('unhandledRejection', (err) => {
console.log(err);
process.exit(1);
});
init();
Thank you for the help! That got me going in the right direction.
Basically your getWalletBalance function is using multiple concepts. callback style functions and inside that you are using await. I have restructured your code a little bit. Hopefully that should fix the issue which you are facing.
'use strict';
const Hapi = require('#hapi/hapi');
const HandyStorage = require('handy-storage');
var ethBalance ='';
// Connection to public blockchain via Infura.io
const Web3 = require("web3");
const web3 = new Web3(new Web3.providers.HttpProvider("https://mainnet.infura.io/v3/cf44bc52af3743bcad5f0b66813f8740"));
// Initialize Handy Storage
const storage = new HandyStorage({
beautify: true
});
//Get ETH address from Handy Storage
storage.connect('./preferences.json');
var walletAddress = storage.state.wallet;
function getWalletBalance() {
return Promise((resolve, reject) => {
web3.eth.getBalance(`${walletAddress}`, (err, result) => {
if (err) {
console.log('There was an error: ' + err);
reject({ error: 'The wallet balance call failed.' });
} else {
resolve(result);
}
});
});
}
// API Server
const init = async () => {
// Connection settings
const server = Hapi.server({
port: 3000,
host: 'localhost'
});
// Get wallet balance
server.route({
method: 'GET',
path: '/walletbalance/',
handler: async (request, h) => {
try {
const result = await getWalletBalance();
ethBalance = await web3.utils.fromWei(result, "ether");
return ethBalance;
} catch (err) {
console.log('Error in walletbalance route');
}
}
});
// 404 error handling
server.route({
method: '*',
path: '/{any*}',
handler: function (request, h) {
return ({
message: 'Error!'
});
}
});
await server.start();
console.log('Server running on %s', server.info.uri);
};
process.on('unhandledRejection', (err) => {
console.log(err);
process.exit(1);
});
init();

How to save a text to speech audio file client side?

desired behaviour
allow user to download text to speech audio file by clicking a button, like this official demo:
https://text-to-speech-starter-kit.ng.bluemix.net
what i've tried
i am using:
https://github.com/watson-developer-cloud/node-sdk
i can generate an audio file server side but can't figure out how to send that file back to the client for them to save - so i am trying to generate it client side instead.
attempt 01: generate audio file server side
server.js (works)
const fs = require('fs');
const TextToSpeechV1 = require('ibm-watson/text-to-speech/v1');
const textToSpeech = new TextToSpeechV1({
iam_apikey: '{apikey}',
});
const synthesizeParams = {
text: 'Hello world',
accept: 'audio/wav',
voice: 'en-US_AllisonVoice',
};
textToSpeech.synthesize(synthesizeParams)
.then(audio => {
audio.pipe(fs.createWriteStream('hello_world.wav'));
})
.catch(err => {
console.log('error:', err);
});
for reference, according to the docs, the .synthesize() method response type is:
NodeJS.ReadableStream|FileObject|Buffer
attempt 02: generate audio file client side
server.js - required to get token (works)
var AuthorizationV1 = require('ibm-watson/authorization/v1');
var iam_apikey = local_settings.TEXT_TO_SPEECH_IAM_APIKEY;
var url = local_settings.TEXT_TO_SPEECH_URL;
var authorization = new AuthorizationV1({
iam_apikey: iam_apikey,
url: url
});
const api_tts_token_get = async (req, res) => {
authorization.getToken(function(err, token) {
if (!token) {
console.log('error:', err);
} else {
res.json({ token: token, url: url });
}
});
}
app.route("/api/:api_version/text-to-speech/token")
.get(api_tts_token_get);
client.js (doesn’t work)
var TextToSpeechV1 = require('ibm-watson/text-to-speech/v1');
const get_token = (parameters) => {
$.ajax({
url: "/api/v1/text-to-speech/token",
data: parameters,
dataType: 'json',
cache: false,
headers: headers,
success: function(results) {
var token = results.token;
var url = results.url;
var textToSpeech = new TextToSpeechV1({ token: token, url: url });
var synthesizeParams = {
text: 'hello world!',
accept: 'audio/wav',
voice: 'en-US_AllisonV3Voice'
};
textToSpeech.synthesize(synthesizeParams, function(err, result) {
if (err) {
return console.log(err);
}
console.log(result);
});
},
statusCode: {
500: function() {
console.log("that didn't work");
}
}
});
}
webpack.config.js
added per instructions at:
https://github.com/watson-developer-cloud/node-sdk/tree/master/examples/webpack#important-notes
node: {
// see http://webpack.github.io/docs/configuration.html#node
// and https://webpack.js.org/configuration/node/
fs: 'empty',
net: 'empty',
tls: 'empty'
},
chrome dev tools errors:
xhr.js:108 Refused to set unsafe header "User-Agent"
The provided value 'stream' is not a valid enum value of type XMLHttpRequestResponseType.
Access to XMLHttpRequest at 'https://***.watsonplatform.net/text-to-speech/api/v1/synthesize?voice=en-US_AllisonV3Voice'
from origin 'http://localhost:3000' has been blocked by CORS policy:
Request header field x-ibmcloud-sdk-analytics is not allowed by
Access-Control-Allow-Headers in preflight response.
Error: Response not received. Body of error is HTTP ClientRequest object
at RequestWrapper.formatError (requestwrapper.js:218)
at eval (requestwrapper.js:206)
Here is one solution i have figured out.
It generates the audio file server side and sends it back via res.download().
The only caveat is that you can't use $.ajax() but rather something like:
window.open("/api/v1/audio?file_id=12345");
server.js
var TextToSpeechV1 = require('ibm-watson/text-to-speech/v1');
const api_audio_get = async (req, res) => {
var query_parameters = req.query;
var file_id = query_parameters.file_id;
var textToSpeech = new TextToSpeechV1({
iam_apikey: local_settings.TEXT_TO_SPEECH_IAM_APIKEY,
url: local_settings.TEXT_TO_SPEECH_URL
});
const synthesizeParams = {
text: 'here is test voice',
accept: 'audio/wav',
voice: 'en-US_AllisonV3Voice',
};
textToSpeech.synthesize(
synthesizeParams,
function(err, audio) {
if (err) {
console.log(err);
return;
}
// see: https://stackoverflow.com/a/46413467
// this allows you to create temp file on server, send it, then delete it
var filename = file_id + ".wav";
var absPath = path.join(__dirname, "/my_files/", filename);
var relPath = path.join("./my_files", filename); // path relative to server root
// see: https://nodejs.org/en/knowledge/advanced/streams/how-to-use-fs-create-write-stream/
var write_stream = fs.createWriteStream(relPath);
// audio is written to the writestream
audio.pipe(write_stream);
// see: https://stackoverflow.com/questions/19829379/detecting-the-end-of-a-writestream-in-node
write_stream.on('finish', function() {
res.download(absPath, (err) => {
if (err) {
console.log(err);
}
fs.unlink(relPath, (err) => {
if (err) {
console.log(err);
}
console.log("FILE [" + filename + "] REMOVED!");
});
});
});
}
);
}
// route handler
app.route("/api/:api_version/audio")
.get(api_audio_get);
client.js
$(document).on("click", ".download_audio", function() {
window.open("/api/v1/audio?file_id=12345");
});

How to get result of validateFunc in pre of auto created API Rest Hapi

I am new to Hapi.js.I am using "hapi-auth-jwt2" module for authentication token and role verification. I set the scope and sent that scope from the callback of validateFunc . It will worked very well for checking te role based authentication. But i want the result i am returning from the validateFunc but don't know where i can get that.
validateFunc: function (token, request, callback) {
Async.auto({
session: function (done) {
Session.findByCredentials(token.sessionId, token.sessionKey, done);
},
user: ['session', function (results, done) {
if (!results.session) {
return done();
}
User.findById(results.session.user, done);
}],
}, (err, results) => {
if (err) {
return callback(err);
}
if (!results.session) {
return callback(null, false);
}
results.scope = token.scope;
callback(null, Boolean(results.user), results);
});
}
});
};
`
It verify the scope or Role in the domain i.e:-
routeOptions: {
scope:{
createScope:"admin"
},
create: {
pre : function(payload, Log){
console.log("preee runnnig........");
console.log(payload);
}
}
I am getting the payload Json what i am sending from the client side but i want the results i am sending from the callback of validateFunc, because i want to use that data here in pre prior to send the request.I am working on implicitly created API via Rest Hapi Module.
So how can i get that datain pre hooks from the validateFunc . Any help is much appreciated.
Thanks
This is actually a feature that is being worked on and hopefully will be done soon.
For now, you can omit the generated create endpoint and replace it with your own in order to access the request object.
The resulting code would look something like this:
'use strict';
const RestHapi = require('rest-hapi');
module.exports = function (server, mongoose, logger) {
server.route({
method: 'POST',
path: '/pathName',
config: {
handler: function(request, reply) {
/* modify request.payload here and access auth info through request.auth.credentials */
const Model = mongoose.model('modelName');
return RestHapi.create(Model, request.payload, logger)
.then(function (result) {
return reply(result);
})
.catch(function (error) {
return reply(error);
});
},
tags: ['api'],
plugins: {
'hapi-swagger': {}
}
}
});
};

SocketCluster Middleware HandShake with promise

Im building an app that serve both http and ws. Users login first over HTTP to a Laravel Server. That returns a JWT that is used to allow login over WS.
Ihv added a MIDDLEWARE_HANDSHAKE that gets the token and make a request to Laravel Server to ask if that token is valid and the user has access to WS (Not every logged user is allowed to WS);
Client code:
var options = {
host: '127.0.0.1:3000',
query: {
source: 'web',
token: '',
}
};
var socket;
$.post('http://127.0.0.1:8000/authenticate', {
email: 'chadd01#example.org',
password: '1234'
}, function(data, textStatus, xhr) {
options.query.token = data.token;
//ALL PERFECT UNTILL HERE
// Initiate the connection to the ws server
socket = socketCluster.connect(options)
.on('connect', function(data) {
console.log('CONNECTED', data);
})
.on('error', function(data) {
console.log('ERROR', data.message);
});
});
SocketCluster Server code:
scServer.addMiddleware(scServer.MIDDLEWARE_HANDSHAKE, function(request, next) {
var query = url.parse(request.url, true).query;
switch (query.source) {
case 'web':
case 'mobile-app':
validateUser(query)
.then((response) => {
next(); //Allowed
})
.catch((code) => {
next(code); //Blocked with StatusCode
});
break;
default:
next(true, 'NOT_AUTHORIZED'); // Block
break;
}
});
validateUser = (credentials = {}) => {
return new Promise((resolve, reject) => {
request({ url: API + 'webSocket/users/' + credentials.token, method: 'GET' }, (error, response, body) => {
if (response.statusCode === 200) {
resolve(body);
}
reject(response.statusCode);
});
});
};
While implementing this middleware like this i keep getting this response from ws server even when validation is successfull:
WebSocket connection to 'ws://127.0.0.1:3000/socketcluster/?source=web&token=<_TOKEN_>' failed: Connection closed before receiving a handshake response
(index):149 ERROR Socket hung up
But, if i implement the HANDSHAKE_MIDDLEWARE like this:
scServer.addMiddleware(scServer.MIDDLEWARE_HANDSHAKE, function(request, next) {
var validUser = true;
if (validUser){
return next();
}
return next('NOT_A_VALID_USER');
});
All goes fine:
CONNECTED Object {id: "W067vqBc9Ii8MuIqAAAC", pingTimeout: 20000, isAuthenticated: true, authToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbiI6I…xOTV9.E4bLPh4Vjk9ULvfhW6prjBbVt0vOD32k63L1vlDtGrU"}
So the problem seems to be in the Promise callback.
Any advice if this is not the right way to implement?
Thanks.
A big reason why JWT is used on SocketCluster is to handle logging in and authentication, have you considered just using WS?
Take a look at SocketCluster authentication.
Just how your current HTTP code check the login data, you can do the same for WS and use socket.setAuthToken to set the token (here is an example that I used in my project):
socket.setAuthToken({
email: credentials.email,
id: returnedData.id,
permission: returnedData.permission
});
You can then do requests to the WS server still using on/emit, and do a check to see if they are authenticated. Here's a modified snippet of my authCheck function:
const token = socket.getAuthToken();
if (token && token.email) {
console.log('Token Valid, User is: ', token.email);
// user validated - continue with your code
} else {
console.log('Token Invalid, User not authenticated.');
// respond with error message to the user
}

How to write a setup for an NPM package

I am writing my first node.js package to help make a REST API easier to use and I am having trouble formatting the package to allow the person using the package to do the below in their applications.
var Water= require("water-reservation");
var water = Water({username: myusername, password: mypassword});
// Problem here because Water({}) needs to do a REST call to get a bearer
// token to use the API I am trying to simplify. Thus, it tries to
// get_water(callabck) before the bearer_token var is set in my package.
water.get_water(function(err, result){
if(err){
console.log(err);
console.log("----------------------------------------------");
}
else{
console.log(result);
console.log("----------------------------------------------");
}
});
In my current setup I take the user's username and password and pass it to a REST endpoint to get a bearer token that I use in all my REST calls for the package. The async nature will call get_water in the users file before I am done with setting the bearer_token variable in my package.
Here is what my package looks like:
var request = require('request');
var bearer_token = "";
var api_url = "";
var Water = function(credentials){
api_url = credentials.api_url;
var username = credentials.username;
var password = credentials.password;
get_bearer_token(username, password, function(err, access_token){
bearer_token = access_token;
});
};
function get_bearer_token(username, password, callback){
var request_options = {
url: api_url + "/auth",
method: "GET",
'auth': {
'user': username,
'pass': password
}
};
request(request_options, function(err, res, body){
if(err) {
return callback("Water Initialization Error: " + err, null);
}
else {
return callback(null, body);
}
});
}
// Get water
Water.prototype.get_water = function(callback) {
var request_options = {
url: api_url + "/water",
method: "GET",
'auth': {
'bearer': bearer_token
}
};
request(request_options, function(err, res, body){
if(err) {
return callback(err, null);
}
else{
return callback(null, body);
}
});
};
// more package functions...
module.exports = Water;
I am trying to find a way to make the setup so the user can use the package as described above. I am not set on that style, but it seems to be the easiest for a user to understand and use.
The only way I could think of fixing this is add a callback to Water({}) but that would make a lot of mess having the user wrap all their water related code in the callback. I know it can be done by looking at the Twitter package for example, but I still could wrap my mind around how they did it. Any help would be appreciated, thanks.
You should delegate the responsibility of getting the token to when they make a request. That way the package is initialised instantly. To make it more efficient you can cache the token the first time it is fetched. I've made an example here, but you could tidy it up using something like async:
var request = require('request');
var Water = function (credentials) {
this.api_url = credentials.api_url;
this.username = credentials.username;
this.password = credentials.password;
return this;
};
Water.prototype.get_bearer_token = function (callback) {
// We already have the bearer token, so return straight away
if (this.bearer_token) return callback(null, this.bearer_token);
var self = this;
var request_options = {
url: this.api_url + "/auth",
method: "GET",
'auth': {
'user': this.username,
'pass': this.password
}
};
request(request_options, function(err, res, body){
if(err) {
return callback("Water Initialization Error: " + err, null);
}
else {
self.bearer_token = body;
return callback(null, body);
}
});
}
// Get water
Water.prototype.get_water = function(callback) {
var self = this;
this.get_bearer_token(function (err, token) {
if (err) return callback(err);
var request_options = {
url: self.api_url + "/water",
method: "GET",
'auth': {
'bearer': token
}
};
request(request_options, function(err, res, body){
if(err) {
return callback(err, null);
}
else{
return callback(null, body);
}
});
});
};
// more package functions..
module.exports = Water;
To use it:
var Water = require('water-reservation');
var water = new Water({
username: 'username',
password: 'pass',
api_url: 'http://example.com'
});
water.get_water(function (err, res) {
});

Resources