You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
163 lines
4.2 KiB
JavaScript
163 lines
4.2 KiB
JavaScript
3 years ago
|
const mongoose = require('mongoose');
|
||
|
const _ = require('lodash');
|
||
|
const jwt = require('jsonwebtoken');
|
||
|
const crypto = require('crypto');
|
||
|
const bcrypt = require('bcryptjs');
|
||
|
require('dotenv').config();
|
||
|
|
||
|
// JWT Secret
|
||
|
const jwtSecret = process.env.JWT_SECRET;
|
||
|
|
||
|
const UserSchema = new mongoose.Schema({
|
||
|
email: {
|
||
|
type: String,
|
||
|
minlength: 1,
|
||
|
required: true,
|
||
|
trim: true,
|
||
|
unique: true
|
||
|
},
|
||
|
password: {
|
||
|
type: String,
|
||
|
required: true,
|
||
|
minlength: 8
|
||
|
},
|
||
|
sessions: [{
|
||
|
token: {
|
||
|
type: String,
|
||
|
required: true
|
||
|
},
|
||
|
expiresAt: {
|
||
|
type: Number,
|
||
|
required: true
|
||
|
}
|
||
|
}]
|
||
|
});
|
||
|
|
||
|
// Instance Methods
|
||
|
UserSchema.methods.toJSON = function() {
|
||
|
const user = this;
|
||
|
const userObject = user.toObject();
|
||
|
|
||
|
// return the document except the password and sessions (these shouldn't be made available)
|
||
|
return _.omit(userObject, ['password', 'sessions']);
|
||
|
}
|
||
|
|
||
|
UserSchema.methods.generateAccessAuthToken = function() {
|
||
|
const user = this;
|
||
|
return new Promise((resolve, reject) => {
|
||
|
// Create JWT (JSON Web Token)
|
||
|
jwt.sign({ _id: user._id.toHexString() }, jwtSecret, { expiresIn: "15m" }, (err, token) => {
|
||
|
if (!err) {
|
||
|
resolve(token);
|
||
|
} else {
|
||
|
reject();
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
UserSchema.methods.generateRefreshAuthToken = function() {
|
||
|
return new Promise((resolve) => {
|
||
|
crypto.randomBytes(64, (err, buffer) => {
|
||
|
if (!err) {
|
||
|
let token = buffer.toString('hex');
|
||
|
return resolve(token);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
UserSchema.methods.createSession = function() {
|
||
|
let user = this;
|
||
|
|
||
|
return user.generateRefreshAuthToken().then((refreshToken) => {
|
||
|
return saveSessionToDatabase(user, refreshToken);
|
||
|
}).then((refreshToken) => {
|
||
|
return refreshToken;
|
||
|
}).catch((e) => {
|
||
|
return Promise.reject("Failed to Save Session To Database \n" + e);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/* MODEL METHODS (static methods) */
|
||
|
UserSchema.statics.getJWTSecret = () => {
|
||
|
return jwtSecret;
|
||
|
}
|
||
|
|
||
|
UserSchema.statics.findByIdAndToken = function (_id, token) {
|
||
|
// finds user by id and token
|
||
|
// used in auth middleware (verifySession)
|
||
|
const User = this;
|
||
|
|
||
|
return User.findOne({
|
||
|
_id,
|
||
|
'sessions.token': token
|
||
|
});
|
||
|
}
|
||
|
|
||
|
UserSchema.statics.findByCredentials = function (email, password) {
|
||
|
let User = this;
|
||
|
return User.findOne({ email }).then((user) => {
|
||
|
if (!user) return Promise.reject();
|
||
|
|
||
|
return new Promise((resolve, reject) => {
|
||
|
bcrypt.compare(password, user.password, (err, res) => {
|
||
|
if (res) {
|
||
|
resolve(user);
|
||
|
}
|
||
|
else {
|
||
|
reject();
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
UserSchema.statics.hasRefreshTokenExpired = (expiresAt) => {
|
||
|
let secondsSinceEpoch = Date.now() / 1000;
|
||
|
return expiresAt <= secondsSinceEpoch;
|
||
|
}
|
||
|
|
||
|
/* MIDDLEWARE */
|
||
|
// Before a user document is saved, this code runs
|
||
|
UserSchema.pre('save', function (next) {
|
||
|
let user = this;
|
||
|
let costFactor = 10;
|
||
|
|
||
|
if (user.isModified('password')) {
|
||
|
// if the password field has been edited/changed then run this code.
|
||
|
// Generate salt and hash password
|
||
|
bcrypt.genSalt(costFactor, (err, salt) => {
|
||
|
bcrypt.hash(user.password, salt, (err, hash) => {
|
||
|
user.password = hash;
|
||
|
next();
|
||
|
});
|
||
|
});
|
||
|
} else {
|
||
|
next();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
/* HELPER METHODS */
|
||
|
let saveSessionToDatabase = (user, refreshToken) => {
|
||
|
return new Promise((resolve) => {
|
||
|
let expiresAt = generateRefreshTokenExpiryTime();
|
||
|
user.sessions.push({ 'token': refreshToken, expiresAt });
|
||
|
|
||
|
user.save().then(() => {
|
||
|
return resolve(refreshToken);
|
||
|
}).catch((e) => {
|
||
|
this.reject(e);
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
let generateRefreshTokenExpiryTime = () => {
|
||
|
let daysUntilExpire = "10";
|
||
|
let secondsUntilExpire = ((daysUntilExpire * 24) * 60) * 60;
|
||
|
return ((Date.now() / 1000) + secondsUntilExpire);
|
||
|
}
|
||
|
|
||
|
const User = mongoose.model('User', UserSchema);
|
||
|
|
||
|
module.exports = { User }
|