1 | # Artsy Passport
|
2 |
|
3 | [![CircleCI](https://circleci.com/gh/artsy/artsy-passport.svg?style=svg)](https://circleci.com/gh/artsy/artsy-passport)
|
4 |
|
5 | Wires up the common auth handlers, and related security concerns, for Artsy's [Ezel](https://github.com/artsy/ezel)-based apps using [passport](http://passportjs.org/). Used internally at Artsy to DRY up authentication code.
|
6 |
|
7 | ## Breaking changes
|
8 |
|
9 | We mave migrated this app from the module "artsy-passport" to "@artsy/passport", and called that v1.
|
10 |
|
11 | ## Setup
|
12 |
|
13 | #### Make sure you first mount session, body parser, and start [@artsy/xapp](https://github.com/artsy/artsy-xapp).
|
14 |
|
15 | ```coffee
|
16 | app.use express.bodyParser()
|
17 | app.use express.cookieParser('foobar')
|
18 | app.use express.cookieSession()
|
19 | artsyXapp.init -> app.listen()
|
20 | ```
|
21 |
|
22 | #### Then mount Artsy Passport passing a big configuration hash.
|
23 |
|
24 | _Values indicate defaults._
|
25 |
|
26 | ```coffee
|
27 | app.use artsyPassport
|
28 |
|
29 | CurrentUser: # The CurrentUser Backbone model
|
30 |
|
31 | # Pass in env vars
|
32 | # ----------------
|
33 | FACEBOOK_ID: # Facebook app ID
|
34 | FACEBOOK_SECRET: # Facebook app secret
|
35 | ARTSY_ID: # Artsy client id
|
36 | ARTSY_SECRET: # Artsy client secret
|
37 | ARTSY_URL: # SSL Artsy url e.g. https://artsy.net
|
38 | APP_URL: # Url pointing back to your app e.g. http://flare.artsy.net
|
39 | SEGMENT_WRITE_KEY: # Segment write key to track signup
|
40 |
|
41 | # Defaults you probably don't need to touch
|
42 | # -----------------------------------------
|
43 |
|
44 | # Social auth
|
45 | facebookPath: '/users/auth/facebook'
|
46 | facebookCallbackPath: '/users/auth/facebook/callback'
|
47 |
|
48 | # Landing pages
|
49 | loginPagePath: '/log_in'
|
50 | signupPagePath: '/sign_up'
|
51 | settingsPagePath: '/user/edit'
|
52 | afterSignupPagePath: '/personalize'
|
53 |
|
54 | # Misc
|
55 | logoutPath: '/users/sign_out'
|
56 | userKeys: [
|
57 | 'id', 'type', 'name', 'email', 'phone', 'lab_features',
|
58 | 'default_profile_id', 'has_partner_access', 'collector_level'
|
59 | ]
|
60 | ```
|
61 |
|
62 | The keys are cased so it's convenient to pass in a configuration hash. A minimal setup could look like this:
|
63 |
|
64 | ```coffee
|
65 | app.use artsyPassport _.extend config,
|
66 | CurrentUser: CurrentUser
|
67 | ```
|
68 |
|
69 | **Note:** CurrentUser must be a Backbone model with typical `get` and `toJSON` methods.
|
70 |
|
71 | #### Create a login form pointing to your paths.
|
72 |
|
73 | ```jade
|
74 | h1 Login
|
75 | pre!= error
|
76 | a( href=ap.facebookPath ) Login via Facebook
|
77 | form( action=ap.loginPagePath, method='POST' )
|
78 | h3 Login via Email
|
79 | input( name='name' )
|
80 | input( name='email' )
|
81 | input( name='password' )
|
82 | input( type="hidden" name="_csrf" value=csrfToken )
|
83 | button( type='submit' ) Login
|
84 | ```
|
85 |
|
86 | #### And maybe a signup form...
|
87 |
|
88 | ```jade
|
89 | h1 Signup
|
90 | pre!= error
|
91 | a( href=ap.facebookPath ) Signup via Facebook
|
92 | form( action=ap.signupPagePath, method='POST' )
|
93 | h3 Signup via Email
|
94 | input( name='name' )
|
95 | input( name='email' )
|
96 | input( name='password' )
|
97 | input( type="hidden" name="_csrf" value=csrfToken )
|
98 | button( type='submit' ) Signup
|
99 | ```
|
100 |
|
101 | #### And maybe a settings page for linking accounts...
|
102 |
|
103 | ```jade
|
104 | h2 Linked Accounts
|
105 | pre!= error
|
106 | - providers = user.get('authentications').map(function(a) { return a.provider })
|
107 | if providers.indexOf('facebook') > -1
|
108 | | Connected Facebook
|
109 | else
|
110 | a( href=ap.facebookPath ) Connect Facebook
|
111 | ```
|
112 |
|
113 | #### Render the pages
|
114 |
|
115 | ```coffee
|
116 | { loginPagePath, signupPagePath, settingsPagePath, afterSignupPagePath } = artsyPassport.options
|
117 |
|
118 | app.get loginPagePath, (req, res) -> res.render 'login'
|
119 | app.get signupPagePath, (req, res) -> res.render 'signup'
|
120 | app.get settingsPagePath, (req, res) -> res.render 'settings'
|
121 | app.get afterSignupPagePath, (req, res) -> res.render 'personalize'
|
122 | ```
|
123 |
|
124 | #### Access a logged in Artsy user in a variety of ways...
|
125 |
|
126 | In your server-side templates
|
127 |
|
128 | ```jade
|
129 | h1 Hello #{user.get('name')}
|
130 | ```
|
131 |
|
132 | In your client-side code
|
133 |
|
134 | ```coffee
|
135 | CurrentUser = require '../models/current_user.coffee'
|
136 | sd = require('sharify').data
|
137 |
|
138 | user = new CurrentUser(sd.CURRENT_USER)
|
139 | ```
|
140 |
|
141 | In your routers
|
142 |
|
143 | ```coffee
|
144 | app.get '/', (req, res) ->
|
145 | res.send 'Hello ' + req.user.get('name')
|
146 | ```
|
147 |
|
148 | _These forms of user will be null if they're not logged in._
|
149 |
|
150 | ## Sanitize Redirect
|
151 |
|
152 | 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.
|
153 |
|
154 | ```coffee
|
155 | sanitizeRedirect = require 'artsy-passport/sanitize-redirect'
|
156 |
|
157 | location.href = sanitizeRedirect "http://artsy.net%0D%0Aattacker.com/"
|
158 | # Notices the url isn't pointing at artsy.net, so just redirects to /
|
159 | ```
|
160 |
|
161 | ## Contributing
|
162 |
|
163 | Add a `local.artsy.net` entry into your /etc/hosts
|
164 |
|
165 | ```
|
166 | 127.0.0.1 localhost
|
167 | #...
|
168 | 127.0.0.1 local.artsy.net
|
169 | ```
|
170 |
|
171 | Install node modules `yarn` then write a ./config.js that looks something like this:
|
172 |
|
173 | ```js
|
174 | module.exports = {
|
175 | FACEBOOK_ID: '',
|
176 | FACEBOOK_SECRET: '',
|
177 | ARTSY_ID: '',
|
178 | ARTSY_SECRET: '',
|
179 | ARTSY_URL: 'https://api.artsy.net',
|
180 | APP_URL: 'http://local.artsy.net:4000',
|
181 | // An Artsy user that's linked to Facebook
|
182 | ARTSY_EMAIL: 'craig@artsy.net',
|
183 | ARTSY_PASSWORD: '',
|
184 | FACEBOOK_EMAIL: 'craigspaeth@gmail.com',
|
185 | FACEBOOK_PASSWORD: '',
|
186 | }
|
187 | ```
|
188 |
|
189 | Then you can check the example by running `yarn example` and opening [localhost:4000](http://localhost:4000).
|
190 |
|
191 | The tests are a combination of integration and middleware unit tests. To run the whole suite use `yarn test`.
|
192 |
|
193 | ## Publishing to npm
|
194 |
|
195 | ```
|
196 | yarn compile
|
197 | yarn publish
|
198 | ```
|