From f9c967281870133604550019d1cc4ece0e8aaa32 Mon Sep 17 00:00:00 2001 From: Dobie Wollert Date: Wed, 25 Nov 2015 02:29:23 -0800 Subject: [PATCH] More stuff --- app/controllers/account.js | 45 ++- app/controllers/timeclock.js | 520 +++++++++++++++++++++++++++++++++++ app/model/timeClockSpan.js | 58 ++++ config/auth.js | 147 +++++++++- config/config.js | 9 +- config/express.js | 9 + config/routes.js | 9 +- server.js | 8 +- 8 files changed, 786 insertions(+), 19 deletions(-) create mode 100644 app/controllers/timeclock.js create mode 100644 app/model/timeClockSpan.js diff --git a/app/controllers/account.js b/app/controllers/account.js index 8f66462..64914e7 100644 --- a/app/controllers/account.js +++ b/app/controllers/account.js @@ -1,6 +1,43 @@ +var jwt = require('jwt-simple'); +var moment = require('moment-timezone'); -var mongoose = require('mongoose'); -exports.profile = function(req, res) { - res.json(req.user); -}; + +module.exports = function(config) { + + function createJWT(user, uid) { + var payload = { + sub: uid, + oid: user.id, + iat: moment().unix(), + exp: moment().add(14, 'days').unix() + }; + + return jwt.encode(payload, config.auth.jwtSecret); + } + + return { + profile: function(req, res) { + res.json(req.user); + }, + + impersonate: function(req, res) { + + var uid = req.body.uid; + if (!uid) { + return res.json(400); + } + + console.log(req.user.name.first + " " + req.user.name.last + " is requesting to impersonate user " + uid); + if (req.user.perms.indexOf('system.developer') === -1) { + console.log("Access to impersonate user denied"); + return res.json(403); + } else { + console.log("User is a developer"); + } + + console.log("Access token issued to impersonate user."); + res.send({ token: createJWT(req.user, uid) }); + } + } +} diff --git a/app/controllers/timeclock.js b/app/controllers/timeclock.js new file mode 100644 index 0000000..1f0ac1b --- /dev/null +++ b/app/controllers/timeclock.js @@ -0,0 +1,520 @@ +"use strict"; + +var mongoose = require('mongoose'); +var moment = require('moment-timezone'); +var _ = require('lodash'); +var Promise = require('bluebird'); +var TimeClockSpan = mongoose.model('TimeClockSpan'); +var Workorder = mongoose.model('Workorder'); + +var NON_BILLABLES = ['shop', 'break', 'pto', 'meeting', 'event', 'weather', 'holiday']; +var TASK_TYPES = ['workday', 'workorder', 'nonBillable']; + +function MultipleSpansError(spans) { + Error.captureStackTrace(this, MultipleSpansError); + + this.name = 'MultipleSpansError'; + this.message = 'Encountered multiple spans when one was expected'; + this.spans = spans; +} +MultipleSpansError.prototype = Object.create(Error.prototype); + +function findUserSpans(user, day) { + var startOfDay = day.clone().startOf('day').utc().toDate(); + var endOfDay = day.clone().endOf('day').utc().toDate(); + + var query = { + start: { + '$gte': startOfDay, + '$lte': endOfDay + }, + user: user.id + }; + + return TimeClockSpan + .find(query) + .exec(); +} + +function findUserWorkorders(user, day) { + var startOfDay = day.clone().startOf('day').toDate(); + var endOfDay = day.clone().endOf('day').toDate(); + + var query = { + deleted: false, + techs: user.id, + 'scheduling.start': { + '$lte': endOfDay + }, + 'scheduling.end': { + '$gte': startOfDay + } + }; + + return Workorder + .find(query) + .populate('client', 'name identifier') + .exec(); +} + +function findUserWorkorder(id, user) { + var query = { + _id: id, + techs: user.id, + deleted: false + }; + + return Workorder + .findOne(query) + .populate('client', 'name identifier contacts address') + .exec(); +} + +function filterSpans(spans, filter) { + filter = { + type: filter.type ? [].concat(filter.type) : undefined, + status: filter.status ? String(filter.status) : undefined, + workorder: filter.workorder ? String(filter.workorder) : undefined, + reason: filter.reason ? String(filter.reason) : undefined + }; + + return _.chain(spans) + .filter(function(span) { + + if (filter.type && filter.type.indexOf(span.type) === -1) { + return false; + } + + if (filter.status && String(span.status) !== filter.status) { + return false; + } + + if (filter.workorder && String(span.workorder) !== filter.workorder) { + return false; + } + + if (filter.reason && span.reason !== filter.reason) { + return false; + } + + return true; + }) + .sortBy('start') + .value(); +} + +function spansStatus(spans, filter) { + if (filter) { + spans = filterSpans(spans, filter); + } + + var result = 'pending'; + _.forEach(spans, function(span) { + if (span.status === 'open') { + result = 'clockedIn'; + return false; + } else { + result = 'clockedOut'; + } + }); + + return result; +} + +function validateClockRequest(req) { + return new Promise(function (resolve, reject) { + + var params = {}; + + var type = req.body.type; + if (!type) { + return reject("Missing required parameter 'type'"); + } + + if (TASK_TYPES.indexOf(type) === -1) { + return reject("Invalid type: '" + type + "'"); + } + + params.type = type; + + if (type === 'workorder') { + var id = req.body.id; + if (!id) { + return reject("Missing required parameter 'id'"); + } + + params.id = id; + } + + if (type === 'nonBillable') { + var reason = req.body.reason; + if (!reason) { + return reject("Missing required parameter 'reason'"); + } + + if (NON_BILLABLES.indexOf(reason) === -1) { + return reject("Invalid reason: '" + reason + "'"); + } + + params.reason = reason; + } + + resolve(params); + }); +} + +function validateWorkorderDetailsRequest(req) { + var params = {}; + + var id = req.param('id'); + if (!id) { + return Promise.reject("Missing required parameter 'id'"); + } + + params.id = id; + + return Promise.resolve(params); +} + +function handleStatusRequest(spans, workorders) { + + var results = {}; + + var workdaySpans = _.filter(spans, { type: 'workday' }); + + results.tasks = []; + + results.tasks = results.tasks.concat({ + type: 'workday', + status: spansStatus(workdaySpans), + spans: _.map(workdaySpans, spanToResponse) + }); + + results.tasks = results.tasks.concat(_.chain(workorders) + .sortBy('scheduling.start') + .map(function(workorder) { + var workorderSpans = filterSpans(spans, { type: 'workorder', workorder: workorder.id }); + + return { + type: 'workorder', + id: workorder.id, + title: workorder.client.name, + start: moment(workorder.scheduling.start).utc().toISOString(), + end: moment(workorder.scheduling.end).utc().toISOString(), + status: spansStatus(workorderSpans), + spans: _.map(workorderSpans, spanToResponse) + }; + }) + .value() + ); + + results.tasks = results.tasks.concat(_.chain(NON_BILLABLES) + .map(function(nonBillable) { + var nonBillableSpans = filterSpans(spans, { reason: nonBillable }); + + return { + type: 'nonBillable', + reason: nonBillable, + status: spansStatus(nonBillableSpans), + spans: _.map(nonBillableSpans, spanToResponse) + }; + }) + .value() + ); + + return results; +} + +function handleClockInRequest(params, user, spans, workorders, now) { + var workdayStatus = spansStatus(spans, { type: 'workday' }); + + if (params.type === 'workday') { + if (workdayStatus === 'clockedIn') { + return Promise.reject('Already clocked in'); + } + + var span = new TimeClockSpan({ + user: user.id, + type: 'workday', + status: 'open', + start: now.clone().utc().toDate() + }); + + } else { + if (workdayStatus !== 'clockedIn') { + return Promise.reject('Not clocked into day'); + } + + var allTasksStatus = spansStatus(spans, {type: ['workorder', 'nonBillable']}); + if (allTasksStatus === 'clockedIn') { + return Promise.reject('Already clocked in'); + } + + if (params.type === 'workorder') { + var workorder = _.find(workorders, {id: params.id}); + if (!workorder) { + return Promise.reject('Invalid workorder'); + } + + handleClockInExceptions(user, workorder, spans, now); + + var span = new TimeClockSpan({ + user: user.id, + type: 'workorder', + status: 'open', + start: now.clone().utc().toDate(), + workorder: workorder.id, + client: workorder.client.id + }); + } + + if (params.type === 'nonBillable') { + var span = new TimeClockSpan({ + user: user.id, + type: 'nonBillable', + status: 'open', + start: now.clone().utc().toDate(), + reason: params.reason + }); + } + } + + return span.save().then(spanToResponse); +} + +function handleClockInExceptions(user, workorder, spans, now) { + + var closedWorkordersSpans = filterSpans(spans, { type: 'workorder', status: 'closed' }); + var isFirstWorkorder = closedWorkordersSpans.length == 0; + + if (isFirstWorkorder) { + var start = moment(workorder.scheduling.start); + var minutes = now.diff(start, 'minutes'); + + if (minutes > 15) { + reportException({ + user: user, + workorder: workorder, + reason: 'User is late to first workorder.' + }); + } + } else { + var previousWorkorderSpan = _.last(closedWorkordersSpans); + + if (previousWorkorderSpan.workorder != workorder.id) { + console.log(previousWorkorderSpan); + + var end = moment(previousWorkorderSpan.end); + var minutes = now.diff(end, 'minutes'); + console.log("Time between tasks: ", minutes); + + if (minutes < 5) { + reportException({ + user: user, + workorder: workorder, + reason: 'User clocked in to next workorder too quickly.' + }); + } + + if (minutes > 75) { + reportException({ + user: user, + workorder: workorder, + reason: 'Too much travel time detected between jobs.' + }); + } + } + } +} + +function reportException(exception) { + // TODO: Actually send emails for exceptions. + + console.log('--- EXCEPTION ---', exception.reason); +} + +function handleClockOutRequest(params, user, spans, workorders, now) { + var workdaySpans = filterSpans(spans, { type: 'workday' }); + var workdayStatus = spansStatus(workdaySpans); + + if (workdayStatus !== 'clockedIn') { + return Promise.reject('Not clocked in'); + } + + if (params.type === 'workday') { + var allTasksStatus = spansStatus(spans, {type: ['workorder', 'nonBillable']}); + if (allTasksStatus === 'clockedIn') { + return Promise.reject('Cannot clock out while tasks are still open.'); + } + + var span = ensureSingularSpan(filterSpans(workdaySpans, {status: 'open'})); + } + + if (params.type === 'workorder') { + var workorder = _.find(workorders, {id: params.id}); + if (!workorder) { + return Promise.reject('Invalid workorder'); + } + + var workorderSpans = filterSpans(spans, { type: 'workorder', workorder: workorder.id }); + var workorderStatus = spansStatus(workorderSpans); + + if (workorderStatus !== 'clockedIn') { + return Promise.reject('Not clocked in'); + } + + var span = ensureSingularSpan(filterSpans(workorderSpans, {status: 'open'})); + } + + if (params.type === 'nonBillable') { + var nonBillableSpans = filterSpans(spans, { type: 'nonBillable', reason: params.reason }); + var nonBillableStatus = spansStatus(nonBillableSpans); + + if (nonBillableStatus !== 'clockedIn') { + return Promise.reject('Not clocked in'); + } + + var span = ensureSingularSpan(filterSpans(nonBillableSpans, {status: 'open'})); + } + + span.status = 'closed'; + span.end = now.clone().utc().toDate(); + span.duration = moment(span.end).diff(span.start, 'seconds'); + + return span.save().then(spanToResponse); +} + +function handleWorkorderDetailsRequest(params, user, spans, workorder, today) { + if (!workorder) { + return Promise.reject('Invalid workorder'); + } + + var workorderSpans = filterSpans(spans, { type: 'workorder', workorder: workorder.id }); + var workorderStatus = spansStatus(workorderSpans); + + workorder = workorder.toObject(); + workorder.timeclock = { + type: 'workorder', + id: workorder._id, + title: workorder.client.name, + start: moment(workorder.scheduling.start).utc().toISOString(), + end: moment(workorder.scheduling.end).utc().toISOString(), + status: workorderStatus, + spans: _.map(workorderSpans, spanToResponse), + blocked: false + }; + + if (workorderStatus != 'clockedIn') { + var workdayStatus = spansStatus(spans, { type: 'workday' }); + var otherSpansStatus = spansStatus(spans, {type: ['workorder', 'nonBillable']}); + + if (workdayStatus != 'clockedIn' || otherSpansStatus == 'clockedIn') { + workorder.timeclock.blocked = true; + } + } + + return workorder; +} + +function spanToResponse(span) { + return { + start: span.start, + end: span.end, + duration: span.duration + }; +} + +function ensureSingularSpan(spans) { + if (spans.length != 1) { + throw new MultipleSpansError(spans); + } + + return spans[0]; +} + +function responseHandler(res) { + return function(data) { + res.json(data); + }; +} + +function errorHandler(res) { + return function(error) { + if (typeof error === 'string') { + res.json(400, { + error: { + message: error + } + }); + } else { + console.error(error.stack); + res.json(500, 'Internal error'); + } + }; +} + +module.exports = function() { + return { + index: function(req, res) { + + //TODO: Check to make sure user has a valid timesheet. + + var today = moment(); + + var spans = findUserSpans(req.user, today); + var workorders = findUserWorkorders(req.user, today); + + Promise.join(spans, workorders, handleStatusRequest) + .then(responseHandler(res)) + .catch(errorHandler(res)); + }, + + clockIn: function(req, res) { + + //TODO: Check to make sure user has a valid timesheet. + + var today = moment(); + + var params = validateClockRequest(req); + var spans = findUserSpans(req.user, today); + var workorders = findUserWorkorders(req.user, today); + + Promise.join(params, req.user, spans, workorders, today, handleClockInRequest) + .then(responseHandler(res)) + .catch(errorHandler(res)); + }, + + clockOut: function(req, res) { + + //TODO: Check to make sure user has a valid timesheet. + + var today = moment(); + + var params = validateClockRequest(req); + var spans = findUserSpans(req.user, today); + var workorders = findUserWorkorders(req.user, today); + + Promise.join(params, req.user, spans, workorders, today, handleClockOutRequest) + .then(responseHandler(res)) + .catch(errorHandler(res)); + }, + + workorderDetails: function(req, res) { + + var today = moment(); + + validateWorkorderDetailsRequest(req) + .then(function(params) { + var spans = findUserSpans(req.user, today); + var workorder = findUserWorkorder(params.id, req.user); + + return Promise.join(params, req.user, spans, workorder, today, handleWorkorderDetailsRequest) + }) + .then(responseHandler(res)) + .catch(errorHandler(res)); + + + Promise.join + } + } +}; diff --git a/app/model/timeClockSpan.js b/app/model/timeClockSpan.js new file mode 100644 index 0000000..1a392b0 --- /dev/null +++ b/app/model/timeClockSpan.js @@ -0,0 +1,58 @@ +"use strict"; + +var mongoose = require('mongoose'); +var Schema = mongoose.Schema; +var ObjectId = Schema.ObjectId; + +var schema = new Schema({ + + user: { + type: ObjectId, + ref: 'User' + }, + + client: { + type: ObjectId, + ref: 'Client' + }, + + workorder: { + type: ObjectId, + ref: 'Workorder' + }, + + status: { + type: String, + enum: ['open', 'closed'] + }, + + start: { + type: Date + }, + + end: { + type: Date + }, + + duration: { + type: Number, + min: 0 + }, + + type: { + type: String, + enum: [ 'workorder', 'workday', 'nonBillable' ] + }, + + reason: { + type: String, + enum: ['shop', 'break', 'pto', 'meeting', 'event', 'weather', 'holiday'] + }, + + notes: { + type: String, + trim: true + } +}); + +module.exports = mongoose.model('TimeClockSpan', schema); diff --git a/config/auth.js b/config/auth.js index 4b17735..cd59319 100644 --- a/config/auth.js +++ b/config/auth.js @@ -1,6 +1,85 @@ var log = require('log4node'); +var mongoose = require('mongoose'); +var User = mongoose.model('User'); +var request = require('request'); +var jwt = require('jwt-simple'); +var moment = require('moment'); + +var ACCESS_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token'; +var PEOPLE_API_URL = 'https://www.googleapis.com/plus/v1/people/me/openIdConnect'; + +module.exports = function(app, passport, config) { + + function createJWT(user) { + var payload = { + sub: user._id, + iat: moment().unix(), + exp: moment().add(14, 'days').unix() + }; + + return jwt.encode(payload, config.auth.jwtSecret); + } + + app.post('/auth2', function(req, res) { + var params = { + code: req.body.code, + client_id: req.body.clientId, + client_secret: config.auth.clientSecret, + redirect_uri: req.body.redirectUri, + grant_type: 'authorization_code' + }; + + request.post(ACCESS_TOKEN_URL, { json: true, form: params }, function(err, response, token) { + console.log(token); + + var accessToken = token.access_token; + var headers = { + Authorization: 'Bearer ' + accessToken + }; + + request.get({ url: PEOPLE_API_URL, headers: headers, json: true }, function(err, response, profile) { + if (profile.error) { + return res.status(500).send({ message: profile.error.message }); + } + + User.findOne({ email: profile.email.toLowerCase() }, function(err, user) { + if (err) { + return res.status(500).send(err); + } + + if (!user || !user.hasPermission('system.login')) { + return res.status(403).send({ message: "You are not authorized to access this portal."}); + } + + user.accessToken = token.access_token; + + if (token.refresh_token) { + user.refreshToken = token.refresh_token; + } + + if (profile.given_name) { + user.name.first = profile.given_name; + } + + if (profile.family_name) { + user.name.last = profile.family_name; + } + + if (profile.picture) { + user.picture = profile.picture.replace('sz=50', 'sz=200'); + } + + user.save() + .then(function() { + res.send({ token: createJWT(user) }); + }); + }); + }); + }) + }); + + -module.exports = function(app, passport) { app.get('/auth', function(req, res, next) { console.dir(req.headers); req.session.redirectUrl = req.headers['referer']; @@ -52,15 +131,68 @@ module.exports = function(app, passport) { })(req, res, next); }); + function createAuthenticator(error) { + return function(req, res, next) { + var onError = function() { + error(req, res, next); + }; + + var onSuccess = function(user) { + log.setPrefix(function(level) { + return '[' + new Date().toUTCString() + '] ' + level.toUpperCase() + ' ' + user.name.first + ' ' + user.name.last + ' | '; + }); + next(); + } + + if (!req.isAuthenticated()) { + if (!req.headers.authorization) { + return onError(); + } + + var token = req.headers.authorization.split(' ')[1]; + var payload = null; + try { + payload = jwt.decode(token, config.auth.jwtSecret); + } catch (err) { + return onError(); + } + + if (payload.exp <= moment().unix()) { + return onError(); + } + + User.findById(payload.sub, function(err, user) { + console.log('Loaded User'); + req.user = user; + + onSuccess(user); + }); + } else { + onSuccess(req.user); + } + } + } + + return { + requiresUiLogin: createAuthenticator(function(req, res, next) { + res.redirect('/login'); + }), + + requiresApiAccess: createAuthenticator(function(req, res, next) { + res.send(403); + }) + }; + +/* return { requiresUiLogin: function(req, res, next) { if (!req.isAuthenticated()) { return res.redirect('/login'); } - log.setPrefix(function(level) { - return '[' + new Date().toUTCString() + '] ' + level.toUpperCase() + ' ' + req.user.name.first + ' ' + req.user.name.last + ' | '; - }); + log.setPrefix(function(level) { + return '[' + new Date().toUTCString() + '] ' + level.toUpperCase() + ' ' + req.user.name.first + ' ' + req.user.name.last + ' | '; + }); next(); }, requiresApiAccess: function(req, res, next) { @@ -68,10 +200,11 @@ module.exports = function(app, passport) { return res.send(403); } - log.setPrefix(function(level) { - return '[' + new Date().toUTCString() + '] ' + level.toUpperCase() + ' ' + req.user.name.first + ' ' + req.user.name.last + ' | '; - }); + log.setPrefix(function(level) { + return '[' + new Date().toUTCString() + '] ' + level.toUpperCase() + ' ' + req.user.name.first + ' ' + req.user.name.last + ' | '; + }); next(); } }; +*/ }; diff --git a/config/config.js b/config/config.js index d1065cf..698bfee 100644 --- a/config/config.js +++ b/config/config.js @@ -8,7 +8,7 @@ module.exports = { clientSecret: '8MRNar9E_pRTOGTQonPzYOW_', callback: 'http://devel.portal.atlanticbiomedical.com/auth/callback', accessToken: 'ya29.AHES6ZR-vUVEh7CZzsEeGFSHqFfXtU1-LHyEAidi0CKhDGQ', - refreshToken: '1/exRXjTaGNlWEo-HZZWyn4NTwJ4TY3wKb-_npce21c50', + refreshToken: '1/exRXjTaGNlWEo-HZZWyn4NTwJ4TY3wKb-_npce21c50' }, email: { user: 'api@atlanticbiomedical.com', @@ -18,7 +18,7 @@ module.exports = { host: 'biomed.akira.gs', user: 'biomed_prod', password: 'wUw3RB8rrXX4HwKj', - database: 'biomed_prod', + database: 'biomed_prod' } }, prod: { @@ -28,16 +28,17 @@ module.exports = { auth: { clientId: '333768673996-8epedo3je5h59n4l97v4dv8nofs7qnee.apps.googleusercontent.com', clientSecret: 'afu9KhKxckWJ3Tk6uxzp9Pg6', - callback: 'http://portal.atlanticbiomedical.com/auth/callback', + callback: 'http://localhost:9000/auth/callback', // accessToken: 'ya29.AHES6ZT1Sj1vpgidR2I_ksLdlV_VeZUjkitnZ01cP6VRrknjUEVbuw', // refreshToken: '1/XQW9P9FNYm6jikTsV8HOIuPAo1APYhwTH5CLhq9263g' accessToken: 'ya29.1.AADtN_Xjt0PK6YVs8q5csiQFXQg2ZDtrVhsH6P4a5zm0mHqhGx0Nnjx4Jk68Gw', refreshToken: '1/_5SkDLYmsi4XNaQyAzld-W5-GEqEqt5byH6VkI-j5QI', + jwtSecret: '97v4dvcsiQFXQg28nofedo3jemsi4XNaQy5h59n4l97m0mHqhGx0Nnjxv4dv8n' }, email: { user: 'api@atlanticbiomedical.com', - password: 'success4', + password: 'success4' }, mysql: { host: 'localhost', diff --git a/config/express.js b/config/express.js index b8261b8..e0d2e20 100644 --- a/config/express.js +++ b/config/express.js @@ -1,4 +1,5 @@ var express = require('express'); +var cors = require('cors'); var ClusterStore = require('strong-cluster-connect-store')(express.session); module.exports = function(app, config, passport, piler) { @@ -23,6 +24,14 @@ module.exports = function(app, config, passport, piler) { app.use(passport.initialize()); app.use(passport.session()); + // allow cors + app.use(cors({ + origin: function(origin, callback) { + callback(null, true); + }, + credentials: true + })); + // use piler for asset management piler.bind(); diff --git a/config/routes.js b/config/routes.js index 7e8cf7c..2291676 100644 --- a/config/routes.js +++ b/config/routes.js @@ -94,6 +94,12 @@ module.exports = function(app, auth, piler, calendar, directory, config) { app.post('/api/test_runs', testRuns.create); app.post('/api/test_runs/:test_run_id', testRuns.update); + var timeclock = require('../app/controllers/timeclock')(); + app.get('/api/timeclock', timeclock.index); + app.post('/api/timeclock/clock_in', timeclock.clockIn); + app.post('/api/timeclock/clock_out', timeclock.clockOut); + app.get('/api/timeclock/workorder/:id', timeclock.workorderDetails); + var pms = require('../app/controllers/pms'); app.get('/api/pms', pms.index); @@ -107,8 +113,9 @@ module.exports = function(app, auth, piler, calendar, directory, config) { app.post('/api/users/:user_id', users.update); app.get('/api/users/:user_id/clocks', users.clocks); - var account = require('../app/controllers/account'); + var account = require('../app/controllers/account')(config); app.get('/api/account', account.profile); + app.post('/api/account/impersonate', account.impersonate); var messages = require('../app/controllers/messages')(config); app.post('/api/messages/send', messages.send); diff --git a/server.js b/server.js index 80ef3dd..c3dffa0 100644 --- a/server.js +++ b/server.js @@ -9,10 +9,12 @@ var fs = require('fs'); var passport = require('passport'); var config = require('./config/config')[env]; var mongoose = require('mongoose'); -var Promise = require('bluebird'); var log = require('log4node'); -Promise.promisifyAll(mongoose); +mongoose.Promise = require('bluebird'); + +var moment = require('moment-timezone'); +moment.tz.setDefault("America/New_York"); var pushoverApi = new pushover({ user: 'aJmPD4KigO0vLwim76n3WqWKwbKA3k', @@ -57,7 +59,7 @@ var piler = require('./config/piler')(app, server, io, config); // Express settings require('./config/express')(app, config, passport, piler); -var auth = require('./config/auth')(app, passport); +var auth = require('./config/auth')(app, passport, config); var calendar = require('./config/calendar')(config);