const mongoose = require('mongoose') const bcrypt = require('bcryptjs') const { randomBytes } = require('crypto') const userSchema = new mongoose.Schema({ email: { type: String, trim: true, lowercase: true, unique: true, required: true, min: 4, max: 255, validate: { validator: v => /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(v), message: "Please enter a valid email" }, }, password: { type: String, required: true, max: 1024, min: 6, }, verify: { type: String, default: randomBytes(3).toString('hex'), }, role: { type: String, max: 16, min: 3, default: 'user', }, sessions: [{ device: { type: String, maxlength: 128, default: "Unknown" }, refreshToken: { type: String, required: true, max: 1024, min: 6 }, updatedAt: { type: Date, default: Date.now }, }], createdAt: { type: Date, default: Date.now, }, }) userSchema.statics.checkRefreshToken = async ({ email, sessionId, refreshToken }) => { if (!email || !sessionId || !refreshToken) throw new Error('Wrong request') const user = await User.findOne({ email }) if (!user) throw new Error('User not found') const session = user.sessions.find(s => s._id.toString() === sessionId) if (!session) throw new Error('Session not found') const isMatch = await bcrypt.compare(refreshToken, session.refreshToken) if (!isMatch) throw new Error('Wrong refresh token') return user } userSchema.statics.newSession = async ({ email, password, device }) => { const user = await User.findOne({ email }) if (!user) throw new Error('Unable to login') const isMatch = await bcrypt.compare(password, user.password) if (!isMatch) throw new Error('Unable to login') const refreshToken = randomBytes(32).toString('hex') user.sessions.push({ device, refreshToken: await bcrypt.hash(refreshToken, 8) }) const newUser = await user.save() return { user: newUser, refreshToken } } userSchema.statics.refreshExistingToken = async ({ user, sessionId }) => { const newRefreshToken = randomBytes(32).toString('hex') const id = user.sessions.findIndex(s => s._id.toString() === sessionId); if (!id) throw new Error('Wrong session id') user.sessions[id].refreshToken = await bcrypt.hash(newRefreshToken, 8) await user.save() return newRefreshToken } userSchema.statics.removeSession = async ({ email, sessionId }) => ( await User.findOneAndUpdate( { email }, { $pull: { sessions: { _id: sessionId } } }, { safe: true, multi: false } ) ) userSchema.statics.verify = async ({ email, code }) => ( await User.findOneAndUpdate( { email, verify: code }, { verify: '' }, { new: true } ) ) userSchema.statics.getSessions = async ({ email }) => { const user = await User.findOne({ email }) if (!user) throw new Error('User not found') return user.sessions } userSchema.pre('save', async function(next){ const user = this if (user.isModified('password')) { user.password = await bcrypt.hash(user.password, 8) } next() }) const User = mongoose.model('User', userSchema) module.exports = User