From 1e3f15ceacf8b4cfea89fa5938adc5fe4ddb7f5a Mon Sep 17 00:00:00 2001 From: Patrick Forringer Date: Sun, 20 Nov 2011 16:54:40 -0600 Subject: [PATCH] Starting work on user authentication. --- .gitignore | 3 +- config/default.yaml | 14 ++++++ lib/auth/index.js | 70 +++++++++++++++++++++++++++++ lib/server.js | 17 +++++++- libsrc/auth/index.coffee | 92 +++++++++++++++++++++++++++++++++++++++ libsrc/server.coffee | 19 +++++++- package.json | 4 +- public/css/basics.css | 17 ++++++-- publicsrc/css/basics.less | 27 ++++++++---- views/layout.jade | 42 ++++++++++++------ 10 files changed, 275 insertions(+), 30 deletions(-) create mode 100644 config/default.yaml create mode 100644 lib/auth/index.js create mode 100644 libsrc/auth/index.coffee diff --git a/.gitignore b/.gitignore index a12401f..07e67c7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ node_modules npm-debug.log docs/ .monitor -publicsrc/bootstrap \ No newline at end of file +publicsrc/bootstrap +config/pats-mbp.yaml \ No newline at end of file diff --git a/config/default.yaml b/config/default.yaml new file mode 100644 index 0000000..7425207 --- /dev/null +++ b/config/default.yaml @@ -0,0 +1,14 @@ +debug: false +server: + uri: "localhost" + port: 9000 +auth: + twitter: + # consumerKey: "changeme" + # consumerSecret: "changeme" + facebook: + # appId: "changeme" + # appSecret: "changeme" + github: + # appId: false + # appSecret: false \ No newline at end of file diff --git a/lib/auth/index.js b/lib/auth/index.js new file mode 100644 index 0000000..84ee168 --- /dev/null +++ b/lib/auth/index.js @@ -0,0 +1,70 @@ +(function() { + var everyauth, https; + + everyauth = require('everyauth'); + + https = require('https'); + + module.exports = function(app, config) { + var external, normalizeUserData; + everyauth.debug = config.debug; + external = config.auth; + everyauth.everymodule.handleLogout(function(req, res) { + delete req.session.user; + req.logout(); + res.writeHead(303, { + 'Location': this.logoutRedirectPath() + }); + return res.end(); + }); + if ((external != null ? external.facebook : void 0)) { + everyauth.facebook.appId(external.facebook.appId).appSecret(external.facebook.appSecret).findOrCreateUser(function(session, accessToken, accessTokenExtra, facebookUserMetaData) { + return true; + }).redirectPath('/'); + } + if ((external != null ? external.twitter : void 0)) { + everyauth.twitter.myHostname(config.server.uri).consumerKey(external.twitter.consumerKey).consumerSecret(external.twitter.consumerSecret).findOrCreateUser(function(session, accessToken, accessSecret, twitterUser) { + return true; + }).redirectPath('/'); + } + if ((external != null ? external.github : void 0)) { + everyauth.github.myHostname(config.server.uri).appId(external.github.appId).appSecret(external.github.appSecret).findOrCreateUser(function(session, accessToken, accessTokenExtra, githubUser) { + return true; + }).redirectPath('/'); + } + everyauth.helpExpress(app); + normalizeUserData = function() { + return function(req, res, next) { + var user, _ref, _ref2, _ref3; + if (!((_ref = req.session) != null ? _ref.user : void 0) && ((_ref2 = req.session) != null ? (_ref3 = _ref2.auth) != null ? _ref3.loggedIn : void 0 : void 0)) { + user = {}; + if (req.session.auth.github) { + user.image = 'http://1.gravatar.com/avatar/' + req.session.auth.github.user.gravatar_id + '?s=48'; + user.name = req.session.auth.github.user.name; + user.id = 'github-' + req.session.auth.github.user.id; + } + if (req.session.auth.twitter) { + user.image = req.session.auth.twitter.user.profile_image_url; + user.name = req.session.auth.twitter.user.name; + user.id = 'twitter-' + req.session.auth.twitter.user.id_str; + } + if (req.session.auth.facebook) { + user.image = req.session.auth.facebook.user.picture; + user.name = req.session.auth.facebook.user.name; + user.id = 'facebook-' + req.session.auth.facebook.user.id; + return; + } + req.session.user = user; + } + return next(); + }; + }; + return { + 'middleware': { + 'auth': everyauth.middleware, + 'normalizeUserData': normalizeUserData + } + }; + }; + +}).call(this); diff --git a/lib/server.js b/lib/server.js index 229e4c2..9c1857d 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,10 +1,12 @@ (function() { - var Steps, app, express, path, public, publicsrc, root, steps; + var Steps, app, auth, config, express, path, public, publicsrc, root, steps; express = require('express'); path = require('path'); + config = require('config'); + app = module.exports = express.createServer(); root = path.resolve(__dirname + "/../"); @@ -13,6 +15,8 @@ publicsrc = root + '/publicsrc'; + auth = require('./auth')(app, config); + app.configure(function() { app.set('views', path.resolve(__dirname + '/../views')); app.set('view engine', 'jade'); @@ -26,6 +30,8 @@ secret: 'hgk83kc0qdm298xn', store: new express.session.MemoryStore })); + app.use(auth.middleware.auth()); + app.use(auth.middleware.normalizeUserData()); app.use(express.compiler({ src: publicsrc, dest: public, @@ -46,6 +52,12 @@ return app.use(express.errorHandler()); }); + app.dynamicHelpers({ + session: function(req, res) { + return req.session; + } + }); + app.get('/', function(req, res) { console.log('Connect.sid ', req.cookies['connect.sid']); return res.render('home', { @@ -60,11 +72,12 @@ steps.createServer(app); process.on('uncaughtException', function(err) { + console.log('\u0007'); console.error(err); return console.log(err.stack); }); - app.listen(process.env.PORT || 9000); + app.listen(process.env.PORT || config.server.port || 9000); console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env); diff --git a/libsrc/auth/index.coffee b/libsrc/auth/index.coffee new file mode 100644 index 0000000..1f683e1 --- /dev/null +++ b/libsrc/auth/index.coffee @@ -0,0 +1,92 @@ +everyauth = require 'everyauth' +https = require 'https' + +module.exports = ( app, config ) -> + + everyauth.debug = config.debug + external = config.auth + + everyauth.everymodule.handleLogout (req, res) -> + delete req.session.user + req.logout() + # res.redirect() + res.writeHead(303, { 'Location': @logoutRedirectPath() }) + res.end() + + # Facebook + if (external?.facebook) + everyauth.facebook.appId(external.facebook.appId) + .appSecret(external.facebook.appSecret) + .findOrCreateUser (session, accessToken, accessTokenExtra, facebookUserMetaData) -> + true + .redirectPath('/') + + + # Twitter + if (external?.twitter) + everyauth.twitter + .myHostname(config.server.uri) + .consumerKey(external.twitter.consumerKey) + .consumerSecret(external.twitter.consumerSecret) + .findOrCreateUser (session, accessToken, accessSecret, twitterUser) -> + # actually create user here + true + .redirectPath('/') + + # Github + if (external?.github) + everyauth.github + .myHostname(config.server.uri) + .appId(external.github.appId) + .appSecret(external.github.appSecret) + .findOrCreateUser (session, accessToken, accessTokenExtra, githubUser) -> + true + .redirectPath('/') + + everyauth.helpExpress(app) + + # Fetch and format data so we have an easy object with user data to work with. + normalizeUserData = -> + return (req, res, next) -> + if ( !req.session?.user && req.session?.auth?.loggedIn) + # possibly se a switch here + user = {} + if (req.session.auth.github) + user.image = 'http://1.gravatar.com/avatar/'+req.session.auth.github.user.gravatar_id+'?s=48' + user.name = req.session.auth.github.user.name + user.id = 'github-'+req.session.auth.github.user.id + + if (req.session.auth.twitter) + user.image = req.session.auth.twitter.user.profile_image_url + user.name = req.session.auth.twitter.user.name + user.id = 'twitter-'+req.session.auth.twitter.user.id_str + + if (req.session.auth.facebook) + user.image = req.session.auth.facebook.user.picture + user.name = req.session.auth.facebook.user.name + user.id = 'facebook-'+req.session.auth.facebook.user.id + + # // Need to fetch the users image... + # https.get({ + # 'host': 'graph.facebook.com' + # 'path': '/me/picture?access_token='+req.session.auth.facebook.accessToken + # }, (response) -> + # user.image = response.headers.location + # req.session.user = user + # next() + # .on 'error', (e) -> + # req.session.user = user + # next() + + return + + req.session.user = user + + next() + + return { + 'middleware': { + 'auth': everyauth.middleware + 'normalizeUserData': normalizeUserData + } + } \ No newline at end of file diff --git a/libsrc/server.coffee b/libsrc/server.coffee index cb173ba..3f14b5d 100644 --- a/libsrc/server.coffee +++ b/libsrc/server.coffee @@ -1,12 +1,19 @@ +# Server requirements express = require 'express' path = require 'path' +config = require 'config' +# Create server app = module.exports = express.createServer() root = path.resolve __dirname + "/../" + +# Static directories public = root + '/public' publicsrc = root + '/publicsrc' +auth = require('./auth')(app,config) + app.configure( -> app.set 'views', path.resolve __dirname + '/../views' app.set 'view engine', 'jade' @@ -18,6 +25,10 @@ app.configure( -> secret: 'hgk83kc0qdm298xn' store: new express.session.MemoryStore }) + + app.use auth.middleware.auth() + app.use auth.middleware.normalizeUserData() + app.use express.compiler( src: publicsrc dest: public @@ -32,6 +43,11 @@ app.configure 'development', -> app.configure 'production', -> app.use express.errorHandler() + +app.dynamicHelpers { + session: (req, res) -> + return req.session; +} app.get '/', (req, res) -> console.log 'Connect.sid ', req.cookies['connect.sid'] @@ -46,9 +62,10 @@ steps.createServer app # Catch uncaught exceptions process.on 'uncaughtException', (err) -> + console.log('\u0007') #ringy dingy console.error err console.log err.stack -app.listen(process.env.PORT || 9000) +app.listen( process.env.PORT || config.server.port || 9000 ) console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env) diff --git a/package.json b/package.json index 5474d9b..da24e5c 100644 --- a/package.json +++ b/package.json @@ -17,13 +17,15 @@ "node": "~0.6.0" }, "dependencies": { + "config": "*", "express": "2.5.1", "coffee-script": "1.1.3", "jade": "0.17.0", "nodemon": "*", "skull.io": "0.2.0", "underscore": "*", - "less": "*" + "less": "*", + "everyauth": "0.2.23" }, "devDependencies": { "expresso": "*" diff --git a/public/css/basics.css b/public/css/basics.css index 14c5195..82be7c7 100644 --- a/public/css/basics.css +++ b/public/css/basics.css @@ -37,10 +37,6 @@ body { padding: 20px 20px 10px; margin: -20px -20px 20px; } -/* Styles you shouldn't keep as they are for displaying this base example only */ -.content .span10, .content .span4 { - min-height: 500px; -} /* Give a quick and non-cross-browser friendly divider */ .content .span4 { margin-left: 0; @@ -50,3 +46,16 @@ body { .topbar .btn { border: 0; } +.topbar ul li.account a.user-photo img { + border: medium none; + cursor: pointer; + display: inline-block; + height: 20px; + margin-right: 6px; + margin-top: -3px; + vertical-align: middle; + width: 20px; + -webkit-box-shadow: 0px 1px 0px white; + -moz-box-shadow: 0px 1px 0px white; + box-shadow: 0px 1px 0px white; +} diff --git a/publicsrc/css/basics.less b/publicsrc/css/basics.less index 83c3a5c..c5be067 100644 --- a/publicsrc/css/basics.less +++ b/publicsrc/css/basics.less @@ -1,3 +1,9 @@ +.boxshadow(@x: 0px, @y: 1px, @blur: 2px, @color: rgba(0,0,0,.15) ){ + -webkit-box-shadow: @x @y @blur @color; + -moz-box-shadow: @x @y @blur @color; + box-shadow: @x @y @blur @color; +} + /* Override some defaults */ html, body { background-color: #eee; @@ -20,9 +26,7 @@ body { -webkit-border-radius: 0 0 6px 6px; -moz-border-radius: 0 0 6px 6px; border-radius: 0 0 6px 6px; - -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.15); - -moz-box-shadow: 0 1px 2px rgba(0,0,0,.15); - box-shadow: 0 1px 2px rgba(0,0,0,.15); + .boxshadow(0, 1px, 2px, rgba(0,0,0,.15)) } /* Page header tweaks */ @@ -32,11 +36,6 @@ body { margin: -20px -20px 20px; } -/* Styles you shouldn't keep as they are for displaying this base example only */ -.content .span10, -.content .span4 { - min-height: 500px; -} /* Give a quick and non-cross-browser friendly divider */ .content .span4 { margin-left: 0; @@ -46,4 +45,16 @@ body { .topbar .btn { border: 0; +} + +.topbar ul li.account a.user-photo img { + border: medium none; + cursor: pointer; + display: inline-block; + height: 20px; + margin-right: 6px; + margin-top: -3px; + vertical-align: middle; + width: 20px; + .boxshadow(0px,1px,0px,white) } \ No newline at end of file diff --git a/views/layout.jade b/views/layout.jade index 343a075..1daf2f5 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -17,25 +17,41 @@ html header.topbar .fill .container - a(href="#").brand Project Doc + a(href="/").brand Project Doc ul.nav li.active - a(href="#") Navigation + a(href="/") Home li - a(href="#") Navigation + a(href="/projects") Projects li - a(href="#") Navigation + a(href="/featured") Featured + li + a(href="/tools") Tools li.d ul.nav.secondary-nav - li.dropdown(data-dropdown="login") - a.dropdown-toggle(href="#") Login - ul.dropdown-menu - li - a(href="#") with Twitter - a(href="#") with Facebook - li.divider - li - a(href="#") Register Account + -if(!session.auth) + li.dropdown(data-dropdown="login") + a.dropdown-toggle(href="#") Login + ul.dropdown-menu + li + a(href="/auth/twitter") with Twitter + a(href="/auth/facebook") with Facebook + a(href="/auth/github") with Github + li.divider + li + a(href="/register") Register Account + -else + li.dropdown.account(data-dropdown="account") + a.dropdown-toggle.user-photo(href="#") + img(src=session.user.image) + = session.user.name + ul.dropdown-menu + li + a(href="/settings") Settings + li + a(href="/myProjects") Projects + li + a(href="/logout") Logout section.container .content