session-rememberme
Version:
Add remember-me support to your app with this express middleware.
133 lines (123 loc) • 5.91 kB
JavaScript
// Generated by CoffeeScript 2.7.0
(function() {
var _, bcrypt, crypto;
_ = require("lodash");
bcrypt = require('bcrypt');
crypto = require('crypto');
module.exports = function(configs) {
var defaults, exports, generateRememberMeToken;
if ((configs != null ? configs.loadUser : void 0) == null) {
throw "loadUser must be defined";
}
if ((configs != null ? configs.deleteToken : void 0) == null) {
throw "deleteToken must be defined";
}
if ((configs != null ? configs.deleteAllTokens : void 0) == null) {
throw "deleteAllTokens must be defined";
}
if ((configs != null ? configs.saveNewToken : void 0) == null) {
throw "saveNewToken must be defined";
}
defaults = {
// How log should the user be remembered. Defaults to 90 days.
maxAge: 90 * 24 * 60 * 60 * 1000,
checkAuthenticated: function(req) {
var ref;
// Return true if session is already authenticated
return (req.session != null) && (((ref = req.session) != null ? ref.user : void 0) != null);
},
// userFromData: The user data persisted in browser. Could be an id or a complex id that will be serialized with JSON.stringify.
// return [userRememberMeTokens[], sessionUser]
// userRememberMeTokens: The list of rememberme tokens associated with the user
// sessionUser: The user (typically the user id) to save in session. Will be passed back to setUserInSession
loadUser: function(userFromData) {},
// Will be called with sessionUser passed to loadUser's sessionUser return value when the rememember me token was validated.
// req: the express Request object.
// sessionUser: the user loaded by loadUser. Typically the user id.
// Should load the session user (from DB) based on user info stored in browser.
setUserInSession: function(req, sessionUser) {
// Should store user information in session. This can then be used in checkAuthenticated.
return req.session.user = sessionUser;
},
// Called when a token is invalidated
// sessionUser: the user loaded by loadUser. Typically the user id.
// token: the token to delete
// Return Promise
deleteToken: function(sessionUser, token) {},
// Called when an attack is suspected
// sessionUser: the user loaded by loadUser. Typically the user id.
// Return Promise
// Should remove the token from persistence store
deleteAllTokens: function(sessionUser) {},
// sessionUser: the user loaded by loadUser. Typically the user id.
// newToken: the newly generated token that should be added to persistence store
// Return Promise
// Should remove all tokens from persistence store
saveNewToken: function(sessionUser, newToken) {}
};
configs = _.merge(defaults, configs);
// Will return the new token in http header 'X-Remember-Me'
generateRememberMeToken = async function(sessionUser, currentToken, userFromData, res) {
var newToken;
if (currentToken != null) {
// Delete current token
await configs.deleteToken(sessionUser, currentToken);
}
// Generate a new token
newToken = crypto.randomBytes(32).toString("hex");
await configs.saveNewToken(sessionUser, crypto.createHash('md5').update(newToken).digest('hex'));
// Return the new token in 'X-Remember-Me' header
return res.set('X-Remember-Me', JSON.stringify({
user: userFromData,
token: newToken
}));
};
exports = {};
// Should be called after successfull login AND the remember me option was set.
// Will add a header to the response so you must write response only in the callback.
exports.login = async function(sessionUser, userFromData, res) {
return (await generateRememberMeToken(sessionUser, null, userFromData, res));
};
// Should be called when the user logout.
// Remove the remember me token from storage.
exports.logout = async function(req, res, sessionUser) {
var remembermeData;
// Delete the received token from the list of "remember me" token for that user
remembermeData = req.get('X-Remember-Me');
if (remembermeData != null) {
remembermeData = JSON.parse(remembermeData);
return (await configs.deleteToken(sessionUser, crypto.createHash('md5').update(remembermeData.token).digest('hex')));
}
};
exports.middleware = async function(req, res, next) {
var remembermeData, sessionUser, userFromData, userRememberMeTokens;
if (configs.checkAuthenticated(req)) {
return next();
}
// Try getting it from the headers
remembermeData = req.get('X-Remember-Me');
if (remembermeData != null) {
remembermeData = JSON.parse(remembermeData);
}
if (!(((remembermeData != null ? remembermeData.user : void 0) != null) && ((remembermeData != null ? remembermeData.token : void 0) != null))) {
return next();
}
userFromData = remembermeData.user;
[userRememberMeTokens, sessionUser] = (await configs.loadUser(userFromData));
if (!((sessionUser != null) && (userRememberMeTokens != null))) {
return next();
}
if (_.includes(userRememberMeTokens, crypto.createHash('md5').update(remembermeData.token).digest('hex'))) {
// Set the user in session and handle the request
configs.setUserInSession(req, sessionUser);
// Generate a new remembre me token
await generateRememberMeToken(sessionUser, crypto.createHash('md5').update(remembermeData.token).digest('hex'), userFromData, res);
} else {
// Wipe all user.rememberMeToken token in case this is an attack
await configs.deleteAllTokens(sessionUser);
}
return next();
};
return exports;
};
}).call(this);