Extensible frontend auth module, support for facebook and form.
Showing
8 changed files
with
384 additions
and
0 deletions
.gitignore
0 → 100644
Gruntfile.js
0 → 100644
1 | /* global module */ | ||
2 | |||
3 | module.exports = function (grunt) { | ||
4 | /* global require */ | ||
5 | 'use strict'; | ||
6 | |||
7 | var config = {}; | ||
8 | config.base = 'src'; | ||
9 | config.jshint = { | ||
10 | options: { | ||
11 | }, | ||
12 | browserOptions: { | ||
13 | }, | ||
14 | }; | ||
15 | config.jscs = { | ||
16 | options: { | ||
17 | validateIndentation: 4, | ||
18 | reporter: 'console', | ||
19 | maxErrors: -1, | ||
20 | }, | ||
21 | }; | ||
22 | config.bower = { | ||
23 | directory: 'lib/bower', | ||
24 | }; | ||
25 | config.jasmine = { | ||
26 | withCoverage: true, | ||
27 | }; | ||
28 | var montyPython = require('grunt-monty-python')(grunt); | ||
29 | montyPython.createConfig(config); | ||
30 | }; |
bower.json
0 → 100644
1 | { | ||
2 | "name": "frontend-auth", | ||
3 | "version": "2016.06.27-0", | ||
4 | "authors": [ | ||
5 | "Adam Heath <doogie@brainfood.com>" | ||
6 | ], | ||
7 | "main": [ | ||
8 | "src/scripts/Auth.js" | ||
9 | ], | ||
10 | "private": true, | ||
11 | "ignore": [ | ||
12 | "**/.*", | ||
13 | "node_modules", | ||
14 | "src/lib", | ||
15 | "src/scripts/*.spec.js", | ||
16 | "src/scripts/config.js", | ||
17 | "src/scripts/main.js" | ||
18 | ], | ||
19 | "dependencies": { | ||
20 | "backbone": "", | ||
21 | "jquery": "", | ||
22 | "requirejs": "", | ||
23 | "underscore": "" | ||
24 | } | ||
25 | } |
package.json
0 → 100644
1 | { | ||
2 | "name": "backbone-model-overlay", | ||
3 | "version": "2016.06.27-0", | ||
4 | "main": [ | ||
5 | "src/scripts/Auth.js" | ||
6 | ], | ||
7 | "dependencies": { | ||
8 | "rivets": "", | ||
9 | "requirejs": "" | ||
10 | }, | ||
11 | "devDependencies": { | ||
12 | "bower-requirejs": "~0.9.2", | ||
13 | "grunt": "~0", | ||
14 | "grunt-monty-python": "git+ssh://git@gitlab.brainfood.com:brainfood/frontend-auth.git" | ||
15 | }, | ||
16 | "engines": { | ||
17 | "node": ">=0.8.0" | ||
18 | } | ||
19 | } | ||
20 |
src/scripts/Auth.js
0 → 100644
1 | define(function(require) { | ||
2 | 'use strict'; | ||
3 | var module = require('module'); | ||
4 | var $ = require('jquery'); | ||
5 | var _ = require('underscore'); | ||
6 | var Backbone = require('backbone'); | ||
7 | |||
8 | function bindMethods() { | ||
9 | var args = _.toArray(arguments); | ||
10 | var self = args.shift(); | ||
11 | _.each(args, function(methodName) { | ||
12 | self[methodName] = _.bind(self[methodName], self); | ||
13 | }); | ||
14 | } | ||
15 | function attachStart(self, callback) { | ||
16 | var start = $.Deferred(); | ||
17 | self.start = function() { | ||
18 | start.resolve(); | ||
19 | return this; | ||
20 | }; | ||
21 | return start.done(_.bind(callback, self)); | ||
22 | } | ||
23 | function attachReady(self) { | ||
24 | var deferred = $.Deferred().done(function() { | ||
25 | self.set('isReady', true); | ||
26 | }); | ||
27 | |||
28 | var promise = deferred.promise(); | ||
29 | self.ready = function() { | ||
30 | return promise; | ||
31 | }; | ||
32 | return deferred; | ||
33 | } | ||
34 | |||
35 | var FacebookProvider = Backbone.Model.extend({ | ||
36 | initialize: function(data, globalOptions) { | ||
37 | // early facebook javascript load | ||
38 | require(['facebook']); | ||
39 | FacebookProvider.__super__.initialize.apply(this, arguments); | ||
40 | var self = this; | ||
41 | var fbLoaded = $.Deferred(); | ||
42 | var ready = attachReady(this); | ||
43 | attachStart(this, function() { | ||
44 | require(['facebook'], function(FB) { | ||
45 | FB.init({ | ||
46 | appId: globalOptions.appId, | ||
47 | status: true, | ||
48 | }); | ||
49 | FB.getLoginStatus(function(response) { | ||
50 | self.set({ | ||
51 | 'status': response.status, | ||
52 | 'authResponse': response.authResponse, | ||
53 | }); | ||
54 | fbLoaded.resolve(FB); | ||
55 | }); | ||
56 | }, function() { | ||
57 | /* global console:false */ | ||
58 | console.log('error loading facebook javascript'); | ||
59 | fbLoaded.reject(); | ||
60 | }); | ||
61 | }); | ||
62 | fbLoaded.always(function() { | ||
63 | ready.resolve(); | ||
64 | }); | ||
65 | // log helper | ||
66 | this.on('change:status', function() { | ||
67 | /* global console:false */ | ||
68 | switch (this.get('status')) { | ||
69 | case 'connected': | ||
70 | console.log('FB connected', this.get('authResponse')); | ||
71 | break; | ||
72 | case 'not_authorized': | ||
73 | console.log('FB not authorized'); | ||
74 | break; | ||
75 | default: | ||
76 | console.log('FB not logged in'); | ||
77 | break; | ||
78 | } | ||
79 | }, this); | ||
80 | this.login = function() { | ||
81 | fbLoaded.done(function(FB) { | ||
82 | FB.login(function(response) { | ||
83 | self.set({ | ||
84 | 'status': response.status, | ||
85 | 'authResponse': response.authResponse, | ||
86 | }); | ||
87 | self.trigger('provider:logged-in'); | ||
88 | }); | ||
89 | }); | ||
90 | return self; | ||
91 | }; | ||
92 | this.logout = function() { | ||
93 | fbLoaded.done(function(FB) { | ||
94 | if (self.get('authResponse')) { | ||
95 | FB.logout(function(response) { | ||
96 | self.set({ | ||
97 | 'status': response.status, | ||
98 | 'authResponse': null, | ||
99 | }); | ||
100 | self.trigger('provider:logged-out'); | ||
101 | }); | ||
102 | } else { | ||
103 | self.trigger('provider:logged-out'); | ||
104 | } | ||
105 | }); | ||
106 | return self; | ||
107 | }; | ||
108 | this.attachProviderData = function(data) { | ||
109 | var authResponse = this.get('authResponse') || {}; | ||
110 | _.extend(data, { | ||
111 | facebookAccessToken: authResponse.accessToken, | ||
112 | facebookUserId: authResponse.userID, | ||
113 | }); | ||
114 | }; | ||
115 | bindMethods(this, 'attachProviderData'); | ||
116 | }, | ||
117 | }); | ||
118 | var FormProvider = Backbone.Model.extend({ | ||
119 | initialize: function(data, globalOptions) { | ||
120 | FormProvider.__super__.initialize.apply(this, arguments); | ||
121 | var ready = attachReady(this); | ||
122 | attachStart(this, function() { | ||
123 | ready.resolve(); | ||
124 | }); | ||
125 | this.login = function() { | ||
126 | // TODO: possibly fetch USERNAME/PASSWORD from browser.localStorage | ||
127 | this.trigger('provider:logged-in'); | ||
128 | return this; | ||
129 | }; | ||
130 | this.logout = function() { | ||
131 | this.trigger('provider:logged-out'); | ||
132 | return this; | ||
133 | }; | ||
134 | this.attachProviderData = function(data) { | ||
135 | _.extend(data, { | ||
136 | USERNAME: this.get('USERNAME'), | ||
137 | PASSWORD: this.get('PASSWORD'), | ||
138 | }); | ||
139 | }; | ||
140 | bindMethods(this, 'login', 'logout', 'attachProviderData'); | ||
141 | }, | ||
142 | }); | ||
143 | var Auth = Backbone.Model.extend({ | ||
144 | defaults: function() { | ||
145 | return { | ||
146 | isLoggedIn: false, | ||
147 | permissions: new Backbone.Model(), | ||
148 | user: new Backbone.Model(), | ||
149 | providers: new Backbone.Model(), | ||
150 | }; | ||
151 | }, | ||
152 | initialize: function(data, globalOptions) { | ||
153 | Auth.__super__.initialize.apply(this, arguments); | ||
154 | (function(self, cookieName) { | ||
155 | if (!cookieName) { | ||
156 | return; | ||
157 | } | ||
158 | var sessionId = self.get('sessionId'); | ||
159 | if (sessionId) { | ||
160 | $.cookie('cookieName', sessionId); | ||
161 | } else { | ||
162 | self.set('sessionId', $.cookie(cookieName)); | ||
163 | } | ||
164 | self.on('change:sessionId', function() { | ||
165 | var sessionId = this.get('sessionId'); | ||
166 | if (sessionId) { | ||
167 | $.cookie(cookieName, sessionId); | ||
168 | } else { | ||
169 | $.removeCookie(cookieName); | ||
170 | } | ||
171 | }, self); | ||
172 | })(this, globalOptions.cookieName); | ||
173 | var ready = attachReady(this); | ||
174 | attachStart(this, function() { | ||
175 | _.invoke(this.get('providers').values(), 'start'); | ||
176 | }); | ||
177 | |||
178 | var self = this; | ||
179 | function applyLoginResults(resultData) { | ||
180 | var userData = resultData.user; | ||
181 | var user = self.get('user'); | ||
182 | self.set('isLoggedIn', !!userData); | ||
183 | if (userData) { | ||
184 | user.set(userData); | ||
185 | } else { | ||
186 | user.clear(); | ||
187 | } | ||
188 | self.set('sessionId', resultData.sessionId); | ||
189 | var permissions = self.get('permissions'); | ||
190 | var toErase = {}, toSet = {}; | ||
191 | _.each(permissions.keys(), function(key) { | ||
192 | toErase[key] = false; | ||
193 | }); | ||
194 | _.each(resultData.permissions, function(key) { | ||
195 | delete toErase[key]; | ||
196 | toSet[key] = true; | ||
197 | }); | ||
198 | permissions.set(toErase, {unset: true}).set(toSet); | ||
199 | } | ||
200 | function api(apiName, data) { | ||
201 | var apiHandler = globalOptions.api || function(apiName, data) {}; | ||
202 | return apiHandler(apiName, data, {sessionId: self.get('sessionId')}).done(applyLoginResults); | ||
203 | } | ||
204 | var addProviderData = _.bind(function addProviderData() { | ||
205 | var args = _.toArray(arguments); | ||
206 | var providerToApply, data; | ||
207 | if (typeof args[0] === 'object') { | ||
208 | data = args.shift(); | ||
209 | } | ||
210 | if (typeof args[0] === 'string') { | ||
211 | providerToApply = args[0]; | ||
212 | } | ||
213 | data = _.extend({}, data); | ||
214 | var providers = this.get('providers'); | ||
215 | _.each(providerToApply ? [providerToApply] : providers.keys(), function(providerKey) { | ||
216 | providers.get(providerKey).attachProviderData(data); | ||
217 | }); | ||
218 | return data; | ||
219 | }, this); | ||
220 | var providerReadies = _.map(this.get('providers').values(), function(provider) { | ||
221 | return provider.ready(); | ||
222 | }); | ||
223 | |||
224 | $.when.apply($, providerReadies).done(_.bind(function() { | ||
225 | api('auth:startVisit', addProviderData({ | ||
226 | userAgent: navigator.userAgent, | ||
227 | })).always(function() { | ||
228 | ready.resolve(); | ||
229 | }); | ||
230 | var providers = this.get('providers'); | ||
231 | _.each(providers.keys(), function(providerKey) { | ||
232 | providers.get(providerKey).on('provider:logged-in', function() { | ||
233 | api('auth:login:' + providerKey, addProviderData(providerKey)); | ||
234 | }); | ||
235 | }); | ||
236 | }, this)); | ||
237 | this.login = function(how) { | ||
238 | var provider = how ? this.get('providers').get(how) : null; | ||
239 | if (provider) { | ||
240 | provider.login(); | ||
241 | } | ||
242 | }; | ||
243 | this.logout = function() { | ||
244 | _.invoke(this.get('providers').values(), 'logout'); | ||
245 | api('auth:logout'); | ||
246 | }; | ||
247 | bindMethods(this, 'login', 'logout'); | ||
248 | }, | ||
249 | }, { | ||
250 | FacebookProvider: FacebookProvider, | ||
251 | FormProvider: FormProvider, | ||
252 | }); | ||
253 | return Auth; | ||
254 | }); |
src/scripts/Auth.spec.js
0 → 100644
1 | define(function(require) { | ||
2 | 'use strict'; | ||
3 | |||
4 | var $ = require('jquery'); | ||
5 | window.jQuery = $; | ||
6 | var _ = require('underscore'); | ||
7 | var Backbone = require('backbone'); | ||
8 | var Auth = require('Auth'); | ||
9 | |||
10 | describe('Auth', function() { | ||
11 | it('exists', function() { | ||
12 | expect(Auth).toBeDefined(); | ||
13 | }); | ||
14 | }); | ||
15 | }); |
src/scripts/config.js
0 → 100644
1 | /* global require:true */ | ||
2 | var require; | ||
3 | require = (function() { | ||
4 | 'use strict'; | ||
5 | |||
6 | var require = { | ||
7 | baseUrl: 'scripts', | ||
8 | config: { | ||
9 | 'Auth': {}, | ||
10 | |||
11 | }, | ||
12 | shim: { | ||
13 | backbone: { | ||
14 | deps: ['underscore'], | ||
15 | exports: 'Backbone', | ||
16 | }, | ||
17 | underscore: { | ||
18 | exports: '_', | ||
19 | }, | ||
20 | }, | ||
21 | paths: { | ||
22 | backbone: '../lib/bower/backbone/backbone', | ||
23 | underscore: '../lib/bower/underscore/underscore', | ||
24 | jquery: '../lib/bower/jquery/dist/jquery', | ||
25 | }, | ||
26 | }; | ||
27 | |||
28 | return require; | ||
29 | })(); |
src/scripts/main.js
0 → 100644
-
Please register or sign in to post a comment