HackMD/lib/models/user.js
Sheogorath 70df29790a
Add token based security feature
In the current setup users could be tricked into deleting their data by
providing a malicious link like `[click me](/me/delete)`. This commit
prevents such an easy attack and need the user's deleteToken to get his
data deleted. In case someone requests his deletion by email you can
also ask him for this token.

We can add a GUI that shows it later on.

Signed-off-by: Sheogorath <sheogorath@shivering-isles.com>
2018-05-25 18:26:06 +02:00

177 lines
5.2 KiB
JavaScript

'use strict'
// external modules
var md5 = require('blueimp-md5')
var Sequelize = require('sequelize')
var scrypt = require('scrypt')
// core
var logger = require('../logger')
var {generateAvatarURL} = require('../letter-avatars')
module.exports = function (sequelize, DataTypes) {
var User = sequelize.define('User', {
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: Sequelize.UUIDV4
},
profileid: {
type: DataTypes.STRING,
unique: true
},
profile: {
type: DataTypes.TEXT
},
history: {
type: DataTypes.TEXT
},
accessToken: {
type: DataTypes.STRING
},
refreshToken: {
type: DataTypes.STRING
},
deleteToken: {
type: DataTypes.UUID,
defaultValue: Sequelize.UUIDV4
},
email: {
type: Sequelize.TEXT,
validate: {
isEmail: true
}
},
password: {
type: Sequelize.TEXT,
set: function (value) {
var hash = scrypt.kdfSync(value, scrypt.paramsSync(0.1)).toString('hex')
this.setDataValue('password', hash)
}
}
}, {
instanceMethods: {
verifyPassword: function (attempt) {
if (scrypt.verifyKdfSync(new Buffer(this.password, 'hex'), attempt)) {
return this
} else {
return false
}
}
},
classMethods: {
associate: function (models) {
User.hasMany(models.Note, {
foreignKey: 'ownerId',
constraints: false
})
User.hasMany(models.Note, {
foreignKey: 'lastchangeuserId',
constraints: false
})
},
getProfile: function (user) {
if (!user) {
return null
}
return user.profile ? User.parseProfile(user.profile) : (user.email ? User.parseProfileByEmail(user.email) : null)
},
parseProfile: function (profile) {
try {
profile = JSON.parse(profile)
} catch (err) {
logger.error(err)
profile = null
}
if (profile) {
profile = {
name: profile.displayName || profile.username,
photo: User.parsePhotoByProfile(profile),
biggerphoto: User.parsePhotoByProfile(profile, true)
}
}
return profile
},
parsePhotoByProfile: function (profile, bigger) {
var photo = null
switch (profile.provider) {
case 'facebook':
photo = 'https://graph.facebook.com/' + profile.id + '/picture'
if (bigger) photo += '?width=400'
else photo += '?width=96'
break
case 'twitter':
photo = 'https://twitter.com/' + profile.username + '/profile_image'
if (bigger) photo += '?size=original'
else photo += '?size=bigger'
break
case 'github':
photo = 'https://avatars.githubusercontent.com/u/' + profile.id
if (bigger) photo += '?s=400'
else photo += '?s=96'
break
case 'gitlab':
photo = profile.avatarUrl
if (photo) {
if (bigger) photo = photo.replace(/(\?s=)\d*$/i, '$1400')
else photo = photo.replace(/(\?s=)\d*$/i, '$196')
} else {
photo = generateAvatarURL(profile.username)
}
break
case 'mattermost':
photo = profile.avatarUrl
if (photo) {
if (bigger) photo = photo.replace(/(\?s=)\d*$/i, '$1400')
else photo = photo.replace(/(\?s=)\d*$/i, '$196')
} else {
photo = generateAvatarURL(profile.username)
}
break
case 'dropbox':
// no image api provided, use gravatar
photo = 'https://www.gravatar.com/avatar/' + md5(profile.emails[0].value)
if (bigger) photo += '?s=400'
else photo += '?s=96'
break
case 'google':
photo = profile.photos[0].value
if (bigger) photo = photo.replace(/(\?sz=)\d*$/i, '$1400')
else photo = photo.replace(/(\?sz=)\d*$/i, '$196')
break
case 'ldap':
// no image api provided,
// use gravatar if email exists,
// otherwise generate a letter avatar
if (profile.emails[0]) {
photo = 'https://www.gravatar.com/avatar/' + md5(profile.emails[0])
if (bigger) photo += '?s=400'
else photo += '?s=96'
} else {
photo = generateAvatarURL(profile.username)
}
break
case 'saml':
if (profile.emails[0]) {
photo = 'https://www.gravatar.com/avatar/' + md5(profile.emails[0])
if (bigger) photo += '?s=400'
else photo += '?s=96'
} else {
photo = generateAvatarURL(profile.username)
}
break
}
return photo
},
parseProfileByEmail: function (email) {
var photoUrl = 'https://www.gravatar.com/avatar/' + md5(email)
return {
name: email.substring(0, email.lastIndexOf('@')),
photo: photoUrl + '?s=96',
biggerphoto: photoUrl + '?s=400'
}
}
}
})
return User
}