1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, "__esModule", {
|
4 | value: true
|
5 | });
|
6 | exports.checkEmailExists = checkEmailExists;
|
7 | exports.generateUserId = generateUserId;
|
8 | exports.generateActivationHash = generateActivationHash;
|
9 | exports.generateHash = generateHash;
|
10 | exports.getDefaultIsAdmin = getDefaultIsAdmin;
|
11 | exports.getDefaultUserSlug = getDefaultUserSlug;
|
12 | exports.useSignup = useSignup;
|
13 |
|
14 | var _bcryptNodejs = require('bcrypt-nodejs');
|
15 |
|
16 | var _bcryptNodejs2 = _interopRequireDefault(_bcryptNodejs);
|
17 |
|
18 | var _lodash = require('lodash.kebabcase');
|
19 |
|
20 | var _lodash2 = _interopRequireDefault(_lodash);
|
21 |
|
22 | var _mongodb = require('mongodb');
|
23 |
|
24 | var _passport = require('passport');
|
25 |
|
26 | var _passport2 = _interopRequireDefault(_passport);
|
27 |
|
28 | var _passportLocal = require('passport-local');
|
29 |
|
30 | var _shortid = require('shortid');
|
31 |
|
32 | var _shortid2 = _interopRequireDefault(_shortid);
|
33 |
|
34 | var _format = require('./format');
|
35 |
|
36 | var _jwt = require('./jwt');
|
37 |
|
38 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
39 |
|
40 |
|
41 |
|
42 |
|
43 | var VALID_EMAIL_REGEXP = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
|
44 | var MIN_PASSWORD_LENGTH = 6;
|
45 | var globalUserFields = [];
|
46 |
|
47 | function checkEmailExists(email, usersCollection) {
|
48 | return new Promise(function (resolve) {
|
49 | usersCollection.findOne({ 'local.email': email }).then(function (existingUser) {
|
50 | resolve(!!existingUser);
|
51 | });
|
52 | });
|
53 | }
|
54 |
|
55 | function generateUserId() {
|
56 | return 'user_' + _shortid2.default.generate();
|
57 | }
|
58 |
|
59 |
|
60 |
|
61 | function generateActivationHash() {
|
62 | return _shortid2.default.generate() + _shortid2.default.generate();
|
63 | }
|
64 |
|
65 | function generateHash(password) {
|
66 | return new Promise(function (resolve, reject) {
|
67 | _bcryptNodejs2.default.genSalt(8, function (err, result) {
|
68 | if (err) {
|
69 | reject(err);
|
70 | return;
|
71 | }
|
72 | return resolve(result);
|
73 | });
|
74 | }).then(function (salt) {
|
75 | return new Promise(function (resolve, reject) {
|
76 | _bcryptNodejs2.default.hash(password, salt, null, function (err, hashed) {
|
77 | if (err) {
|
78 | reject(err);
|
79 | return;
|
80 | }
|
81 | return resolve(hashed);
|
82 | });
|
83 | });
|
84 | });
|
85 | }
|
86 |
|
87 | function getDefaultIsAdmin(user) {
|
88 | return false;
|
89 | }
|
90 |
|
91 | function getDefaultUserSlug(user) {
|
92 | var name = user.firstName + '-' + user.lastName;
|
93 | return (0, _lodash2.default)(name.toString());
|
94 | }
|
95 |
|
96 | function useSignup(app, config) {
|
97 |
|
98 | var createJWTforUser = config.createJWTforUser,
|
99 | db = config.db,
|
100 | logger = config.logger,
|
101 | _config$mailer = config.mailer,
|
102 | accountName = _config$mailer.accountName,
|
103 | activationRoute = _config$mailer.activationRoute,
|
104 | senderName = _config$mailer.senderName,
|
105 | projectName = _config$mailer.projectName,
|
106 | send = _config$mailer.send,
|
107 | senderMail = _config$mailer.senderMail,
|
108 | requiredFields = config.requiredFields,
|
109 | routePath = config.routePath;
|
110 |
|
111 | var getFormatUser = config.getFormatUser || _format.getFormatUser;
|
112 | var getIsAdmin = config.getIsAdmin || getDefaultIsAdmin;
|
113 | var getUserSlug = config.getUserSlug || getDefaultUserSlug;
|
114 | var endpoint = config.endpoint || 'signup';
|
115 | var bodyKey = endpoint + 'Body';
|
116 | var messageKey = endpoint + 'Message';
|
117 | var passportKey = 'local-' + endpoint;
|
118 |
|
119 | var usersCollection = db.collection('users');
|
120 |
|
121 | function signupUser(signupData, usersCollection) {
|
122 | return new Promise(function (resolve, reject) {
|
123 |
|
124 | checkEmailExists(signupData.email, usersCollection).then(function (emailExists) {
|
125 | if (emailExists) {
|
126 | logger.debug('signupUser: email already existed');
|
127 | reject(new Error('USER_EMAIL_ALREADY_TAKEN'));
|
128 | } else {
|
129 | logger.debug('signupUser: this email is new');
|
130 | }
|
131 |
|
132 | return new Promise(function (resolve) {
|
133 | var validationError = validateUserSignup(signupData);
|
134 | if (validationError) {
|
135 | reject(validationError);
|
136 | return;
|
137 | }
|
138 | resolve(signupData);
|
139 | });
|
140 | }).then(function (validated) {
|
141 | if (!validated) return;
|
142 |
|
143 | var newUser = {
|
144 | id: generateUserId(),
|
145 |
|
146 | created_at: Date.now(),
|
147 | activationHash: generateActivationHash(),
|
148 | active: false,
|
149 | local: Object.keys(signupData)
|
150 |
|
151 |
|
152 | .reduce(function (newUserLocal, key) {
|
153 |
|
154 |
|
155 | if (signupData[key].length && !globalUserFields.includes(key)) {
|
156 | newUserLocal[key] = signupData[key];
|
157 | }
|
158 | return newUserLocal;
|
159 | }, {}),
|
160 |
|
161 | slug: getUserSlug(signupData)
|
162 | };
|
163 | return newUser;
|
164 | }).then(function (validatedNewUser) {
|
165 | if (!validatedNewUser) return;
|
166 |
|
167 |
|
168 | return generateHash(signupData.password).then(function (hashedPassword) {
|
169 | validatedNewUser.local.password = hashedPassword;
|
170 | return validatedNewUser;
|
171 | });
|
172 | }).then(function (newUserWithHashedPassword) {
|
173 | if (!newUserWithHashedPassword) return;
|
174 |
|
175 | return usersCollection.insertOne(newUserWithHashedPassword).then(function () {
|
176 |
|
177 | resolve(newUserWithHashedPassword);
|
178 | });
|
179 | });
|
180 | });
|
181 | }
|
182 |
|
183 |
|
184 | function validateUserSignup(user) {
|
185 | if (!VALID_EMAIL_REGEXP.test(user.email)) {
|
186 | return new Error('Please enter a valid email');
|
187 | }
|
188 | if (user.password.length < MIN_PASSWORD_LENGTH) {
|
189 | return new Error('Password must be at least 6 characters long');
|
190 | }
|
191 | var error = void 0;
|
192 | requiredFields.forEach(function (field) {
|
193 | if (!user[field] || user[field].trim().length === 0) {
|
194 | error = new Error(field + ' is required');
|
195 | return;
|
196 | }
|
197 | });
|
198 | return error;
|
199 | }
|
200 | function sendActivationEmail(email, activationHash) {
|
201 | var activationUrl = activationRoute + '=' + activationHash;
|
202 | var mailerConfig = {
|
203 | from: projectName + ' <' + senderMail + '>',
|
204 | to: email,
|
205 | subject: 'Confirm your ' + accountName + ' account',
|
206 | html: '<h1>Account confirmation</h1>\n <p>Thanks for signing up.</p>\n <p>Please click on the following link to activate your ' + accountName + ' account:</p>\n <p><a href="' + activationUrl + '">' + activationUrl + '</a></p>\n <br />\n <p>If you did not create this account, please ignore it and the created account will be deleted.<p>\n <br />\n <p>If clicking the link above does not work, please copy and paste the URL into a new browser window instead.<p>\n <br />\n <p>If you have any questions or problems, please let us know by repling to this email.<p>\n <p>Thanks,\n ' + senderName + ' from ' + projectName + '</p>\n '
|
207 | };
|
208 | send(mailerConfig).catch(function (err) {
|
209 | logger.warn('[unhandled] ERROR in sendActivationEmail:', err);
|
210 | });
|
211 | }
|
212 |
|
213 |
|
214 |
|
215 |
|
216 | _passport2.default.serializeUser(function (user, done) {
|
217 | done(null, user.id);
|
218 | });
|
219 |
|
220 | _passport2.default.deserializeUser(function (id, done) {
|
221 | usersCollection.findOne({ id: id }).then(function (user) {
|
222 | if (!user) {
|
223 | done(null, false);
|
224 | return;
|
225 | }
|
226 | var formatUser = getFormatUser(user);
|
227 |
|
228 | if (!user.authToken || !user.authTokenExpiry) {
|
229 | var jwtConfig = createJWTforUser(formatUser);
|
230 | formatUser.authToken = jwtConfig.token;
|
231 | formatUser.authTokenExpiry = jwtConfig.expiry;
|
232 | }
|
233 | done(null, formatUser);
|
234 | }).catch(done);
|
235 | });
|
236 | _passport2.default.use(passportKey, new _passportLocal.Strategy({
|
237 | usernameField: 'email',
|
238 | passwordField: 'password',
|
239 | passReqToCallback: true
|
240 | }, function (req, email, password, done) {
|
241 |
|
242 | req.flash(bodyKey, req.body);
|
243 | signupUser(req.body, usersCollection).then(function (finalUser) {
|
244 |
|
245 | sendActivationEmail(email, finalUser.activationHash);
|
246 |
|
247 | done(null, finalUser);
|
248 | return;
|
249 | }).catch(function (err) {
|
250 | if (err.message === 'USER_EMAIL_ALREADY_TAKEN') {
|
251 | done(null, false, req.flash(messageKey, 'That email is already taken.'));
|
252 | }
|
253 |
|
254 | logger.warn('[unhandled] ERROR in handleSignup:', err);
|
255 |
|
256 | done(null, false, req.flash(messageKey, err.message || 'Signup failed.'));
|
257 | });
|
258 | }));
|
259 |
|
260 | app.post(routePath + '/' + endpoint, _passport2.default.authenticate(passportKey, {
|
261 | successRedirect: '/',
|
262 | failureRedirect: '/' + endpoint,
|
263 | failureFlash: true
|
264 | }));
|
265 |
|
266 |
|
267 | app.get(routePath + '/' + endpoint, function (req, res) {
|
268 | return res.redirect('/');
|
269 | });
|
270 |
|
271 | app.post(routePath + '/activate-account', function (req, res) {
|
272 | var code = req.body.code;
|
273 |
|
274 | if (!code) {
|
275 | res.json({ error: 'Activation code required.' });
|
276 | return;
|
277 | }
|
278 | usersCollection.findOneAndUpdate({ activationHash: code }, { $set: {
|
279 | active: true,
|
280 | admin: getIsAdmin(req.user)
|
281 | } },
|
282 |
|
283 | { returnOriginal: false }).then(function (_ref) {
|
284 | var user = _ref.value;
|
285 |
|
286 | if (!user) {
|
287 | res.json({ error: 'Invalid activation code' });
|
288 | return;
|
289 | }
|
290 | res.json({ user: getFormatUser(user) });
|
291 | }).catch(function (err) {
|
292 | res.json({ error: err.message });
|
293 | return;
|
294 | });
|
295 | });
|
296 |
|
297 | app.get(routePath + '/ask-activate-account', function (req, res) {
|
298 | usersCollection.findOne({
|
299 | id: req.user.id
|
300 | }).then(function (_ref2) {
|
301 | var activationHash = _ref2.activationHash,
|
302 | email = _ref2.local.email;
|
303 |
|
304 | sendActivationEmail(email, activationHash);
|
305 | res.json({
|
306 | text: 'We sent you an email to ' + email
|
307 | });
|
308 | });
|
309 | });
|
310 | } |
\ | No newline at end of file |