1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | var util = require('util');
|
8 | var uuid = require('uuid-v4');
|
9 |
|
10 |
|
11 |
|
12 |
|
13 | var storage = exports._storage = (function() {
|
14 | var tokens = { };
|
15 | return {
|
16 | create: function(id) {
|
17 | var token = uuid();
|
18 | tokens[token] = {
|
19 | id: id,
|
20 | timer: setTimeout(
|
21 | function() {
|
22 | storage.destroy(token);
|
23 | },
|
24 | expireTimeout
|
25 | )
|
26 | };
|
27 | return token;
|
28 | },
|
29 | lookup: function(token) {
|
30 | if (tokens[token]) {
|
31 | return tokens[token].id;
|
32 | }
|
33 | },
|
34 | destroy: function(token) {
|
35 | if (tokens[token]) {
|
36 | clearTimeout(tokens[token].timer);
|
37 | delete tokens[token];
|
38 | }
|
39 | }
|
40 | };
|
41 | }());
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 | var timeoutUnits = {
|
49 | sec: 1000,
|
50 | secs: 1000,
|
51 | min: 1000 * 60,
|
52 | mins: 1000 * 60,
|
53 | hour: 1000 * 60 * 60,
|
54 | hours: 1000 * 60 * 60,
|
55 | day: 1000 * 60 * 60 * 24,
|
56 | days: 1000 * 60 * 60 * 24,
|
57 | week: 1000 * 60 * 60 * 24 * 7,
|
58 | weeks: 1000 * 60 * 60 * 24 * 7
|
59 | };
|
60 | var expireTimeout = 43200000;
|
61 | exports.expireTimeout = function(num, unit) {
|
62 | if (typeof num === 'number' && ! isNaN(num)) {
|
63 | var multiplier = 1;
|
64 | if (unit && timeoutUnits.hasOwnProperty(unit)) {
|
65 | multiplier = timeoutUnits[unit];
|
66 | }
|
67 | expireTimeout = num * multiplier;
|
68 | }
|
69 | return expireTimeout;
|
70 | };
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 | var lookupUsers = function(login, callback) {
|
78 | callback(null, null);
|
79 | };
|
80 | exports.lookupUsers = function(func) {
|
81 | if (typeof func === 'function') {
|
82 | lookupUsers = func;
|
83 | }
|
84 | };
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 | var setPassword = function(id, password, callback) {
|
93 | callback(null, false, null);
|
94 | };
|
95 | exports.setPassword = function(func) {
|
96 | if (typeof func === 'function') {
|
97 | setPassword = func;
|
98 | }
|
99 | };
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 | var sendEmail = function(email, resets, callback) {
|
107 | callback(null, false);
|
108 | };
|
109 | exports.sendEmail = function(func) {
|
110 | if (typeof func === 'function') {
|
111 | sendEmail = func;
|
112 | }
|
113 | };
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 | exports.requestResetToken = function(opts) {
|
121 | opts = merge({
|
122 | next: null,
|
123 | loginParam: 'login',
|
124 | callbackURL: '/password/reset/{token}'
|
125 | }, opts);
|
126 | var func = function(req, res, next) {
|
127 | var login = req.body ? req.body[opts.loginParam] : null;
|
128 | if (! login) {
|
129 | return res.json(jsonError('No login given'), 400);
|
130 | }
|
131 | lookupUsers(login, function(err, users) {
|
132 | if (err) {
|
133 | return res.json(jsonError(err), 500);
|
134 | }
|
135 | if (! users) {
|
136 | return res.json(jsonError('No such user'), 404);
|
137 | }
|
138 | users.users = users.users.map(function(user) {
|
139 | var token = storage.create(user.id);
|
140 | return {
|
141 | token: token,
|
142 | name: user.name,
|
143 | url: opts.callbackURL.replace('{token}', token)
|
144 | };
|
145 | });
|
146 | sendEmail(users.email, users.users, function(err, sent) {
|
147 | if (err) {
|
148 | return res.json(jsonError(err), 500);
|
149 | }
|
150 | if (! opts.next) {
|
151 | return res.send(200);
|
152 | }
|
153 | if (typeof opts.next === 'string') {
|
154 | res.redirect(opts.next);
|
155 | } else if (typeof opts.next === 'function') {
|
156 | opts.next(req, res, next);
|
157 | } else {
|
158 | next();
|
159 | }
|
160 | });
|
161 | });
|
162 | };
|
163 | func._opts = opts;
|
164 | return func;
|
165 | };
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 | exports.resetPassword = function(opts) {
|
173 | opts = merge({
|
174 | next: null,
|
175 | tokenParam: 'token',
|
176 | passwordParam: 'password',
|
177 | confirmParam: 'confirm'
|
178 | }, opts);
|
179 | var func = function(req, res, next) {
|
180 | var params = req.body ? {
|
181 | token: req.body[opts.tokenParam],
|
182 | password: req.body[opts.passwordParam],
|
183 | confirm: req.body[opts.confirmParam]
|
184 | } : { };
|
185 | if (! params.token || ! params.password || ! params.confirm) {
|
186 | return res.json(jsonError('Cannot attempt reset with missing params'), 400);
|
187 | }
|
188 | var id = storage.lookup(params.token);
|
189 | if (! id) {
|
190 | return res.json(jsonError('Request token is invalid'), 401);
|
191 | }
|
192 | if (params.password !== params.confirm) {
|
193 | return res.json(jsonError('Password and confirmation do not match'), 400);
|
194 | }
|
195 | setPassword(id, params.password,
|
196 | function(err, success, validationError) {
|
197 | if (err) {
|
198 | return res.json(jsonError(err), 500);
|
199 | }
|
200 | if (! success) {
|
201 | return res.json(jsonError(validationError), 400);
|
202 | }
|
203 | storage.destroy(params.token);
|
204 | if (! opts.next) {
|
205 | return res.send(200);
|
206 | }
|
207 | if (typeof opts.next === 'string') {
|
208 | res.redirect(opts.next);
|
209 | } else if (typeof opts.next === 'function') {
|
210 | opts.next(req, res, next);
|
211 | } else {
|
212 | next();
|
213 | }
|
214 | }
|
215 | );
|
216 | };
|
217 | func._opts = opts;
|
218 | return func;
|
219 | };
|
220 |
|
221 |
|
222 |
|
223 |
|
224 | function jsonError(msg) {
|
225 | if (msg instanceof Error) {
|
226 | msg = {
|
227 | type: msg.type,
|
228 | message: msg.message,
|
229 | stack: msg.stack,
|
230 | stackArray: msg.stack.split('\n').slice(1).map(function(str) {
|
231 | return str.trim();
|
232 | })
|
233 | };
|
234 | } else {
|
235 | msg = {message: msg};
|
236 | }
|
237 | return {error: msg};
|
238 | }
|
239 |
|
240 | function merge(host) {
|
241 | host = isMutable(host) ? host : { };
|
242 | Array.prototype.slice.call(arguments, 1).forEach(function(arg) {
|
243 | if (isMutable(arg)) {
|
244 | Object.keys(arg).forEach(function(prop) {
|
245 | host[prop] = arg[prop];
|
246 | });
|
247 | }
|
248 | });
|
249 | return host;
|
250 | }
|
251 |
|
252 | function isMutable(value) {
|
253 | return (typeof value === 'object' && value) || typeof value === 'function';
|
254 | }
|
255 |
|
256 |
|