mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-14 21:27:23 +01:00
* update API comments to for `username` restrictions and to use Login Name terminology We use "login name" rather than "username" in user-visible text on the website and (usually) when communicating with users because "username" could be confused with "profile name". Using it in the docs allows you to search for that term. * add alphanumeric and length validation for creating new login name (username) The 'en-US' locale is specified explicitly to ensure we never use another locale. The point of this change is to limit the character set to prevent login names being used to send spam in the Welcome emails, such as Chinese language spam we've had trouble with. * add error messages for bad login names * allow login name to also contain hyphens This is because our automated tests generate user accounts using: let username = generateUUID(); * allow login names to be up to 36 characters long because we use UUIDs as login names in our tests * revert back to using max 20 characters and only a-z, 0-9 for login name. It's been decided to change the username generation in the tests instead. * disable test that is failing because it's redundant Spaces are now prohibited by other code. We can probably delete this test later. I don't want to delete it now, but instead give us time to think about that. * fix typos * revert to login name restrictions that allow us to keep using our existing test code I'm really not comfortable changing our test suite in ways that aren't essential, especially since we're working in a hurry with a larger chance than normal of breaking things. The 36 character length is larger than we initially decided but not so much larger that it's a huge problem. We can reduce it to 20 when we have more time. * limit username length to 20 chars * fix tests
677 lines
20 KiB
JavaScript
677 lines
20 KiB
JavaScript
import {
|
|
generateUser,
|
|
requester,
|
|
translate as t,
|
|
createAndPopulateGroup,
|
|
getProperty,
|
|
} from '../../../../../helpers/api-integration/v3';
|
|
import { ApiUser } from '../../../../../helpers/api-integration/api-classes';
|
|
import { v4 as uuid } from 'uuid';
|
|
import { each } from 'lodash';
|
|
import { encrypt } from '../../../../../../website/server/libs/encryption';
|
|
|
|
function generateRandomUserName () {
|
|
return (Date.now() + uuid()).substring(0, 20);
|
|
}
|
|
|
|
describe('POST /user/auth/local/register', () => {
|
|
context('username and email are free', () => {
|
|
let api;
|
|
|
|
beforeEach(async () => {
|
|
api = requester();
|
|
});
|
|
|
|
it('registers a new user', async () => {
|
|
let username = generateRandomUserName();
|
|
let email = `${username}@example.com`;
|
|
let password = 'password';
|
|
|
|
let user = await api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
expect(user._id).to.exist;
|
|
expect(user.apiToken).to.exist;
|
|
expect(user.auth.local.username).to.eql(username);
|
|
expect(user.profile.name).to.eql(username);
|
|
expect(user.newUser).to.eql(true);
|
|
});
|
|
|
|
xit('remove spaces from username', async () => {
|
|
// TODO can probably delete this test now
|
|
let username = ' usernamewithspaces ';
|
|
let email = 'test@example.com';
|
|
let password = 'password';
|
|
|
|
let user = await api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
expect(user.auth.local.username).to.eql(username.trim());
|
|
expect(user.profile.name).to.eql(username.trim());
|
|
});
|
|
|
|
context('provides default tags and tasks', async () => {
|
|
it('for a generic API consumer', async () => {
|
|
let username = generateRandomUserName();
|
|
let email = `${username}@example.com`;
|
|
let password = 'password';
|
|
|
|
let user = await api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
let requests = new ApiUser(user);
|
|
|
|
let habits = await requests.get('/tasks/user?type=habits');
|
|
let dailys = await requests.get('/tasks/user?type=dailys');
|
|
let todos = await requests.get('/tasks/user?type=todos');
|
|
let rewards = await requests.get('/tasks/user?type=rewards');
|
|
let tags = await requests.get('/tags');
|
|
|
|
expect(habits).to.have.a.lengthOf(0);
|
|
expect(dailys).to.have.a.lengthOf(0);
|
|
expect(todos).to.have.a.lengthOf(1);
|
|
expect(rewards).to.have.a.lengthOf(0);
|
|
|
|
expect(tags).to.have.a.lengthOf(7);
|
|
expect(tags[0].name).to.eql(t('defaultTag1'));
|
|
expect(tags[1].name).to.eql(t('defaultTag2'));
|
|
expect(tags[2].name).to.eql(t('defaultTag3'));
|
|
expect(tags[3].name).to.eql(t('defaultTag4'));
|
|
expect(tags[4].name).to.eql(t('defaultTag5'));
|
|
expect(tags[5].name).to.eql(t('defaultTag6'));
|
|
expect(tags[6].name).to.eql(t('defaultTag7'));
|
|
});
|
|
|
|
xit('for Web', async () => {
|
|
api = requester(
|
|
null,
|
|
{'x-client': 'habitica-web'},
|
|
);
|
|
let username = generateRandomUserName();
|
|
let email = `${username}@example.com`;
|
|
let password = 'password';
|
|
|
|
let user = await api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
let requests = new ApiUser(user);
|
|
|
|
let habits = await requests.get('/tasks/user?type=habits');
|
|
let dailys = await requests.get('/tasks/user?type=dailys');
|
|
let todos = await requests.get('/tasks/user?type=todos');
|
|
let rewards = await requests.get('/tasks/user?type=rewards');
|
|
let tags = await requests.get('/tags');
|
|
|
|
expect(habits).to.have.a.lengthOf(3);
|
|
expect(habits[0].text).to.eql(t('defaultHabit1Text'));
|
|
expect(habits[0].notes).to.eql('');
|
|
expect(habits[1].text).to.eql(t('defaultHabit2Text'));
|
|
expect(habits[1].notes).to.eql('');
|
|
expect(habits[2].text).to.eql(t('defaultHabit3Text'));
|
|
expect(habits[2].notes).to.eql('');
|
|
|
|
expect(dailys).to.have.a.lengthOf(0);
|
|
|
|
expect(todos).to.have.a.lengthOf(1);
|
|
expect(todos[0].text).to.eql(t('defaultTodo1Text'));
|
|
expect(todos[0].notes).to.eql(t('defaultTodoNotes'));
|
|
|
|
expect(rewards).to.have.a.lengthOf(1);
|
|
expect(rewards[0].text).to.eql(t('defaultReward1Text'));
|
|
expect(rewards[0].notes).to.eql('');
|
|
|
|
expect(tags).to.have.a.lengthOf(7);
|
|
expect(tags[0].name).to.eql(t('defaultTag1'));
|
|
expect(tags[1].name).to.eql(t('defaultTag2'));
|
|
expect(tags[2].name).to.eql(t('defaultTag3'));
|
|
expect(tags[3].name).to.eql(t('defaultTag4'));
|
|
expect(tags[4].name).to.eql(t('defaultTag5'));
|
|
expect(tags[5].name).to.eql(t('defaultTag6'));
|
|
expect(tags[6].name).to.eql(t('defaultTag7'));
|
|
});
|
|
});
|
|
|
|
context('does not provide default tags and tasks', async () => {
|
|
it('for Android', async () => {
|
|
api = requester(
|
|
null,
|
|
{'x-client': 'habitica-android'},
|
|
);
|
|
let username = generateRandomUserName();
|
|
let email = `${username}@example.com`;
|
|
let password = 'password';
|
|
|
|
let user = await api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
let requests = new ApiUser(user);
|
|
|
|
let habits = await requests.get('/tasks/user?type=habits');
|
|
let dailys = await requests.get('/tasks/user?type=dailys');
|
|
let todos = await requests.get('/tasks/user?type=todos');
|
|
let rewards = await requests.get('/tasks/user?type=rewards');
|
|
let tags = await requests.get('/tags');
|
|
|
|
expect(habits).to.have.a.lengthOf(0);
|
|
expect(dailys).to.have.a.lengthOf(0);
|
|
expect(todos).to.have.a.lengthOf(0);
|
|
expect(rewards).to.have.a.lengthOf(0);
|
|
expect(tags).to.have.a.lengthOf(0);
|
|
});
|
|
|
|
it('for iOS', async () => {
|
|
api = requester(
|
|
null,
|
|
{'x-client': 'habitica-ios'},
|
|
);
|
|
let username = generateRandomUserName();
|
|
let email = `${username}@example.com`;
|
|
let password = 'password';
|
|
|
|
let user = await api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
let requests = new ApiUser(user);
|
|
|
|
let habits = await requests.get('/tasks/user?type=habits');
|
|
let dailys = await requests.get('/tasks/user?type=dailys');
|
|
let todos = await requests.get('/tasks/user?type=todos');
|
|
let rewards = await requests.get('/tasks/user?type=rewards');
|
|
let tags = await requests.get('/tags');
|
|
|
|
expect(habits).to.have.a.lengthOf(0);
|
|
expect(dailys).to.have.a.lengthOf(0);
|
|
expect(todos).to.have.a.lengthOf(0);
|
|
expect(rewards).to.have.a.lengthOf(0);
|
|
expect(tags).to.have.a.lengthOf(0);
|
|
});
|
|
});
|
|
|
|
it('enrolls new users in an A/B test', async () => {
|
|
let username = generateRandomUserName();
|
|
let email = `${username}@example.com`;
|
|
let password = 'password';
|
|
|
|
let user = await api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
await expect(getProperty('users', user._id, '_ABtests')).to.eventually.be.a('object');
|
|
});
|
|
|
|
it('includes items awarded by default when creating a new user', async () => {
|
|
let username = generateRandomUserName();
|
|
let email = `${username}@example.com`;
|
|
let password = 'password';
|
|
|
|
let user = await api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
expect(user.items.quests.dustbunnies).to.equal(1);
|
|
expect(user.purchased.background.violet).to.be.ok;
|
|
expect(user.preferences.background).to.equal('violet');
|
|
});
|
|
|
|
it('requires password and confirmPassword to match', async () => {
|
|
let username = generateRandomUserName();
|
|
let email = `${username}@example.com`;
|
|
let password = 'password';
|
|
let confirmPassword = 'not password';
|
|
|
|
await expect(api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword,
|
|
})).to.eventually.be.rejected.and.eql({
|
|
code: 400,
|
|
error: 'BadRequest',
|
|
message: t('invalidReqParams'),
|
|
});
|
|
});
|
|
|
|
it('requires a username', async () => {
|
|
let email = `${generateRandomUserName()}@example.com`;
|
|
let password = 'password';
|
|
let confirmPassword = 'password';
|
|
|
|
await expect(api.post('/user/auth/local/register', {
|
|
email,
|
|
password,
|
|
confirmPassword,
|
|
})).to.eventually.be.rejected.and.eql({
|
|
code: 400,
|
|
error: 'BadRequest',
|
|
message: t('invalidReqParams'),
|
|
});
|
|
});
|
|
|
|
it('requires an email', async () => {
|
|
let username = generateRandomUserName();
|
|
let password = 'password';
|
|
|
|
await expect(api.post('/user/auth/local/register', {
|
|
username,
|
|
password,
|
|
confirmPassword: password,
|
|
})).to.eventually.be.rejected.and.eql({
|
|
code: 400,
|
|
error: 'BadRequest',
|
|
message: t('invalidReqParams'),
|
|
});
|
|
});
|
|
|
|
it('requires a valid email', async () => {
|
|
let username = generateRandomUserName();
|
|
let email = 'notanemail@sdf';
|
|
let password = 'password';
|
|
|
|
await expect(api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
})).to.eventually.be.rejected.and.eql({
|
|
code: 400,
|
|
error: 'BadRequest',
|
|
message: t('invalidReqParams'),
|
|
});
|
|
});
|
|
|
|
it('fails on a habitica.com email', async () => {
|
|
let username = generateRandomUserName();
|
|
let email = `${username}@habitica.com`;
|
|
let password = 'password';
|
|
|
|
await expect(api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
})).to.eventually.be.rejected.and.eql({
|
|
code: 400,
|
|
error: 'BadRequest',
|
|
message: 'User validation failed',
|
|
});
|
|
});
|
|
|
|
it('fails on a habitrpg.com email', async () => {
|
|
let username = generateRandomUserName();
|
|
let email = `${username}@habitrpg.com`;
|
|
let password = 'password';
|
|
|
|
await expect(api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
})).to.eventually.be.rejected.and.eql({
|
|
code: 400,
|
|
error: 'BadRequest',
|
|
message: 'User validation failed',
|
|
});
|
|
});
|
|
|
|
it('requires a password', async () => {
|
|
let username = generateRandomUserName();
|
|
let email = `${username}@example.com`;
|
|
let confirmPassword = 'password';
|
|
|
|
await expect(api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
confirmPassword,
|
|
})).to.eventually.be.rejected.and.eql({
|
|
code: 400,
|
|
error: 'BadRequest',
|
|
message: t('invalidReqParams'),
|
|
});
|
|
});
|
|
});
|
|
|
|
context('attach to facebook user', () => {
|
|
let user;
|
|
let email = 'some@email.net';
|
|
let username = 'some-username';
|
|
let password = 'some-password';
|
|
beforeEach(async () => {
|
|
user = await generateUser();
|
|
});
|
|
it('checks onlySocialAttachLocal', async () => {
|
|
await expect(user.post('/user/auth/local/register', {
|
|
email,
|
|
username,
|
|
password,
|
|
confirmPassword: password,
|
|
})).to.eventually.be.rejected.and.eql({
|
|
code: 401,
|
|
error: 'NotAuthorized',
|
|
message: t('onlySocialAttachLocal'),
|
|
});
|
|
});
|
|
it('succeeds', async () => {
|
|
await user.update({ 'auth.facebook.id': 'some-fb-id', 'auth.local': { ok: true } });
|
|
await user.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
await user.sync();
|
|
expect(user.auth.local.username).to.eql(username);
|
|
expect(user.auth.local.email).to.eql(email);
|
|
});
|
|
});
|
|
|
|
context('login is already taken', () => {
|
|
let username, email, api;
|
|
|
|
beforeEach(async () => {
|
|
api = requester();
|
|
username = generateRandomUserName();
|
|
email = `${username}@example.com`;
|
|
|
|
return generateUser({
|
|
'auth.local.username': username,
|
|
'auth.local.lowerCaseUsername': username,
|
|
'auth.local.email': email,
|
|
});
|
|
});
|
|
|
|
it('rejects if username is already taken', async () => {
|
|
let uniqueEmail = `${generateRandomUserName()}@exampe.com`;
|
|
let password = 'password';
|
|
|
|
await expect(api.post('/user/auth/local/register', {
|
|
username,
|
|
email: uniqueEmail,
|
|
password,
|
|
confirmPassword: password,
|
|
})).to.eventually.be.rejected.and.eql({
|
|
code: 401,
|
|
error: 'NotAuthorized',
|
|
message: t('usernameTaken'),
|
|
});
|
|
});
|
|
|
|
it('rejects if email is already taken', async () => {
|
|
let uniqueUsername = generateRandomUserName();
|
|
let password = 'password';
|
|
|
|
await expect(api.post('/user/auth/local/register', {
|
|
username: uniqueUsername,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
})).to.eventually.be.rejected.and.eql({
|
|
code: 401,
|
|
error: 'NotAuthorized',
|
|
message: t('emailTaken'),
|
|
});
|
|
});
|
|
});
|
|
|
|
context('req.query.groupInvite', () => {
|
|
let api, username, email, password;
|
|
|
|
beforeEach(() => {
|
|
api = requester();
|
|
username = generateRandomUserName();
|
|
email = `${username}@example.com`;
|
|
password = 'password';
|
|
});
|
|
|
|
it('does not crash the signup process when it\'s invalid', async () => {
|
|
let user = await api.post('/user/auth/local/register?groupInvite=aaaaInvalid', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
expect(user._id).to.be.a('string');
|
|
});
|
|
|
|
it('supports invite using req.query.groupInvite', async () => {
|
|
let { group, groupLeader } = await createAndPopulateGroup({
|
|
groupDetails: { type: 'party', privacy: 'private' },
|
|
});
|
|
|
|
let invite = encrypt(JSON.stringify({
|
|
id: group._id,
|
|
inviter: groupLeader._id,
|
|
sentAt: Date.now(), // so we can let it expire
|
|
}));
|
|
|
|
let user = await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
expect(user.invitations.parties[0].id).to.eql(group._id);
|
|
expect(user.invitations.parties[0].name).to.eql(group.name);
|
|
expect(user.invitations.parties[0].inviter).to.eql(groupLeader._id);
|
|
});
|
|
|
|
it('awards achievement to inviter', async () => {
|
|
let { group, groupLeader } = await createAndPopulateGroup({
|
|
groupDetails: { type: 'party', privacy: 'private' },
|
|
});
|
|
|
|
let invite = encrypt(JSON.stringify({
|
|
id: group._id,
|
|
inviter: groupLeader._id,
|
|
sentAt: Date.now(),
|
|
}));
|
|
|
|
await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
await groupLeader.sync();
|
|
expect(groupLeader.achievements.invitedFriend).to.be.true;
|
|
});
|
|
|
|
it('user not added to a party on expired invite', async () => {
|
|
let { group, groupLeader } = await createAndPopulateGroup({
|
|
groupDetails: { type: 'party', privacy: 'private' },
|
|
});
|
|
|
|
let invite = encrypt(JSON.stringify({
|
|
id: group._id,
|
|
inviter: groupLeader._id,
|
|
sentAt: Date.now() - 6.912e8, // 8 days old
|
|
}));
|
|
|
|
let user = await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
expect(user.invitations.party).to.eql({});
|
|
});
|
|
|
|
it('adds a user to a guild on an invite of type other than party', async () => {
|
|
let { group, groupLeader } = await createAndPopulateGroup({
|
|
groupDetails: { type: 'guild', privacy: 'private' },
|
|
});
|
|
|
|
let invite = encrypt(JSON.stringify({
|
|
id: group._id,
|
|
inviter: groupLeader._id,
|
|
sentAt: Date.now(),
|
|
}));
|
|
|
|
let user = await api.post(`/user/auth/local/register?groupInvite=${invite}`, {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
expect(user.invitations.guilds[0]).to.eql({
|
|
id: group._id,
|
|
name: group.name,
|
|
inviter: groupLeader._id,
|
|
});
|
|
});
|
|
});
|
|
|
|
context('successful login via api', () => {
|
|
let api, username, email, password;
|
|
|
|
beforeEach(() => {
|
|
api = requester();
|
|
username = generateRandomUserName();
|
|
email = `${username}@example.com`;
|
|
password = 'password';
|
|
});
|
|
|
|
it('sets all site tour values to -2 (already seen)', async () => {
|
|
let user = await api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
expect(user.flags.tour).to.not.be.empty;
|
|
|
|
each(user.flags.tour, (value) => {
|
|
expect(value).to.eql(-2);
|
|
});
|
|
});
|
|
|
|
it('populates user with default todos, not no other task types', async () => {
|
|
let user = await api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
expect(user.tasksOrder.todos).to.not.be.empty;
|
|
expect(user.tasksOrder.dailys).to.be.empty;
|
|
expect(user.tasksOrder.habits).to.be.empty;
|
|
expect(user.tasksOrder.rewards).to.be.empty;
|
|
});
|
|
|
|
it('populates user with default tags', async () => {
|
|
let user = await api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
expect(user.tags).to.not.be.empty;
|
|
});
|
|
});
|
|
|
|
context('successful login with habitica-web header', () => {
|
|
let api, username, email, password;
|
|
|
|
beforeEach(() => {
|
|
api = requester({}, {'x-client': 'habitica-web'});
|
|
username = generateRandomUserName();
|
|
email = `${username}@example.com`;
|
|
password = 'password';
|
|
});
|
|
|
|
it('sets all common tutorial flags to true', async () => {
|
|
let user = await api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
expect(user.flags.tour).to.not.be.empty;
|
|
|
|
each(user.flags.tutorial.common, (value) => {
|
|
expect(value).to.eql(true);
|
|
});
|
|
});
|
|
|
|
it('populates user with default todos, habits, and rewards', async () => {
|
|
let user = await api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
expect(user.tasksOrder.todos).to.be.empty;
|
|
expect(user.tasksOrder.dailys).to.be.empty;
|
|
expect(user.tasksOrder.habits).to.be.empty;
|
|
expect(user.tasksOrder.rewards).to.be.empty;
|
|
});
|
|
|
|
it('populates user with default tags', async () => {
|
|
let user = await api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
expect(user.tags).to.not.be.empty;
|
|
});
|
|
|
|
it('adds the correct tags to the correct tasks', async () => {
|
|
let user = await api.post('/user/auth/local/register', {
|
|
username,
|
|
email,
|
|
password,
|
|
confirmPassword: password,
|
|
});
|
|
|
|
let requests = new ApiUser(user);
|
|
|
|
let habits = await requests.get('/tasks/user?type=habits');
|
|
let todos = await requests.get('/tasks/user?type=todos');
|
|
|
|
expect(habits).to.have.a.lengthOf(0);
|
|
expect(todos).to.have.a.lengthOf(0);
|
|
});
|
|
});
|
|
});
|