UNPKG

artsy-passport

Version:

Wires up the common auth handlers for Artsy's [Ezel](ezeljs.com)-based apps using [passport](http://passportjs.org/).

236 lines (185 loc) 6.64 kB
# Artsy Passport [![CircleCI](https://circleci.com/gh/artsy/artsy-passport.svg?style=svg)](https://circleci.com/gh/artsy/artsy-passport) Wires up the common auth handlers, and related security concerns, for Artsy's [Ezel](http://ezeljs.com)-based apps using [passport](http://passportjs.org/). Used internally at Artsy to DRY up authentication code. ## Breaking changes in 2.0 * The app is now shipped as a single JS file. ## Setup #### Make sure you first mount session, body parser, and start [artsy-xapp](https://github.com/artsy/artsy-xapp). ```coffee app.use express.bodyParser() app.use express.cookieParser('foobar') app.use express.cookieSession() artsyXapp.init -> app.listen() ``` #### Then mount Artsy Passport passing a big configuration hash. _Values indicate defaults._ ```coffee app.use artsyPassport CurrentUser: # The CurrentUser Backbone model # Pass in env vars # ---------------- FACEBOOK_ID: # Facebook app ID FACEBOOK_SECRET: # Facebook app secret TWITTER_KEY: # Twitter consumer key TWITTER_SECRET: # Twitter consumer secret TWITTER_KEY: # Twitter consumer key TWITTER_SECRET: # Twitter consumer secret LINKEDIN_KEY: # Linkedin app key LINKEDIN_SECRET: # Linkedin app secret ARTSY_ID: # Artsy client id ARTSY_SECRET: # Artsy client secret ARTSY_URL: # SSL Artsy url e.g. https://artsy.net APP_URL: # Url pointing back to your app e.g. http://flare.artsy.net SEGMENT_WRITE_KEY: # Segment write key to track signup # Defaults you probably don't need to touch # ----------------------------------------- # Social auth linkedinPath: '/users/auth/linkedin' linkedinCallbackPath: '/users/auth/linkedin/callback' facebookPath: '/users/auth/facebook' facebookCallbackPath: '/users/auth/facebook/callback' twitterPath: '/users/auth/twitter' twitterCallbackPath: '/users/auth/twitter/callback' twitterLastStepPath: '/users/auth/twitter/email' twitterSignupTempEmail: (token) -> hash = crypto.createHash('sha1').update(token).digest('hex') "#{hash.substr 0, 12}@artsy.tmp" # Landing pages loginPagePath: '/log_in' signupPagePath: '/sign_up' settingsPagePath: '/user/edit' afterSignupPagePath: '/personalize' # Misc logoutPath: '/users/sign_out' userKeys: [ 'id', 'type', 'name', 'email', 'phone', 'lab_features', 'default_profile_id', 'has_partner_access', 'collector_level' ] ``` The keys are cased so it's convenient to pass in a configuration hash. A minimal setup could look like this: ```coffee app.use artsyPassport _.extend config, CurrentUser: CurrentUser ``` **Note:** CurrentUser must be a Backbone model with typical `get` and `toJSON` methods. #### Create a login form pointing to your paths. ```jade h1 Login pre!= error a( href=ap.facebookPath ) Login via Facebook a( href=ap.twitterPath ) Login via Twitter form( action=ap.loginPagePath, method='POST' ) h3 Login via Email input( name='name' ) input( name='email' ) input( name='password' ) input( type="hidden" name="_csrf" value=csrfToken ) button( type='submit' ) Login ``` #### And maybe a signup form... ```jade h1 Signup pre!= error a( href=ap.facebookPath ) Signup via Facebook a( href=ap.twitterPath ) Signup via Twitter form( action=ap.signupPagePath, method='POST' ) h3 Signup via Email input( name='name' ) input( name='email' ) input( name='password' ) input( type="hidden" name="_csrf" value=csrfToken ) button( type='submit' ) Signup ``` #### And maybe a settings page for linking accounts... ```jade h2 Linked Accounts pre!= error - providers = user.get('authentications').map(function(a) { return a.provider }) if providers.indexOf('facebook') > -1 | Connected Facebook else a( href=ap.facebookPath ) Connect Facebook br if providers.indexOf('twitter') > -1 | Connected Twitter else a( href=ap.twitterPath ) Connect Twitter br if providers.indexOf('linkedin') > -1 | Connected LinkedIn else a( href=ap.linkedinPath ) Connect LinkedIn ``` #### Finally there's this weird "one last step" UI for twitter to store emails after signup. ```jade h1 Just one more step pre!= error form( method='post', action=ap.twitterLastStepPath ) input( type="hidden" name="_csrf" value=csrfToken ) input.bordered-input( name='email' ) button( type='submit' ) Join Artsy ``` #### Render the pages ```coffee { loginPagePath, signupPagePath, settingsPagePath, afterSignupPagePath, twitterLastStepPath } = artsyPassport.options app.get loginPagePath, (req, res) -> res.render 'login' app.get signupPagePath, (req, res) -> res.render 'signup' app.get settingsPagePath, (req, res) -> res.render 'settings' app.get afterSignupPagePath, (req, res) -> res.render 'personalize' app.get twitterLastStepPath, (req, res) -> res.render 'twitter_last_step' ``` #### Access a logged in Artsy user in a variety of ways... In your server-side templates ```jade h1 Hello #{user.get('name')} ``` In your client-side code ```coffee CurrentUser = require '../models/current_user.coffee' sd = require('sharify').data user = new CurrentUser(sd.CURRENT_USER) ``` In your routers ```coffee app.get '/', (req, res) -> res.send 'Hello ' + req.user.get('name') ``` _These forms of user will be null if they're not logged in._ ## Sanitize Redirect If you implement a fancier auth flow that involves client-side redirecting back, you may find this helper useful in avoiding ["open redirect"](https://github.com/artsy/artsy-passport/issues/68) attacks. ```coffee sanitizeRedirect = require 'artsy-passport/sanitize-redirect' location.href = sanitizeRedirect "http://artsy.net%0D%0Aattacker.com/" # Notices the url isn't pointing at artsy.net, so just redirects to / ``` ## Contributing Add a `local.artsy.net` entry into your /etc/hosts ``` 127.0.0.1 localhost #... 127.0.0.1 local.artsy.net ``` Install node modules `npm install` then write a ./config.coffee that looks something like this: ```coffee module.exports = FACEBOOK_ID: '' FACEBOOK_SECRET: '' TWITTER_KEY: '' TWITTER_SECRET: '' LINKEDIN_KEY: '' LINKEDIN_SECRET: '' ARTSY_ID: '' ARTSY_SECRET: '' ARTSY_URL: 'https://api.artsy.net' APP_URL: 'http://local.artsy.net:4000' # An Artsy user that's linked to Facebook and Twitter ARTSY_EMAIL: 'craig@artsy.net' ARTSY_PASSWORD: '' TWITTER_EMAIL: 'craigspaeth@gmail.com' TWITTER_PASSWORD: '' FACEBOOK_EMAIL: 'craigspaeth@gmail.com' FACEBOOK_PASSWORD: '' ``` Then you can check the example by running `npm run example` and opening [localhost:4000](http://localhost:4000). The tests are a combination of integration and middleware unit tests. To run the whole suite use `npm test`.