mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 07:07:35 +01:00
v3: several fixes to class system, move /logout outside of api
This commit is contained in:
@@ -169,5 +169,6 @@
|
||||
"regIdRequired": "RegId is required",
|
||||
"pushDeviceAdded": "Push device added successfully",
|
||||
"pushDeviceAlreadyAdded": "The user already has the push device",
|
||||
"resetComplete": "Reset has completed"
|
||||
"resetComplete": "Reset completed",
|
||||
"lvl10ChangeClass": "To change class you must be at least level 10."
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@ import {
|
||||
module.exports = function changeClass (user, req = {}, analytics) {
|
||||
let klass = _.get(req, 'query.class');
|
||||
|
||||
if (klass === 'warrior' || klass === 'rogue' || klass === 'wizard' || klass === 'healer') {
|
||||
// user.flags.classSelected is set to false after the user paid the 3 gems
|
||||
if (user.stats.lvl < 10) {
|
||||
throw new NotAuthorized(i18n.t('lvl10ChangeClass', req.language));
|
||||
} else if (!user.flags.classSelected && (klass === 'warrior' || klass === 'rogue' || klass === 'wizard' || klass === 'healer')) {
|
||||
user.stats.class = klass;
|
||||
user.flags.classSelected = true;
|
||||
|
||||
|
||||
@@ -162,7 +162,6 @@
|
||||
"name": "habitica",
|
||||
"title": "Habitica",
|
||||
"version": "3.0.0",
|
||||
"url": "https://habitica-v3.herokuapp.com",
|
||||
"sampleUrl": "https://habitica-v3.herokuapp.com"
|
||||
"url": "https://habitica-v3.herokuapp.com/api-v3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ describe('POST /user/change-class', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
user = await generateUser({
|
||||
'flags.classSelected': false,
|
||||
'stats.lvl': 10,
|
||||
});
|
||||
});
|
||||
|
||||
// More tests in common code unit tests
|
||||
|
||||
@@ -57,6 +57,7 @@ describe('PUT /user', () => {
|
||||
'flags unless whitelisted': {'flags.dropsEnabled': true},
|
||||
webhooks: {'preferences.webhooks': [1, 2, 3]},
|
||||
sleep: {'preferences.sleep': true},
|
||||
'disable classes': {'preferences.disableClasses': true},
|
||||
};
|
||||
|
||||
each(protectedOperations, (data, testName) => {
|
||||
|
||||
@@ -12,9 +12,36 @@ describe('shared.ops.changeClass', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
user.stats.lvl = 11;
|
||||
user.stats.flagSelected = false;
|
||||
});
|
||||
|
||||
it('user is not level 10', (done) => {
|
||||
user.stats.lvl = 9;
|
||||
try {
|
||||
changeClass(user, {query: {class: 'rogue'}});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('lvl10ChangeClass'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
context('req.query.class is a valid class', () => {
|
||||
it('errors if user.stats.flagSelected is true and user.balance < 0.75', (done) => {
|
||||
user.flags.classSelected = true;
|
||||
user.preferences.disableClasses = false;
|
||||
user.balance = 0;
|
||||
|
||||
try {
|
||||
changeClass(user, {query: {class: 'rogue'}});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(NotAuthorized);
|
||||
expect(err.message).to.equal(i18n.t('notEnoughGems'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('changes class', () => {
|
||||
user.stats.class = 'healer';
|
||||
user.items.gear.owned.armor_rogue_1 = true; // eslint-disable-line camelcase
|
||||
@@ -41,13 +68,12 @@ describe('shared.ops.changeClass', () => {
|
||||
});
|
||||
});
|
||||
|
||||
context('req.query.class is missing', () => {
|
||||
context('req.query.class is missing or user.stats.flagSelected is true', () => {
|
||||
it('has user.preferences.disableClasses === true', () => {
|
||||
user.balance = 1;
|
||||
user.preferences.disableClasses = true;
|
||||
user.preferences.autoAllocate = true;
|
||||
user.stats.points = 45;
|
||||
user.stats.lvl = 3;
|
||||
user.stats.str = 1;
|
||||
user.stats.con = 2;
|
||||
user.stats.per = 3;
|
||||
@@ -71,7 +97,7 @@ describe('shared.ops.changeClass', () => {
|
||||
expect(user.stats.con).to.equal(0);
|
||||
expect(user.stats.per).to.equal(0);
|
||||
expect(user.stats.int).to.equal(0);
|
||||
expect(user.stats.points).to.equal(3);
|
||||
expect(user.stats.points).to.equal(11);
|
||||
expect(user.flags.classSelected).to.equal(false);
|
||||
});
|
||||
|
||||
@@ -90,7 +116,6 @@ describe('shared.ops.changeClass', () => {
|
||||
it('and at least 3 gems', () => {
|
||||
user.balance = 1;
|
||||
user.stats.points = 45;
|
||||
user.stats.lvl = 3;
|
||||
user.stats.str = 1;
|
||||
user.stats.con = 2;
|
||||
user.stats.per = 3;
|
||||
@@ -112,7 +137,7 @@ describe('shared.ops.changeClass', () => {
|
||||
expect(user.stats.con).to.equal(0);
|
||||
expect(user.stats.per).to.equal(0);
|
||||
expect(user.stats.int).to.equal(0);
|
||||
expect(user.stats.points).to.equal(3);
|
||||
expect(user.stats.points).to.equal(11);
|
||||
expect(user.flags.classSelected).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,8 +4,7 @@ import passport from 'passport';
|
||||
import nconf from 'nconf';
|
||||
import {
|
||||
authWithHeaders,
|
||||
authWithSession,
|
||||
} from '../../middlewares/api-v3/auth';
|
||||
} from '../../middlewares/api-v3/auth';
|
||||
import {
|
||||
NotAuthorized,
|
||||
BadRequest,
|
||||
@@ -52,17 +51,18 @@ async function _handleGroupInvitation (user, invite) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /api/v3/user/auth/local/register Register a new user with email, username and password or attach local auth to a social user
|
||||
* @api {post} /api/v3/user/auth/local/register Register
|
||||
* @apiDescription Register a new user with email, username and password or attach local auth to a social user
|
||||
* @apiVersion 3.0.0
|
||||
* @apiName UserRegisterLocal
|
||||
* @apiGroup User
|
||||
*
|
||||
* @apiParam {String} username Username of the new user
|
||||
* @apiParam {String} email Email address of the new user
|
||||
* @apiParam {String} password Password for the new user account
|
||||
* @apiParam {String} confirmPassword Password confirmation
|
||||
* @apiParam {String} username Body parameter - Username of the new user
|
||||
* @apiParam {String} email Body parameter - Email address of the new user
|
||||
* @apiParam {String} password Body parameter - Password for the new user
|
||||
* @apiParam {String} confirmPassword Body parameter - Password confirmation
|
||||
*
|
||||
* @apiSuccess {Object} user The user object, if we just attached local auth to a social user then only user.auth.local
|
||||
* @apiSuccess {Object} user The user object, if local auth was just attached to a social user then only user.auth.local
|
||||
*/
|
||||
api.registerLocal = {
|
||||
method: 'POST',
|
||||
@@ -165,13 +165,14 @@ function _loginRes (user, req, res) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /api/v3/user/auth/local/login Login an user with email / username and password
|
||||
* @api {post} /api/v3/user/auth/local/login Login
|
||||
* @apiDescription Login an user with email / username and password
|
||||
* @apiVersion 3.0.0
|
||||
* @apiName UserLoginLocal
|
||||
* @apiGroup User
|
||||
*
|
||||
* @apiParam {String} username Username or email of the user
|
||||
* @apiParam {String} password The user's password
|
||||
* @apiParam {String} username Body parameter - Username or email of the user
|
||||
* @apiParam {String} password Body parameter - The user's password
|
||||
*
|
||||
* @apiSuccess {String} _id The user's unique identifier
|
||||
* @apiSuccess {String} apiToken The user's api token that must be used to authenticate requests.
|
||||
@@ -227,7 +228,7 @@ function _passportFbProfile (accessToken) {
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
// Called as a callback by Facebook (or other social providers)
|
||||
// Called as a callback by Facebook (or other social providers). Internal route
|
||||
api.loginSocial = {
|
||||
method: 'POST',
|
||||
url: '/user/auth/social', // this isn't the most appropriate url but must be the same as v2
|
||||
@@ -280,13 +281,16 @@ api.loginSocial = {
|
||||
};
|
||||
|
||||
/**
|
||||
* @api {put} /api/v3/user/auth/update-username
|
||||
* @api {put} /api/v3/user/auth/update-username Update username
|
||||
* @apiDescription Update the username of a local user
|
||||
* @apiVersion 3.0.0
|
||||
* @apiName updateUsername
|
||||
* @apiName UpdateUsername
|
||||
* @apiGroup User
|
||||
* @apiParam {string} password The password
|
||||
* @apiParam {string} username New username
|
||||
* @apiSuccess {Object} The new username
|
||||
*
|
||||
* @apiParam {string} password Body parameter - The current user password
|
||||
* @apiParam {string} username Body parameter - The new username
|
||||
|
||||
* @apiSuccess {String} username The new username
|
||||
**/
|
||||
api.updateUsername = {
|
||||
method: 'PUT',
|
||||
@@ -326,13 +330,16 @@ api.updateUsername = {
|
||||
|
||||
/**
|
||||
* @api {put} /api/v3/user/auth/update-password
|
||||
* @apiDescription Update the password of a local user
|
||||
* @apiVersion 3.0.0
|
||||
* @apiName updatePassword
|
||||
* @apiName UpdatePassword
|
||||
* @apiGroup User
|
||||
* @apiParam {string} password The old password
|
||||
* @apiParam {string} newPassword The new password
|
||||
* @apiParam {string} confirmPassword Password confirmation
|
||||
* @apiSuccess {Object} The success message
|
||||
*
|
||||
* @apiParam {string} password Body parameter - The old password
|
||||
* @apiParam {string} newPassword Body parameter - The new password
|
||||
* @apiParam {string} confirmPassword Body parameter - New password confirmation
|
||||
*
|
||||
* @apiSuccess {Object} emoty An empty object
|
||||
**/
|
||||
api.updatePassword = {
|
||||
method: 'PUT',
|
||||
@@ -364,12 +371,15 @@ api.updatePassword = {
|
||||
};
|
||||
|
||||
/**
|
||||
* @api {post} /api/v3/user/reset-password
|
||||
* @api {post} /api/v3/user/reset-password Reser password
|
||||
* @apiDescription Reset the user password
|
||||
* @apiVersion 3.0.0
|
||||
* @apiName resetPassword
|
||||
* @apiName ResetPassword
|
||||
* @apiGroup User
|
||||
* @apiParam {string} email email
|
||||
* @apiSuccess {Object} The success message
|
||||
*
|
||||
* @apiParam {string} email Body parameter - The email address of the user
|
||||
*
|
||||
* @apiSuccess {string} message The localized success message
|
||||
**/
|
||||
api.resetPassword = {
|
||||
method: 'POST',
|
||||
@@ -414,15 +424,16 @@ api.resetPassword = {
|
||||
};
|
||||
|
||||
/**
|
||||
* @api {put} /api/v3/user/auth/update-email
|
||||
* @api {put} /api/v3/user/auth/update-email Update email
|
||||
* @apiDescription Che the user email
|
||||
* @apiVersion 3.0.0
|
||||
* @apiName UpdateEmail
|
||||
* @apiGroup User
|
||||
*
|
||||
* @apiParam {string} newEmail The new email address.
|
||||
* @apiParam {string} password The user password.
|
||||
* @apiParam {string} Body parameter - newEmail The new email address.
|
||||
* @apiParam {string} Body parameter - password The user password.
|
||||
*
|
||||
* @apiSuccess {Object} An object containing the new email address
|
||||
* @apiSuccess {string} email The updated email address
|
||||
*/
|
||||
api.updateEmail = {
|
||||
method: 'PUT',
|
||||
@@ -450,7 +461,7 @@ api.updateEmail = {
|
||||
|
||||
const firebaseTokenGenerator = new FirebaseTokenGenerator(nconf.get('FIREBASE:SECRET'));
|
||||
|
||||
// Internal route TODO expose?
|
||||
// Internal route
|
||||
api.getFirebaseToken = {
|
||||
method: 'POST',
|
||||
url: '/user/auth/firebase',
|
||||
@@ -471,12 +482,13 @@ api.getFirebaseToken = {
|
||||
};
|
||||
|
||||
/**
|
||||
* @api {delete} /api/v3/user/auth/social/:network Delete a social authentication method (only facebook supported)
|
||||
* @api {delete} /api/v3/user/auth/social/:network Delete social authentication method
|
||||
* @apiDescription Remove a social authentication method (only facebook supported) from a user profile. The user must have local authentication enabled
|
||||
* @apiVersion 3.0.0
|
||||
* @apiName UserDeleteSocial
|
||||
* @apiGroup User
|
||||
*
|
||||
* @apiSuccess {Object} response Empty object
|
||||
* @apiSuccess {Object} empty Empty object
|
||||
*/
|
||||
api.deleteSocial = {
|
||||
method: 'DELETE',
|
||||
@@ -495,16 +507,4 @@ api.deleteSocial = {
|
||||
},
|
||||
};
|
||||
|
||||
// Internal route
|
||||
api.logout = {
|
||||
method: 'GET',
|
||||
url: '/user/auth/logout', // TODO this is under /api/v3 route, should be accessible through habitica.com/logout
|
||||
middlewares: [authWithSession],
|
||||
async handler (req, res) {
|
||||
req.logout(); // passportjs method
|
||||
req.session = null;
|
||||
res.redirect('/');
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = api;
|
||||
|
||||
@@ -109,6 +109,7 @@ let acceptablePUTPaths = _.reduce(require('./../../models/user').schema.paths, (
|
||||
let restrictedPUTSubPaths = [
|
||||
'stats.class',
|
||||
|
||||
'preferences.disableClasses',
|
||||
'preferences.sleep',
|
||||
'preferences.webhooks',
|
||||
];
|
||||
|
||||
21
website/src/controllers/top-level/auth.js
Normal file
21
website/src/controllers/top-level/auth.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import {
|
||||
authWithSession,
|
||||
} from '../../middlewares/api-v3/auth';
|
||||
|
||||
let api = {};
|
||||
|
||||
// Internal authentication routes
|
||||
|
||||
// Logout the user from the website.
|
||||
api.logout = {
|
||||
method: 'GET',
|
||||
url: '/logout',
|
||||
middlewares: [authWithSession],
|
||||
async handler (req, res) {
|
||||
req.logout(); // passportjs method
|
||||
req.session = null;
|
||||
res.redirect('/');
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = api;
|
||||
Reference in New Issue
Block a user