artsy-passport
v2.0.4
Published
Wires up the common auth handlers for Artsy's [Ezel](ezeljs.com)-based apps using [passport](http://passportjs.org/).
Downloads
242
Readme
Artsy Passport
Wires up the common auth handlers, and related security concerns, for Artsy's Ezel-based apps using passport. 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.
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.
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:
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.
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...
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...
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.
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
{ 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
h1 Hello #{user.get('name')}
In your client-side code
CurrentUser = require '../models/current_user.coffee'
sd = require('sharify').data
user = new CurrentUser(sd.CURRENT_USER)
In your routers
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" attacks.
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:
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: '[email protected]'
ARTSY_PASSWORD: ''
TWITTER_EMAIL: '[email protected]'
TWITTER_PASSWORD: ''
FACEBOOK_EMAIL: '[email protected]'
FACEBOOK_PASSWORD: ''
Then you can check the example by running npm run example
and opening localhost:4000.
The tests are a combination of integration and middleware unit tests. To run the whole suite use npm test
.