Merge branch 'release' into develop

This commit is contained in:
Sabe Jones
2019-07-22 16:38:07 -05:00
9 changed files with 31 additions and 101 deletions

View File

@@ -67,12 +67,6 @@
"SLACK_FLAGGING_URL": "https://hooks.slack.com/services/id/id/id", "SLACK_FLAGGING_URL": "https://hooks.slack.com/services/id/id/id",
"SLACK_SUBSCRIPTIONS_URL": "https://hooks.slack.com/services/id/id/id", "SLACK_SUBSCRIPTIONS_URL": "https://hooks.slack.com/services/id/id/id",
"SLACK_URL": "https://hooks.slack.com/services/some-url", "SLACK_URL": "https://hooks.slack.com/services/some-url",
"SMTP_HOST": "example.com",
"SMTP_PASS": "password",
"SMTP_PORT": 587,
"SMTP_SERVICE": "Gmail",
"SMTP_TLS": "true",
"SMTP_USER": "user@example.com",
"STRIPE_API_KEY": "aaaabbbbccccddddeeeeffff00001111", "STRIPE_API_KEY": "aaaabbbbccccddddeeeeffff00001111",
"STRIPE_PUB_KEY": "22223333444455556666777788889999", "STRIPE_PUB_KEY": "22223333444455556666777788889999",
"TEST_DB_URI": "mongodb://localhost/habitrpg_test", "TEST_DB_URI": "mongodb://localhost/habitrpg_test",

View File

@@ -13,28 +13,30 @@ const questScrolls = shared.content.quests;
const progressCount = 1000; const progressCount = 1000;
let count = 0; let count = 0;
let backupUsers;
async function updateGroup (group) { async function updateGroup (group) {
count++; count++;
if (group && group.quest && group.quest.leader) { if (group && group.quest && group.quest.key && group.quest.leader) {
const quest = questScrolls[group.quest.key]; const quest = questScrolls[group.quest.key];
const leader = await User.findOne({_id: group.quest.leader}).exec(); const leader = await User.findOne({_id: group.quest.leader}).exec();
if (!leader) return; if (leader && quest) {
await User.update({
_id: leader._id,
migration: {$ne: MIGRATION_NAME},
}, {
$set: {migration: MIGRATION_NAME},
$inc: {
balance: 1,
[`items.quests.${group.quest.key}`]: 1,
},
}).exec();
await User.update({ _id: leader._id }, { // unsubscribe from all is already checked by sendTxnEmail
$set: {migration: MIGRATION_NAME}, if (leader.preferences && leader.preferences.emailNotifications && leader.preferences.emailNotifications.majorUpdates !== false) {
$inc: { sendTxnEmail(leader, 'groups-outage');
balance: 1, }
[`items.quests.${group.quest.key}`]: 1,
},
}).exec();
// unsubscribe from all is already checked by sendTxnEmail
if (leader.preferences && leader.preferences.emailNotifications && leader.preferences.emailNotifications.majorUpdates !== false) {
sendTxnEmail(leader, 'groups-outage');
} }
} }
@@ -58,7 +60,7 @@ module.exports = async function processUsers () {
while (true) { // eslint-disable-line no-constant-condition while (true) { // eslint-disable-line no-constant-condition
const groupsPromise = new Promise((resolve, reject) => { const groupsPromise = new Promise((resolve, reject) => {
backupGroups backupGroups
.find(query, { .find(query, {
limit: 250, limit: 250,
sort: {_id: 1} sort: {_id: 1}
}) })
@@ -66,7 +68,7 @@ module.exports = async function processUsers () {
resolve(foundGroupInBackup); resolve(foundGroupInBackup);
}).catch(e => { }).catch(e => {
reject(e); reject(e);
}) });
}); });
const groups = await groupsPromise; const groups = await groupsPromise;
@@ -77,7 +79,7 @@ module.exports = async function processUsers () {
break; break;
} else { } else {
query._id = { query._id = {
$gt: groups[groups.length - 1], $gt: groups[groups.length - 1]._id,
}; };
} }

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "habitica", "name": "habitica",
"version": "4.104.1", "version": "4.104.2",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@@ -1,7 +1,7 @@
{ {
"name": "habitica", "name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.", "description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.104.1", "version": "4.104.2",
"main": "./website/server/index.js", "main": "./website/server/index.js",
"dependencies": { "dependencies": {
"@google-cloud/trace-agent": "^4.0.0", "@google-cloud/trace-agent": "^4.0.0",
@@ -69,7 +69,6 @@
"nconf": "^0.10.0", "nconf": "^0.10.0",
"node-gcm": "^1.0.2", "node-gcm": "^1.0.2",
"node-sass": "^4.9.0", "node-sass": "^4.9.0",
"nodemailer": "^6.0.0",
"ora": "^3.2.0", "ora": "^3.2.0",
"pageres": "^5.1.0", "pageres": "^5.1.0",
"passport": "^0.4.0", "passport": "^0.4.0",

View File

@@ -1,9 +1,7 @@
/* eslint-disable global-require */ /* eslint-disable global-require */
import got from 'got'; import got from 'got';
import nconf from 'nconf'; import nconf from 'nconf';
import nodemailer from 'nodemailer';
import requireAgain from 'require-again'; import requireAgain from 'require-again';
import logger from '../../../../website/server/libs/logger';
import { TAVERN_ID } from '../../../../website/server/models/group'; import { TAVERN_ID } from '../../../../website/server/models/group';
import { defer } from '../../../helpers/api-unit.helper'; import { defer } from '../../../helpers/api-unit.helper';
@@ -35,42 +33,6 @@ function getUser () {
describe('emails', () => { describe('emails', () => {
let pathToEmailLib = '../../../../website/server/libs/email'; let pathToEmailLib = '../../../../website/server/libs/email';
describe('sendEmail', () => {
let sendMailSpy;
beforeEach(() => {
sendMailSpy = sandbox.stub().returns(defer().promise);
sandbox.stub(nodemailer, 'createTransport').returns({
sendMail: sendMailSpy,
});
});
afterEach(() => {
sandbox.restore();
});
it('can send an email using the default transport', () => {
let attachEmail = requireAgain(pathToEmailLib);
attachEmail.send();
expect(sendMailSpy).to.be.calledOnce;
});
it('logs errors', (done) => {
sandbox.stub(logger, 'error');
let attachEmail = requireAgain(pathToEmailLib);
attachEmail.send();
expect(sendMailSpy).to.be.calledOnce;
defer().reject();
// wait for unhandledRejection event to fire
setTimeout(() => {
expect(logger.error).to.be.calledOnce;
done();
}, 20);
});
});
describe('getUserInfo', () => { describe('getUserInfo', () => {
it('returns an empty object if no field request', () => { it('returns an empty object if no field request', () => {
let attachEmail = requireAgain(pathToEmailLib); let attachEmail = requireAgain(pathToEmailLib);
@@ -84,7 +46,7 @@ describe('emails', () => {
let user = getUser(); let user = getUser();
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']); let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
expect(data).to.have.property('name', user.profile.name); expect(data).to.have.property('name', user.auth.local.username);
expect(data).to.have.property('email', user.auth.local.email); expect(data).to.have.property('email', user.auth.local.email);
expect(data).to.have.property('_id', user._id); expect(data).to.have.property('_id', user._id);
expect(data).to.have.property('canSend', true); expect(data).to.have.property('canSend', true);
@@ -95,11 +57,11 @@ describe('emails', () => {
let getUserInfo = attachEmail.getUserInfo; let getUserInfo = attachEmail.getUserInfo;
let user = getUser(); let user = getUser();
delete user.profile.name; delete user.profile.name;
delete user.auth.local; delete user.auth.local.email;
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']); let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
expect(data).to.have.property('name', user.profile.name); expect(data).to.have.property('name', user.auth.local.username);
expect(data).to.have.property('email', user.auth.facebook.emails[0].value); expect(data).to.have.property('email', user.auth.facebook.emails[0].value);
expect(data).to.have.property('_id', user._id); expect(data).to.have.property('_id', user._id);
expect(data).to.have.property('canSend', true); expect(data).to.have.property('canSend', true);
@@ -114,7 +76,7 @@ describe('emails', () => {
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']); let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
expect(data).to.have.property('name', user.profile.name); expect(data).to.have.property('name', user.auth.local.username);
expect(data).not.to.have.property('email'); expect(data).not.to.have.property('email');
expect(data).to.have.property('_id', user._id); expect(data).to.have.property('_id', user._id);
expect(data).to.have.property('canSend', true); expect(data).to.have.property('canSend', true);

View File

@@ -16,6 +16,7 @@ describe('payments/index', () => {
beforeEach(async () => { beforeEach(async () => {
user = new User(); user = new User();
user.profile.name = 'sender'; user.profile.name = 'sender';
user.auth.local.username = 'sender';
await user.save(); await user.save();
group = generateGroup({ group = generateGroup({

View File

@@ -278,9 +278,6 @@
"passwordConfirmationMatch": "Password confirmation doesn't match password.", "passwordConfirmationMatch": "Password confirmation doesn't match password.",
"passwordResetPage": "Reset Password", "passwordResetPage": "Reset Password",
"passwordReset": "If we have your email on file, instructions for setting a new password have been sent to your email.", "passwordReset": "If we have your email on file, instructions for setting a new password have been sent to your email.",
"passwordResetEmailSubject": "Password Reset for Habitica",
"passwordResetEmailText": "If you requested a password reset for <%= username %> on Habitica, head to <%= passwordResetLink %> to set a new one. The link will expire after 24 hours. If you haven't requested a password reset, please ignore this email.",
"passwordResetEmailHtml": "If you requested a password reset for <strong><%= username %></strong> on Habitica, <a href=\"<%= passwordResetLink %>\">click here</a> to set a new one. The link will expire after 24 hours.<br/><br>If you haven't requested a password reset, please ignore this email.",
"invalidLoginCredentialsLong": "Uh-oh - your email address / username or password is incorrect.\n- Make sure they are typed correctly. Your username and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".", "invalidLoginCredentialsLong": "Uh-oh - your email address / username or password is incorrect.\n- Make sure they are typed correctly. Your username and password are case-sensitive.\n- You may have signed up with Facebook or Google-sign-in, not email so double-check by trying them.\n- If you forgot your password, click \"Forgot Password\".",
"invalidCredentials": "There is no account that uses those credentials.", "invalidCredentials": "There is no account that uses those credentials.",
"accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the Community Guidelines (https://habitica.com/static/community-guidelines) or Terms of Service (https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please include your @Username in the email.", "accountSuspended": "This account, User ID \"<%= userId %>\", has been blocked for breaking the Community Guidelines (https://habitica.com/static/community-guidelines) or Terms of Service (https://habitica.com/static/terms). For details or to ask to be unblocked, please email our Community Manager at <%= communityManagerEmail %> or ask your parent or guardian to email them. Please include your @Username in the email.",

View File

@@ -11,7 +11,7 @@ import {
BadRequest, BadRequest,
} from '../../libs/errors'; } from '../../libs/errors';
import * as passwordUtils from '../../libs/password'; import * as passwordUtils from '../../libs/password';
import { send as sendEmail } from '../../libs/email'; import { sendTxn as sendTxnEmail } from '../../libs/email';
import { validatePasswordResetCodeAndFindUser, convertToBcrypt} from '../../libs/password'; import { validatePasswordResetCodeAndFindUser, convertToBcrypt} from '../../libs/password';
import { encrypt } from '../../libs/encryption'; import { encrypt } from '../../libs/encryption';
import { import {
@@ -303,19 +303,9 @@ api.resetPassword = {
user.auth.local.passwordResetCode = passwordResetCode; user.auth.local.passwordResetCode = passwordResetCode;
sendEmail({ sendTxnEmail(user, 'reset-password', [
from: 'Habitica <admin@habitica.com>', {name: 'PASSWORD_RESET_LINK', content: link},
to: email, ]);
subject: res.t('passwordResetEmailSubject'),
text: res.t('passwordResetEmailText', {
username: user.auth.local.username,
passwordResetLink: link,
}),
html: res.t('passwordResetEmailHtml', {
username: user.auth.local.username,
passwordResetLink: link,
}),
});
await user.save(); await user.save();
} }

View File

@@ -1,4 +1,3 @@
import nodemailer from 'nodemailer';
import nconf from 'nconf'; import nconf from 'nconf';
import { TAVERN_ID } from '../models/group'; import { TAVERN_ID } from '../models/group';
import { encrypt } from './encryption'; import { encrypt } from './encryption';
@@ -16,25 +15,11 @@ const EMAIL_SERVER = {
}; };
const BASE_URL = nconf.get('BASE_URL'); const BASE_URL = nconf.get('BASE_URL');
let smtpTransporter = nodemailer.createTransport({
service: nconf.get('SMTP_SERVICE'),
auth: {
user: nconf.get('SMTP_USER'),
pass: nconf.get('SMTP_PASS'),
},
});
// Send email directly from the server using the smtpTransporter,
// used only to send password reset emails because users unsubscribed on Mandrill wouldn't get them
export function send (mailData) {
return smtpTransporter.sendMail(mailData); // promise
}
export function getUserInfo (user, fields = []) { export function getUserInfo (user, fields = []) {
let info = {}; let info = {};
if (fields.indexOf('name') !== -1) { if (fields.indexOf('name') !== -1) {
info.name = user.profile && user.profile.name; info.name = user.auth && user.auth.local.username;
} }
if (fields.indexOf('email') !== -1) { if (fields.indexOf('email') !== -1) {