forked from kaverti/website
482 lines
11 KiB
JavaScript
482 lines
11 KiB
JavaScript
let bcrypt = require('bcryptjs')
|
|
let randomColor = require('randomcolor')
|
|
var passportLocalSequelize = require('passport-local-sequelize');
|
|
let pagination = require('../lib/pagination.js')
|
|
const Errors = require('../lib/errors.js')
|
|
var crypto = require("crypto");
|
|
var cryptoRandomString = require("crypto-random-string");
|
|
module.exports = (sequelize, DataTypes) => {
|
|
let User = sequelize.define('User', {
|
|
username: {
|
|
type: DataTypes.STRING(191),
|
|
unique: {
|
|
msg: 'Username is already taken, try another!',
|
|
fields: ['username']
|
|
},
|
|
validate: {
|
|
is: {
|
|
args: [/^[a-zA-Z0-9_]*$/],
|
|
msg: 'Username can only contain numbers and letters'
|
|
},
|
|
len: {
|
|
args: [3, 16],
|
|
msg: 'username must be between 3 and 16 characters'
|
|
},
|
|
isString (val) {
|
|
if(typeof val !== 'string') {
|
|
throw new sequelize.ValidationError('username must be a string')
|
|
}
|
|
},
|
|
containsNoBlankCharacters (val) {
|
|
if(/\s/g.test(val)) {
|
|
throw new sequelize.ValidationError('username can\'t contain blank characters')
|
|
}
|
|
}
|
|
}
|
|
},
|
|
email: {
|
|
type: DataTypes.TEXT,
|
|
unique: {
|
|
msg: 'This email is in use by another Kaverti account',
|
|
fields: ['email']
|
|
},
|
|
validate: {
|
|
isEmail: {
|
|
args: true,
|
|
msg: 'Email is not formatted correctly'
|
|
},
|
|
isString (val) {
|
|
if(typeof val !== 'string') {
|
|
throw new sequelize.ValidationError('email must be a string')
|
|
}
|
|
},
|
|
len: {
|
|
args: [5, 100],
|
|
msg: 'email must be between 5 and 100 characters'
|
|
}
|
|
}
|
|
},
|
|
description: {
|
|
type: DataTypes.TEXT,
|
|
validate: {
|
|
isString (val) {
|
|
if(typeof val !== 'string') {
|
|
throw new sequelize.ValidationError('description must be a string')
|
|
}
|
|
},
|
|
len: {
|
|
args: [0, 256],
|
|
msg: 'description must be less than 256 characters'
|
|
}
|
|
}
|
|
},
|
|
color: {
|
|
type: DataTypes.STRING,
|
|
defaultValue () {
|
|
return randomColor()
|
|
}
|
|
},
|
|
theme: {
|
|
type: DataTypes.STRING,
|
|
defaultValue: 'light',
|
|
values: ['light', 'dark'],
|
|
isIn: {
|
|
args: [['light', 'dark']],
|
|
msg: "Theme can only be one of the pre-defined options"
|
|
},
|
|
},
|
|
lastRewardDate: {
|
|
type: DataTypes.DATE
|
|
},
|
|
contributor: {
|
|
type: DataTypes.BOOLEAN,
|
|
defaultValue: false,
|
|
isBoolean: {
|
|
msg: "Can only be a true or false value"
|
|
}
|
|
},
|
|
userWallOptOut: {
|
|
type: DataTypes.BOOLEAN,
|
|
defaultValue: false
|
|
},
|
|
cookieOptOut: {
|
|
type: DataTypes.BOOLEAN,
|
|
defaultValue: false
|
|
},
|
|
hash: {
|
|
type: DataTypes.STRING,
|
|
allowNull: false,
|
|
validate: {
|
|
len: {
|
|
args: [6, 50],
|
|
msg: 'password must be between 6 and 50 characters'
|
|
},
|
|
isString (val) {
|
|
if(typeof val !== 'string') {
|
|
throw new sequelize.ValidationError('Please enter your password')
|
|
}
|
|
}
|
|
}
|
|
},
|
|
admin: {
|
|
type: DataTypes.BOOLEAN,
|
|
defaultValue: false
|
|
},
|
|
executive: {
|
|
type: DataTypes.BOOLEAN,
|
|
defaultValue: false
|
|
},
|
|
developerMode: {
|
|
type: DataTypes.BOOLEAN,
|
|
defaultValue: false,
|
|
validate: {
|
|
isBoolean: {
|
|
msg: 'Developer mode can only be true or false.'
|
|
}
|
|
}
|
|
},
|
|
experimentMode: {
|
|
type: DataTypes.BOOLEAN,
|
|
defaultValue: false
|
|
},
|
|
bot: {
|
|
type: DataTypes.BOOLEAN,
|
|
defaultValue: false
|
|
},
|
|
booster: {
|
|
type: DataTypes.BOOLEAN,
|
|
defaultValue: false
|
|
},
|
|
koins: {
|
|
type: DataTypes.BIGINT,
|
|
defaultValue: "250"
|
|
},
|
|
currency2: {
|
|
type: DataTypes.BIGINT,
|
|
defaultValue: "0"
|
|
},
|
|
system: {
|
|
type: DataTypes.BOOLEAN,
|
|
defaultValue: false
|
|
},
|
|
hidden: {
|
|
type: DataTypes.BOOLEAN,
|
|
defaultValue: false
|
|
},
|
|
bodyColor: {
|
|
type: DataTypes.STRING(191),
|
|
defaultValue: false
|
|
},
|
|
headColor: {
|
|
type: DataTypes.STRING(191),
|
|
defaultValue: false
|
|
},
|
|
leftLegColor: {
|
|
type: DataTypes.STRING(191),
|
|
defaultValue: false
|
|
},
|
|
rightLegColor: {
|
|
type: DataTypes.STRING(191),
|
|
defaultValue: false
|
|
},
|
|
leftArmColor: {
|
|
type: DataTypes.STRING(191),
|
|
defaultValue: false
|
|
},
|
|
rightArmColor: {
|
|
type: DataTypes.STRING(191),
|
|
defaultValue: false
|
|
},
|
|
emailVerified: {
|
|
type: DataTypes.BOOLEAN,
|
|
defaultValue: false
|
|
},
|
|
emailToken: {
|
|
type: DataTypes.STRING,
|
|
defaultValue: false
|
|
},
|
|
passwordResetToken: {
|
|
type: DataTypes.STRING,
|
|
required: false
|
|
},
|
|
level: {
|
|
type: DataTypes.BIGINT,
|
|
required: true,
|
|
default: 1
|
|
},
|
|
levelProgress: {
|
|
type: DataTypes.BIGINT,
|
|
required: true,
|
|
default: 25
|
|
},
|
|
passwordResetExpiry: {
|
|
type: DataTypes.DATE,
|
|
required: false
|
|
},
|
|
passwordResetOptOut: {
|
|
type: DataTypes.BOOLEAN,
|
|
default: false,
|
|
required: false
|
|
},
|
|
passwordResetEnabled: {
|
|
type: DataTypes.BOOLEAN,
|
|
required: false,
|
|
},
|
|
deleteCode: {
|
|
type: DataTypes.TEXT,
|
|
required: false,
|
|
default: false,
|
|
defaultValue: false
|
|
},
|
|
deleteEnabled: {
|
|
type: DataTypes.BOOLEAN,
|
|
default: false,
|
|
defaultValue: false
|
|
},
|
|
picture: {
|
|
type: DataTypes.TEXT('long'),
|
|
validate: {
|
|
isString (val) {
|
|
if(typeof val !== 'string') {
|
|
throw new sequelize.ValidationError('avatar must be a string')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}, {
|
|
instanceMethods: {
|
|
async rand() {
|
|
await this.update({ emailToken: cryptoRandomString({length: 250})})
|
|
},
|
|
async randPasswordReset() {
|
|
if(User) {
|
|
await this.update({ passwordResetToken: cryptoRandomString({length: 250}), deleteEnabled: true })
|
|
} else {
|
|
throw Errors.accountDoesNotExist
|
|
}
|
|
},
|
|
async randAccountDelete() {
|
|
if(User) {
|
|
await this.update({ deleteCode: cryptoRandomString({length: 1024}), deleteEnabled: true})
|
|
} else {
|
|
throw Errors.accountDoesNotExist
|
|
}
|
|
},
|
|
async emailVerify() {
|
|
await this.update({ emailVerified: true })
|
|
},
|
|
async updatePassword (currentPassword, newPassword) {
|
|
if(currentPassword === newPassword) {
|
|
throw Errors.passwordSame
|
|
} else if(typeof currentPassword !== 'string' || typeof newPassword !== 'string') {
|
|
throw new sequelize.ValidationError('Please enter your password')
|
|
}
|
|
|
|
let correctPassword = await bcrypt.compare(currentPassword, this.hash)
|
|
|
|
if(correctPassword) {
|
|
await this.update({ hash: newPassword })
|
|
} else {
|
|
throw Errors.invalidLoginCredentials
|
|
}
|
|
},
|
|
async reward () {
|
|
let ms = Date.now() - this.lastRewardDate
|
|
let dayMs = 1000*60*60*24
|
|
|
|
//Has less than 1 day passed
|
|
//since generating token?
|
|
return ms / dayMs < 1
|
|
},
|
|
async doReward () {
|
|
if(User.lastRewardDate) {
|
|
if(User.lastRewardDate.reward()) {
|
|
throw Errors.invalidToken
|
|
} else {
|
|
throw Errors.invalidPassKey
|
|
}
|
|
} else {
|
|
console.log("idk")
|
|
}
|
|
},
|
|
async recoveryUpdatePassword (password) {
|
|
if(typeof password !== 'string') {
|
|
throw new sequelize.ValidationError('Please enter your password')
|
|
}
|
|
await this.update({ hash: password, passwordResetEnabled: false })
|
|
},
|
|
/* async GenerateJWT() {
|
|
const today = new Date();
|
|
const expirationDate = new Date(today);
|
|
expirationDate.setDate(today.getDate() + 60);
|
|
|
|
let payload = {
|
|
id: this.id,
|
|
email: this.email,
|
|
username: this.username,
|
|
};
|
|
|
|
return jwt.sign(payload, "AUSDHIASDHAHDAiyrgy3476rty734we6yrgwesyufeyhurfehyrurgty7346ertg645e37t6rgyu", {
|
|
expiresIn: parseInt(expirationDate.getTime() / 1000, 10)
|
|
});
|
|
}, */
|
|
async updateEmail (emailCurrentPassword, newEmail) {
|
|
if(typeof emailCurrentPassword !== 'string' || typeof newEmail !== 'string') {
|
|
throw new sequelize.ValidationError('input must be a string')
|
|
}
|
|
|
|
let correctPassword = await bcrypt.compare(emailCurrentPassword, this.hash)
|
|
|
|
if(correctPassword) {
|
|
await this.update({ email: newEmail, emailVerified: false, emailToken: cryptoRandomString({length: 16})})
|
|
} else {
|
|
throw Errors.invalidLoginCredentials
|
|
}
|
|
},
|
|
async updateUsername (username, password) {
|
|
if(typeof username !== 'string') {
|
|
throw new sequelize.ValidationError('Please enter your new username')
|
|
}
|
|
if(typeof password !== 'string') {
|
|
throw new sequelize.ValidationError('Please enter your password')
|
|
}
|
|
|
|
let correctPassword = await bcrypt.compare(password, this.hash)
|
|
if(correctPassword) {
|
|
await this.update({username: username, koins: this.koins - 200})
|
|
} else {
|
|
throw Errors.invalidLoginCredentials
|
|
}
|
|
},
|
|
async comparePassword (password) {
|
|
return await bcrypt.compare(password, this.hash)
|
|
},
|
|
async destroyVerifyPassword (password) {
|
|
if(typeof password !== 'string') {
|
|
throw Errors.invalidLoginCredentials
|
|
}
|
|
|
|
let correctPassword = await bcrypt.compare(password, this.hash)
|
|
|
|
if(correctPassword) {
|
|
await this.destroy()
|
|
} else {
|
|
throw Errors.invalidLoginCredentials
|
|
}
|
|
},
|
|
async getMeta (limit) {
|
|
let Post = sequelize.models.Post
|
|
let meta = {}
|
|
|
|
let nextId = await pagination.getNextIdDesc(Post, { userId: this.id }, this.Posts)
|
|
|
|
if(nextId === null) {
|
|
meta.nextURL = null
|
|
meta.nextPostsCount = 0
|
|
} else {
|
|
meta.nextURL =
|
|
`/api/v1/user/${this.username}?posts=true&limit=${limit}&from=${nextId - 1}`
|
|
|
|
meta.nextPostsCount = await pagination.getNextCount(
|
|
Post, this.Posts, limit,
|
|
{ UserId: this.id },
|
|
true
|
|
)
|
|
}
|
|
|
|
return meta
|
|
},
|
|
async getWallMeta (limit) {
|
|
let Post = sequelize.models.userWall
|
|
let meta = {}
|
|
|
|
let nextId = await pagination.getNextIdDesc(Post, { userId: this.id }, this.Posts)
|
|
|
|
if(nextId === null) {
|
|
meta.nextURL = null
|
|
meta.nextPostsCount = 0
|
|
} else {
|
|
meta.nextURL =
|
|
``
|
|
|
|
meta.nextPostsCount = await pagination.getNextCount(
|
|
Post, this.Post, limit,
|
|
{ Post: this.id },
|
|
true
|
|
)
|
|
}
|
|
|
|
return meta
|
|
}
|
|
},
|
|
classMethods: {
|
|
associate (models) {
|
|
User.hasMany(models.Post)
|
|
User.hasMany(models.Thread)
|
|
User.hasMany(models.userWall)
|
|
User.belongsToMany(models.Conversation, { through: models.UserConversation })
|
|
User.belongsToMany(models.Ip, { through: 'UserIp' })
|
|
},
|
|
includeOptions (from, limit) {
|
|
let models = sequelize.models
|
|
let options = models.Post.includeOptions()
|
|
|
|
return [{
|
|
model: models.Post,
|
|
include: options,
|
|
limit,
|
|
where: { postNumber: { $gte: from } },
|
|
order: [['id', 'ASC']]
|
|
}]
|
|
},
|
|
includeWallOptions (from, limit) {
|
|
let models = sequelize.models
|
|
|
|
return [{
|
|
model: models.userWall,
|
|
limit,
|
|
where: { postNumber: { $gte: from } },
|
|
order: [['id', 'ASC']]
|
|
}]
|
|
},
|
|
async canBeUser (passkey) {
|
|
let { User, PassKey } = sequelize.models
|
|
|
|
if(User) {
|
|
if(PassKey) {
|
|
let passkey = await PassKey.findOne({ where: { passkey } })
|
|
|
|
if(passkey && PassKey.isValid()) {
|
|
await passkey.destroy()
|
|
|
|
return true
|
|
} else {
|
|
throw Errors.invalidPassKey
|
|
}
|
|
} else {
|
|
throw Errors.sequelizeValidation(sequelize, {
|
|
error: 'Invalid PassKey',
|
|
path: 'passkey'
|
|
})
|
|
}
|
|
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
},
|
|
hooks: {
|
|
async afterValidate(user, options) {
|
|
if(user.changed('hash') && user.hash.length <= 50) {
|
|
user.hash = await bcrypt.hash(user.hash, 12)
|
|
}
|
|
|
|
options.hooks = false
|
|
return options
|
|
}
|
|
}
|
|
})
|
|
|
|
return User
|
|
}
|