diff --git a/.gitignore b/.gitignore index 7a3e824b2d..012cd7cae1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ public/gen node_modules #lib/ *.swp +.idea* \ No newline at end of file diff --git a/Makefile b/Makefile index e27a82a557..8fc09bff47 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,5 @@ compile: ./node_modules/coffee-script/bin/coffee -bw -o ./lib -c ./src + +test-casper: + casperjs test ./test diff --git a/lib/app/index.js b/lib/app/index.js index df21a3bb1c..ffe329ddc8 100644 --- a/lib/app/index.js +++ b/lib/app/index.js @@ -20,23 +20,17 @@ helpers = require('./helpers'); helpers.viewHelpers(view); get('/:uidParam?', function(page, model, _arg, next) { - var q, sess, uidParam, userId; + var sess, uidParam; uidParam = _arg.uidParam; - if (uidParam === 'privacy' || uidParam === 'terms') { + if (uidParam === 'privacy' || uidParam === 'terms' || uidParam === 'auth') { return next(); } sess = model.session; - if (sess.auth && sess.auth.facebook) { - q = model.query('users').withEveryauth('facebook', sess.auth.facebook.id); - model.fetch(q, function(err, user) { - if (user && user.get('id') !== sess.auth.id) { - sess.auth.id = user.get('id'); - return page.redirect('/'); - } - }); + if (sess.habitRpgAuth && sess.habitRpgAuth.facebook) { + model.set('_facebookAuthenticated', true); } - userId = model.get('_userId'); - return model.subscribe("users." + userId, function(err, user) { + model.set('_userId', sess.userId); + return model.subscribe("users." + sess.userId, function(err, user) { model.ref('_user', user); model.set('_items', { armor: content.items.armor[parseInt(user.get('items.armor')) + 1], diff --git a/lib/server/auth.js b/lib/server/auth.js index 88c6c8e944..dabecd1f52 100644 --- a/lib/server/auth.js +++ b/lib/server/auth.js @@ -1,80 +1,81 @@ // Generated by CoffeeScript 1.3.3 -var conf, derby, model, sess; +var conf, content, derby, req, schema; conf = require("./conf"); derby = require('derby'); -model = void 0; +schema = require('../app/schema'); -sess = void 0; +content = require('../app/content'); -module.exports.setupPurlAuth = function(req) { - var acceptableUid, uidParam; +req = void 0; + +module.exports.setRequest = function(r) { + return req = r; +}; + +module.exports.newUserAndPurl = function() { + var acceptableUid, guid, model, newUser, sess, task, uidParam, _i, _len, _ref; model = req.getModel(); - sess = req.session; - sess.userId || (sess.userId = derby.uuid()); - sess.auth || (sess.auth = { - userId: sess.userId - }); + sess = model.session; uidParam = req.url.split('/')[1]; + if (!sess.userId) { + sess.userId = derby.uuid(); + newUser = require('node.extend')(true, {}, schema.userSchema); + _ref = content.defaultTasks; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + task = _ref[_i]; + guid = task.id = require('derby/node_modules/racer').uuid(); + newUser.tasks[guid] = task; + switch (task.type) { + case 'habit': + newUser.habitIds.push(guid); + break; + case 'daily': + newUser.dailyIds.push(guid); + break; + case 'todo': + newUser.todoIds.push(guid); + break; + case 'reward': + newUser.rewardIds.push(guid); + } + } + model.set("users." + sess.userId, newUser); + } acceptableUid = require('guid').isGuid(uidParam) || (uidParam === '3' || uidParam === '9'); if (acceptableUid && sess.userId !== uidParam) { - sess.userId = uidParam; + return sess.userId = uidParam; } - return model.set('_userId', sess.userId); }; module.exports.setupEveryauth = function(everyauth) { everyauth.debug = true; everyauth.everymodule.findUserById(function(id, callback) { - return model.fetch("users." + id, function(err, user) { - var content, guid, newUser, schema, task, _i, _len, _ref; - if (user && user.get('id')) { - return callback(null, user.get()); - } else { - schema = require('../app/schema'); - content = require('../app/content'); - newUser = require('node.extend')(true, {}, schema.userSchema); - _ref = content.defaultTasks; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - task = _ref[_i]; - guid = task.id = require('derby/node_modules/racer').uuid(); - newUser.tasks[guid] = task; - switch (task.type) { - case 'habit': - newUser.habitIds.push(guid); - break; - case 'daily': - newUser.dailyIds.push(guid); - break; - case 'todo': - newUser.todoIds.push(guid); - break; - case 'reward': - newUser.rewardIds.push(guid); - } - } - model.set("users." + id, newUser); - return callback(null, newUser); - } - }); + return callback(null, null); }); return everyauth.facebook.appId(process.env.FACEBOOK_KEY).appSecret(process.env.FACEBOOK_SECRET).findOrCreateUser(function(session, accessToken, accessTokenExtra, fbUserMetadata) { - var q; + var model, q; + session.habitRpgAuth || (session.habitRpgAuth = {}); + session.habitRpgAuth.facebook = fbUserMetadata.id; + model = req.getModel(); q = model.query('users').withEveryauth('facebook', fbUserMetadata.id); model.fetch(q, function(err, user) { + var id; + id = user && user.get() && user.get()[0].id; console.log({ err: err, - user: user + id: id, + fbUserMetadata: fbUserMetadata }); - if (user.get('id')) { - return sess.userId = user.get('id'); + if (id && id !== session.userId) { + return session.userId = id; } else { - model.setNull("users." + sess.userId + ".auth", { + model.setNull("users." + session.userId + ".auth", { 'facebook': {} }); - return model.set("users." + sess.userId + ".auth.facebook", fbUserMetadata); + return model.set("users." + session.userId + ".auth.facebook", fbUserMetadata); } }); return fbUserMetadata; diff --git a/lib/server/index.js b/lib/server/index.js index 65e85669e7..6a0a1c6bea 100644 --- a/lib/server/index.js +++ b/lib/server/index.js @@ -50,6 +50,8 @@ auth.setupQueries(store); auth.setupEveryauth(everyauth); +auth.setupAccessControl(store); + ONE_YEAR = 1000 * 60 * 60 * 24 * 365; root = path.dirname(path.dirname(__dirname)); @@ -59,11 +61,9 @@ publicPath = path.join(root, 'public'); habitrpgMiddleware = function(req, res, next) { var model; model = req.getModel(); - auth.setupPurlAuth(req); - auth.setupAccessControl(store); - model.set('_stripePubKey', process.env.STRIPE_PUB_KEY); - model.set('_nodeEnv', process.env.NODE_ENV); model.set('_mobileDevice', /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent'))); + auth.setRequest(req); + auth.newUserAndPurl(); return next(); }; @@ -79,4 +79,4 @@ expressApp.use(express.favicon()).use(gzippo.staticGzip(publicPath, { }) })).use(store.modelMiddleware()).use(habitrpgMiddleware).use(everyauth.middleware()).use(app.router()).use(expressApp.router).use(serverError(root)); -require('./serverRoutes')(expressApp); +require('./serverRoutes')(expressApp, root, derby); diff --git a/lib/server/serverRoutes.js b/lib/server/serverRoutes.js index b570ce2f67..9cac621342 100644 --- a/lib/server/serverRoutes.js +++ b/lib/server/serverRoutes.js @@ -3,7 +3,7 @@ var scoring; scoring = require('../app/scoring'); -module.exports = function(expressApp) { +module.exports = function(expressApp, root, derby) { expressApp.get('/:uid/up/:score?', function(req, res) { var model, score; score = parseInt(req.params.score) || 1; @@ -50,10 +50,10 @@ module.exports = function(expressApp) { staticPages = derby.createStatic(root); return staticPages.render('terms', res); }); - expressApp.all('*', function(req) { - throw "404: " + req.url; - }); - return expressApp.post('/', function(req) { + expressApp.post('/', function(req) { return require('../app/reroll').stripeResponse(req); }); + return expressApp.all('*', function(req) { + throw "404: " + req.url; + }); }; diff --git a/src/app/index.coffee b/src/app/index.coffee index 2821977213..e4da0fedce 100644 --- a/src/app/index.coffee +++ b/src/app/index.coffee @@ -16,20 +16,13 @@ helpers.viewHelpers(view) get '/:uidParam?', (page, model, {uidParam}, next) -> #FIXME figure out a better way to do this - return next() if (uidParam == 'privacy' or uidParam == 'terms') - + return next() if (uidParam == 'privacy' or uidParam == 'terms' or uidParam == 'auth') + sess = model.session - if sess.auth && sess.auth.facebook - q = model.query('users').withEveryauth('facebook', sess.auth.facebook.id) - model.fetch q, (err, user) -> - if (user && user.get('id')!=sess.auth.id) - sess.auth.id = user.get('id') - return page.redirect('/') - - userId = model.get '_userId' - - model.subscribe "users.#{userId}", (err, user) -> - + if sess.habitRpgAuth && sess.habitRpgAuth.facebook + model.set('_facebookAuthenticated', true) + model.set '_userId', sess.userId + model.subscribe "users.#{sess.userId}", (err, user) -> model.ref '_user', user # Store diff --git a/src/server/auth.coffee b/src/server/auth.coffee index 478df7bdb7..9255b40372 100644 --- a/src/server/auth.coffee +++ b/src/server/auth.coffee @@ -1,58 +1,50 @@ conf = require("./conf") derby = require('derby') +schema = require('../app/schema') +content = require('../app/content') -# FIXME I need access to model in everyauth, so that I can test whether a user exists in the database or -# create a new one otherwise. Everyauth configs must be declared before expressApp.use(...); howeve,r model is -# setup during expresApp.use(...). So my hack is - declare model here, define everyauth configs early in the server (like -# we should), then use our own middleware (habitrpgMiddleware) to model during expresApp.use(...) -model = undefined -sess = undefined +# Need this for later use by EveryAuth in the MiddleWare +req = undefined +module.exports.setRequest = (r) -> + req = r -# Ultimate goal: set `sess.auth.userId` - -## PURL authentication -# This is temporary and will go away when everyauth is setup. It tests to see if -# a UUID was used (bookmarked private url), and restores the user session if so -module.exports.setupPurlAuth = (req) -> +module.exports.newUserAndPurl = () -> model = req.getModel() - sess = req.session - - # Setup userId for new users - sess.userId ||= derby.uuid() - sess.auth ||= {userId: sess.userId} # prepare for everyauth - # Previously saved session (eg, http://localhost/{guid}) (temporary solution until authentication built) + sess = model.session uidParam = req.url.split('/')[1] + + ## -------- (1) New user -------- + # They get to play around before creating a new account. + unless sess.userId + sess.userId = derby.uuid() + # deep clone, else further new users get duplicate objects + newUser = require('node.extend')(true, {}, schema.userSchema) + for task in content.defaultTasks + guid = task.id = require('derby/node_modules/racer').uuid() + newUser.tasks[guid] = task + switch task.type + when 'habit' then newUser.habitIds.push guid + when 'daily' then newUser.dailyIds.push guid + when 'todo' then newUser.todoIds.push guid + when 'reward' then newUser.rewardIds.push guid + model.set "users.#{sess.userId}", newUser + + ## -------- (2) PURL -------- + # eg, http://localhost/{guid}), legacy - will be removed eventually + # tests if UUID was used (bookmarked private url), and restores that session acceptableUid = require('guid').isGuid(uidParam) or (uidParam in ['3','9']) if acceptableUid && sess.userId!=uidParam - # TODO test whether user exists: ```model.fetch("users.#{uidParam}", function(err,user){if(user.get(..){})}})```, but doesn't seem to work + # TODO check if in database - issue with accessControl which is on current uid? sess.userId = uidParam - model.set '_userId', sess.userId -## Setup callbacks for serializing/deserializing users to/from model. module.exports.setupEveryauth = (everyauth) -> - + everyauth.debug = true everyauth.everymodule.findUserById (id, callback) -> - model.fetch "users.#{id}", (err, user) -> - if user && user.get('id') - callback null, user.get() # FIXME what format are we supposed to return user? - else - # Create new user if none exists - # deep clone, else further new users get duplicate objects - schema = require('../app/schema') - content = require('../app/content') - newUser = require('node.extend')(true, {}, schema.userSchema) - for task in content.defaultTasks - guid = task.id = require('derby/node_modules/racer').uuid() - newUser.tasks[guid] = task - switch task.type - when 'habit' then newUser.habitIds.push guid - when 'daily' then newUser.dailyIds.push guid - when 'todo' then newUser.todoIds.push guid - when 'reward' then newUser.rewardIds.push guid - model.set "users.#{id}", newUser - callback null, newUser + # will never be called, can't fetch user from database at this point on the server + # see https://github.com/codeparty/racer/issues/39. Handled in app/auth.coffee for now + callback null, null # Facebook Authentication Logic everyauth @@ -60,29 +52,28 @@ module.exports.setupEveryauth = (everyauth) -> .appId(process.env.FACEBOOK_KEY) .appSecret(process.env.FACEBOOK_SECRET) .findOrCreateUser( (session, accessToken, accessTokenExtra, fbUserMetadata) -> - # usersByFbId[fbUserMetadata.id] or (usersByFbId[fbUserMetadata.id] = addUser("facebook", fbUserMetadata)) + + # Put it in the session for later use + # FIXME shouldn't this be set by everyauth? (session.auth.facebook) + session.habitRpgAuth ||= {} + session.habitRpgAuth.facebook = fbUserMetadata.id + + model = req.getModel() q = model.query('users').withEveryauth('facebook', fbUserMetadata.id) - model.fetch q, (err,user) -> - console.log {err:err,user:user} #FIXME this is always returning user:null, however; this runs fine on the client - if user.get('id') - sess.userId = user.get('id') # is necessary? + model.fetch q, (err, user) -> + id = user && user.get() && user.get()[0].id + console.log {err:err, id:id, fbUserMetadata:fbUserMetadata} + # Has user been tied to facebook account already? + if (id && id!=session.userId) + session.userId = id + # Else tie user to their facebook account else - model.setNull "users.#{sess.userId}.auth", {'facebook':{}} - model.set "users.#{sess.userId}.auth.facebook", fbUserMetadata + model.setNull "users.#{session.userId}.auth", {'facebook':{}} + model.set "users.#{session.userId}.auth.facebook", fbUserMetadata + fbUserMetadata ).redirectPath "/" - # addUser = (source, sourceUser) -> - # user = undefined - # if arguments.length is 1 # password-based - # user = sourceUser = source - # user.id = ++nextUserId - # return usersById[nextUserId] = user - # else # non-password-based - # user = usersById[++nextUserId] = id: nextUserId - # user[source] = sourceUser - # user - module.exports.setupQueries = (store) -> ## Setup Queries store.query.expose 'users', 'withId', (id) -> diff --git a/src/server/index.coffee b/src/server/index.coffee index db1044fae5..d4b66aa94b 100644 --- a/src/server/index.coffee +++ b/src/server/index.coffee @@ -29,6 +29,7 @@ store = derby.createStore listen: server auth.setupQueries(store) auth.setupEveryauth(everyauth) +auth.setupAccessControl(store) ONE_YEAR = 1000 * 60 * 60 * 24 * 365 root = path.dirname path.dirname __dirname @@ -36,16 +37,10 @@ publicPath = path.join root, 'public' habitrpgMiddleware = (req, res, next) -> model = req.getModel() - - auth.setupPurlAuth(req) - auth.setupAccessControl(store) - - model.set '_stripePubKey', process.env.STRIPE_PUB_KEY - model.set '_nodeEnv', process.env.NODE_ENV - ## Set _mobileDevice to true or false so view can exclude portions from mobile device model.set '_mobileDevice', /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header 'User-Agent') - + auth.setRequest(req) # Need to pass into auth, so auth can save as private variable used later by EveryAuth + auth.newUserAndPurl() next() expressApp @@ -79,4 +74,4 @@ expressApp .use(expressApp.router) .use(serverError root) -require('./serverRoutes')(expressApp) +require('./serverRoutes')(expressApp, root, derby) diff --git a/src/server/serverRoutes.coffee b/src/server/serverRoutes.coffee index 2b4be2447a..11810bf98e 100644 --- a/src/server/serverRoutes.coffee +++ b/src/server/serverRoutes.coffee @@ -1,6 +1,6 @@ scoring = require('../app/scoring') -module.exports = (expressApp) -> +module.exports = (expressApp, root, derby) -> expressApp.get '/:uid/up/:score?', (req, res) -> score = parseInt(req.params.score) || 1 @@ -27,9 +27,9 @@ module.exports = (expressApp) -> expressApp.get '/terms', (req, res) -> staticPages = derby.createStatic root staticPages.render 'terms', res - - expressApp.all '*', (req) -> - throw "404: #{req.url}" - + expressApp.post '/', (req) -> require('../app/reroll').stripeResponse(req) + + expressApp.all '*', (req) -> + throw "404: #{req.url}" diff --git a/views/app/index.html b/views/app/index.html index 97c33d6135..bf3c70900b 100644 --- a/views/app/index.html +++ b/views/app/index.html @@ -11,15 +11,13 @@