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