@materia/users
Version:
Signin/signup your users in your Materia application
407 lines (379 loc) • 11.3 kB
JavaScript
const uuid = require('uuid/v4');
const bcrypt = require('bcryptjs');
const md5 = require('md5');
const crypto = require('crypto');
class UserModel {
get config() {
return this.app.addons.addonsConfig && this.app.addons.addonsConfig['@materia/users'];
}
constructor(app, model) {
this.app = app;
this.model = model;
}
listWithGravatar(params) {
return this.app.entities.get('user')
.getQuery('list').run({
page: params.page,
limit: params.limit
}, { raw: true }).then(result => {
result.data = result.data.map(user => {
return Object.assign({}, user, {
gravatar: 'http://www.gravatar.com/avatar/' + md5(user.email) + '?d=mm&s=32'
});
});
return result;
})
}
userInfo(params) {
return this.app.entities
.get('user')
.getQuery('get')
.run(
{
id_user: params.id_user
},
{ raw: true }
)
.then(user => {
delete user.password;
if (user.key_email !== undefined) {
delete user.key_email;
}
if (user.key_password !== undefined) {
delete user.key_password;
}
if (user.new_email !== undefined && user.new_email === null) {
delete user.new_email;
}
if (
this.config &&
this.config.user_profile_enabled &&
this.config.user_profile_entity
) {
const userProfileEntity = this.app.entities.get(
this.config.user_profile_entity
);
return userProfileEntity
.getQuery('getByUserId')
.run(
{
id_user: params.id_user
},
{ raw: true }
)
.then(userProfile => {
return Object.assign({}, user, userProfile);
})
.catch(e => {
return Promise.reject((e && e.message) || e || 'Unauthorized');
});
} else {
return user;
}
});
}
_translate(message, user, redirect_url) {
message = message.replace(
new RegExp(`\\{{redirect_url\\}}`, 'gi'),
redirect_url
);
this.app.entities
.get('user')
.getFields()
.forEach(field => {
if (
field.name != 'password' &&
field.name != 'key' &&
field.name != 'verified'
) {
let e = new RegExp(`\\{\\{user.${field.name}\\}\\}`, 'gi');
message = message.replace(e, user[field.name]);
}
});
return message;
}
signup(params) {
let user = this.app.entities.get('user');
let password = params.password;
return bcrypt.hash(password, 10).then(encryptedPassword => {
params.password = encryptedPassword;
params.key_email = uuid();
const paramsProfile = {};
Object.keys(params).forEach(paramName => {
if (['email', 'password', 'key_email'].indexOf(paramName) == -1) {
paramsProfile[paramName] = params[paramName];
delete params[paramName];
}
});
return user
.getQuery('create')
.run(params, { raw: true })
.then(created => {
let p = Promise.resolve(created);
if (
this.config &&
this.config.user_profile_enabled &&
this.config.user_profile_entity
) {
const profileEntity = this.app.entities.get(
this.config.user_profile_entity
);
paramsProfile.id_user = created.id_user;
p = profileEntity
.getQuery('create')
.run(paramsProfile, { raw: true });
}
return p;
})
.then(created => {
created.email = params.email;
return this.sendVerificationEmail({ id_user: created.id_user })
.then(() => created)
.catch(e => {
return created;
});
});
})
}
sendVerificationEmail(params) {
if (
this.config &&
this.config.email_verification &&
this.config.email_addon
) {
return this.app.entities
.get('user')
.getQuery('get')
.run(
{
id_user: params.id_user
},
{ raw: true }
)
.then(user => {
return this.userInfo(params).then(userSecure => {
if (userSecure.verified && !user.new_email) {
return Promise.reject(
`Email '${userSecure.email}' has already been verified`
);
}
let verify_url = `${this.app.server.getBaseUrl()}/user/verify/${
user.id_user
}/${user.key_email}`;
let templateId = this.config.template_signup;
let subject = this.config.subject_signup;
if (user.new_email) {
templateId = this.config.template_change_email;
subject = this.config.subject_change_email;
}
let entity,
query = 'sendTemplate',
params = {
to: user.new_email ? user.new_email : user.email,
templateId: templateId,
subject: subject,
variables: JSON.stringify({
url_email_verification: verify_url
})
};
if (this.config.email_addon == '@materia/mailjet') {
entity = 'mailjet_message';
} else if (this.config.email_addon == '@materia/sendgrid') {
entity = 'sendgrid';
}
const emailEntity = this.app.entities.get(entity);
if (!emailEntity) {
return Promise.reject(
'addon ' +
this.config.email_addon +
' is not correctly installed: Entity not found.'
);
}
const emailQuery = emailEntity.getQuery(query);
if (!emailQuery) {
return Promise.reject(
'addon ' +
this.config.email_addon +
' is not correctly installed: Query not found.'
);
}
return emailQuery.run(params).then(() => userSecure);
});
});
} else {
return Promise.reject('Email verification disabled');
}
}
verifyEmail(params) {
let userEntity = this.app.entities.get('user');
return userEntity
.getQuery('get')
.run(
{
id_user: params.id_user
},
{ raw: true }
)
.then(user => {
let isSignup = true;
if (
user.key_email == params.key_email &&
(!user.verified || user.new_email)
) {
let updates = {
verified: true,
key_email: null,
id_user: user.id_user
};
if (user.new_email) {
updates.email = user.new_email;
updates.new_email = '';
isSignup = false;
}
return userEntity
.getQuery('update')
.run(updates)
.then(() => {
if (this.config.method == 'token') {
const token = uuid();
const tokenHash = crypto
.createHash('sha1')
.update(token)
.digest('hex');
var expires = new Date();
expires.setDate(expires.getDate() + 1);
return this.app.entities.get('user_token').getQuery('create').run({
id_user: params.id_user,
expires_in: expires,
scope: '["*"]',
token: tokenHash
}).then(() => token);
} else {
return Promise.resolve();
}
}).then(token => {
if (isSignup) {
return this.config.redirect_signup + (token ? '?access_token=' + token : '');
} else {
return this.config.redirect_change_email + (token ? '?access_token=' + token : '');
}
});
} else {
return Promise.reject('User email found but the key mismatch.');
}
});
}
_buildRedirectUri(redirect_url, params) {
let newUrl = redirect_url;
if (newUrl.substr(0, 1) == '/') {
newUrl = this.app.server.getBaseUrl('/');
newUrl = newUrl.substr(0, newUrl.length - 1) + redirect_url;
}
if (newUrl.split('?').length > 1) {
newUrl += '&';
} else {
newUrl += '?';
}
Object.keys(params).forEach((p, index) => {
newUrl += p + '=' + params[p];
if (index + 1 < Object.keys(params).length) {
newUrl += '&';
}
});
return newUrl;
}
_execLostPassword(user) {
let key = uuid();
return this.app.entities
.get('user')
.getQuery('update')
.run({
key_password: key,
id_user: user.id_user
})
.then(() => {
return this.userInfo(user).then(userSecure => {
const redirect_url = this._buildRedirectUri(
this.config.redirect_lost_password,
{
id_user: user.id_user,
key: key
}
);
const subject = this._translate(
this.config.subject_lost_password,
user,
redirect_url
);
let entity, query, params;
if (this.config.email_addon == '@materia/mailjet') {
entity = 'mailjet_message';
query = 'sendTemplate';
params = {
to: user.email,
templateId: this.config.template_lost_password,
subject: subject,
variables: {
url_lost_password: redirect_url
}
};
}
//send the email
return this.app.entities
.get(entity)
.getQuery(query)
.run(params);
});
});
}
canResetPassword(params) {
let userEntity = this.app.entities.get('user');
return userEntity
.getQuery('get')
.run(
{
id_user: params.id_user
},
{ raw: true }
)
.then(user => {
if (user.key_password == params.key) {
return Promise.resolve(user);
} else {
return Promise.reject('key does not match');
}
});
}
/**
*
* @param {*} params
*/
lostPassword(params) {
if (!this.config.email_verification) {
return Promise.reject(
'Email verification not configured - Lost password functionality disabled'
);
}
return this.app.entities
.get('user')
.getQuery('getByEmail')
.run(
{
email: params.email
},
{ raw: true }
)
.then(user => {
if (user) {
return this._execLostPassword(user);
} else {
return Promise.reject(new Error('Invalid email'));
}
})
.then(() => {
return { emailSent: true };
})
.catch(err => Promise.reject(err.message));
}
}
module.exports = UserModel;