From d063c419107b52d87e8217e2635ff1af7ed2c629 Mon Sep 17 00:00:00 2001 From: Asiapenguin Date: Mon, 4 May 2020 13:28:13 -0700 Subject: [PATCH 1/6] Forgot password endpoint done with documentation --- controllers/README.md | 19 +++ controllers/auth.js | 84 +++++++++++--- package-lock.json | 179 ++++++++++++++++++++++++++++- package.json | 3 +- server.js | 4 +- services/refresh-token.service.js | 2 +- services/reset-password.service.js | 52 +++++++++ services/text-messaging.service.js | 26 +++++ utils/error_handling.js | 69 +---------- utils/validations.js | 84 ++++++++++++++ 10 files changed, 432 insertions(+), 90 deletions(-) create mode 100644 services/reset-password.service.js create mode 100644 services/text-messaging.service.js create mode 100644 utils/validations.js diff --git a/controllers/README.md b/controllers/README.md index 9e4fa60..460e26f 100644 --- a/controllers/README.md +++ b/controllers/README.md @@ -47,6 +47,25 @@ HTTP Response: } ``` +### Apply for a temporary password (Forgot Password) + +POST "/reset-password" + +Request Body: +``` +{ + "username": string, + "phone": string, +} +``` + +HTTP Response: +``` +{ + "temporaryPassword": string +} +``` + ## Users ### Getting responders of a user diff --git a/controllers/auth.js b/controllers/auth.js index 9a659c8..7da5590 100644 --- a/controllers/auth.js +++ b/controllers/auth.js @@ -2,7 +2,7 @@ let jwt = require("jsonwebtoken"); var bcrypt = require("bcrypt"); const randToken = require("rand-token"); -const { customValidationResult } = require("../utils/error_handling"); +const { customValidationResult } = require("../utils/validations"); var handle = require("../utils/error_handling"); var UserModel = require("../models/user").model; @@ -12,6 +12,8 @@ var RefreshTokenService = require("../services/refresh-token.service"); var AvailbilityService = require("../services/availability.service"); var OnlineService = require("../services/online.service"); var UserService = require("../services/user.service"); +var ResetPasswordService = require("../services/reset-password.service"); +var TextMessagingService = require("../services/text-messaging.service"); const TOKEN_DURATION = "15m"; @@ -31,19 +33,33 @@ async function login(req, res) { handle.notFound(res, err.message); } + let temporaryPassword = await ResetPasswordService.getTemporaryPassword( + username + ); + try { - if (bcrypt.compareSync(password, result.password)) { + if ( + !bcrypt.compareSync(password, result.password) && + !bcrypt.compareSync( + password, + temporaryPassword ? temporaryPassword : "" + ) + ) { + return handle.unauthorized(res, "Username or password incorrect"); + } else { let token = jwt.sign({ id: result._id }, process.env.SECRET, { - expiresIn: TOKEN_DURATION + expiresIn: TOKEN_DURATION, }); - let refreshToken = randToken.uid(128) - RefreshTokenService.deleteRefreshToken(result._id) - RefreshTokenService.addRefreshToken(result._id, refreshToken) + let refreshToken = randToken.uid(128); + RefreshTokenService.deleteRefreshToken(result._id); + RefreshTokenService.addRefreshToken(result._id, refreshToken); try { await OnlineService.setOnline(result._id.toString()); - var onlineStatus = await OnlineService.checkOnlineStatus(result._id.toString()); + var onlineStatus = await OnlineService.checkOnlineStatus( + result._id.toString() + ); if (onlineStatus && result.naloxoneAvailability) { AvailbilityService.setAvailable(result._id.toString()); } else { @@ -56,19 +72,19 @@ async function login(req, res) { try { await metricService.updateUserLoginTime(username); } catch (err) { - handle.notFound(res, 'Cannot find user in metrics database'); + handle.notFound(res, "Cannot find user in metrics database"); } + await ResetPasswordService.removeTemporaryPassword(username); + res.status(200).json({ success: true, message: "Authentication successful!", token: token, refreshToken: refreshToken, id: result._id, - naloxoneAvailability: result.naloxoneAvailability + naloxoneAvailability: result.naloxoneAvailability, }); - } else { - handle.unauthorized(res, "Username or password incorrect"); } } catch (err) { handle.internalServerError(res, "Bcrypt compareSync failed"); @@ -99,7 +115,7 @@ async function signup(req, res) { email: email, password: bcrypt.hashSync(pass, 10), phone: phone, - naloxoneAvailability: false + naloxoneAvailability: false, }); try { @@ -115,8 +131,11 @@ async function signup(req, res) { try { await metricService.addNewUserToMetrics(result.id, username); } catch (err) { - console.log(err) - handle.internalServerError(res, "Cannot add new user to metrics database") + console.log(err); + handle.internalServerError( + res, + "Cannot add new user to metrics database" + ); } res.status(200).json(result); @@ -132,19 +151,50 @@ async function useRefreshToken(req, res) { let refreshToken = req.body.refreshToken; if (await RefreshTokenService.checkRefreshToken(userId, refreshToken)) { let token = jwt.sign({ id: userId }, process.env.SECRET, { - expiresIn: TOKEN_DURATION + expiresIn: TOKEN_DURATION, }); // Consider generating new refresh token and returning it so that refresh token will also expire res.status(200).json({ token: token }); } else { - handle.unauthorized(res, "Refresh token and user id do not match") + handle.unauthorized(res, "Refresh token and user id do not match"); } } } +async function resetPassword(req, res) { + const errors = customValidationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } else { + let username = req.body.username; + let phone = req.body.phone; + + let user = null; + try { + user = await UserService.findUserByUsername(username); + } catch (err) { + return handle.notFound(res, err.message); + } + + if (phone != user.phone) + return handle.notFound(res, "Username and phone number do not match"); + + const temporaryPassword = await ResetPasswordService.applyTemporaryPassword( + user.username + ); + + // Send text message with temporary password to the specified phone number + TextMessagingService.sendTemporaryPassword(user.phone, temporaryPassword); + + res.status(200).json({ + temporaryPassword: temporaryPassword, + }); + } +} module.exports = { login, signup, useRefreshToken, -} \ No newline at end of file + resetPassword, +}; diff --git a/package-lock.json b/package-lock.json index 976da4c..e36abc1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1069,23 +1069,83 @@ } } }, + "@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, "@types/chai": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.4.tgz", "integrity": "sha512-7qvf9F9tMTzo0akeswHPGqgUx/gIaJqrOEET/FCD8CFRkSUHlygQiM5yB6OvjrtdxBVLSyw7COJubsFYs0683g==", "dev": true }, + "@types/connect": { + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", + "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", + "requires": { + "@types/node": "*" + } + }, "@types/cookiejar": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.1.tgz", "integrity": "sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw==", "dev": true }, + "@types/express": { + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.6.tgz", + "integrity": "sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.5.tgz", + "integrity": "sha512-578YH5Lt88AKoADy0b2jQGwJtrBxezXtVe/MBqWXKZpqx91SnC0pVkVCcxcytz3lWW+cHBYDi3Ysh0WXc+rAYw==", + "requires": { + "@types/node": "*", + "@types/range-parser": "*" + } + }, + "@types/mime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", + "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==" + }, "@types/node": { "version": "12.12.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.3.tgz", - "integrity": "sha512-opgSsy+cEF9N8MgaVPnWVtdJ3o4mV2aMHvDq7thkQUFt0EuOHJon4rQpJfhjmNHB+ikl0Cd6WhWIErOyQ+f7tw==", - "dev": true + "integrity": "sha512-opgSsy+cEF9N8MgaVPnWVtdJ3o4mV2aMHvDq7thkQUFt0EuOHJon4rQpJfhjmNHB+ikl0Cd6WhWIErOyQ+f7tw==" + }, + "@types/qs": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.1.tgz", + "integrity": "sha512-lhbQXx9HKZAPgBkISrBcmAcMpZsmpe/Cd/hY7LGZS5OfkySUBItnPZHgQPssWYUET8elF+yCFBbP1Q0RZPTdaw==" + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" + }, + "@types/serve-static": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", + "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } }, "@types/superagent": { "version": "3.8.7", @@ -1252,6 +1312,11 @@ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -1280,6 +1345,14 @@ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, "babel-plugin-dynamic-import-node": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", @@ -1930,6 +2003,11 @@ "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", "dev": true }, + "dayjs": { + "version": "1.8.26", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.26.tgz", + "integrity": "sha512-KqtAuIfdNfZR5sJY1Dixr2Is4ZvcCqhb0dZpCOt5dGEFiMzoIbjkTSzUb4QKTCsP+WNpGwUjAFIZrnZvUxxkhw==" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2488,6 +2566,24 @@ "is-buffer": "~2.0.3" } }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -4867,6 +4963,11 @@ "find-up": "^3.0.0" } }, + "pop-iterate": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pop-iterate/-/pop-iterate-1.0.1.tgz", + "integrity": "sha1-zqz9q0q/NT16DyqqLB/Hs/lBO6M=" + }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -4938,11 +5039,26 @@ "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==", "dev": true }, + "q": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/q/-/q-2.0.3.tgz", + "integrity": "sha1-dbjbAlWhpa+C9Yw/Oqoe/sfQ0TQ=", + "requires": { + "asap": "^2.0.0", + "pop-iterate": "^1.0.1", + "weak-map": "^1.0.5" + } + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" + }, "rand-token": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/rand-token/-/rand-token-0.4.0.tgz", @@ -5408,6 +5524,11 @@ "semver": "^5.1.0" } }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, "resolve": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", @@ -5448,6 +5569,11 @@ "glob": "^7.1.3" } }, + "rootpath": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/rootpath/-/rootpath-0.1.2.tgz", + "integrity": "sha1-Wzeah9ypBum5HWkKWZQ5vvJn6ms=" + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -5480,6 +5606,11 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, + "scmp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", + "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==" + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -5999,6 +6130,31 @@ } } }, + "twilio": { + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/twilio/-/twilio-3.43.0.tgz", + "integrity": "sha512-PAF4mLpoGmWvpqSSQHNz/RGqkW/vwxjywsoDadQF599fztUmX0ETWcKxXe2N32KUObTUdUQRqjhhETSmXYkZiA==", + "requires": { + "@types/express": "^4.17.3", + "axios": "^0.19.2", + "dayjs": "^1.8.21", + "jsonwebtoken": "^8.5.1", + "lodash": "^4.17.15", + "q": "2.0.x", + "qs": "^6.9.1", + "rootpath": "^0.1.2", + "scmp": "^2.1.0", + "url-parse": "^1.4.7", + "xmlbuilder": "^13.0.2" + }, + "dependencies": { + "qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" + } + } + }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -6157,6 +6313,15 @@ "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" }, + "url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "url-parse-lax": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", @@ -6204,6 +6369,11 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, + "weak-map": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/weak-map/-/weak-map-1.0.5.tgz", + "integrity": "sha1-eWkVhNmGB/UHC9O3CkDmuyLkAes=" + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -6335,6 +6505,11 @@ "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", "dev": true }, + "xmlbuilder": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz", + "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 91c1a11..0b1981e 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "morgan": "^1.9.1", "pg": "^7.18.1", "rand-token": "^0.4.0", - "redis": "^2.8.0" + "redis": "^2.8.0", + "twilio": "^3.43.0" }, "devDependencies": { "@babel/cli": "^7.6.4", diff --git a/server.js b/server.js index ecf6263..2df1120 100644 --- a/server.js +++ b/server.js @@ -1,7 +1,7 @@ require("dotenv").config({ path: __dirname + "/.env" }); const InitializationService = require("./services/initialization.service"); InitializationService.initialize(); -const { validateSignup, validateLogin, validateUseRefreshToken, validateDeleteRefreshToken } = require("./utils/error_handling"); +const { validateSignup, validateLogin, validateUseRefreshToken, validateResetPassword } = require("./utils/validations"); const user = require("./controllers/user"); const auth = require("./controllers/auth"); const alarmMetrics = require("./controllers/metrics/alarm"); @@ -29,6 +29,7 @@ app.get("/", (req, res) => res.send("Server is up")) app.post("/signup", validateSignup(), auth.signup); app.post("/login", validateLogin(), auth.login); app.post("/refresh-token", validateUseRefreshToken(), auth.useRefreshToken); +app.post("/reset-password", validateResetPassword(), auth.resetPassword); app.post("/users/:id/responders", middleware.checkToken, user.addResponders); @@ -39,6 +40,7 @@ app.get("/users/:id/location", middleware.checkToken, user.getLocation); app.post("/users/:id/status", middleware.checkToken, user.toggleOnlineAndNaloxoneAvailabilityStatus); + app.get("/users/search", middleware.checkToken, user.searchUsers); app.get("/users/:id", middleware.checkToken, user.userInfo); diff --git a/services/refresh-token.service.js b/services/refresh-token.service.js index 03c83d6..0252b84 100644 --- a/services/refresh-token.service.js +++ b/services/refresh-token.service.js @@ -11,7 +11,7 @@ async function addRefreshToken(userId, refreshToken) { async function checkRefreshToken(userId, refreshToken) { try { - const tokenExists = await redis.hexistsAsync(refreshTokens, userId); + const tokenExists = await redis.hexistsAsync(refreshTokens, userId.toString()); if (tokenExists) { return await redis.hgetAsync(refreshTokens, userId.toString()) == refreshToken; } diff --git a/services/reset-password.service.js b/services/reset-password.service.js new file mode 100644 index 0000000..bf8dd46 --- /dev/null +++ b/services/reset-password.service.js @@ -0,0 +1,52 @@ +const redis = require("./redis"); +const bcrypt = require("bcrypt"); +const temporaryPasswords = "temporary_passwords"; + +function createTemporaryPassword() { + let result = ""; + const characters = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + const length = characters.length; + + for (let i = 0; i < 5; i++) { + result += characters.charAt(Math.floor(Math.random() * length)); + } + + return result; +} + +async function applyTemporaryPassword(username) { + const temporaryPassword = createTemporaryPassword(); + try { + await redis.hsetAsync( + temporaryPasswords, + username.toString(), + bcrypt.hashSync(temporaryPassword, 10) + ); + return temporaryPassword; + } catch (err) { + console.error("redis applyTemporaryPassword error: ", err.message); + } +} + +async function getTemporaryPassword(username) { + try { + return await redis.hgetAsync(temporaryPasswords, username.toString()); + } catch (err) { + console.error("redis getTemporaryPassword error: ", err.message); + } +} + +async function removeTemporaryPassword(username) { + try { + await redis.hdelAsync(temporaryPasswords, username.toString()); + } catch (err) { + console.error("redis removeTemporaryPassword error: ", err.message); + } +} + +module.exports = { + applyTemporaryPassword, + getTemporaryPassword, + removeTemporaryPassword, +}; diff --git a/services/text-messaging.service.js b/services/text-messaging.service.js new file mode 100644 index 0000000..7429e97 --- /dev/null +++ b/services/text-messaging.service.js @@ -0,0 +1,26 @@ +require("dotenv"); + +const TWILIO_ACCOUNT_SID = process.env.TWILIO_ACCOUNT_SID; +const TWILIO_AUTH_TOKEN = process.env.TWILIO_AUTH_TOKEN; +const TWILIO_PHONE_NUMBER = process.env.TWILIO_PHONE_NUMBER; + +const twilio = require("twilio")(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN); + +const sendTemporaryPassword = (phone, temporaryPassword) => { + twilio.messages + .create({ + body: `You have requested a temporary password: ${temporaryPassword}`, + from: TWILIO_PHONE_NUMBER, + to: `+1${phone}`, + }) + .then((message) => console.log(`Message sent: ${message.sid}`)) + .catch((err) => + console.error( + `TextMessagingService sendTemporaryPassword error: ${err.message}` + ) + ); +}; + +module.exports = { + sendTemporaryPassword, +}; diff --git a/utils/error_handling.js b/utils/error_handling.js index c9af7c5..c70aa78 100644 --- a/utils/error_handling.js +++ b/utils/error_handling.js @@ -1,6 +1,3 @@ -const msg = require("./error_messages"); -const { body, validationResult } = require("express-validator"); - class ErrorFormat { constructor(message) { this.message = message; @@ -35,73 +32,9 @@ function notFound(res, details, statusType) { }); } -const customValidationResult = validationResult.withDefaults({ - formatter: error => { - return { - message: error.msg, - param: error.param, - location: error.location - }; - } -}); - -function validateSignup() { - return [ - body("username") - .exists() - .bail() - .withMessage(msg.USERNAME_MANDATORY) - .isLength({ min: 5 }) - .bail() - .withMessage(msg.USERNAME_CONDITION), - body("password") - .exists() - .bail() - .withMessage(msg.PASSWORD_MANDATORY) - // TODO: Review password policy - .isLength({ min: 5 }) - .bail() - .withMessage(msg.PASSWORD_CONDITION), - body("phone") - .exists() - .bail() - .withMessage(msg.PHONE_MANDATORY) - ]; -} - -function validateLogin() { - return [ - body("username") - .exists() - .bail() - .withMessage(msg.USERNAME_MANDATORY), - body("password") - .exists() - .bail() - .withMessage(msg.PASSWORD_MANDATORY) - ]; -} - -function validateUseRefreshToken() { - return [ - body("userId") - .exists() - .bail() - .withMessage(msg.USER_ID_MANDATORY), - body("refreshToken") - .exists() - .bail() - .withMessage(msg.REFRESH_TOKEN_MANDATORY) - ]; -} - module.exports = { badRequest, unauthorized, internalServerError, - notFound, - customValidationResult, - validateSignup, - validateLogin, - validateUseRefreshToken, + notFound }; diff --git a/utils/validations.js b/utils/validations.js new file mode 100644 index 0000000..c19ac95 --- /dev/null +++ b/utils/validations.js @@ -0,0 +1,84 @@ +const msg = require("./error_messages"); +const { body, validationResult } = require("express-validator"); + +const customValidationResult = validationResult.withDefaults({ + formatter: error => { + return { + message: error.msg, + param: error.param, + location: error.location + }; + } +}); + +const validateSignup = () => { + return [ + body("username") + .exists() + .bail() + .withMessage(msg.USERNAME_MANDATORY) + .isLength({ min: 5 }) + .bail() + .withMessage(msg.USERNAME_CONDITION), + body("password") + .exists() + .bail() + .withMessage(msg.PASSWORD_MANDATORY) + // TODO: Review password policy + .isLength({ min: 5 }) + .bail() + .withMessage(msg.PASSWORD_CONDITION), + body("phone") + .exists() + .bail() + .withMessage(msg.PHONE_MANDATORY) + ]; +} + +const validateLogin = () => { + return [ + body("username") + .exists() + .bail() + .withMessage(msg.USERNAME_MANDATORY), + body("password") + .exists() + .bail() + .withMessage(msg.PASSWORD_MANDATORY) + ]; +} + +const validateUseRefreshToken = () => { + return [ + body("userId") + .exists() + .bail() + .withMessage(msg.USER_ID_MANDATORY), + body("refreshToken") + .exists() + .bail() + .withMessage(msg.REFRESH_TOKEN_MANDATORY) + ]; +} + +const validateResetPassword = () => { + return [ + body("username") + .exists() + .bail() + .withMessage(msg.USERNAME_MANDATORY), + body("phone") + .exists() + .bail() + .withMessage(msg.PHONE_MANDATORY) + ]; +} + + +module.exports = { + customValidationResult, + validateSignup, + validateLogin, + validateUseRefreshToken, + validateResetPassword +} \ No newline at end of file From 31eb3b1d6624860cff8517155c91b01ec53f68b2 Mon Sep 17 00:00:00 2001 From: Asiapenguin Date: Mon, 4 May 2020 13:33:31 -0700 Subject: [PATCH 2/6] Refactor, less code --- controllers/auth.js | 2 +- services/reset-password.service.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/controllers/auth.js b/controllers/auth.js index 7da5590..d163406 100644 --- a/controllers/auth.js +++ b/controllers/auth.js @@ -42,7 +42,7 @@ async function login(req, res) { !bcrypt.compareSync(password, result.password) && !bcrypt.compareSync( password, - temporaryPassword ? temporaryPassword : "" + temporaryPassword ) ) { return handle.unauthorized(res, "Username or password incorrect"); diff --git a/services/reset-password.service.js b/services/reset-password.service.js index bf8dd46..bae8841 100644 --- a/services/reset-password.service.js +++ b/services/reset-password.service.js @@ -31,7 +31,9 @@ async function applyTemporaryPassword(username) { async function getTemporaryPassword(username) { try { - return await redis.hgetAsync(temporaryPasswords, username.toString()); + let temporaryPassword = await redis.hgetAsync(temporaryPasswords, username.toString()); + + return temporaryPassword ? temporaryPassword : ""; } catch (err) { console.error("redis getTemporaryPassword error: ", err.message); } From 94175ae3cd9557fc250ba5bfb8761783a8d2c02b Mon Sep 17 00:00:00 2001 From: Asiapenguin Date: Mon, 4 May 2020 13:34:02 -0700 Subject: [PATCH 3/6] Spacing --- controllers/auth.js | 5 +---- services/reset-password.service.js | 5 ++++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/controllers/auth.js b/controllers/auth.js index d163406..c83adfe 100644 --- a/controllers/auth.js +++ b/controllers/auth.js @@ -40,10 +40,7 @@ async function login(req, res) { try { if ( !bcrypt.compareSync(password, result.password) && - !bcrypt.compareSync( - password, - temporaryPassword - ) + !bcrypt.compareSync(password, temporaryPassword) ) { return handle.unauthorized(res, "Username or password incorrect"); } else { diff --git a/services/reset-password.service.js b/services/reset-password.service.js index bae8841..098d119 100644 --- a/services/reset-password.service.js +++ b/services/reset-password.service.js @@ -31,7 +31,10 @@ async function applyTemporaryPassword(username) { async function getTemporaryPassword(username) { try { - let temporaryPassword = await redis.hgetAsync(temporaryPasswords, username.toString()); + let temporaryPassword = await redis.hgetAsync( + temporaryPasswords, + username.toString() + ); return temporaryPassword ? temporaryPassword : ""; } catch (err) { From 7b8593868296787e667cf345af6350b00e9ed21b Mon Sep 17 00:00:00 2001 From: Asiapenguin Date: Mon, 4 May 2020 13:46:29 -0700 Subject: [PATCH 4/6] /login now returns whether the user used temporary password or not --- controllers/auth.js | 84 ++++++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/controllers/auth.js b/controllers/auth.js index c83adfe..891957b 100644 --- a/controllers/auth.js +++ b/controllers/auth.js @@ -38,51 +38,55 @@ async function login(req, res) { ); try { - if ( - !bcrypt.compareSync(password, result.password) && - !bcrypt.compareSync(password, temporaryPassword) - ) { + let passwordCompare = bcrypt.compareSync(password, result.password); + let temporaryPasswordCompare = bcrypt.compareSync( + password, + temporaryPassword + ); + + if (!passwordCompare && !temporaryPasswordCompare) { return handle.unauthorized(res, "Username or password incorrect"); - } else { - let token = jwt.sign({ id: result._id }, process.env.SECRET, { - expiresIn: TOKEN_DURATION, - }); - - let refreshToken = randToken.uid(128); - RefreshTokenService.deleteRefreshToken(result._id); - RefreshTokenService.addRefreshToken(result._id, refreshToken); - - try { - await OnlineService.setOnline(result._id.toString()); - var onlineStatus = await OnlineService.checkOnlineStatus( - result._id.toString() - ); - if (onlineStatus && result.naloxoneAvailability) { - AvailbilityService.setAvailable(result._id.toString()); - } else { - AvailbilityService.setUnavailable(result._id.toString()); - } - } catch (err) { - console.log("redis error: ", err.message); - } + } - try { - await metricService.updateUserLoginTime(username); - } catch (err) { - handle.notFound(res, "Cannot find user in metrics database"); - } + let token = jwt.sign({ id: result._id }, process.env.SECRET, { + expiresIn: TOKEN_DURATION, + }); - await ResetPasswordService.removeTemporaryPassword(username); + let refreshToken = randToken.uid(128); + RefreshTokenService.deleteRefreshToken(result._id); + RefreshTokenService.addRefreshToken(result._id, refreshToken); + + try { + await OnlineService.setOnline(result._id.toString()); + var onlineStatus = await OnlineService.checkOnlineStatus( + result._id.toString() + ); + if (onlineStatus && result.naloxoneAvailability) { + AvailbilityService.setAvailable(result._id.toString()); + } else { + AvailbilityService.setUnavailable(result._id.toString()); + } + } catch (err) { + console.log("redis error: ", err.message); + } - res.status(200).json({ - success: true, - message: "Authentication successful!", - token: token, - refreshToken: refreshToken, - id: result._id, - naloxoneAvailability: result.naloxoneAvailability, - }); + try { + await metricService.updateUserLoginTime(username); + } catch (err) { + handle.notFound(res, "Cannot find user in metrics database"); } + + await ResetPasswordService.removeTemporaryPassword(username); + + res.status(200).json({ + success: true, + message: "Authentication successful!", + token: token, + refreshToken: refreshToken, + id: result._id, + naloxoneAvailability: result.naloxoneAvailability, + usedTemporaryPassword: temporaryPasswordCompare, + }); } catch (err) { handle.internalServerError(res, "Bcrypt compareSync failed"); } From db1a0560e8effec4b31d659a5e666e8785494f57 Mon Sep 17 00:00:00 2001 From: Asiapenguin Date: Mon, 4 May 2020 15:17:53 -0700 Subject: [PATCH 5/6] Update user password endpoint --- controllers/README.md | 12 ++++++++++- controllers/user.js | 19 +++++++++++++++++ server.js | 2 +- services/user.service.js | 45 ++++++++++++++++++++++++++++++++-------- utils/validations.js | 8 +++++++ 5 files changed, 75 insertions(+), 11 deletions(-) diff --git a/controllers/README.md b/controllers/README.md index 460e26f..14fc379 100644 --- a/controllers/README.md +++ b/controllers/README.md @@ -145,7 +145,7 @@ HTTP Response: } ``` -## Updating location of a user +### Updating location of a user PUT "/users/:id/location" @@ -160,6 +160,16 @@ Request Body: } ``` +### Update attributes of a user + +PUT "/users/:id/password" + +Request Body: +``` +{ + password: string +} +``` ### Getting Responder Count diff --git a/controllers/user.js b/controllers/user.js index 36bd471..a2103f3 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -1,4 +1,6 @@ +const bcrypt = require("bcrypt"); + var ObjectId = require("mongodb").ObjectId; var handle = require("../utils/error_handling"); var UserModel = require("../models/user").model; @@ -250,6 +252,22 @@ async function toggleOnlineAndNaloxoneAvailabilityStatus(req, res) { } } +async function updateUserPassword(req, res) { + let password = req.body.password; + let attributesToUpdate = { + password: bcrypt.hashSync(password, 10) + } + try { + await UserService.updateUserById(req.params.id, attributesToUpdate); + } catch(err) { + return handle.notFound(res, err.message); + } + + res.status(200).json({ + message: "User password updated" + }) +} + async function updateLocation(req, res) { var query = { _id: new ObjectId(req.params.id) }; try { @@ -341,6 +359,7 @@ module.exports = { deleteResponders, searchUsers, toggleOnlineAndNaloxoneAvailabilityStatus, + updateUserPassword, updateLocation, getLocation, getResponderCount, diff --git a/server.js b/server.js index 2df1120..75e81d8 100644 --- a/server.js +++ b/server.js @@ -39,7 +39,7 @@ app.put("/users/:id/location", middleware.checkToken, user.updateLocation); app.get("/users/:id/location", middleware.checkToken, user.getLocation); app.post("/users/:id/status", middleware.checkToken, user.toggleOnlineAndNaloxoneAvailabilityStatus); - +app.put("/users/:id/password", middleware.checkToken, user.updateUserPassword); app.get("/users/search", middleware.checkToken, user.searchUsers); diff --git a/services/user.service.js b/services/user.service.js index 124b0dd..28ac042 100644 --- a/services/user.service.js +++ b/services/user.service.js @@ -6,12 +6,11 @@ const findUserById = async (userId, withPassword = false) => { if (!withPassword) { user = await UserModel.findOne({ - _id: new ObjectId(userId) - }) - .select("-password"); + _id: new ObjectId(userId), + }).select("-password"); } else { user = await UserModel.findOne({ - _id: new ObjectId(userId) + _id: new ObjectId(userId), }); } @@ -26,11 +25,10 @@ const findUserByUsername = async (username, withPassword = false) => { let user = null; if (!withPassword) { - user = await UserModel.findOne({ username: username }) - .select("-password"); + user = await UserModel.findOne({ username: username }).select("-password"); } else { user = await UserModel.findOne({ - username: username + username: username, }); } @@ -41,7 +39,35 @@ const findUserByUsername = async (username, withPassword = false) => { } }; -const cleanUserAttributes = user => { +const updateUserById = async ( + userId, + attributesToUpdate, + withPassword = false +) => { + let user = null; + + if (!withPassword) { + user = await UserModel.findOneAndUpdate( + { _id: new ObjectId(userId) }, + attributesToUpdate, + { new: true } + ).select("-password"); + } else { + user = await UserModel.findOneAndUpdate( + { _id: new ObjectId(userId) }, + attributesToUpdate, + { new: true } + ); + } + + if (!user) { + throw new Error(`User with user id ${userId} cannot be updated`) + } else { + return user; + } +}; + +const cleanUserAttributes = (user) => { if (user.password) { delete user.password; } @@ -55,5 +81,6 @@ const cleanUserAttributes = user => { module.exports = { findUserById, findUserByUsername, - cleanUserAttributes + updateUserById, + cleanUserAttributes, }; diff --git a/utils/validations.js b/utils/validations.js index c19ac95..88111c5 100644 --- a/utils/validations.js +++ b/utils/validations.js @@ -74,6 +74,14 @@ const validateResetPassword = () => { ]; } +const validateUpdateUserPassword = () => { + return [ + body("password") + .exists() + .bail() + .withMessage(msg.PASSWORD_MANDATORY) + ] +} module.exports = { customValidationResult, From 54d3ccf6050b45ca54ba66976c93e9bc50decc93 Mon Sep 17 00:00:00 2001 From: Asiapenguin Date: Mon, 4 May 2020 15:18:23 -0700 Subject: [PATCH 6/6] Spacing --- controllers/user.js | 101 +++++++++++++++++++++++---------------- services/user.service.js | 2 +- utils/validations.js | 59 +++++++---------------- 3 files changed, 79 insertions(+), 83 deletions(-) diff --git a/controllers/user.js b/controllers/user.js index a2103f3..867e1fc 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -1,4 +1,3 @@ - const bcrypt = require("bcrypt"); var ObjectId = require("mongodb").ObjectId; @@ -24,9 +23,8 @@ async function userInfo(req, res) { res.status(200).json(result); } async function getResponders(req, res) { - const user = await UserModel.findOne({ - _id: new ObjectId(req.params.id) + _id: new ObjectId(req.params.id), }).lean(); const userLat = user.location ? user.location.coords.lat : null; @@ -37,18 +35,22 @@ async function getResponders(req, res) { let responders = user.responders; for (let r of responders) { var responder = await UserModel.findOne({ - _id: new ObjectId(r.id) + _id: new ObjectId(r.id), }).lean(); let availbilityStatus = false; if (responder.location && user.location) - availbilityStatus = await AvailbilityService.checkAvailabilityStatusWithDistance(r.id, userLat, userLng); + availbilityStatus = await AvailbilityService.checkAvailabilityStatusWithDistance( + r.id, + userLat, + userLng + ); returnInfo.push({ id: r.id, username: responder.username, - availbilityStatus: availbilityStatus + availbilityStatus: availbilityStatus, }); } res.status(200).json({ responders: returnInfo }); @@ -57,10 +59,9 @@ async function getResponders(req, res) { } } - async function getResponderCount(req, res) { const user = await UserModel.findOne({ - _id: new ObjectId(req.params.id) + _id: new ObjectId(req.params.id), }).lean(); const userLat = user.location ? user.location.coords.lat : null; @@ -71,13 +72,17 @@ async function getResponderCount(req, res) { let count = 0; for (let r of responders) { var responder = await UserModel.findOne({ - _id: new ObjectId(r.id) + _id: new ObjectId(r.id), }).lean(); let availbilityStatus = false; if (responder.location && user.location) - availbilityStatus = await AvailbilityService.checkAvailabilityStatusWithDistance(r.id, userLat, userLng); + availbilityStatus = await AvailbilityService.checkAvailabilityStatusWithDistance( + r.id, + userLat, + userLng + ); if (availbilityStatus == true) count++; } @@ -100,14 +105,17 @@ async function addResponders(req, res) { //validating responders to be added try { var foundUser = await UserModel.findOne({ - _id: new ObjectId(respondersToAdd[i].id) + _id: new ObjectId(respondersToAdd[i].id), }); } catch { validFlag = false; //not single String of 12 bytes or a string of 24 hex characters break; } - if (foundUser == null || user.responders.find(e => e.id === foundUser.id)) { + if ( + foundUser == null || + user.responders.find((e) => e.id === foundUser.id) + ) { validFlag = false; //does not exist in database break; } @@ -120,14 +128,16 @@ async function addResponders(req, res) { user.responders.push(respondersToAdd[i]); let responder = await UserModel.findOne({ - _id: new ObjectId(respondersToAdd[i].id) + _id: new ObjectId(respondersToAdd[i].id), }).lean(); - let availabilityStatus = await AvailbilityService.checkAvailabilityStatus(respondersToAdd[i].id); + let availabilityStatus = await AvailbilityService.checkAvailabilityStatus( + respondersToAdd[i].id + ); returnInfo.push({ id: respondersToAdd[i].id, username: responder.username, - availabilityStatus: availabilityStatus + availabilityStatus: availabilityStatus, }); } @@ -158,7 +168,9 @@ async function deleteResponders(req, res) { var responders = user.get("responders"); let respondersToDeleteAreValid = true; for (let i of respondersToDelete) { - respondersToDeleteAreValid = responders.some(responder => responder["id"] === i.id); + respondersToDeleteAreValid = responders.some( + (responder) => responder["id"] === i.id + ); if (!respondersToDeleteAreValid) break; } @@ -166,18 +178,21 @@ async function deleteResponders(req, res) { for (let i of respondersToDelete) { user.responders.pull({ id: i.id }); let responder = await UserModel.findOne({ - _id: new ObjectId(i.id) + _id: new ObjectId(i.id), }).lean(); returnInfo.push({ id: i.id, - username: responder.username + username: responder.username, }); } user.save(); res.status(200).json({ respondersDeleted: returnInfo }); } else { - handle.badRequest(res, "At least one of the responders is not valid to delete for this user"); + handle.badRequest( + res, + "At least one of the responders is not valid to delete for this user" + ); } } @@ -216,7 +231,7 @@ async function toggleOnlineAndNaloxoneAvailabilityStatus(req, res) { AvailbilityService.setUnavailable(req.params.id); res.status(200).json({ id: req.params.id, - online: false + online: false, }); } catch { handle.internalServerError("Failed to set offline status."); @@ -227,7 +242,7 @@ async function toggleOnlineAndNaloxoneAvailabilityStatus(req, res) { var result = await UserModel.findOneAndUpdate( query, { - naloxoneAvailability: req.body.naloxoneAvailability + naloxoneAvailability: req.body.naloxoneAvailability, }, { new: true } ).lean(); @@ -236,15 +251,19 @@ async function toggleOnlineAndNaloxoneAvailabilityStatus(req, res) { } try { - var onlineStatus = await OnlineService.checkOnlineStatus(result._id.toString()); + var onlineStatus = await OnlineService.checkOnlineStatus( + result._id.toString() + ); if (req.body.naloxoneAvailability && onlineStatus) { AvailbilityService.setAvailable(req.params.id); } else { AvailbilityService.setUnavailable(req.params.id); } res.status(200).json({ - naloxoneAvailability: await AvailbilityService.checkAvailabilityStatus(req.params.id), - message: "Availability status has been changed" + naloxoneAvailability: await AvailbilityService.checkAvailabilityStatus( + req.params.id + ), + message: "Availability status has been changed", }); } catch { handle.internalServerError("Failed to set naloxone availability status."); @@ -255,17 +274,17 @@ async function toggleOnlineAndNaloxoneAvailabilityStatus(req, res) { async function updateUserPassword(req, res) { let password = req.body.password; let attributesToUpdate = { - password: bcrypt.hashSync(password, 10) - } + password: bcrypt.hashSync(password, 10), + }; try { await UserService.updateUserById(req.params.id, attributesToUpdate); - } catch(err) { + } catch (err) { return handle.notFound(res, err.message); } res.status(200).json({ - message: "User password updated" - }) + message: "User password updated", + }); } async function updateLocation(req, res) { @@ -276,8 +295,8 @@ async function updateLocation(req, res) { { location: { coords: req.body.coords, - note: req.body.note && req.body.note - } + note: req.body.note && req.body.note, + }, }, { new: true } ).lean(); @@ -287,18 +306,18 @@ async function updateLocation(req, res) { res.status(200).json({ id: result._id, location: result.location, - note: result.note + note: result.note, }); } async function getLocation(req, res) { const result = await UserModel.findOne({ - _id: new ObjectId(req.params.id) + _id: new ObjectId(req.params.id), }).lean(); if (result) { const data = { - location: result.location + location: result.location, }; res.status(200).json(data); } else { @@ -310,10 +329,10 @@ async function addPushToken(req, res) { try { var result = await UserModel.findOneAndUpdate( { - _id: new ObjectId(req.params.id) + _id: new ObjectId(req.params.id), }, { - pushToken: req.body.pushToken + pushToken: req.body.pushToken, }, { new: true } ).lean(); @@ -323,7 +342,7 @@ async function addPushToken(req, res) { res.status(200).json({ id: result._id, - pushToken: result.pushToken + pushToken: result.pushToken, }); } @@ -332,8 +351,8 @@ async function respondingTo(req, res) { var query = UserModel.find({ responders: { - $elemMatch: { id: userId } - } + $elemMatch: { id: userId }, + }, }); try { @@ -345,7 +364,7 @@ async function respondingTo(req, res) { } res.status(200).json({ - respondingTo: userRespondingTo + respondingTo: userRespondingTo, }); } catch (err) { handle.internalServerError(res, "Failed to query Help Request database"); @@ -364,5 +383,5 @@ module.exports = { getLocation, getResponderCount, addPushToken, - respondingTo + respondingTo, }; diff --git a/services/user.service.js b/services/user.service.js index 28ac042..d6024cb 100644 --- a/services/user.service.js +++ b/services/user.service.js @@ -61,7 +61,7 @@ const updateUserById = async ( } if (!user) { - throw new Error(`User with user id ${userId} cannot be updated`) + throw new Error(`User with user id ${userId} cannot be updated`); } else { return user; } diff --git a/utils/validations.js b/utils/validations.js index 88111c5..d900024 100644 --- a/utils/validations.js +++ b/utils/validations.js @@ -2,13 +2,13 @@ const msg = require("./error_messages"); const { body, validationResult } = require("express-validator"); const customValidationResult = validationResult.withDefaults({ - formatter: error => { + formatter: (error) => { return { message: error.msg, param: error.param, - location: error.location + location: error.location, }; - } + }, }); const validateSignup = () => { @@ -28,65 +28,42 @@ const validateSignup = () => { .isLength({ min: 5 }) .bail() .withMessage(msg.PASSWORD_CONDITION), - body("phone") - .exists() - .bail() - .withMessage(msg.PHONE_MANDATORY) + body("phone").exists().bail().withMessage(msg.PHONE_MANDATORY), ]; -} +}; const validateLogin = () => { return [ - body("username") - .exists() - .bail() - .withMessage(msg.USERNAME_MANDATORY), - body("password") - .exists() - .bail() - .withMessage(msg.PASSWORD_MANDATORY) + body("username").exists().bail().withMessage(msg.USERNAME_MANDATORY), + body("password").exists().bail().withMessage(msg.PASSWORD_MANDATORY), ]; -} +}; const validateUseRefreshToken = () => { return [ - body("userId") - .exists() - .bail() - .withMessage(msg.USER_ID_MANDATORY), + body("userId").exists().bail().withMessage(msg.USER_ID_MANDATORY), body("refreshToken") .exists() .bail() - .withMessage(msg.REFRESH_TOKEN_MANDATORY) + .withMessage(msg.REFRESH_TOKEN_MANDATORY), ]; -} +}; const validateResetPassword = () => { return [ - body("username") - .exists() - .bail() - .withMessage(msg.USERNAME_MANDATORY), - body("phone") - .exists() - .bail() - .withMessage(msg.PHONE_MANDATORY) + body("username").exists().bail().withMessage(msg.USERNAME_MANDATORY), + body("phone").exists().bail().withMessage(msg.PHONE_MANDATORY), ]; -} +}; const validateUpdateUserPassword = () => { - return [ - body("password") - .exists() - .bail() - .withMessage(msg.PASSWORD_MANDATORY) - ] -} + return [body("password").exists().bail().withMessage(msg.PASSWORD_MANDATORY)]; +}; module.exports = { customValidationResult, validateSignup, validateLogin, validateUseRefreshToken, - validateResetPassword -} \ No newline at end of file + validateResetPassword, +};