Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] midburn session #341

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v7.4.0
v7.9.0
81 changes: 46 additions & 35 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,24 @@ var path = require('path');
var favicon = require('serve-favicon');
var morganLogger = require('morgan');
var bodyParser = require('body-parser');
var passport = require('passport');
var session = require('express-session');
// var passport = require('passport');
var flash = require('connect-flash');
var cookieParser = require('cookie-parser');
var fileUpload = require('express-fileupload');
var log = require('./libs/logger')(module);
var recaptcha = require('express-recaptcha');
var compileSass = require('express-compile-sass');
var recaptchaConfig = require('config').get('recaptcha');
var KnexSessionStore = require('connect-session-knex')(session);
var knex = require('./libs/db').knex;
const authExpress = require('./libs/auth-express');

// var session = require('express-session');
// var KnexSessionStore = require('connect-session-knex')(session);
// var knex = require('./libs/db').knex;

log.info('Spark is starting...');

// Creating Express application
var app = express();
const app = express();

// FavIcon registration
app.use(favicon(path.join(__dirname, '/public/favicon.ico')));
Expand All @@ -42,6 +44,8 @@ app.use(bodyParser.urlencoded({
app.use(cookieParser());
app.use(fileUpload());

const auth = authExpress(app);

var root = process.cwd();
app.use(compileSass({
root: root + '/public',
Expand All @@ -61,22 +65,22 @@ app.use(function(req, res, next) {
next();
});

// Passport setup
require('./libs/passport')(passport);

// using session storage in DB - allows multiple server instances + cross session support between node js apps
var sessionStore = new KnexSessionStore({
knex: knex
});
app.use(session({
secret: 'SparklePoniesAreFlyingOnEsplanade', //TODO check - should we put this on conifg / dotenv files?
resave: false,
saveUninitialized: false,
maxAge: 1000 * 60 * 30,
store: sessionStore
}));
app.use(passport.initialize());
app.use(passport.session()); // persistent login sessions
// // Passport setup
// require('./libs/passport')(passport);
//
// // using session storage in DB - allows multiple server instances + cross session support between node js apps
// var sessionStore = new KnexSessionStore({
// knex: knex
// });
// app.use(session({
// secret: 'SparklePoniesAreFlyingOnEsplanade', //TODO check - should we put this on conifg / dotenv files?
// resave: false,
// saveUninitialized: false,
// maxAge: 1000 * 60 * 30,
// store: sessionStore
// }));
// app.use(passport.initialize());
// app.use(passport.session()); // persistent login sessions

app.use(flash()); // use connect-flash for flash messages stored in session

Expand Down Expand Up @@ -149,7 +153,7 @@ if (app.get('env') === 'development') {
app.use('/dev', require('./routes/dev_routes'));
require('./routes/fake_drupal')(app);
}
require('./routes/main_routes.js')(app, passport);
require('./routes/main_routes.js')(app);

app.use('/:lng?/admin', require('./routes/admin_routes'));

Expand All @@ -163,24 +167,23 @@ var mail = require('./libs/mail');
mail.setup(app);

// API
require('./routes/api_routes')(app, passport);
require('./routes/api_routes')(app, auth);

// Camps / API
// TODO this is not the right way to register routes
require('./routes/api_routes.js')(app, passport);
require('./routes/api_camps_routes')(app, passport);
require('./routes/camps_routes')(app, passport);
require('./routes/api_camps_routes')(app);
require('./routes/camps_routes')(app);
require('./routes/api/v1/camps')(app); // CAMPS PUBLIC API
require('./routes/api_camps_routes')(app, passport);
require('./routes/api_camps_routes')(app);

// Camps
require('./routes/camps_routes')(app, passport);
require('./routes/camps_routes')(app);

//TODO this is not the right way to register routes
var ticket_routes = require('./routes/ticket_routes');
app.use('/:lng/tickets/', ticket_routes);

require('./routes/volunteers_routes')(app, passport);
require('./routes/volunteers_routes')(app);

// Recaptcha setup with siteId & secret
recaptcha.init(recaptchaConfig.sitekey, recaptchaConfig.secretkey);
Expand All @@ -190,14 +193,18 @@ log.info('Spark environment: NODE_ENV =', process.env.NODE_ENV, ', app.env =', a
// ==============
// Error handlers
// ==============

// Catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found: ' + req.url);
err.status = 404;
next(err);
app.use(function (err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!')
});

// Catch 404 and forward to error handler
app.use(function(req, res, next) {
const err = new Error('Not Found: ' + req.url);
err.status = 404;
next(err);
});

// Development error handler - will print stacktrace
if (app.get('env') === 'development') {

Expand Down Expand Up @@ -245,6 +252,10 @@ process.on('unhandledRejection', function(reason, p) {
log.error("Possibly Unhandled Rejection at: Promise ", p, " reason: ", reason);
});

// process.on('uncaughtException', (reason, p) => {
// log.error("Possibly Unhandled Rejection at: Promise ", p, " reason: ", reason);
// });

process.on('warning', function(warning) {
log.warn(warning.name); // Print the warning name
log.warn(warning.message); // Print the warning message
Expand Down
33 changes: 33 additions & 0 deletions libs/auth-express.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const auth = require('./auth')();
const unless = require('express-unless');

const devApi = path => path.startsWith('/dev');
const loginUrl = path => path.startsWith('/jwt-login') || path.endsWith('/login') || path.endsWith('/signup') || path === '/';
const staticContent = path => path.startsWith('/bower_components') || path.startsWith('/scripts');

const containLoginToken = req => (req.cookies && req.cookies[auth.SessionCookieName]) || (req.headers && req.headers['authorization']) || false;

const shouldProtectApi = req => staticContent(req.path) || devApi(req.path) || (loginUrl(req.path) && !containLoginToken(req));

const sessionRenewalMiddleware = (req, res, next) => {
if (req.sparkSessionRenew) {
const sparkSession = req.sparkSession;
sparkSession.exp = Date.now() + auth.TokenTTL;
const token = auth.sign(sparkSession);
res.cookie(auth.SessionCookieName, token, {maxAge: 60 * 60 * 1000, httpOnly: true});
delete req.sparkSessionRenew;
}
next();
};

module.exports = app => {
app.use(auth.initialize());

const authenticate = auth.authenticate();
authenticate.unless = unless;

app.use(authenticate.unless(req => shouldProtectApi(req)));
app.use(sessionRenewalMiddleware);

return auth;
};
76 changes: 76 additions & 0 deletions libs/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// auth.js
const passport = require("passport");
const passportJWT = require("passport-jwt");
const _ = require("lodash");
const users = require("./users");
const ExtractJwt = passportJWT.ExtractJwt;
const Strategy = passportJWT.Strategy;
const config = require('config');
const jwt = require('jwt-simple');
const apiTokensConfig = config.get('api_tokens');

const FiveMinutes = 5*60*1000;
const OneHour = 60*60*1000;

const SessionCookieName = 'spark_session';
const Algorithm = 'HS256';
const TokenTTL = OneHour;
const TokenRenewalPeriod = FiveMinutes;

const cookieExtractor = req => {
if (!_.isUndefined(req.cookies && req.cookies[SessionCookieName])) {
return req.cookies[SessionCookieName];
}
return null;
};

const params = {
secretOrKey: apiTokensConfig.token,
jwtFromRequest: ExtractJwt.fromExtractors([cookieExtractor, ExtractJwt.fromAuthHeader()]),
passReqToCallback: true,
ignoreExpiration: true
};

const convertToSparkSession = user => {
return {
email: _.get(user, 'attributes.email', '') || '',
name: _.get(user, 'attributes.name', '') || '',
uid: _.get(user, 'attributes.user_id', -1),
exp: Date.now() + TokenTTL,
iat: Date.now()
};
};

const sign = session => jwt.encode(session, apiTokensConfig.token, Algorithm);

const isExpired = session => session.exp < Date.now();
const isRenewable = session => (session.exp + TokenTTL + TokenRenewalPeriod) > Date.now();

module.exports = () => {
const strategy = new Strategy(params, (req, payload, done) => {
if (isExpired(payload)) {
if (isRenewable(payload)) {
req.sparkSessionRenew = true;
} else {
return done(null, false);
}
}

req.sparkSession = payload;
return done(null, payload);
});
passport.use(strategy);
return {
initialize: () => passport.initialize(),
authenticate: () => passport.authenticate("jwt", { session: false }),
login: (username, password) => {
return users.login(username, password)
.then(convertToSparkSession)
.then(sign)
},
sign: sign,
SessionCookieName: SessionCookieName,
TokenTTL: TokenTTL,
TokenRenewalPeriod: TokenRenewalPeriod
};
};
Loading