TwoToc code
[YouAndWeb_TwoToc] / server / api / user / user.model.js
1 'use strict';
2
3 var mongoose = require('bluebird').promisifyAll(require('mongoose'));
4 var Schema = mongoose.Schema;
5 var crypto = require('crypto');
6 var authTypes = ['github', 'twitter', 'facebook', 'google'];
7 var nodemailer = require('nodemailer');
8
9 var UserSchema = new Schema({
10   name: String,
11   firstName: String,
12   lastName: String,
13   email: {
14     type: String,
15     lowercase: true
16   },
17   role: {
18     type: String,
19     default: 'user'
20   },
21   password: String,
22   provider: String,
23   salt: String,
24   birthDate: Date,
25   facebook: {},
26   twitter: {},
27   google: {},
28   github: {},
29   rating: Number,
30   ratingCount: Number,
31   place: String,
32   location: {
33     type: [Number],
34     index: '2d'
35   },
36   dateSubscribed: {
37     type: Date,
38     default: Date.now()
39   }
40 });
41
42 /**
43  * Virtuals
44  */
45
46 // Public profile information
47 UserSchema
48   .virtual('profile')
49   .get(function() {
50     return {
51       'name': this.name,
52       'role': this.role
53     };
54   });
55
56 // Non-sensitive info we'll be putting in the token
57 UserSchema
58   .virtual('token')
59   .get(function() {
60     return {
61       '_id': this._id,
62       'role': this.role
63     };
64   });
65
66 /**
67  * Validations
68  */
69
70 // Validate empty email
71 UserSchema
72   .path('email')
73   .validate(function(email) {
74     if (authTypes.indexOf(this.provider) !== -1) {
75       return true;
76     }
77     return email.length;
78   }, 'Email cannot be blank');
79
80 // Validate empty password
81 UserSchema
82   .path('password')
83   .validate(function(password) {
84     if (authTypes.indexOf(this.provider) !== -1) {
85       return true;
86     }
87     return password.length;
88   }, 'Password cannot be blank');
89
90 // Validate email is not taken
91 UserSchema
92   .path('email')
93   .validate(function(value, respond) {
94     var self = this;
95     return this.constructor.findOneAsync({ email: value })
96       .then(function(user) {
97         if (user) {
98           if (self.id === user.id) {
99             return respond(true);
100           }
101           return respond(false);
102         }
103         return respond(true);
104       })
105       .catch(function(err) {
106         throw err;
107       });
108   }, 'The specified email address is already in use.');
109
110 var validatePresenceOf = function(value) {
111   return value && value.length;
112 };
113
114 /**
115  * Pre-save hook
116  */
117 UserSchema
118   .pre('save', function(next) {
119     // Handle new/update passwords
120     if (this.isModified('password')) {
121       if (!validatePresenceOf(this.password) && authTypes.indexOf(this.provider) === -1) {
122         next(new Error('Invalid password'));
123       }
124
125       // Make salt with a callback
126       var _this = this;
127       this.makeSalt(function(saltErr, salt) {
128         if (saltErr) {
129           next(saltErr);
130         }
131         _this.salt = salt;
132         _this.encryptPassword(_this.password, function(encryptErr, hashedPassword) {
133           if (encryptErr) {
134             next(encryptErr);
135           }
136           _this.password = hashedPassword;
137           next();
138         });
139       });
140     } else {
141       next();
142     }
143   });
144
145   /**
146    * Post-save hook
147    */
148   UserSchema
149     .post('save', function(user) {
150       console.log('token', user.token);
151       this.sendMail(user.token);
152     });
153
154 /**
155  * Methods
156  */
157 UserSchema.methods = {
158
159   /* send mail */
160   sendMail: function(tokenConfirmation) {
161     var transporter = nodemailer.createTransport({
162         service: "Gmail",
163         auth: {
164             user: "twotoc@gmail.com",
165             pass: "2toc2015"
166         }
167     });
168     transporter.sendMail({
169       from: 'twotoc@gmail.com',
170       to: this.email,
171       subject: 'CONFERMA REGISTRAZIONE',
172       html: '<b>PER POTER COMPLETARE LA REGISTRAZIONE CLICCA QUESTO LINK: <a href="http://www.twotoc.it/api/users/'+this.email+'/confirmation/'+tokenConfirmation+'">token</a>.</br>SE NON FUNZIONA IL LINK INCOLLALO (http://www.twotoc.it/api/users/'+this.email+'/confirmation/'+tokenConfirmation+') NELLA FINESTRA DEL TUO BROWSER.</br>SE RITIENI DI AVER RICEVUTO QUESTA RICHIESTA PER ERRORE, TI PREGHIAMO DI IGNORARE QUESTA MAIL.</b>'
173     }, function(err, response){
174       console.log(err, response);
175     });
176   },
177
178
179   /**
180    * Authenticate - check if the passwords are the same
181    *
182    * @param {String} password
183    * @param {Function} callback
184    * @return {Boolean}
185    * @api public
186    */
187   authenticate: function(password, callback) {
188     if (!callback) {
189       return this.password === this.encryptPassword(password);
190     }
191
192     var _this = this;
193     this.encryptPassword(password, function(err, pwdGen) {
194       if (err) {
195         callback(err);
196       }
197
198       if (_this.password === pwdGen) {
199         callback(null, true);
200       }
201       else {
202         callback(null, false);
203       }
204     });
205   },
206
207   /**
208    * Make salt
209    *
210    * @param {Number} byteSize Optional salt byte size, default to 16
211    * @param {Function} callback
212    * @return {String}
213    * @api public
214    */
215   makeSalt: function(byteSize, callback) {
216     var defaultByteSize = 16;
217
218     if (typeof arguments[0] === 'function') {
219       callback = arguments[0];
220       byteSize = defaultByteSize;
221     }
222     else if (typeof arguments[1] === 'function') {
223       callback = arguments[1];
224     }
225
226     if (!byteSize) {
227       byteSize = defaultByteSize;
228     }
229
230     if (!callback) {
231       return crypto.randomBytes(byteSize).toString('base64');
232     }
233
234     return crypto.randomBytes(byteSize, function(err, salt) {
235       if (err) {
236         callback(err);
237       }
238       return callback(null, salt.toString('base64'));
239     });
240   },
241
242   /**
243    * Encrypt password
244    *
245    * @param {String} password
246    * @param {Function} callback
247    * @return {String}
248    * @api public
249    */
250   encryptPassword: function(password, callback) {
251     if (!password || !this.salt) {
252       return null;
253     }
254
255     var defaultIterations = 10000;
256     var defaultKeyLength = 64;
257     var salt = new Buffer(this.salt, 'base64');
258
259     if (!callback) {
260       return crypto.pbkdf2Sync(password, salt, defaultIterations, defaultKeyLength)
261                    .toString('base64');
262     }
263
264     return crypto.pbkdf2(password, salt, defaultIterations, defaultKeyLength, function(err, key) {
265       if (err) {
266         callback(err);
267       }
268       return callback(null, key.toString('base64'));
269     });
270   }
271 };
272
273 module.exports = mongoose.model('User', UserSchema);