Sliding expiration for access token with Loopback - node.js

I've installed Loopback and enabled ACL for a couple of models. I noticed that the Access Token is valid for ever, I would like to change this period somehow to, for example, an hour. But even better would be to reset this period when activity occurs (sliding expiration)
I've checked the documentation but couldn't fine anything about this subject. Any help/guidance would be appreciated!

When you call the login method you can specify a ttl property in seconds(I believe by default it's 2 weeks if you don't specify). Then you can have sliding expiration by having the following middleware:
app.use(loopback.token()); // You should have this already
app.use(function(req, res, next) {
// Make sure this middleware is registered after loopback.token
var token = req.accessToken;
if (!token) {
return next();
}
var now = new Date();
if ( now.getTime() - token.created.getTime() < 1000 ) {
return next();
}
req.accessToken.created = now;
req.accessToken.ttl = 604800; //one week
req.accessToken.save(next);
});

Related

Limit session per User - ExpressJS

I am using mongoDB as a Database and passportjs for authentication.
I want to have a maximum of 3 sessions per user.
For that, I created a sessionCount field on my mongo document.
everytime the user logs in, I increment the count and when they log out, I decrement.
But the problem arises when the session auto-expires. The session count stays the same.
Q. is there any way to "detect" session expiration so that I could decrement that sessionCount field ?
I am also facing the same kind of issue I solve it by using this way->
app.use(function(req, res, next) {
//Checking previously set cookie (if there is one)
var session = JSON.parse(req.cookies['session'] || '');
if (session && new Date(session.expires) < new Date()) {
// decrement the count
}
next();
});

Restrict API endpoint by total requests of each user

I am currently exploring solutions to limit access to an API endpoint on NodeJS by total number of requests per month.
For instance, I want the free plan users to access the /api endpoint up to a total of 100 requests per month, and the premium plan users to have 5000 requests per month.
The naïve way to get around it is by implementing a passport middleware to get the user's plan and then keep track of the count:
app.get("/api", requireAuth, async (req, res, next) => {
try {
// Check if user ran out of requests
if (req.user.apiRequestsLeft === 0) {
res.send("You ran out of API requests!")
} else {
// Decrement the allocated requests
req.user.apiRequestsLeft--;
await req.user.save();
res.send(user)
}
} catch (err) {
next(err);
}
});
My concerns are:
Performance/Scalability issues of having to update a MongoDB document each time there's a request - is this feasible or will I hit a problem when the app grows?
Resetting the count - should this be a daily cronjob that looks at the timestamp of 'registration' of each and every user, compute if a month has passed and reset allotted requests accordingly, or is there a better way of designing something like this?
Performance/Scalability issues of having to update a MongoDB document
each time there's a request - is this feasible or will I hit a problem
when the app grows?
Definitely. You will soon experience heavy mongoDB traffic and it will hit performance bottleneck. In my opinion, you should use a faster in-memory database like Redis to handle the situation. You can even use the Redis as the session-store which will reduce the load on MongoDB. That way, MongoDB can be utilized for other Business queries.
Resetting the count - should this be a daily cronjob that looks at the
timestamp of 'registration' of each and every user, compute if a month
has passed and reset allotted requests accordingly, or is there a
better way of designing something like this?
A better way would be to achieve the resetting part in the middleware itself.
Here is some code that explains my solution.
Sample design of Quota object would be:
{
type: "FREE_USER", /** or "PREMIUM_USER" */
access_limit: 100, /** or 5000 */
exhausted_requests: 42 /** How many requests the user has made so far this month */
last_reset_timestamp: 1547796508728 /** When was the exhausted_requests set to 0 last time */
}
With that design. Your middleware that checks the quota would look something like:
const checkQuota = async (req, res, next) => {
const user = req.user;
const userQuotaStr = await redis.getAsync(user.id)
let userQuota;
/** Check if we have quota information about user */
if (userQuotaStr != null) {
/** We have previously saved quota information */
userQuota = JSON.parse(userQuotaStr);
/**
* Check if we should reset the exhausted_requests
* Assuming that all the requests are reset on the First Day of each month.
*/
if ( isStartOfMonth() ) {
/**
* It is First Day of the month. We might need to reset the `exhausted_requests`
* Check the difference between `Date.now()` and `userQuota.last_reset_timestamp`
* to determine whether we should reset or not
*/
if ( shouldResetTimeStamp(userQuota.last_reset_timestamp) ) {
userQuota.exhausted_requests = 0
userQuota.last_reset_timestamp = Date.now()
}
}
} else {
/** We do not have previously saved quota information. Prepare one */
userQuota = {
type: user.type,
access_limit: user.access_limit,
exhausted_requests: 0,
last_reset_timestamp: Date.now()
}
}
/** Incredement the counter to account the current request */
userQuota.exhausted_requests++
/** Update in database */
redis.set(user.id, JSON.stringify(userQuota))
if ( userQuota.exhausted_requests >= userQuota.access_limit ) {
/** User has reached the quota limit. Deny the request. set with 401 or 403 status code */
} else {
/** User can access the API. call next() */
}
}
Of course, the snippet is is incomplete. It just gives you the idea about how to go about writing that middleware.
Here is how you can use the middleware for your APIs:
/** If requests to routes are under the quota */
app.get("/api/quota-routes", requireAuth, checkQuota, /** Mount the actual middleware here */)
/** If requests to routes are unlimited, just remove the checkQuota middleware */
app.get("/api/unlimited-routes", requireAuth, /** Mount the actual middleware here */)
rate-limiter-flexible package helps with counters and automatically expire counters.
const opts = {
storeClient: mongoConn,
points: 5000, // Number of points
duration: 60 * 60 * 24 * 30, // Per month
};
const rateLimiterMongo = new RateLimiterMongo(opts);
const rateLimiterMiddleware = (req, res, next) => {
// req.userId should be set before this middleware
const key = req.userId ? req.userId : req.ip;
const pointsToConsume = req.userId ? 1 : 50;
rateLimiterMongo.consume(key, pointsToConsume)
.then(() => {
next();
})
.catch(_ => {
res.status(429).send('Too Many Requests');
});
};
app.use(rateLimiterMiddleware);
Note, this example is not bound to calendar month, but counts events from the first event within the next month after it. You could set custom duration with
block to strictly connect counters expiry to calendar months.
This code should easily handle about 1k-2k requests per second on basic server. You could also use Redis limiter or Mongo limiter with sharding options.
Also, it provides In-memory Block strategy to avoid too much requests to MongoDB/Redis/any store.
Alternatively, play with get method from rate-limiter-flexible to decrease amount of unnecessary counters updates. get method is much-much faster than increment.

socketio-jwt disconnect expired tokens

I am able to authenticate using socketio-jwt and everything is working great. The problem I'm running into is if I set an expiration to a minimum time and let it expire I can continue to emit and receive messages on the connection. As long as I don't refresh the page the connection persists. Once I refresh the connection is disconnected and I am required to reconnect. Is it possible to have the server check for expired tokens and disconnect the connection?
The library does not support this feature, you can validate the token on each socket io event that you want.
In a Github Issue a contributor answered with this analogy:
The id_token is like your national
passport, both have an expiration, you can enter a country as long as your
passport is not expired and most countries will not keep track of
expiration to hunt you down.
You can handle this manually using a socketio middleware for example:
const timer = require('long-timeout')
function middleware (socket, next) {
const decodedToken = socket.user // Assuming the decoded user is save on socket.user
if (!decodedToken.exp) {
return next()
}
const expiresIn = (decodedToken.exp - Date.now() / 1000) * 1000
const timeout = timer.setTimeout(() => socket.disconnect(true), expiresIn)
socket.on('disconnect', () => timer.clearTimeout(timeout))
return next()
}

Expiring routes generation NodeJS

I want to setup temporary routes with a unique random string path on ExpressJS. These routes should be dynamically created and should give a 404 if somebody tries to use ie: http://example.com/login/a434bcd34d920bdfe 30 min after that uniqueid was created.
Any ideas on how to do that? I'm pretty new to NodeJS but judging from what I've seen there should be a library that does that :)
Something like this maybe?
// =========== app.js ============
app.get('/generate_url', function(req, res) {
// random string: "a434bcd34d920bdfe"
var extension = randomstring.generate();
var dynamicController = require('./login/'+extension);
dynamicController.init(app);
// Should expire in 20 minutes
dynamicController.expire(20*60)
res.status(200).send();
}
// =========== login.js ============
login.post('/login/:uniqueid', function(req, res) {
// uniqueid should match the extension generated before
var uniqueid = req.query.unique;
var username = req.body.username;
// Do something with this info
}
I think this is far from working fine but at least maybe somebody who's done
You asking two question:
How to create the route.
How to save the data about expired links.
1. How to create the Route:
You have to create a route that receieve the traffic from all the users, and check that the specific URL is valid.
First you create a route that get traffic from all the users.
app.get('/myroute/:id',function(){
/* This route will get any url that start with /myroute/
For example /myroute/abc
/myroute/def
*/
// req.params.id == what the URL is entered
if (is_expired(req.params.id)) return res.end('Sorry your link has expired')
res.send('Great you logged in!')
})
app.post('/login/:uniqueid', function(req, res) {
set_expire(req.params.uniquieid,30*1000*60) //30 minutes = 30*60*1000 miliseconds.
})
2. How to save the data about expired links.
How to implement set_expire and is_expired
You need to implement is using any kind of database. Redis is very good for that. I will show you example how to do it
using setTimeout. It will work. But if the server restart, all the users will be logged out.
users={}
function is_expired(uid){
return users[uid]
}
function set_expire(uid,time){
users[uid]=true
setTimeout(function(){
delete users[uid]
},time)
}
Usually this is done with a generic router handler that then consults some data store to see if the ID sent in the request is valid.
Also, note that a value that comes from a route specification like '/login/:uniqueid' is found in req.params.uniqueid, not in req.query.uniqueid. req.query is for actual query parameters (things after the ? in the URL).
var validIds = {};
// =========== app.js ============
app.get('/generate_url', function(req, res) {
// generate random string: "a434bcd34d920bdfe"
// make sure it's not already in use
var extension;
do {
extension = randomstring.generate();
} while (validIds[extension]);
// save this random string as a valid ID and store the expiration time
validIds[extension] = {expiration: Date.now() + 20 * 60 * 1000};
// return the random value to the caller
res.json(extension);
}
app.post('/login/:uniqueid', function(req, res) {
// uniqueid should match the extension generated before
var uniqueid = req.params.unique;
var username = req.body.username;
// now see if the id is still valid
var idInfo = validIds[uniqueid];
if (!idInfo || idInfo.expiration < Date.now()) {
return res.sendStatus(404);
}
// id is valid, do something with this info
}
Then, you can have some interval timer that regular cleans up any expired ids. This is just housekeeping to keep the validIds object from growing forever - the actual expiration value is still checked before validating the id.
// clean up expired validIds object every 10 minutes
setInterval(function() {
var now = Date.now();
for (var id in validIds) {
if (validIds[id].expiration < now) {
delete validIds[id];
}
}
}, 10 * 60 * 1000);
Note: Since you probably want your uniqueIDs to survive a server restart, you need to regularly persist them to some sort of backing store (database, flat file, etc...) and then you need to read that data in upon server startup.
If you want any sort of user activity to "renew" the timeout on the uniqueid, you can install some middleware that resets the time at an appropriate access.

Parameters passed in Express.js middleware gets cached?

I've been building my first node.js app using express.js.
It has been fun :)
I must be having some kind of misconception going, so here goes.
I have some route defined as such:
app.all('/account/summary', apiPOST('AccountSummary', {FromDate: 'default', ToDate: 'default'}), function(req, res){
var data=req.apiJSON;
console.log(data);
res.render('accountsummary', {locals: data, layout: 'layouts/layout'});
});
apiPOST() is defined as such:
apiPOST = function (operation, postParams) {
return function (req, res, next){
console.log('RIGHT AT START');
console.log(postParams);
console.log('END');
var currentDate = new Date();
var day = ('0'+currentDate.getDate()).slice(-2);
var month = ('0'+(currentDate.getMonth() + 1)).slice(-2);
var year = ('0'+currentDate.getFullYear()).slice(-4);
console.log('BEFORE DATES');
console.log(postParams);
if (typeof(postParams.FromDate)!='undefined' && (postParams.FromDate=='default' || postParams.FromDate=='')){
postParams.FromDate=year+'-'+month+'-'+day;
}
if (typeof(postParams.ToDate)!='undefined' && (postParams.ToDate=='default' || postParams.ToDate=='')){
postParams.ToDate=year+'-'+month+'-'+day;
}
//automatically add all posted data to postParams
if (typeof(req.body)!='undefined'){
for (var key in req.body){
if (req.body.hasOwnProperty(key)){
postParams[key]=req.body[key];
}
}
}
// here is do some talking to an XML web service and convert it to JSON;
// we use our postParams variable to POST
next();
}
}
First off this works fine. When reaching the page on a GET request, it defaults both FromDate and ToDate to today. This page has a form that you may post to specify a new FromData and ToDate. the posted data automatically get added to the postParams and that also works fine.
The problem that I am experiencing is that the next time a user visits the page using GET, the previously POSTed data is still around and so it default to that and not to today.
I cannot figure out why that data is still available. By the looks of it, it is not being posted, but rather remembered in postParams. Is postParams now global?
Thanks!
What's happening is that you're calling apiPOST only once, during the app.all call to configure that route and that one call creates the one postParams parameter object that all future invocations of apiPOST's returned function will share.

Resources