Force share.js use the same express session data - node.js

I have a simple express app that use session middleware together with passport-local middleware. Then I use share.js with browserchannel to stream data to server via share.listen(stream). All in align with documentation here.
My problem is that I cannot access session data (modified by passport-local and containing userID that was logged in) within stream. I need it to be able to restrict/grant access within client.on('message', function(data) {..}); based on some logic, but what of first importance is to check that the message came from logged in user. There, if I try to read ID it will be different from what potencialy is inside req.user._id. It seems that there share.js or browserchannel uses some different session, maybe?..
Here's the code:
var app = express();
var express = require('express');
...
// SETUP AND INIT
app.use(cookieParser());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true,
limit: 1024 * 1024 * 10
}));
app.use(methodOverride());
app.use(session({
secret: global.CONFIG.session.secret,
maxAge: new Date(Date.now() + 1000 * 60 * 60 * 24 * 2),
store: new MongoStore(global.CONFIG.mongo),
resave: true,
saveUninitialized: true
}));
app.use(express.static(__dirname + '/build'));
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());
// Create the sharejs server instance.
var backend = livedb.client(livedbMongo(global.CONFIG.mongo.url, false));
var share = sharejs.server.createClient({
db: backend
});
app.use(browserChannel(function(client) {
var stream = new Duplex({objectMode: true});
stream._write = function(chunk, encoding, callback) {
if (client.state !== 'closed') {
client.send(chunk);
}
callback();
};
stream._read = function() {
};
stream.headers = client.headers;
stream.remoteAddress = stream.address;
client.on('message', function(data) {
console.log(client.id) // <- I wish it was the same as in req.user._id..
stream.push(data);
});
stream.on('error', function(msg) {
client.stop();
});
client.on('close', function(reason) {
stream.emit('close');
stream.emit('end');
stream.end();
});
// Actually pass the stream to ShareJS
share.listen(stream);
}));

It seems to me, from looking at the code, that there might be a solution that won't require hacking the module:
var browserChannel = require('browserchannel').server;
var middleware = browserChannel(options, function(session, req) {
if (req.user) {
session.user = req.user;
}
});
app.use(middleware);
See here.

I have the same problem and I solved it by wrapping the browserchannel middleware constructor in a custom constructor:
function myMiddlewareConstructor () {
var request;
var bcMiddleware = browserChannel(function (client) {
//here you see the request
});
return function (req,res,next) {
request = req;
bcMiddleware(req,res,next);
}
}
app.use(myMiddlewareConstructor());
It avoids having to change the browserchannel code.

After several days of inspecting the code I have found a solution. If we look at this line in browserchannel/dist/server.js we can see that the session is being created using some information from initial request. We can modify this part of code by adding
session = createSession(req.connection.remoteAddress, query, req.headers);
// ----------- we add this ------------
session.user = {};
if( req.user )
session.user = req.user;
// ------------------------------------
This will add user session details from initial request to the session variable.

Related

How to only allow one user connection alive at the same time with Nodejs and Expressjs?

I just need to manage the concurrence on my app built with nodejs on the top of the nestjs framework.
As a far as I know, the most simplest way to do that is controlling that online one session user in expressjs is alive.
I am not taking care about security or whatever other issues, just want to know how many users are connected and restricting it to only one user session till its session is expired.
Here is my codebase
var express = require('express');
var session = require('express-session');
var app = express();
var numConnections = 0;
app.use(session({
cookieName: 'sessionTest',
secret: 'eg[isfd-8yF9-7w2315df{}+Ijsli;;to8',
cookie: {
secure: false,
maxAge: 1000 * 10,
sameSite: true
}
}));
app.use((req, res, next) => {
console.log(req.session.store)
console.log(req.session.ip)
console.log(req.session.useragent)
console.log(req.connection.remoteAddress)
console.log(req.headers['user-agent'])
if (numConnections === 0
// && req.session
) {
req.session.ip = req.connection.remoteAddress;
req.session.useragent = req.headers['user-agent'];
req.session.page_views = 1;
res.send("Welcome to this page for the first time!");
numConnections++;
console.log(req.session);
next();
}
else if (numConnections == 1 &&
req.session.ip === req.connection.remoteAddress
&& req.session.useragent === req.headers['user-agent']
) {
req.session.page_views++;
res.send("You visited this page " + req.session.page_views + " times");
console.log('TEST');
next();
} else {
console.log('There is someone using the app!!!');
return res.sendStatus(401);
}
})
app.listen(3001);
I really appreciate if someone can help me
You could use the store to retrieve the current amount of open sessions. The doc says stores may implement length and/or all methods. However, it appears that only the default MemoryStore handles these. You can look at all the compatible store implementations at the bottom of the page and pick the one that fits your environment.
It probably (i.e. not tested) looks like this:
var session = require('express-session');
var memoryStoreThatWillBeChangedBeforeLiveEnvironment = new MemoryStore();
...
app.use(session({
...
store: memoryStoreThatWillBeChangedBeforeLiveEnvironment
}));
app.use((req, res, next) => {
memoryStoreThatWillBeChangedBeforeLiveEnvironment.length((err, size) => {
if (err) return res.status(418).send("I'm a teapot");
var numConnections = size;
// call your code here
})
});
(Quite obviously, if an implementation only offers the all method, you can count the returned array of sessions.)

Cannot set Express session from inside callback

I know there's a few questions similar to this already, but they all have crucial differences which do not give me the solution.
I'm using client-session module. I've set everything up exactly like it's specified in the docs, but for some reason I can't set session values from async callback.
Here is my code
router.get('/auth', function(req, res, next) {
var params = req.query;
var act = params.act;
switch (act) {
case Constants.ACT_LOGIN_CHECK:
console.log('SESSION CHECK >>> ', JSON.stringify(req.session), req.session.isNew);
res.send(`SESSION CHECK >>> ${JSON.stringify(req.session)} ${req.session.isNew}`);
break;
case Constants.ACT_LOGIN_REQUEST:
var code = params.code;
var state = params.state;
login(code, state, function(result) {
if (result) {
if (result.accessToken) {
// console.log("WRITING SESSION BEFORE: ", JSON.stringify(req.session), req.session.isNew);
req.session.accessToken = result.accessToken;
req.session.userId = result.userId;
req.session.firstName = result.firstName;
req.session.lastName = result.lastName;
// req.session.photoURL = result.photoURL;
req.session.save();
console.log(`SESSION SET >>> ${JSON.stringify(req.session)} ${req.session.isNew}`);
res.send(`SESSION SET >>> ${JSON.stringify(req.session)} ${req.session.isNew}`);
}
} else {
res.end();
}
});
break;
case Constants.ACT_LOGOUT_REQUEST:
req.session = null;
res.send(`LOGGED OUT, SESSION >>> ${JSON.stringify(req.session)}`);
console.log('LOGGED OUT, SESSION >>> ', JSON.stringify(req.session));
break;
}
});
As you can see, I'm trying to set the session from callback function passed as third argument to my login() function. I can't say it doesn't work at all but it works, like, one time in a million. I'm not finilazing the response before the session is set, but anyway, when I check the session, this is what i get in the vast majority of cases:
SESSION CHECK >>> {} true
Even though this output
Is also shown all the time I set the session.
But if I simplify the code to this:
router.get('/auth', function(req, res, next) {
var params = req.query;
var act = params.act;
if (act == Constants.ACT_LOGIN_CHECK) {
console.log('SESSION CHECK >>> ', JSON.stringify(req.session), req.session.isNew);
res.send(`SESSION CHECK >>> ${JSON.stringify(req.session)} ${req.session.isNew}`);
} else if (act == Constants.ACT_LOGIN_REQUEST) {
req.session.accessToken = "595a751fa174ecbda109b14339b827941b58f7d0b10b495d0f85819e749e0b42c320dcda71520342cd020";
req.session.userId = 2195783;
req.session.firstName = "John";
req.session.lastName = "Doe";
req.session.save();
console.log('SETTING SESSION >>> ', JSON.stringify(req.session), req.session.isNew);
res.send(`SESSION SET >>> ${JSON.stringify(req.session)} ${req.session.isNew}`);
} else {
req.session = null;
console.log(`SESSION KILLED >>> ${JSON.stringify(req.session)}`);
res.send(`SESSION KILLED >>> ${JSON.stringify(req.session)}`);
}
}
It all works like a charm. The session is set / cleared / checked every time I want, with no errors or bugs.
I've been struggling with this issue for 3 days now :(. I've also tried different modules like client-sessions and several others. The same result. Seems like the problem is deeper.
So is it possible at all to save session from async callback? I can't set it until I get the necessary data from social network
p.s. Here is the actual initialization code from app.js
var Constants = require('./constants');
var express = require('express');
var path = require('path');
var app = express();
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieSession = require('./cookiesession');
var bodyParser = require('body-parser');
const user = require('./routes/user');
const auth = require('./routes/auth');
const posting = require('./routes/posting');
const messages = require('./messages');
const mongo = require('./mongo');
app.use(express.static(path.join(__dirname, 'public')));
app.set('trust proxy', true);
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieSession({
secret: Constants.COOKIE_SESSION_KEY_1
}));
app.use(function (req, res, next) {
req.sessionOptions.maxAge = Constants.COOKIE_SESSION_MAX_AGE;
next();
})
app.use('/api/', user);
app.use('/api/', posting);
app.use('/api/', auth);
module.exports = app;
MORE DETAILS NOW:
If the callback is executed in 1 second, the session will be set. But if it takes up more time, like 3+ seconds, it won't.
I've simplified my login() function like this:
function login(code, state, onComplete) {
setTimeout(function() {
onComplete({
accessToken: "sfsd646481351gsFFHgDrgrrg",
userId: "user",
firstName: "John",
lastName: "Doe"
})}, 1000
);
}
So in case I set 1000 ms to setTimeout() it works fine, but if I set it to 3000, it won't. Looks like express finalizes response before the callback is called
ANOTHER DETAIL:
I've just found out, that this only happens if I proxy the request from reactjs app. If I call API (express) server directly with postman, it won't matter how much time the response will wait, the cookie will be set with no problems
I found a reason why cookies were not set. It was because I made request to social network and it returned its headers before I could set mine

node.js session values aren't accessible in second api call

i am facing the following issue:
(I am using node-client-sessions module)
I send an ajax request, through the browser, to my API : /api/request1
In my api.js I handle that request, calculate some stuff and write some results into the session like this.
router.post('/request1', function(req, response, next) {
// some wield calculations
req.session.calcData = { // some content }; 
// some other calculations
console.log(req.session.calcData); // logs the correct object
response.send('success');
}
After receiving the success on client side I send another api call like this for example: /api/request2
router.post('/request2', function(req, response, next) {
// here i want to use some of the results from the previous calculations which i stored in the calcData session.
console.log(req.session.calcData); // this logs undefined
}
Shouldn't req.session.calcData be available in both functions?
Enviroment Info
Express Framework 4.x
app.js :
...
var app = express();
...
var session = require('client-sessions');
...
app.use(session({
cookieName: 'session',
secret: 'random_string_goes_here',
duration: 30 * 60 * 9999999,
activeDuration: 5 * 60 * 1000,
}));
...
app.use('/api', api);
According to example at client-session, you must use req.csession and req.csflush();
var express = require('express');
var path = require('path')
var cs = require('client-session');
var clientSession = cs('mysecretkey');
var app = express();
app.use(clientSession.connect());
app.get('/', function(req, res){
var count = req.csession['count'];
if(!count) count = 1;
else count++;
req.csession['count'] = count;
//sync to cookie session equal to res.csflush(),make sure to call it before response
req.csflush();
res.send(count.toString());
});
app.listen(8124);
console.log('Server running at http://127.0.0.1:8124/');

Strongloop passport component returns "active" from loopback.getCurrentContext() empty

I have this var context = loopback.getCurrentContext(); that returns me under context.active.acccessToken the current Token used in the call (tested in the Explorer).
Now, when trying to use the Passport component, I copied the code form the server.js example git and put it on my boot/aaa-scripts.js the context.active varible is an empty {}.
// Passport configurators..
var loopbackPassport = require('loopback-component-passport');
var PassportConfigurator = loopbackPassport.PassportConfigurator;
var passportConfigurator = new PassportConfigurator(app);
// attempt to build the providers/passport config
var config = {};
try {
config = require('../../providers.json');
} catch (err) {
console.trace(err);
process.exit(1); // fatal
}
// The access token is only available after boot
app.middleware('auth', loopback.token({
model: app.models.AccessToken
}));
app.middleware('session:before', loopback.cookieParser(app.get('cookieSecret')));
app.middleware('session', loopback.session({
secret: 'kitty',
saveUninitialized: true,
resave: true
}));
passportConfigurator.init();
passportConfigurator.setupModels({
userModel: app.models.Member,
userIdentityModel: app.models.UserIdentity,
userCredentialModel: app.models.UserCredential
});
for (var s in config) {
var c = config[s];
c.session = c.session !== false;
passportConfigurator.configureProvider(s, c);
}
var ensureLoggedIn = require('connect-ensure-login').ensureLoggedIn;
app.get('/auth/account', ensureLoggedIn('/'), function(req, res, next) {
res.send(req.user);
});
I have commented out parts of the copied code, and the part that's getting me trouble is:
// The access token is only available after boot
app.middleware('auth', loopback.token({
model: app.models.AccessToken
}));
app.middleware('session:before', loopback.cookieParser(app.get('cookieSecret'));
app.middleware('session', loopback.session({
secret: 'kitty',
saveUninitialized: true,
resave: true
}));
I have tried both AccessToken and accessToken
What I'm missing?
You may need to attach a User-related models to your datasource first:
app.models.AccessToken.attachTo(dataSource);
Since I'm Using an Angular app, I ended up commenting this 3 lines
app.middleware('auth', loopback.token({
model: app.models.AccessToken
}));
Everything seens to be working all right.

How to access express.session.MemoryStore via socket.io objects?

I am doing this in a login function
app.post('/teacherlogin', function(request, response) {
var username = request.body.username;
var password = request.body.password;
con.query('SELECT t_id from login_teacher where username="'+username+'" and password="'+password+'"',function(err,results){
if(results.length > 0) {
request.session.regenerate(function(){
request.session.user = username;
request.session.type = 'teacher';
request.session.id = results[0].t_id;
response.redirect('/teacherhome');
});
} else {
response.redirect('teacherlogin');
}
});
});
now I want to emit the 'id' and 'type' I have stored to the session object. How should I do this? I have read this article but being inexperienced I am facing difficulty in using it. I have used it in my code
var MemoryStore = express.session.MemoryStore;
var sessionStore = new MemoryStore();
app.use(express.bodyParser());
app.use(express.cookieParser('secret text'));
app.use(express.session({
store: sessionStore,
secret: 'secret',
key: 'express.sid'}
));
and
var Session = require('connect').middleware.session.Session;
io.set('authorization', function (data, accept) {
if (data.headers.cookie) {
data.cookie = require('cookie').parse(data.headers.cookie);
data.sessionID = data.cookie['express.sid'].split('.')[0];
console.log('data.sessionID "'+data.sessionID);
data.sessionStore = sessionStore;
sessionStore.get(data.sessionID, function (err, session) {
if (err || !session) {
accept('Error', false);
} else {
data.session = new Session(data, session);
accept(null, true);
}
});
} else {
return accept('No cookie transmitted.', false);
}
});
I am not getting any thing in the session object. I tried to log the contents of the sessionStore and it seems to be empty! Does that mean the information I am storing in the session isn't being stored in the sessionStore? If yes, what should I do to store it there? and if it is stored there why isn't the sessionStore.get function unable to find it?
I am not sure if you're still working on this, but you can access session data with just a MemoryStore. After all how else would Express use it if it didn't work? :)
A simple way to demonstrate MemoryStore working is this:
var express = require("express")
, app = express()
, sessionStore = new express.session.MemoryStore();
// middleware
app.use(express.cookieParser());
app.use(express.session({store: sessionStore, secret: "mysecret"}));
// any endpoint sets a cookie
app.get("/", function(req,res) {
res.send('ok');
});
// This endpoint reveals it
app.get("/session", function(req, res){
sessionStore.get(req.sessionID, function(err, data) {
res.send({err: err, data:data});
});
});
app.listen(3000);
Hitting / followed by /session results in a response of:
{
"err": null,
"data": {
"cookie": {
"originalMaxAge": null,
"expires": null,
"httpOnly": true,
"path": "/"
}
}
}
I suspect your issue may be how you are getting the sessionID from the socket, but it is definitely possible to extract a session from a MemoryStore. Also, remember that restarting the Express server will destroy all of your sessions so you'll need a new cookie after each restart.
You have to use a database to store your express session, then parse the cookie data inside the socket.io definition and with the information obtained get the session info from the database, here is a complete example:
https://stackoverflow.com/a/13098742/218418
You can also use the session ID parsed from the cookie and join the user into a "chat room" with the name of the session.

Resources