Prefix getting repeated in connect-redis - node.js

I am trying to use connect-redis: "~3.0.1" with express-session: "~1.12.1" in my web app to store user sessions. But when I give the prefix field in RedisStore, its getting repeated two times. Please tell if I am doing something wrong.
"mySession:mySession:yzO1mRhloENUMYLkAz2nZprcfvcFMNHY"
"mySession:mySession:0L8prCJAoq0CmJ9tTwTJ_smQ4fH2R_H9"
While searching I came across similar issue with laravel code:
https://github.com/laravel/framework/issues/5353
Please tell if something similar is happening here and if yes, what is the workaround.
Below is the code I am using:
var sessionMiddleware = session({
secret : 'secretPass',
store: new RedisStore({
prefix:'mySession:',
ttl: 1800}),
resave: false,
saveUninitialized: false
});
app.use(function (req, res, next) {
var tries = 3;
function lookupSession(error) {
if (error) {
return next(error);
}
tries -= 1
if (req.session !== undefined) {
return next();
}
if (tries < 0) {
var errorMsg = 'Error in getting session. Please refresh the page.';
return next(new Error(errorMsg));
}
sessionMiddleware(req, res, lookupSession);
}
lookupSession();
})

I was able to fix the issue by creating redis client before hand and passing it to the session object as mentioned below and not depend on connect-redis to create it for me.
redis_client = require('redis').createClient();
var sessionMiddleware = session({
secret : 'secretPass',
store: new RedisStore({
prefix:'mySession:',
ttl: 1800,
client: redis_client}),
resave: false,
saveUninitialized: false
});
If i dont pass the client, the below code in connect-redis.js is passing the options while creating the redis client and then is adding one extra prefix.
// convert to redis connect params
if (options.client) {
this.client = options.client;
}
else if (options.socket) {
this.client = redis.createClient(options.socket, options);
}
else {
this.client = redis.createClient(options);
}

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.)

Not able to integrate Keycloak with Sails. Able to integrate Keycloak with Express

Keycloak is an open-source authentication and identity management solution written in Java. It provides a nodejs adapter using which I am able to successfully integrate with express. Here is the routes file which works:
'use strict';
module.exports = function(app) {
var Keycloak = require('keycloak-connect');
var session = require('express-session');
var memoryStore = new session.MemoryStore();
app.use(session({
secret: 'mySecret',
resave: false,
saveUninitialized: true,
store: memoryStore
}));
var keycloak = new Keycloak({
store: memoryStore
});
app.use(keycloak.middleware({
logout: '/logout',
admin: '/'
}));
// var lRController = require('../controllers/LRController');
//
// app.route('/lrs').get(lRController.list_all_lrs).post(lRController.create_a_lr);
var DeliveryOrderController = require('../controllers/DeliveryOrderController');
app.route('/').get(keycloak.protect(), DeliveryOrderController.getAllDos)
app.route('/api/dos').get(keycloak.protect(), DeliveryOrderController.getAllDos).post(DeliveryOrderController.createDo);
app.route('/api/do').put(DeliveryOrderController.updateDo);
app.route('/api/do/:doNumber').get(DeliveryOrderController.getDoByDoNumber);
app.route('/api/do/location/:locationId').get(DeliveryOrderController.getDoByLocation);
app.route('/api/do/branch/:branchId').get(DeliveryOrderController.getDoByBranch);
app.route('/api/do').delete(DeliveryOrderController.deleteDo);
var TransportDeliveryOrderController = require('../controllers/TransportDeliveryOrderController');
app.route('/api/tdos').get(TransportDeliveryOrderController.getAllTdos).post(TransportDeliveryOrderController.createTdo);
app.route('/api/tdo').put(TransportDeliveryOrderController.updateTdo);
app.route('/api/tdo/:tdoNumber').get(TransportDeliveryOrderController.getTdoByTdoNumber);
app.route('/api/tdo/status/:status').get(TransportDeliveryOrderController.getTdoByStatus);
app.route('/api/tdo/status/:status/do/:doNumber').get(TransportDeliveryOrderController.getTdoByStatusAndDo);
};
As you can see in the Delivery order routes, I have two routes(copies of the same route) protected by keycloak.protect(). I am trying to do the same in sails. I have the following questions for doing that.
a. To integrate keycloak into express the following things are done to protect the routes
Require Keycloak and express session:
var Keycloak = require('keycloak-connect');
var session = require('express-session');
Define a memory store for the storing the sessions:
var memoryStore = new session.MemoryStore();
Include the session as middleware in express
app.use(session({
secret: 'mySecret',
resave: false,
saveUninitialized: true,
store: memoryStore
}));
Initiate Keycloak:
var keycloak = new Keycloak({
store: memoryStore
});
Include keycloak Middleware into express middleware:
app.use(keycloak.middleware({
logout: '/logout',
admin: '/'
}));
Protect the route using keycloak.protect()
app.route('/api/dos').get(keycloak.protect(),DeliveryOrderController.getAllDos).post(DeliveryOrderController.createDo);
I need to establish similar steps in sails. How do I do these things in sails?
I am assuming http.js is where I add middleware. If I do, how do access keycloak in routes.js to use keycloak.protect().
For instance I can add the protect function in the following manner:
'/foo': [
keycloak.protect(),
{ controller: 'user', action: 'find' }
]
Here is the nodejs adapter for keycloak - https://github.com/keycloak/keycloak-nodejs-connect
I finally found an answer for this.
The problem is that keycloak.middleware() returns a list of functions and app.use() is happy with that.
Sails takes the http.middleware list and adds to it and calls app.use on the resulting list. If you just include the keycloak.middleware() you have a list of functions which includes an array of functions. Express ignores the array since it is not a function.
You need to expand the list into separate functions. Create a keycloak object at the top of http and initialize it.
Then put this at the bottom of the config/http.js file:
function expandList() {
var newOrder = [];
for (let i in module.exports.http.middleware.order)
{
var label = module.exports.http.middleware.order[i];
var functor = module.exports.http.middleware[label];
if (functor && functor.constructor === Array) {
for (let j in functor) {
let newlabel = label + "." + j;
newOrder.push(newlabel);
module.exports.http.middleware[newlabel] = functor[j];
}
} else {
newOrder.push(label);
}
};
module.exports.http.middleware.order = newOrder;
return "";
}
var result = init();
Inside the http.middleware object you need to use:
keycloakMiddleware: keycloak.middleware(),
and add it to order array.
Also add a policy to call protect and include this:
var kc = sails.config.http.keycloak.protect();
return kc(req, resp, next);
Hope this helps if you still need to solve this.
Maybe Sails should accept an array and expand it before calling Express
The above answer does not work for Sails 1.0. It now requires that the middleware be a function, not an array and keycloak returns an array from keycloak.middleware.
Here is what seems to work:
Create a service: KeycloakService
var session = require('express-session');
var Keycloak = require('keycloak-connect');
var memoryStore = new session.MemoryStore();
var KeycloakConfig = {
"realm": "somerealm,
"auth-server-url" : "https://server.com/auth",
"resource" : "someresource,
};
module.exports = {
config: KeycloakConfig,
setConfig: function (config) {
return new Keycloak({ store: memoryStore }, config);
}
}
Now in http.js put the following at the top
var KeycloakService = require('../api/services/KeycloakService');
var masterKeycloak = setupKeycloak();
var kcMiddleware = masterKeycloak.middleware();
function setupKeycloak() {
if (KeycloakService.keycloak == null) {
var config = Object.assign({}, KeycloakService.config);
config.bearerOnly = true;
KeycloakService.keycloak = KeycloakService.setConfig(config);
}
return KeycloakService.keycloak;
}
function recurseCallFunction(arr, i, req, res, next) {
if (arr.length > i) {
arr[i](req, res, () => recurseCallFunction(arr, i+1, req, res, next));
} else {
next();
}
}
Then in the middleware.order array include "keycloakMiddleware" and below the order array use
'keycloakMiddleware': function (req, res, next) {recurseCallFunction(kcMiddleware, 0, req, res, next);}
You will also need to define sails.config.http.keycloak: masterKeycloak
This will provide a function that recursively calls the middles functions in Keycloak.
You will need a policy defined. That policy can do:
var keycloak = sails.config.http.keycloak;
if (!req.hostname) {
req.hostname = req.host;
}
var authWithKeycloak = keycloak.protect();
if (authWithKeycloak) {
authWithKeycloak(req, res, next);
} else {
sails.log.warn('Keycloak authentication could not obtain a protection checker. Contact Developers');
}
This technique should help with Keycloak Policy Enforcer. You can add to the config to enable those and use them per the documentation with the keycloak.protect. I do not know anything about enforcers so I cannot help further on that.

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.

Force share.js use the same express session data

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.

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