Skip to content

Commit 87a551d

Browse files
authored
Merge pull request #187 from hackmcgill/feature/change-password
Feature/change password
2 parents c7c3f38 + 315201e commit 87a551d

11 files changed

Lines changed: 266 additions & 10 deletions

File tree

constants/role.constant.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const accountRole = {
1111
"routes": [
1212
Constants.Routes.authRoutes.login,
1313
Constants.Routes.authRoutes.logout,
14+
Constants.Routes.authRoutes.changePassword,
1415
Constants.Routes.authRoutes.getSelfRoleBindindings,
1516
Constants.Routes.accountRoutes.getSelf,
1617
Constants.Routes.accountRoutes.getSelfById,

constants/routes.constant.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ const authRoutes = {
2121
"getAnyRoleBindings": {
2222
requestType: Constants.REQUEST_TYPES.GET,
2323
uri: "/api/auth/rolebindings/" + Constants.ROLE_CATEGORIES.ALL
24-
}
24+
},
25+
"changePassword": {
26+
requestType: Constants.REQUEST_TYPES.PATCH,
27+
uri: "/api/auth/password/change"
28+
},
2529
};
2630

2731
const accountRoutes = {

docs/api/api_data.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,70 @@ define({
447447
"url": "https://api.mchacks.ca/api/account/:id"
448448
}]
449449
},
450+
{
451+
"type": "patch",
452+
"url": "/auth/password/change",
453+
"title": "change password for logged in user",
454+
"name": "changePassword",
455+
"group": "Authentication",
456+
"version": "0.0.8",
457+
"parameter": {
458+
"fields": {
459+
"Parameter": [{
460+
"group": "Parameter",
461+
"type": "String",
462+
"optional": false,
463+
"field": "oldPassword",
464+
"description": "<p>The current password of the user</p>"
465+
},
466+
{
467+
"group": "Parameter",
468+
"type": "String",
469+
"optional": false,
470+
"field": "newPassword",
471+
"description": "<p>The new password of the user</p>"
472+
}
473+
]
474+
},
475+
"examples": [{
476+
"title": "Request-Example:",
477+
"content": "{ \n \"oldPassword\": \"password12345\",\n \"newPassword\": \"password123456\"\n}",
478+
"type": "json"
479+
}]
480+
},
481+
"success": {
482+
"fields": {
483+
"Success 200": [{
484+
"group": "Success 200",
485+
"type": "string",
486+
"optional": false,
487+
"field": "message",
488+
"description": "<p>Success message</p>"
489+
},
490+
{
491+
"group": "Success 200",
492+
"type": "object",
493+
"optional": false,
494+
"field": "data",
495+
"description": "<p>empty</p>"
496+
}
497+
]
498+
},
499+
"examples": [{
500+
"title": "Success-Response: ",
501+
"content": "{\"message\": \"Successfully reset password\", \"data\": {}}",
502+
"type": "json"
503+
}]
504+
},
505+
"permission": [{
506+
"name": ": Must be logged in"
507+
}],
508+
"filename": "routes/api/auth.js",
509+
"groupTitle": "Authentication",
510+
"sampleRequest": [{
511+
"url": "https://api.mchacks.ca/api/auth/password/change"
512+
}]
513+
},
450514
{
451515
"type": "post",
452516
"url": "/auth/confirm/:token",

docs/api/api_data.json

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,70 @@
446446
"url": "https://api.mchacks.ca/api/account/:id"
447447
}]
448448
},
449+
{
450+
"type": "patch",
451+
"url": "/auth/password/change",
452+
"title": "change password for logged in user",
453+
"name": "changePassword",
454+
"group": "Authentication",
455+
"version": "0.0.8",
456+
"parameter": {
457+
"fields": {
458+
"Parameter": [{
459+
"group": "Parameter",
460+
"type": "String",
461+
"optional": false,
462+
"field": "oldPassword",
463+
"description": "<p>The current password of the user</p>"
464+
},
465+
{
466+
"group": "Parameter",
467+
"type": "String",
468+
"optional": false,
469+
"field": "newPassword",
470+
"description": "<p>The new password of the user</p>"
471+
}
472+
]
473+
},
474+
"examples": [{
475+
"title": "Request-Example:",
476+
"content": "{ \n \"oldPassword\": \"password12345\",\n \"newPassword\": \"password123456\"\n}",
477+
"type": "json"
478+
}]
479+
},
480+
"success": {
481+
"fields": {
482+
"Success 200": [{
483+
"group": "Success 200",
484+
"type": "string",
485+
"optional": false,
486+
"field": "message",
487+
"description": "<p>Success message</p>"
488+
},
489+
{
490+
"group": "Success 200",
491+
"type": "object",
492+
"optional": false,
493+
"field": "data",
494+
"description": "<p>empty</p>"
495+
}
496+
]
497+
},
498+
"examples": [{
499+
"title": "Success-Response: ",
500+
"content": "{\"message\": \"Successfully reset password\", \"data\": {}}",
501+
"type": "json"
502+
}]
503+
},
504+
"permission": [{
505+
"name": ": Must be logged in"
506+
}],
507+
"filename": "routes/api/auth.js",
508+
"groupTitle": "Authentication",
509+
"sampleRequest": [{
510+
"url": "https://api.mchacks.ca/api/auth/password/change"
511+
}]
512+
},
449513
{
450514
"type": "post",
451515
"url": "/auth/confirm/:token",

docs/api/api_project.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ define({
99
"apidoc": "0.3.0",
1010
"generator": {
1111
"name": "apidoc",
12-
"time": "2018-12-01T22:43:17.010Z",
12+
"time": "2018-12-03T02:22:00.260Z",
1313
"url": "http://apidocjs.com",
1414
"version": "0.17.6"
1515
}

docs/api/api_project.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"apidoc": "0.3.0",
1010
"generator": {
1111
"name": "apidoc",
12-
"time": "2018-12-01T22:43:17.010Z",
12+
"time": "2018-12-03T02:22:00.260Z",
1313
"url": "http://apidocjs.com",
1414
"version": "0.17.6"
1515
}

middlewares/auth.middleware.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,27 @@ async function retrieveRoleBindings(req, res, next) {
126126
return next();
127127
}
128128

129+
/**
130+
* Checks that the oldPassword is the current password for the logged in user. If the password is correct,
131+
* then updates the password to the string in newPassword.
132+
* @param {{user: {email: string}, body: {oldPassword: string, newPassword: string}} req
133+
* @param {*} res
134+
* @param {*} next
135+
*/
136+
async function changePassword(req, res, next) {
137+
const acc = await Services.Account.getAccountIfValid(req.user.email, req.body.oldPassword);
138+
// user's old password is correct
139+
if (!!acc) {
140+
req.body.account = await Services.Account.updatePassword(req.user.id, req.body.newPassword);
141+
return next();
142+
} else {
143+
return next({
144+
status: 401,
145+
message: Constants.Error.AUTH_401_MESSAGE,
146+
});
147+
}
148+
}
149+
129150
/**
130151
* Middleware that sends an email to reset the password for the inputted email address.
131152
* @param {{body: {email:String}}} req the request object
@@ -432,5 +453,6 @@ module.exports = {
432453
addCreationRoleBindings: Middleware.Util.asyncMiddleware(addCreationRoleBindings),
433454
resendConfirmAccountEmail: Middleware.Util.asyncMiddleware(resendConfirmAccountEmail),
434455
retrieveRoleBindings: Middleware.Util.asyncMiddleware(retrieveRoleBindings),
435-
retrieveRoles: Middleware.Util.asyncMiddleware(retrieveRoles)
456+
retrieveRoles: Middleware.Util.asyncMiddleware(retrieveRoles),
457+
changePassword: Middleware.Util.asyncMiddleware(changePassword),
436458
};

middlewares/validators/auth.validator.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ module.exports = {
55
ForgotPasswordValidator: [
66
VALIDATOR.emailValidator("body", "email", false)
77
],
8+
ChangePasswordValidator: [
9+
VALIDATOR.passwordValidator("body", "oldPassword", false),
10+
VALIDATOR.passwordValidator("body", "newPassword", false)
11+
],
812
ResetPasswordValidator: [
9-
VALIDATOR.passwordValidator("body","password", false),
13+
VALIDATOR.passwordValidator("body", "password", false),
1014
//The json web token is provided via the header with param "Authentication".
11-
VALIDATOR.jwtValidator("header","X-Reset-Token", process.env.JWT_RESET_PWD_SECRET, false)
15+
VALIDATOR.jwtValidator("header", "X-Reset-Token", process.env.JWT_RESET_PWD_SECRET, false)
1216
],
1317
accountConfirmationValidator: [
14-
VALIDATOR.jwtValidator("param","token", process.env.JWT_CONFIRM_ACC_SECRET, false)
18+
VALIDATOR.jwtValidator("param", "token", process.env.JWT_CONFIRM_ACC_SECRET, false)
1519
]
16-
};
20+
};

routes/api/auth.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,38 @@ module.exports = {
9797
Controllers.Auth.sentResetEmail
9898
);
9999

100+
/**
101+
* @api {patch} /auth/password/change change password for logged in user
102+
* @apiName changePassword
103+
* @apiGroup Authentication
104+
* @apiVersion 0.0.8
105+
*
106+
* @apiParam {String} oldPassword The current password of the user
107+
* @apiParam {String} newPassword The new password of the user
108+
*
109+
* @apiParamExample {json} Request-Example:
110+
* {
111+
* "oldPassword": "password12345",
112+
* "newPassword": "password123456"
113+
* }
114+
*
115+
* @apiSuccess {string} message Success message
116+
* @apiSuccess {object} data empty
117+
* @apiSuccessExample {json} Success-Response:
118+
* {"message": "Successfully reset password", "data": {}}
119+
*
120+
* @apiPermission: Must be logged in
121+
*/
122+
authRouter.route("/password/change").patch(
123+
Middleware.Auth.ensureAuthenticated(),
124+
Middleware.Auth.ensureAuthorized(),
125+
Middleware.Validator.Auth.ChangePasswordValidator,
126+
Middleware.parseBody.middleware,
127+
128+
Middleware.Auth.changePassword,
129+
Controllers.Auth.resetPassword,
130+
);
131+
100132
//untested
101133
/**
102134
* @api {post} /auth/password/reset reset password

tests/account.test.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,71 @@ describe("POST reset password", function () {
359359
});
360360
});
361361

362+
describe("PATCH change password for logged in user", function () {
363+
const successChangePassword = {
364+
"oldPassword": Admin1.password,
365+
"newPassword": "password12345"
366+
};
367+
const failChangePassword = {
368+
"oldPassword": "WrongPassword",
369+
"newPassword": "password12345"
370+
};
371+
// fail on authentication
372+
it("should fail to change the user's password because they are not logged in", function (done) {
373+
chai.request(server.app)
374+
.patch("/api/auth/password/change")
375+
.type("application/json")
376+
.send(failChangePassword)
377+
.end(function (err, res) {
378+
res.should.have.status(401);
379+
res.should.be.json;
380+
res.body.should.have.property("message");
381+
res.body.message.should.equal(Constants.Error.AUTH_401_MESSAGE);
382+
done();
383+
});
384+
});
385+
// success case
386+
it("should change the logged in user's password to a new password", function (done) {
387+
util.auth.login(agent, Admin1, (error) => {
388+
if (error) {
389+
agent.close();
390+
return done(error);
391+
}
392+
agent
393+
.patch("/api/auth/password/change")
394+
.type("application/json")
395+
.send(successChangePassword)
396+
.end(function (err, res) {
397+
res.should.have.status(200);
398+
res.should.be.json;
399+
res.body.should.have.property("message");
400+
res.body.message.should.equal("Successfully reset password");
401+
done();
402+
});
403+
});
404+
});
405+
// fail case because old password in incorrect
406+
it("should fail to change the logged in user's password to a new password because old password is incorrect", function (done) {
407+
util.auth.login(agent, Admin1, (error) => {
408+
if (error) {
409+
agent.close();
410+
return done(error);
411+
}
412+
agent
413+
.patch("/api/auth/password/change")
414+
.type("application/json")
415+
.send(failChangePassword)
416+
.end(function (err, res) {
417+
res.should.have.status(401);
418+
res.should.be.json;
419+
res.body.should.have.property("message");
420+
res.body.message.should.equal(Constants.Error.AUTH_401_MESSAGE);
421+
done();
422+
});
423+
});
424+
});
425+
});
426+
362427
describe("GET retrieve permissions", function () {
363428
it("should SUCCEED and retrieve the rolebindings for the user", function (done) {
364429
util.auth.login(agent, storedAccount1, (error) => {

0 commit comments

Comments
 (0)