Node.js + Socket.io | Set custom headers on the server - javascript

I use Helmet with Express to set quite some security HTTP headers from the server side. This is nicely done, when rendering client pages on top of the node.js app, using:
var app = express();
app.use(helmet());
..
res.render("pages/index", data);
All the resources on the index page will have the Helmet headers. Unfortunately, socket.io does its own header management. So, anything that comes after /socket.io/ will have insecure/its own headers. For example here:
<https_path>/socket.io/socket.io.js
<https_path>/socket.io/?EIO=3&transport=polling&t=Lj4CFnj&sid=ILskOFWbHUaU6grTAAAA
Hence, I want to set custom headers for all socket.io items manually.
This is how I require socket.io (excerpt only):
/src/app.js
var express = require("express");
var sio = require("socket.io");
var app = express();
var io = require("./../lib/io.js").initialize(app.listen(REST_PORT, () => {
logger.info("Application ready on port " + REST_PORT + " . Environment: " + NODE_ENV);
}));
/lib/io.js
exports = module.exports = {};
var sio = require("socket.io");
exports.initialize = function(server) {
var options = {
cookie: false,
extraHeaders: {
"X-Custom-Header-For-My-Project": "Custom stuff",
}
};
io = sio(server, options);
io.on("connection", function(socket) {
// logic
)};
The "extraHeaders" option doesn´t work, I guess it could only with socket.io-client. I did large amount of googling around, but not luck on this.
Also looked around how to use socket.request (apparently it helps with headers, according to: here), but I couldn´t figure that out either.
Could you guys help?

extraHeaders options will work as below, as you need to remove "transports: ['polling']," in case you are using, and use below pattern. This worked for me, and was able to send custom headers.
package used :- "socket.io-client": "^2.2.0",
this.socket = io(environment.host, {
path: `/api/backend/socket.io`,
origins: '*:*',
// transports: ['polling'],
transportOptions: {
polling: {
extraHeaders: {
'authorization': token,
'user-id' : userId
}
}
}
})
Ref:- https://socket.io/docs/client-api/#With-extraHeaders

Related

How to connect with elasticache using redis in nodejs

I have created a elasticache redis cluster in aws.
Now I have endpoint(url + port)
Url looks "myredis.abc.xyz.cache.amazonaws.com" and port 6379
Now I want to store Key:Value pair in it, like we store in native Redis (redis.set, redis.get)
How to write nodejs code for it, I don't know. I searched a lot but didn't find any right resource.
Anyone can help me in this?
I tried following code but didn't work
ElastiCacheClient,
AddTagsToResourceCommand,
} = require("#aws-sdk/client-elasticache");
const conf = {
host: "myredis.abc.xyz.cache.amazonaws.com",
port: 6379,
region: "us-east-1",
};
const client = new ElastiCacheClient(conf);
const param = { manoj: "kumawat" };
const command = new AddTagsToResourceCommand(param);
const data = await client.send(command);
console.log(data);

Get client IP adress trough Heroku

I have a Heroku app with Cloudflare and I try to display a different version according to the localization of the client.
It works well in developpment but it's not the case in production (the /en is always displayed, not the /fr).
I use the express-ip npm package.
The code:
const express = require('express');
const router = express.Router();
const expressip = require("express-ip");
router.use(expressip().getIpInfoMiddleware);
router.get("/", function ipFrance(req, res) {
const ipInfo = req.ipInfo;
const ipInfoRegion = req.ipInfo.region;
const ipInfoCountry = req.ipInfo.country;
//var message = `Hey, you are browsing from ${ipInfoRegion}, ${ipInfoCountry}`;
if(ipInfoCountry == "FR" || ipInfoRegion == "Wallonia") {
res.redirect("/fr");
} else {
res.redirect("/en");
}
});
module.exports = router;
Don't offer translations based on IP address. There's an HTTP header for that, and an express API method req.acceptsLanguages() which uses that header:
router.get("/", function (req, res) {
if (req.acceptsLanguages("fr")) {
res.redirect("/fr");
} else {
res.redirect("/en");
}
});
Some French-native people may like browsing in English, and other parts of the world may prefer to browse in French. Let them make that decision, don't make it for them.

IBM Watson WebSocket Connection failure. HTTP authentication failed; no valid credentials avaliable

I am working on a speech-to-text web app using the IBM Watson Speech to text API. The API is fetched on the click of a button. But whenever I click the button. I get the above-mentioned error. I Have stored my API key and URL in a .env file.
I tried a lot but keep on getting this error. Please Help me out as I am new to all this.
I got server.js from the Watson Github Repo
Server.js
'use strict';
/* eslint-env node, es6 */
const env = require('dotenv');
env.config();
const express = require('express');
const app = express();
const AuthorizationV1 = require('watson-developer-cloud/authorization/v1');
const SpeechToTextV1 = require('watson-developer-cloud/speech-to-text/v1');
const TextToSpeechV1 = require('watson-developer-cloud/text-to-speech/v1');
const vcapServices = require('vcap_services');
const cors = require('cors');
// allows environment properties to be set in a file named .env
// on bluemix, enable rate-limiting and force https
if (process.env.VCAP_SERVICES) {
// enable rate-limiting
const RateLimit = require('express-rate-limit');
app.enable('trust proxy'); // required to work properly behind Bluemix's reverse proxy
const limiter = new RateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
delayMs: 0 // disable delaying - full speed until the max limit is reached
});
// apply to /api/*
app.use('/api/', limiter);
// force https - microphone access requires https in Chrome and possibly other browsers
// (*.mybluemix.net domains all have built-in https support)
const secure = require('express-secure-only');
app.use(secure());
}
app.use(express.static(__dirname + '/static'));
app.use(cors())
// token endpoints
// **Warning**: these endpoints should probably be guarded with additional authentication & authorization for production use
// speech to text token endpoint
var sttAuthService = new AuthorizationV1(
Object.assign(
{
iam_apikey: process.env.SPEECH_TO_TEXT_IAM_APIKEY, // if using an RC service
url: process.env.SPEECH_TO_TEXT_URL ? process.env.SPEECH_TO_TEXT_URL : SpeechToTextV1.URL
},
vcapServices.getCredentials('speech_to_text') // pulls credentials from environment in bluemix, otherwise returns {}
)
);
app.use('/api/speech-to-text/token', function(req, res) {
sttAuthService.getToken(function(err, token) {
if (err) {
console.log('Error retrieving token: ', err);
res.status(500).send('Error retrieving token');
return;
}
res.send(token);
});
});
const port = process.env.PORT || process.env.VCAP_APP_PORT || 3002;
app.listen(port, function() {
console.log('Example IBM Watson Speech JS SDK client app & token server live at http://localhost:%s/', port);
});
// Chrome requires https to access the user's microphone unless it's a localhost url so
// this sets up a basic server on port 3001 using an included self-signed certificate
// note: this is not suitable for production use
// however bluemix automatically adds https support at https://<myapp>.mybluemix.net
if (!process.env.VCAP_SERVICES) {
const fs = require('fs');
const https = require('https');
const HTTPS_PORT = 3001;
const options = {
key: fs.readFileSync(__dirname + '/keys/localhost.pem'),
cert: fs.readFileSync(__dirname + '/keys/localhost.cert')
};
https.createServer(options, app).listen(HTTPS_PORT, function() {
console.log('Secure server live at https://localhost:%s/', HTTPS_PORT);
});
}
App.js
import React, {Component} from 'react';
import 'tachyons';
//import WatsonSpeech from 'ibm-watson';
var recognizeMic = require('watson-speech/speech-to-text/recognize-microphone');
class App extends Component {
onListenClick = () => {
fetch('http://localhost:3002/api/speech-to-text/token')
.then(function(response) {
return response.text();
}).then(function (token) {
var stream = recognizeMic({
token: token, // use `access_token` as the parameter name if using an RC service
objectMode: true, // send objects instead of text
extractResults: true, // convert {results: [{alternatives:[...]}], result_index: 0} to {alternatives: [...], index: 0}
format: false // optional - performs basic formatting on the results such as capitals an periods
});
stream.on('data', function(data) {
console.log('error 1')
console.log(data);
});
stream.on('error', function(err) {
console.log('error 2')
console.log(err);
});
//document.querySelector('#stop').onclick = stream.stop.bind(stream);
}).catch(function(error) {
console.log('error 3')
console.log(error);
});
}
render() {
return(
<div>
<h2 className="tc"> Hello, and welcome to Watson Speech to text api</h2>
<button onClick={this.onListenClick}>Listen to Microphone</button>
</div>
);
}
}
export default App
Since the only code you show is fetching an authorisation token then I guess that that is what is throwing the authentication failure. I am not sure how old the code you are using is, but the mechanism you are using was used when the STT service credentials are userid / password. The mechanism became unreliable when IAM keys started to be used.
Your sample is still using watson-developer-cloud, but that has been superseded by ibm-watson. As migrating the code to ibm-watson will take a lot of rework, you can continue to use watson-developer-cloud.
If do you stick with watson-developer-cloud and you want to get hold of a token, with an IAM Key then use:
AuthIAMV1 = require('ibm-cloud-sdk-core/iam-token-manager/v1'),
...
tokenService = new AuthIAMV1.IamTokenManagerV1({iamApikey : apikey});
...
tokenService.getToken((err, res) => {
if (err) {
...
} else {
token = res;
...
}
});

Emiting websocket message from routes

I'm trying to setup my server with websockets so that when I update something via my routes I can also emit a websocket message when something on that route is updated.
The idea is to save something to my Mongo db when someone hits the route /add-team-member for example then emit a message to everyone who is connected via websocket and is a part of whatever websocket room that corresponds with that team.
I've followed the documentation for socket.io to setup my app in the following way:
App.js
// there's a lot of code in here which sets what to use on my app but here's the important lines
const app = express();
const routes = require('./routes/index');
const sessionObj = {
secret: process.env.SECRET,
key: process.env.KEY,
resave: false,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection }),
secret : 'test',
cookie:{_expires : Number(process.env.COOKIETIME)}, // time im ms
}
app.use(session(sessionObj));
app.use(passport.initialize());
app.use(passport.session());
module.exports = {app,sessionObj};
start.js
const mongoose = require('mongoose');
const passportSocketIo = require("passport.socketio");
const cookieParser = require('cookie-parser');
// import environmental variables from our variables.env file
require('dotenv').config({ path: 'variables.env' });
// Connect to our Database and handle an bad connections
mongoose.connect(process.env.DATABASE);
// import mongo db models
require('./models/user');
require('./models/team');
// Start our app!
const app = require('./app');
app.app.set('port', process.env.PORT || 7777);
const server = app.app.listen(app.app.get('port'), () => {
console.log(`Express running → PORT ${server.address().port}`);
});
const io = require('socket.io')(server);
io.set('authorization', passportSocketIo.authorize({
cookieParser: cookieParser,
key: app.sessionObj.key, // the name of the cookie where express/connect stores its session_id
secret: app.sessionObj.secret, // the session_secret to parse the cookie
store: app.sessionObj.store, // we NEED to use a sessionstore. no memorystore please
success: onAuthorizeSuccess, // *optional* callback on success - read more below
fail: onAuthorizeFail, // *optional* callback on fail/error - read more below
}));
function onAuthorizeSuccess(data, accept){}
function onAuthorizeFail(data, message, error, accept){}
io.on('connection', function(client) {
client.on('join', function(data) {
client.emit('messages',"server socket response!!");
});
client.on('getmessage', function(data) {
client.emit('messages',data);
});
});
My problem is that I have a lot of mongo DB save actions that are going on in my ./routes/index file and I would like to be able to emit message from my routes rather than from the end of start.js where socket.io is connected.
Is there any way that I could emit a websocket message from my ./routes/index file even though IO is setup further down the line in start.js?
for example something like this:
router.get('/add-team-member', (req, res) => {
// some io.emit action here
});
Maybe I need to move where i'm initializing the socket.io stuff but haven't been able to find any documentation on this or perhaps I can access socket.io from routes already somehow?
Thanks and appreciate the help, let me know if anything is unclear!
As mentioned above, io is in your global scope. If you do
router.get('/add-team-member', (req, res) => {
io.sockets.emit('AddTeamMember');
});
Then every client connected, if listening to that event AddTeamMember, will run it's associated .on function on their respective clients. This is probably the easiest solution, and unless you're expecting a huge wave of users without any plans of load balancing, this should be suitable for the time being.
Another alternative you can go:
socket.io lib has a rooms functionality where you can join and emit using the io object itself https://socket.io/docs/rooms-and-namespaces/ if you have a knack for this, it'd look something like this:
io.sockets.in('yourroom').broadcast('AddTeamMember');
This would essentially do the same thing as the top, only instead of broadcasting to every client, it'd only broadcast to those that are exclusive to that room. You'd have to basically figure out a way to get that users socket into the room //before// they made the get request, or in other words, make them exclusive. That way you can reduce the amount of load your server has to push out whenever that route request is made.
Lastly, if neither of the above options work for you, and you just absolutely have to send to that singular client when they initiate it, then it's going to get messy, because you have to have some sort of id to that person, and since you have no reference, you'd have to store all your sockets upon connection, and then make a comparison. I do not fully recommend something like this, because well, I haven't ever tested it, and don't know what type of repercussions could happen, but here is a jist of an idea I had:
app.set('trust proxy', true)
var SOCKETS = []
io.on('connection', function(client) {
SOCKETS.push(client);
client.on('join', function(data) {
client.emit('messages',"server socket response!!");
});
client.on('getmessage', function(data) {
client.emit('messages',data);
});
});
router.get('/add-team-member', (req, res) => {
for (let i=0; i< SOCKETS.length; i++){
if(SOCKETS[i].request.connection.remoteAddress == req.ip)
SOCKETS[i].emit('AddTeamMember');
}
});
Keep in mind, if you do go down this route, you're gonna need to maintain that array when users disconnect, and if you're doing session management, that's gonna get hairy really really quick.
Good luck, let us know your results.
Yes, it is possible, you just have to attach the instance of socket.io as long as you get a request on your server.
Looking to your file start.js you just have to replace your functions as:
// Start our app!
const app = require('./app');
app.app.set('port', process.env.PORT || 7777);
const io = require('socket.io')(app.app);
const server = app.app.listen(app.app.get('port'), () => {
server.on('request', function(request, response){
request.io = io;
}
console.log(`Express running → PORT ${server.address().port}`);
});
now when you receive an event that you want to emit some message to the clients you can use your io instance from the request object.
router.get('/add-team-member', (req, res) => {
req.io.sockets.emit('addteammember', {member: 6});
//as you are doing a broadcast you just need broadcast msg
....
res.status(200)
res.end()
});
Doing that i also were able to integrate with test framework like mocha, and test the events emited too...
I did some integrations like that, and in my experience the last thing to do was emit the msg to instances in the socket.
As a good practice the very begining of middleware functions i had were doing data validation, data sanitization and cleaning data.
Here is my working example:
var app = require('../app');
var server = require('http').Server(app);
var io = require('socket.io')(server);
io.on('connection', function(client) {
client.emit('connected');
client.on('disconnect', function() {
console.log('disconnected', client.id);
});
});
server.on('request', function(request, response) {
request.io = io;
});
pg.initialize(app.config.DATABASEURL, function(err){
if(err){
throw err;
}
app.set('port', process.env.PORT || 3000);
var server1 = server.listen(app.get('port'), function(){
var host = 'localhost';
var port = server1.address().port;
console.log('Example app listening at http://%s:%s', host, port);
});
});
Your io is actually the socket object, you can emit events from this object to any specific user by -
io.to(userSocketId).emit('eventName', data);
Or you can broadcast by -
io.emit('eventName', data);
Just create require socket.io before using it :)
You can use emiter-adapter to emit data to client in other process/server. It use redis DB as backend for emitting messages.
I did something similar in the past, using namespaces.
Let's say your client connect to your server using "Frontend" as the namespace.
My solution was to create the instance of socket.io as a class in a separate file:
websockets/index.js
const socket = require('socket.io');
class websockets {
constructor(server) {
this.io = socket(server);
this.frontend = new Frontend(this.io);
this.io.use((socket, next) => {
// put here the logic to authorize your users..
// even better in a separate file :-)
next();
});
}
}
class Frontend {
constructor(io) {
this.nsp = io.of('/Frontend');
[ ... ]
}
}
module.exports = websockets;
Then in App.js
const app = require('express')();
const server = require('http').createServer(app);
const websockets = require('./websockets/index');
const WS = new websockets(server);
app.use('/', (req, res, next) => {
req.websocket = WS;
next();
}, require('./routes/index'));
[ ... ]
Finally, your routes can do:
routes/index.js
router.get('/add-team-member', (req, res) => {
req.websocket.frontend.nsp.emit('whatever', { ... });
[ ... ]
});

ERR_SSL_PROTOCOL_ERROR - Socket.io and Node.js

I’m working with the Node.js server and using the Socket.io to manage connections by Socket but I’m having a problem with the SSL certificate.
Lot of users can access the Node.js server normally, but others users doesn’t access and they receive this error:
When I set my server I have this SSL options:
var privateKey = fs.readFileSync('/root/cert/key.key', 'utf8').toString();
var certificate = fs.readFileSync('/root/cert/cert.crt', 'utf8').toString();
var ca = fs.readFileSync('/root/cert/ca.crt').toString();
var credentials = { key: privateKey, cert: certificate, ca: ca };
var app = express();
var httpsServer = https.createServer(credentials, app);
var io = require('socket.io')(httpsServer);
Somebody knows how to resolve this error? The problem is only some users receive this error.
Well, as far as I can see, you are missing a listening port for you https server.
var httpsServer = https.createServer(credentials, app).listen(<port>);
I had so much trouble with ERR_SSL_PROTOCOL_ERROR that I have to give you my solution
NodeJS server 16.15.0 with "socket.io": "2.4.1" (I assume it works with other NodeJS versions)
const path = require("path");
const fs = require("fs");
const io = require("socket.io")();
const folder = path.join(__dirname, "ssl");
const privateKey = fs.readFileSync(path.join(folder, "server_key.pem"), "utf8");
const certificate = fs.readFileSync(path.join(folder, "server_cert.pem"), "utf8");
const optSsl = {
key: privateKey,
cert: certificate,
ca: [certificate],
requestCert: false, // put true if you want a client certificate, tested and it works
rejectUnauthorized: false,
};
const server = require("https");
const webServer = server.createServer(optSsl);
webServer.listen(8025); // port number
io.listen(webServer);
io.on("connection", (client) => {
/*...*/
});
/*...*/
Angular 10.2.7 with "ngx-socket-io": "3.2.0" (I assume it works also with other Angular versions)
import { Socket, SocketIoConfig } from 'ngx-socket-io';
/*...*/
socket: Socket;
socketConfig: SocketIoConfig;
socketConfig = {
url: "https://myserver:8025",
options: {},
};
socket = new Socket(socketConfig);
socket.connect();
Looks like your apache2 is not properly configured.
Check if you have your config file enabled:
a2ensite default-ssl
And then restart the server.

Categories

Resources