mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
Merge branch 'release' into develop
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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
2
package-lock.json
generated
@@ -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": {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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.",
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user