From 940a3fa3eb8edd80e15e9b2aa65b6f1fbab456b3 Mon Sep 17 00:00:00 2001 From: "Vaibhav D. Aren" Date: Sat, 8 Aug 2020 00:34:13 +0530 Subject: [PATCH] frontend sync master gsoc (#169) * added update restrictions, techStacks in project and orgId to a user * added edit restriction mechanism * intial deactivation mechanism * intial logging mechanism and deactive account * issue fix * fixing org creation issue * Fixes issues in testing (#160) * fix failing test file user.test.js * fixes EADDRINUSE while testing * fixes issues in prposal route * fixed issues in org route and added new tests * Moving Google Analytics requests to backend (#154) * Moving Google Analytics requetss to backend * Requested Changes * Minor changes * Adding code to prevent attacks (#153) * modified user's api (#168) * modified user's api * login options during login * Changes for reactions (#166) Co-authored-by: Rupeshiya Co-authored-by: Devesh Verma Co-authored-by: Kumar Saurabh Raj <39027928+ksraj123@users.noreply.github.com> Co-authored-by: Asel Peiris Co-authored-by: pranjals149 <42092291+pranjals149@users.noreply.github.com> --- .env.dev | 1 + app.js | 55 ++- app/controllers/analytics.js | 104 +++++ app/controllers/auth.js | 3 - app/controllers/comment.js | 4 +- app/controllers/event.js | 18 +- app/controllers/notification.js | 1 - app/controllers/organization.js | 69 ++-- app/controllers/post.js | 85 ++++- app/controllers/project.js | 12 +- app/controllers/proposal.js | 2 - app/controllers/user.js | 121 ++++-- app/middleware/auth.js | 2 +- app/models/Organisation.js | 8 + app/models/Post.js | 18 + app/models/Project.js | 1 + app/models/Proposal.js | 3 + app/models/User.js | 9 +- app/routes/analytics.js | 22 ++ app/routes/event.js | 2 +- app/routes/organisation.js | 7 + app/routes/post.js | 10 +- app/routes/project.js | 2 +- app/routes/user.js | 16 +- app/utils/response-helper.js | 2 +- app/utils/settingHelpers.js | 127 +++++++ config/gAnalytics.js | 10 + config/winston.js | 71 ++++ package-lock.json | 654 +++++++++++++++++++++++++++++++- package.json | 6 +- test/organisation.test.js | 6 +- test/proposal.test.js | 11 +- test/user.test.js | 40 +- 33 files changed, 1393 insertions(+), 109 deletions(-) create mode 100644 app/controllers/analytics.js create mode 100644 app/routes/analytics.js create mode 100644 app/utils/settingHelpers.js create mode 100644 config/gAnalytics.js create mode 100644 config/winston.js diff --git a/.env.dev b/.env.dev index b5e54cd..39acea1 100644 --- a/.env.dev +++ b/.env.dev @@ -4,3 +4,4 @@ JWT_SECRET="thisismysupersecrettokenjustkidding" DATABASE_URL="mongodb://mongo:27017/donut-development" SENDGRID_API_KEY='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' SOCKET_PORT=8810 +clientbaseurl = "http://localhost:3000/" diff --git a/app.js b/app.js index a743490..19b1162 100644 --- a/app.js +++ b/app.js @@ -1,15 +1,18 @@ require('./config/mongoose') const express = require('express') -const logger = require('morgan') +const morgan = require('morgan') const cookieParser = require('cookie-parser') const createError = require('http-errors') const path = require('path') + const socket = require('socket.io') const multer = require('multer') const bodyParser = require('body-parser') const cors = require('cors') +var winston = require('./config/winston') const fileConstants = require('./config/fileHandlingConstants') + const indexRouter = require('./app/routes/index') const authRouter = require('./app/routes/auth') const usersRouter = require('./app/routes/user') @@ -21,6 +24,7 @@ const commentRouter = require('./app/routes/comment') const projectRouter = require('./app/routes/project') const notificationRouter = require('./app/routes/notification') const proposalRouter = require('./app/routes/proposal') +const analyticsRouter = require('./app/routes/analytics') const app = express() const server = require('http').Server(app) @@ -33,7 +37,9 @@ app.use(bodyParser.urlencoded(fileConstants.fileParameters)) const memoryStorage = multer.memoryStorage() app.use(multer({ storage: memoryStorage }).single('file')) -server.listen(process.env.SOCKET_PORT || 8810) +if (process.env.NODE_ENV !== 'testing') { + server.listen(process.env.SOCKET_PORT || 8810) +} // WARNING: app.listen(80) will NOT work here! const io = socket.listen(server) @@ -43,11 +49,41 @@ io.on('connection', (socket) => { io.emit('user connected') }) +app.use(helmet()); +app.use(hpp()); + +const csrfMiddleware = csurf({ + cookie: true +}); + +app.use(session({ + secret: 'codeuino', + resave: false, + saveUninitialized: true, + cookie: { + secure: true, + httpOnly: true + } +})); + +app.use(cookieParser()); +app.use(csrfMiddleware); + // view engine setup app.set('views', path.join(__dirname, 'views')) app.set('view engine', 'ejs') -app.use(logger('tiny')) +morgan.token('data', (req, res) => { + return JSON.stringify(req.body) +}) + +app.use( + morgan( + ':remote-addr - :remote-user [:date[clf]] ":method :url" :status :res[content-length] ":referrer" ":user-agent" :data', + { stream: winston.stream } + ) +) + app.use(express.json()) app.use(express.urlencoded({ extended: false })) app.use(cookieParser()) @@ -68,6 +104,7 @@ app.use('/shortUrl', shortUrlRouter) app.use('/comment', commentRouter) app.use('/project', projectRouter) app.use('/proposal', proposalRouter) +app.use('/analytics', analyticsRouter) // catch 404 and forward to error handler app.use(function (req, res, next) { @@ -80,9 +117,21 @@ app.use(function (err, req, res, next) { res.locals.message = err.message res.locals.error = req.app.get('env') === 'development' ? err : {} + // To include winston logging (Error) + winston.error( + `${err.status || 500} - ${err.message} - ${req.originalUrl} - ${req.method} - ${req.ip} - ${req.body}` + ) + // render the error page res.status(err.status || 500) res.render('error') + + // Socket event error handler (On max event) + req.io.on('error', function (err) { + console.error('------REQ ERROR') + console.error(err.stack) + }) + next() }) module.exports = { app, io } diff --git a/app/controllers/analytics.js b/app/controllers/analytics.js new file mode 100644 index 0000000..adcc31f --- /dev/null +++ b/app/controllers/analytics.js @@ -0,0 +1,104 @@ +const { google } = require('googleapis') +const analytics = google.analytics('v3') +const jwt = require('../../config/gAnalytics') +const viewId = process.env.VIEW_ID +const HANDLER = require('../utils/response-helper') +const HttpStatus = require('http-status-codes') + +module.exports = { + getBrowser: async (req, res, next) => { + const { startDate, endDate, proposalId } = req.body + console.log(req.body) + try { + const result = await analytics.data.ga.get({ + auth: jwt, + ids: `ga:${viewId}`, + metrics: 'ga:users', + dimensions: ['ga:browser'], + 'start-date': startDate, + 'end-date': endDate, + filters: `ga:pagePath==/${proposalId}` + }) + res.status(HttpStatus.OK).json({ analytics: result.data.rows }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + + getCountries: async (req, res, next) => { + const { startDate, endDate, proposalId } = req.body + + try { + const result = await analytics.data.ga.get({ + auth: jwt, + ids: `ga:${viewId}`, + metrics: 'ga:users', + dimensions: ['ga:country'], + 'start-date': startDate, + 'end-date': endDate, + filters: `ga:pagePath==/${proposalId}` + }) + res.status(HttpStatus.OK).json({ analytics: result.data.rows }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + + getDevice: async (req, res, next) => { + const { startDate, endDate, proposalId } = req.body + + try { + const result = await analytics.data.ga.get({ + auth: jwt, + ids: `ga:${viewId}`, + metrics: 'ga:users', + dimensions: ['ga:deviceCategory'], + 'start-date': startDate, + 'end-date': endDate, + filters: `ga:pagePath==/${proposalId}` + }) + res.status(HttpStatus.OK).json({ analytics: result.data.rows }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + + getTopProposals: async (req, res, next) => { + const { startDate, endDate } = req.body + + try { + const result = await analytics.data.ga.get({ + auth: jwt, + ids: `ga:${viewId}`, + metrics: 'ga:pageviews', + dimensions: ['ga:pagePath'], + 'start-date': startDate, + 'end-date': endDate, + filters: 'ga:pagePath!=/homepage' + }) + res.status(HttpStatus.OK).json({ analytics: result.data }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + + getProposalViews: async (req, res, next) => { + const { startDate, endDate, proposalId } = req.body + + try { + const result = await analytics.data.ga.get({ + auth: jwt, + ids: `ga:${viewId}`, + metrics: 'ga:pageviews', + dimensions: ['ga:date'], + 'start-date': startDate, + 'end-date': endDate, + filters: `ga:pagePath==/${proposalId}` + }) + + res.status(HttpStatus.OK).json({ analytics: result.data.rows }) + } catch (error) { + HANDLER.handleError(res, error) + } + } +} diff --git a/app/controllers/auth.js b/app/controllers/auth.js index 744c8dc..20f101f 100644 --- a/app/controllers/auth.js +++ b/app/controllers/auth.js @@ -9,9 +9,6 @@ module.exports = { const token = await user.generateAuthToken() res.send({ user: user, token: token }) } catch (error) { - if (process.env.NODE_ENV !== 'production') { - console.log(error.name, '-', error.message) - } res.status(HttpStatus.BAD_REQUEST).json({ error: error.message }) } }, diff --git a/app/controllers/comment.js b/app/controllers/comment.js index f78bb6b..7517731 100644 --- a/app/controllers/comment.js +++ b/app/controllers/comment.js @@ -112,7 +112,7 @@ module.exports = { }) comment.votes.upVotes.user.unshift(userId) await comment.save() - res.status(HttpStatus.OK).json({ comment: comment }) + return res.status(HttpStatus.OK).json({ comment: comment }) } catch (error) { HANDLER.handleError(res, error) } @@ -143,7 +143,7 @@ module.exports = { }) comment.votes.downVotes.user.unshift(userId) await comment.save() - res.status(HttpStatus.OK).json({ comment: comment }) + return res.status(HttpStatus.OK).json({ comment: comment }) } catch (error) { HANDLER.handleError(res, error) } diff --git a/app/controllers/event.js b/app/controllers/event.js index b8898cb..b7e48ea 100644 --- a/app/controllers/event.js +++ b/app/controllers/event.js @@ -4,6 +4,7 @@ const HttpStatus = require('http-status-codes') const permission = require('../utils/permission') const helper = require('../utils/paginate') const notificationHelper = require('../utils/notif-helper') +const settingsHelper = require('../utils/settingHelpers') const notification = { heading: '', content: '', @@ -33,10 +34,17 @@ module.exports = { try { const event = await Event.findById(id) if (!event) { - return res.status(HttpStatus.BAD_REQUEST).json({ message: 'No post exists' }) + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'No post exists' }) } - // check for permission (TODO AFTER PREVIOUS PR MERGED) - updates.forEach(update => { + // check for permission and edit permission + if (!permission.check(req, res, event.createdBy) || (!settingsHelper.canEdit())) { + return res.status(HttpStatus.FORBIDDEN).json({ msg: 'Bad update request' }) + } + // if edit allowed check allowed limit time + if (!settingsHelper.isEditAllowedNow(event.createdAt)) { + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'Edit limit expired!' }) + } + updates.forEach((update) => { event[update] = req.body[update] }) await event.save() @@ -168,7 +176,6 @@ module.exports = { const events = await Event.find({ eventDate: { $gt: Date.now() } }, {}, helper.paginate(req)) .sort({ eventDate: -1 }) .exec() - console.log('Upcoming events ', events) return res.status(HttpStatus.OK).json({ events }) } catch (error) { HANDLER.handleError(res, next) @@ -177,7 +184,8 @@ module.exports = { getAllEventByUser: async (req, res, next) => { try { - const events = await Event.find({ createdBy: req.user._id }, {}, helper.paginate(req)) + const { id } = req.params + const events = await Event.find({ createdBy: id }, {}, helper.paginate(req)) .sort({ eventDate: -1 }) .populate('createdBy', '_id name.firstName name.lastName') .exec() diff --git a/app/controllers/notification.js b/app/controllers/notification.js index 96147b4..7fd4027 100644 --- a/app/controllers/notification.js +++ b/app/controllers/notification.js @@ -46,7 +46,6 @@ module.exports = { getProposalNotifications: async (req, res, next) => { try { const notifications = await ProposalNotifications.find({}) - console.log(notifications) return res.status(HttpStatus.OK).json({ notifications }) } catch (error) { HANDLER.handleError(res, error) diff --git a/app/controllers/organization.js b/app/controllers/organization.js index 92627d8..1791547 100644 --- a/app/controllers/organization.js +++ b/app/controllers/organization.js @@ -2,12 +2,14 @@ const Organization = require('../models/Organisation') const HANDLER = require('../utils/response-helper') const HttpStatus = require('http-status-codes') const helper = require('../utils/uploader') +const paginater = require('../utils/paginate') const notificationHelper = require('../utils/notif-helper') const User = require('../models/User') const Project = require('../models/Project') const Event = require('../models/Event') const permission = require('../utils/permission') const TAGS = require('../utils/notificationTags') +const Organisation = require('../models/Organisation') const notification = { heading: '', content: '', @@ -21,13 +23,13 @@ module.exports = { helper.mapToDb(req, org) } try { - await org.save() - req.io.emit('new org created', { data: org.name }) + const orgData = await org.save() + req.io.emit('new org created', { data: orgData.name }) notification.heading = 'New org!' - notification.content = `${org.name} is created!` + notification.content = `${orgData.name} is created!` notification.tag = TAGS.NEW notificationHelper.addToNotificationForAll(req, res, notification, next) - return res.status(HttpStatus.CREATED).json({ org }) + return res.status(HttpStatus.CREATED).json({ orgData }) } catch (error) { HANDLER.handleError(res, error) } @@ -69,7 +71,7 @@ module.exports = { helper.mapToDb(req, org) } await org.save() - res.status(HttpStatus.OK).json({ organization: org }) + return res.status(HttpStatus.OK).json({ organization: org }) } catch (error) { HANDLER.handleError(res, error) } @@ -172,23 +174,20 @@ module.exports = { req.io.emit('org under maintenance', { data: organization.name }) notification.heading = 'Maintenance mode on!' notification.content = `${organization.name} is kept under maintenance!` - notificationHelper.addToNotificationForAll( - req, - res, - notification, - next - ) - return res - .status(HttpStatus.OK) - .json({ msg: 'Organization is kept under the maintenance!!' }) + notificationHelper.addToNotificationForAll(req, res, notification, next) + return res.status(HttpStatus.OK).json({ + maintenance: true, + msg: 'Organization is kept under the maintenance!!' + }) } req.io.emit('org revoked maintenance', { data: organization.name }) notification.heading = 'Maintenance mode off!' notification.content = `${organization.name} is revoked from maintenance!` - return res - .status(HttpStatus.OK) - .json({ msg: 'Organization is recovered from maintenance!!' }) + return res.status(HttpStatus.OK).json({ + maintenance: false, + msg: 'Organization is recovered from maintenance!!' + }) } else { return res .status(HttpStatus.BAD_REQUEST) @@ -209,15 +208,10 @@ module.exports = { .status(HttpStatus.NOT_FOUND) .json({ msg: 'No Organization found!' }) } - // check if user is admin or not - const adminIds = organization.adminInfo.adminId - const isAdmin = adminIds.indexOf(req.user.id) const updates = Object.keys(req.body) - console.log('req.body ', req.body) - console.log('isAdmin ', isAdmin) const allowedUpdates = ['settings', 'permissions', 'authentication'] // if admin then check if valid update - if (isAdmin !== -1) { + if (req.user.isAdmin === true) { const isValidOperation = updates.every((update) => { return allowedUpdates.includes(update) }) @@ -227,9 +221,7 @@ module.exports = { organization.options[update] = req.body[update] }) await organization.save() - return res - .status(HttpStatus.OK) - .json({ msg: 'Successfully updated!' }) + return res.status(HttpStatus.OK).json({ organization }) } // invalid update return res @@ -272,8 +264,8 @@ module.exports = { { 'name.firstName': { $regex: regex } }, { 'name.lastName': { $regex: regex } } ] - }) - .select('name email isAdmin info.about.designation isRemoved') + }, {}, paginater.paginate(req)) + .select('name email isAdmin info.about.designation isRemoved createdAt') .lean() .sort({ createdAt: -1 }) .exec() @@ -282,7 +274,7 @@ module.exports = { } return res.status(HttpStatus.OK).json({ member }) } else { - const members = await User.find({}) + const members = await User.find({}, {}, paginater.paginate(req)) .select('name email isAdmin info.about.designation isRemoved') .lean() .sort({ createdAt: -1 }) @@ -315,7 +307,6 @@ module.exports = { // console.log('Permitted to removeAdmin') // REMOVE ADMINS FROM ADMINS LIST const admins = org.adminInfo.adminId - console.log('adminIds ', admins) const removableIndex = admins.indexOf(userId) if (removableIndex === -1) { return res @@ -327,11 +318,29 @@ module.exports = { await org.save() // also make isAdmin false const user = await User.findById(userId) + if (!user) { + return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No such user exists!' }) + } user.isAdmin = false await user.save() return res.status(HttpStatus.OK).json({ org }) } catch (error) { HANDLER.handleError(res, error) } + }, + + // GET ORG LOGIN OPTIONS + getOrgLoginOptions: async (req, res, next) => { + try { + const org = await Organisation.find({}) + .lean() + .exec() + if (org.length == 0) { + return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such organization exists!' }) + } + return res.status(HttpStatus.OK).json({ methods: org[0].options.authentication }) + } catch(error) { + HANDLER.handleError(res, error) + } } } diff --git a/app/controllers/post.js b/app/controllers/post.js index affa4dc..1521f4d 100644 --- a/app/controllers/post.js +++ b/app/controllers/post.js @@ -5,6 +5,7 @@ const HttpStatus = require('http-status-codes') const imgUploadHelper = require('../utils/uploader') const permission = require('../utils/permission') const helper = require('../utils/paginate') +const settingsHelper = require('../utils/settingHelpers') module.exports = { // CREATE POST @@ -67,11 +68,21 @@ module.exports = { .status(HttpStatus.BAD_REQUEST) .json({ message: 'No post exists' }) } - if (!permission.check(req, res, post.userId)) { + // permission check for admin and creator || edit allowed or not + if ( + !permission.check(req, res, post.userId) || + !settingsHelper.canEdit() + ) { return res .status(HttpStatus.FORBIDDEN) .json({ message: 'Bad update request' }) } + // if allowed edit limit check + if (!settingsHelper.isEditAllowedNow(post.createdAt)) { + return res + .status(HttpStatus.BAD_REQUEST) + .json({ msg: 'Edit limit expired!' }) + } updates.forEach((update) => { post[update] = req.body[update] }) @@ -137,6 +148,51 @@ module.exports = { upvote: async (req, res, next) => { const { id } = req.params const userId = req.user.id.toString() + const reactionType = req.body.reactionType + + try { + const post = await PostModel.findById(id) + if (!post) { + return res + .status(HttpStatus.NOT_FOUND) + .json({ error: 'No post found' }) + } + // CHECKS IF THE USER HAS ALREADY UPVOTED THE COMMENT + post.votes.upVotes.user.filter((user) => { + if (JSON.stringify(user) === JSON.stringify(userId)) { + return res + .status(HttpStatus.BAD_REQUEST) + .json({ error: 'Bad request' }) + } + }) + switch (reactionType) { + case 'like': + post.votes.upVotes.user.unshift(userId) + break + case 'heart': + post.votes.heart.user.unshift(userId) + break + case 'happy': + post.votes.happy.user.unshift(userId) + break + case 'donut': + post.votes.donut.user.unshift(userId) + break + default: + } + await post.save() + res.status(HttpStatus.OK).json({ post: post }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + + // REMOVE REACTION + removeReaction: async (req, res, next) => { + const { id } = req.params + const userId = req.user.id.toString() + const reactionType = req.body.reactionType + try { const post = await PostModel.findById(id) if (!post) { @@ -152,7 +208,30 @@ module.exports = { .json({ error: 'Bad request' }) } }) - post.votes.upVotes.user.unshift(userId) + switch (reactionType) { + case 'like': + post.votes.upVotes.user = post.votes.upVotes.user.filter(item => + item !== userId + ) + break + case 'heart': + post.votes.heart.user = post.votes.heart.user.filter(item => + item !== userId + ) + console.log(post.votes.heart.user) + break + case 'happy': + post.votes.happy.user = post.votes.happy.user.filter(item => + item !== userId + ) + break + case 'donut': + post.votes.donut.user = post.votes.donut.user.filter(item => + item !== userId + ) + break + default: + } await post.save() res.status(HttpStatus.OK).json({ post: post }) } catch (error) { @@ -163,7 +242,7 @@ module.exports = { getPostByUser: async (req, res, next) => { try { const posts = await PostModel.find( - { userId: req.user._id }, + { userId: req.params.id }, {}, helper.paginate(req) ) diff --git a/app/controllers/project.js b/app/controllers/project.js index 784f9e4..203dc8f 100644 --- a/app/controllers/project.js +++ b/app/controllers/project.js @@ -3,6 +3,7 @@ const HANDLER = require('../utils/response-helper') const HttpStatus = require('http-status-codes') const helper = require('../utils/paginate') const permission = require('../utils/permission') +const settingsHelper = require('../utils/settingHelpers') module.exports = { createProject: async (req, res, next) => { @@ -64,6 +65,15 @@ module.exports = { if (!project) { return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No such project exits!' }) } + // permission check for admin and creator || is edit allowed + if (!permission.check(req, res, project.createdBy) || (!settingsHelper.canEdit())) { + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'Bad Update Request!' }) + } + // if allowed check edit limit + if (!settingsHelper.isEditAllowedNow(project.createdAt)) { + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'Edit limit expired!' }) + } + // check if valid edit if (!isValidOperation) { return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'Invalid update!' }) } @@ -95,7 +105,7 @@ module.exports = { }, projectCreatedByUser: async (req, res, next) => { try { - const { id } = req.user + const { id } = req.params const projects = await Project.find({ createdBy: id }, {}, helper.paginate(req)) .populate('createdBy', '_id name.firstName name.lastName email') .sort({ updatedAt: -1 }) diff --git a/app/controllers/proposal.js b/app/controllers/proposal.js index d550cf1..3aab6bb 100644 --- a/app/controllers/proposal.js +++ b/app/controllers/proposal.js @@ -131,7 +131,6 @@ module.exports = { try { const proposalId = req.body.proposalId - console.log(proposalId) const result = await ProposalModel.findByIdAndDelete(proposalId) const creator = result.creator @@ -214,7 +213,6 @@ module.exports = { }, getAllProposals: async (req, res, next) => { - console.log('All proposals called') try { const proposals = await ProposalModel.find({}) if (!proposals.length) { diff --git a/app/controllers/user.js b/app/controllers/user.js index 622d812..d581b7d 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -7,7 +7,9 @@ const HANDLER = require('../utils/response-helper') const notificationHelper = require('../utils/notif-helper') const Projects = require('../models/Project') const Events = require('../models/Event') +const Organization = require('../models/Organisation') const TAGS = require('../utils/notificationTags') +const settingHelper = require('../utils/settingHelpers') const notification = { heading: '', content: '', @@ -20,25 +22,54 @@ module.exports = { const user = new User(req.body) try { const isRegisteredUserExists = await User.findOne({ firstRegister: true }) + const Org = await Organization.find({}).lean().exec() // for the first user who will be setting up the platform for their community if (!isRegisteredUserExists) { user.isAdmin = true user.firstRegister = true } - await user.save() + if (Org.length > 0) { + user.orgId = Org[0]._id + } + const data = await user.save() + if (!isRegisteredUserExists || req.body.isAdmin === true) { + settingHelper.addAdmin(data._id) + } const token = await user.generateAuthToken() // Added fn to send email to activate account with warm message await emailController.sendEmail(req, res, next, token) return res.status(HttpStatus.CREATED).json({ user: user, token: token }) } catch (error) { - console.log(error) return res.status(HttpStatus.NOT_ACCEPTABLE).json({ error: error }) } }, // GET USER PROFILE userProfile: async (req, res, next) => { try { - const user = req.user + const id = req.params.id ? req.params.id : req.user._id + const user = await User.findById({ _id: id }) + .populate('followings', [ + 'name.firstName', + 'name.lastName', + 'info.about.designation', + '_id', + 'isAdmin' + ]) + .populate('followers', [ + 'name.firstName', + 'name.lastName', + 'info.about.designation', + '_id', + 'isAdmin' + ]) + .populate('blocked', [ + 'name.firstName', + 'name.lastName', + 'info.about.designation', + '_id', + 'isAdmin' + ]) + .exec() if (!user) { return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No such user exist!' }) } @@ -52,12 +83,18 @@ module.exports = { userProfileUpdate: async (req, res, next) => { const updates = Object.keys(req.body) const allowedUpdates = [ - 'name', - 'email', 'phone', 'info', - 'about' + 'about', + 'isDeactivated' ] + // added control as per org settings + if (settingHelper.canChangeName()) { + allowedUpdates.unshift('name') + } + if (settingHelper.canChangeEmail()) { + allowedUpdates.unshift('email') + } const isValidOperation = updates.every((update) => { return allowedUpdates.includes(update) }) @@ -67,11 +104,13 @@ module.exports = { } try { + const { id } = req.params + const user = await User.findById(id) updates.forEach((update) => { - req.user[update] = req.body[update] + user[update] = req.body[update] }) - await req.user.save() - return res.status(HttpStatus.OK).json({ data: req.user }) + await user.save() + return res.status(HttpStatus.OK).json({ data: user }) } catch (error) { return res.status(HttpStatus.BAD_REQUEST).json({ error }) } @@ -89,9 +128,6 @@ module.exports = { await user.save() return res.status(HttpStatus.OK).json({ success: true, token }) } catch (error) { - if (process.env.NODE_ENV !== 'production' && error) { - console.log('Error in forgotPasswordRequest ', error) - } return res.status(HttpStatus.BAD_REQUEST).json({ error }) } }, @@ -118,18 +154,12 @@ module.exports = { notification.heading = 'Forgot password!' notification.content = 'Password successfully updated!' notification.tag = TAGS.UPDATE - notificationHelper.addToNotificationForUser(id, res, notification, next) + await notificationHelper.addToNotificationForUser(id, res, notification, next) return res.status(HttpStatus.OK).json({ updated: true }) } else { - if (process.env.NODE_ENV !== 'production') { - console.log('token expired') - } res.status(HttpStatus.BAD_REQUEST).json({ error: 'Token expired' }) } } catch (error) { - if (process.env.NODE_ENV !== 'production' && error) { - console.log('Something went wrong ', error) - } res.status(HttpStatus.BAD_REQUEST).json({ error }) } }, @@ -179,7 +209,7 @@ module.exports = { notification.heading = 'Account activate!' notification.content = 'Account successfully activated!' notification.tag = TAGS.ACTIVATE - notificationHelper.addToNotificationForUser(user._id, res, notification, next) + await notificationHelper.addToNotificationForUser(user._id, res, notification, next) return res.status(HttpStatus.OK).json({ msg: 'Succesfully activated!' }) } } catch (Error) { @@ -189,22 +219,38 @@ module.exports = { // GET INVITE LINK getInviteLink: async (req, res, next) => { - const token = jwt.sign({ _id: req.user._id, expiry: Date.now() + 24 * 3600 * 1000 }, process.env.JWT_SECRET) - const inviteLink = `${req.protocol}://${req.get('host')}/user/invite/${token}` - return res.status(HttpStatus.OK).json({ inviteLink: inviteLink }) + try { + const { role } = req.query + const token = jwt.sign({ _id: req.user._id, role: role, expiry: Date.now() + 24 * 3600 * 1000 }, process.env.JWT_SECRET) + const inviteLink = `${req.protocol}://${req.get('host')}/user/invite/${token}` + return res.status(HttpStatus.OK).json({ inviteLink: inviteLink }) + } catch (error) { + HANDLER.handleError(res, error) + } }, // PROCESS THE INVITE LINK processInvite: async (req, res, next) => { - const { token } = req.params - const decodedToken = jwt.verify(token, process.env.JWT_SECRET) - // check if token not expired and sender exist in db then valid request - const user = await User.findById(decodedToken._id) - if (user && Date.now() <= decodedToken.expiry) { - console.log('Valid invite!') - return res.status(HttpStatus.OK).json({ success: true, msg: 'Redirect user to register in client side!' }) + try { + const { token } = req.params + const decodedToken = jwt.verify(token, process.env.JWT_SECRET) + // check if token not expired and sender exist in db then valid request + const user = await User.findById(decodedToken._id) + if (user && Date.now() <= decodedToken.expiry) { + console.log('Valid invite!') + if (decodedToken.role === 'user') { + // TODO: CHANGE THE URL IN PRODUCTION (in env file) + return res.redirect(process.env.clientbaseurl) + } + if (decodedToken.role === 'admin') { + // TODO: CHANGE THE URL IN PRODUCTION (in env file) + return res.redirect(`${process.env.clientbaseurl}/admin`) + } + } + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'Invalid token!' }) + } catch (error) { + HANDLER.handleError(res, error) } - return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'Invalid token!' }) }, // ADD TO THE FOLLOWINGS LIST @@ -245,7 +291,7 @@ module.exports = { notification.heading = 'New follower!' notification.content = `${req.user.name.firstName} started following you!` notification.tag = TAGS.FOLLOWER - notificationHelper.addToNotificationForUser(user._id, res, notification, next) + await notificationHelper.addToNotificationForUser(user._id, res, notification, next) const userData = await User.findById(req.user._id) .populate('followings', ['name.firstName', 'name.lastName', 'info.about.designation', '_id', 'isAdmin']) .populate('followers', ['name.firstName', 'name.lastName', 'info.about.designation', '_id', 'isAdmin']) @@ -344,7 +390,6 @@ module.exports = { if (user.isAdmin === true) { const blockedIds = user.blocked.map(item => item._id) const unblockIndex = blockedIds.indexOf(id) - console.log('UnblockIndex ', unblockIndex) if (unblockIndex !== -1) { user.blocked.splice(unblockIndex, 1) await user.save() @@ -389,5 +434,15 @@ module.exports = { } catch (error) { HANDLER.handleError(res, error) } + }, + // DEACTIVATE ACCOUNT (BY USER ITSELF) + deactivateAccount: async (req, res, next) => { + try { + req.user.isActivated = !req.user.isActivated + const user = await req.user.save() + return res.status(HttpStatus.OK).json({ user }) + } catch (error) { + HANDLER.handleError(error) + } } } diff --git a/app/middleware/auth.js b/app/middleware/auth.js index 67c266c..58bca4a 100644 --- a/app/middleware/auth.js +++ b/app/middleware/auth.js @@ -32,7 +32,7 @@ const auth = async (req, res, next) => { 'isAdmin' ]) .exec() - console.log(user) + // console.log(user) if (!user) { throw new Error() diff --git a/app/models/Organisation.js b/app/models/Organisation.js index 8891abe..1a9af8e 100644 --- a/app/models/Organisation.js +++ b/app/models/Organisation.js @@ -110,6 +110,14 @@ const orgSchema = new Schema({ enum: ['English', 'French', 'German'], default: 'English' }, + canEdit: { + type: Boolean, + default: true + }, + editingLimit: { + type: String, + default: 'Always' + }, timeFormat: { type: String, enum: ['24', '12'], diff --git a/app/models/Post.js b/app/models/Post.js index 36b2a7d..9f1680d 100644 --- a/app/models/Post.js +++ b/app/models/Post.js @@ -36,6 +36,24 @@ const PostSchema = new Schema({ type: Schema.Types.ObjectId, ref: 'User' }] + }, + heart: { + user: [{ + type: Schema.Types.ObjectId, + ref: 'User' + }] + }, + happy: { + user: [{ + type: Schema.Types.ObjectId, + ref: 'User' + }] + }, + donut: { + user: [{ + type: Schema.Types.ObjectId, + ref: 'User' + }] } }, comments: { diff --git a/app/models/Project.js b/app/models/Project.js index 7d5ada8..abe0ff1 100644 --- a/app/models/Project.js +++ b/app/models/Project.js @@ -33,6 +33,7 @@ const projectSchema = new Schema({ trim: true } }, + techStacks: [], image: { type: Buffer, contentType: String diff --git a/app/models/Proposal.js b/app/models/Proposal.js index 4a1f97b..813be0c 100644 --- a/app/models/Proposal.js +++ b/app/models/Proposal.js @@ -7,6 +7,9 @@ const proposalSchema = new Schema( type: String, required: true }, + organization: { + type: String + }, content: { type: String, required: true diff --git a/app/models/User.js b/app/models/User.js index 9dd5946..9238156 100644 --- a/app/models/User.js +++ b/app/models/User.js @@ -77,7 +77,7 @@ const UserSchema = new mongoose.Schema({ twitter: { type: String }, - instagram: { + github: { type: String }, linkedin: { @@ -135,6 +135,10 @@ const UserSchema = new mongoose.Schema({ } } }, + orgId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Organization' + }, notifications: [ { heading: { @@ -212,7 +216,8 @@ const UserSchema = new mongoose.Schema({ createdAt: { type: Date, required: true, - default: Date.now() + default: Date.now(), + select: true }, updatedAt: { type: Date, diff --git a/app/routes/analytics.js b/app/routes/analytics.js new file mode 100644 index 0000000..3c84b3e --- /dev/null +++ b/app/routes/analytics.js @@ -0,0 +1,22 @@ +const express = require('express') +const router = express.Router() +const analyticsController = require('../controllers/analytics') +const auth = require('../middleware/auth') +const isUnderMaintenance = require('../middleware/maintenance') + +// Get Browser analytics +router.post('/browser', isUnderMaintenance, auth, analyticsController.getBrowser) + +// Get country analytics +router.post('/countries', isUnderMaintenance, auth, analyticsController.getCountries) + +// Get Device analytics +router.post('/device', isUnderMaintenance, auth, analyticsController.getDevice) + +// Get most viewed Proposals +router.post('/mostviewed', isUnderMaintenance, auth, analyticsController.getTopProposals) + +// Get Views of a specific proposal +router.post('/views', isUnderMaintenance, auth, analyticsController.getProposalViews) + +module.exports = router diff --git a/app/routes/event.js b/app/routes/event.js index 0d0dc02..30efa12 100644 --- a/app/routes/event.js +++ b/app/routes/event.js @@ -58,7 +58,7 @@ router.delete( // GET ALL EVENT POSTED BY A USER router.get( - '/me/all', + '/:id/all', isUnderMaintenance, auth, eventController.getAllEventByUser diff --git a/app/routes/organisation.js b/app/routes/organisation.js index a69abc9..80b7ba7 100644 --- a/app/routes/organisation.js +++ b/app/routes/organisation.js @@ -84,4 +84,11 @@ router.patch( OrgController.removeAdmin ) +// GET ORG LOGIN OPTIONS (CALLED JUST BEFORE LOGIN) +router.get( + '/login/options', + isUnderMaintenance, + OrgController.getOrgLoginOptions +) + module.exports = router diff --git a/app/routes/post.js b/app/routes/post.js index 0208282..4f45c63 100644 --- a/app/routes/post.js +++ b/app/routes/post.js @@ -56,9 +56,17 @@ router.patch( postController.upvote ) +// REMOVE REACTION FROM POST +router.patch( + '/removereaction/:id', + isUnderMaintenance, + auth, + postController.removeReaction +) + // GET POST PER USER router.get( - '/me/all', + '/:id/all', auth, postController.getPostByUser ) diff --git a/app/routes/project.js b/app/routes/project.js index 178a3f6..c6d7026 100644 --- a/app/routes/project.js +++ b/app/routes/project.js @@ -46,7 +46,7 @@ router.delete( // GET PROJECTS CREATED BY A USER router.get( - '/me/all', + '/:id/all', isUnderMaintenance, auth, projectController.projectCreatedByUser diff --git a/app/routes/user.js b/app/routes/user.js index 0c9be6e..76de54c 100644 --- a/app/routes/user.js +++ b/app/routes/user.js @@ -15,7 +15,7 @@ router.post( // get user profile router.get( - '/me', + '/:id', isUnderMaintenance, auth, userController.userProfile @@ -23,7 +23,7 @@ router.get( // update user info router.patch( - '/me', + '/:id', isUnderMaintenance, auth, userController.userProfileUpdate @@ -45,7 +45,7 @@ router.patch( // get invite link (for sender) router.get( - '/invite', + '/link/invite', isUnderMaintenance, auth, userController.getInviteLink @@ -116,7 +116,7 @@ router.patch( // GET PERSONAL OVERVIEW router.get( - '/overview', + '/me/overview', isUnderMaintenance, auth, userController.getPersonalOverview @@ -130,4 +130,12 @@ router.patch( userController.removeUser ) +// DEACTIVATE ACCOUNT BY USER +router.patch( + '/deactivate/toggler', + isUnderMaintenance, + auth, + userController.deactivateAccount +) + module.exports = router diff --git a/app/utils/response-helper.js b/app/utils/response-helper.js index 6001b5e..6b390d7 100644 --- a/app/utils/response-helper.js +++ b/app/utils/response-helper.js @@ -4,7 +4,7 @@ exports.handleError = (res, err) => { console.log(err) } // Sends error to user - res.status(err.code).json({ + return res.status(err.code).json({ errors: { msg: err.message } diff --git a/app/utils/settingHelpers.js b/app/utils/settingHelpers.js new file mode 100644 index 0000000..fe1e448 --- /dev/null +++ b/app/utils/settingHelpers.js @@ -0,0 +1,127 @@ +const Organization = require('../models/Organisation') +const moment = require('moment') + +module.exports = { + isEnabledEmail: async () => { + try { + const org = await Organization.find({}).lean().exec() + console.log('org ', org[0]) + // by default it's true + if (org.length === 0) { + return true + } + // check if allowed in org settings + if (org[0].options.settings.enableEmail) { + console.log('yes email isEnabledEmail') + return true + } + // not allowed + return false + } catch (err) { + console.log(err) + } + }, + canChangeName: async () => { + try { + const org = await Organization.find({}).lean().exec() + console.log('org ', org[0]) + + // by default it's true + if (org.length === 0) { + return true + } + // check if allowed in org settings + if (org[0].options.permissions.canChangeName) { + console.log('yes can canChangeName') + return true + } + // not allowed + return false + } catch (error) { + console.log(error) + } + }, + canChangeEmail: async () => { + try { + const org = await Organization.find({}).lean().exec() + console.log('org ', org[0]) + + // by default true + if (org.length === 0) { + return true + } + // check if allowed in org settings + if (org[0].options.permissions.canChangeEmail) { + console.log('yes can canChangeEmail') + return true + } + // not allowed + return false + } catch (error) { + console.log(error) + } + }, + canCreateOrManageUser: async () => { + try { + const org = await Organization.find({}).lean().exec() + console.log('org ', org[0]) + if (org[0].options.permissions.canCreateManage) { + return org[0].options.permissions.canCreateManage + } + return 'BOTH' + } catch (error) { + console.log(error) + } + }, + canSendInvite: async () => { + try { + const org = await Organization.find({}).lean().exec() + // check if allowed + if (org[0].options.permissions.sendInvite) { + return org[0].options.permissions.sendInvite + } + return 'BOTH' + } catch (error) { + console.log(error) + } + }, + canEdit: async () => { + try { + const org = await Organization.find({}).lean().exec() + if (org[0].options.settings.canEdit) { + return true + } + // not allowed + return false + } catch (error) { + console.log(error) + } + }, + isEditAllowedNow: async (createdAt) => { + try { + const org = await Organization.find({}).lean().exec() + const limit = org[0].options.settings.editingLimit + if (limit !== 'Always') { + const now = moment().format('YYYY-MM-DD hh:mm:ss') + const allowedTill = moment(createdAt).add(limit, 'minutes').format('YYYY-MM-DD hh:mm:ss') + if (now < allowedTill) { + return true + } + return false + } + // Always allowed + return true + } catch (error) { + console.log(error) + } + }, + addAdmin: async (userId) => { + try { + const org = await Organization.find({}) + org[0].adminInfo.adminId.unshift(userId) + await org[0].save() + } catch (error) { + console.log('error ', error) + } + } +} diff --git a/config/gAnalytics.js b/config/gAnalytics.js new file mode 100644 index 0000000..5da67a0 --- /dev/null +++ b/config/gAnalytics.js @@ -0,0 +1,10 @@ +const { google } = require('googleapis') +const clientEMail = process.env.CLIENT_EMAIL +const privateKey = process.env.PRIVATE_KEY +const scope = process.env.SCOPE + +module.exports = new google.auth.JWT({ + email: clientEMail, + key: privateKey, + scopes: [scope] +}) diff --git a/config/winston.js b/config/winston.js new file mode 100644 index 0000000..021d62d --- /dev/null +++ b/config/winston.js @@ -0,0 +1,71 @@ +var appRoot = require('app-root-path') +var winston = require('winston') + +const { combine, colorize, printf, timestamp } = winston.format + +const logFormat = printf((info) => { + return `[${info.timestamp}] ${info.level}: ${info.message}` +}) + +const rawFormat = printf((info) => { + return `[${info.timestamp}] ${info.level}: ${info.message}` +}) + +// define the custom settings for each transport (file, console) +var options = { + file: { + level: 'info', + filename: `${appRoot}/logs/app.log`, + handleExceptions: true, + json: true, + maxsize: 5242880, // 5MB + maxFiles: 5, + colorize: false + }, + errorFile: { + level: 'error', + name: 'file.error', + filename: `${appRoot}/logs/error.log`, + handleExceptions: true, + json: true, + maxsize: 5242880, // 5MB + maxFiles: 100, + colorize: true + }, + console: { + level: 'debug', + handleExceptions: true, + json: false, + format: combine(colorize(), rawFormat) + } +} + +// instantiate a new Winston Logger with the settings defined above +var logger = winston.createLogger({ + format: combine( + timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + logFormat + ), + transports: [ + new winston.transports.File(options.file), + new winston.transports.Console(options.console) + ], + exitOnError: false // do not exit on handled exceptions +}) + +// create a stream object with a 'write' function that will be used by `morgan` +logger.stream = { + write: function (message, encoding) { + // use the 'info' log level so the output will be picked up by both transports (file and console) + logger.info(message) + } +} + +winston.addColors({ + debug: 'white', + error: 'red', + info: 'green', + warn: 'yellow' +}) + +module.exports = logger diff --git a/package-lock.json b/package-lock.json index fd1b330..8be42e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -210,6 +210,16 @@ } } }, + "@dabh/diagnostics": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", + "integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==", + "requires": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "@jest/console": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", @@ -555,6 +565,14 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -708,6 +726,11 @@ } } }, + "app-root-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz", + "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==" + }, "append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -792,6 +815,11 @@ "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -819,6 +847,11 @@ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, + "async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + }, "async-each": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", @@ -1050,6 +1083,11 @@ "callsite": "1.0.0" } }, + "bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" + }, "binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", @@ -1107,6 +1145,11 @@ } } }, + "bowser": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.9.0.tgz", + "integrity": "sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA==" + }, "boxen": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", @@ -1321,6 +1364,11 @@ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", "dev": true }, + "camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" + }, "capture-exit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", @@ -1512,6 +1560,15 @@ "object-visit": "^1.0.0" } }, + "color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", + "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -1525,6 +1582,29 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + }, + "colorspace": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", + "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "requires": { + "color": "3.0.x", + "text-hex": "1.0.x" + } + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1601,6 +1681,11 @@ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" }, + "content-security-policy-builder": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-2.1.0.tgz", + "integrity": "sha512-/MtLWhJVvJNkA9dVLAp6fg9LxD2gfI6R2Fi1hPmfjYXSahJJzcfvoeDOxSyp4NvxMuwWv3WMssE9o31DoULHrQ==" + }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -1691,6 +1776,16 @@ "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", "dev": true }, + "csrf": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz", + "integrity": "sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==", + "requires": { + "rndm": "1.2.0", + "tsscmp": "1.0.6", + "uid-safe": "2.1.5" + } + }, "cssom": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", @@ -1706,6 +1801,51 @@ "cssom": "0.3.x" } }, + "csurf": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/csurf/-/csurf-1.11.0.tgz", + "integrity": "sha512-UCtehyEExKTxgiu8UHdGvHj4tnpE/Qctue03Giq5gPgMQ9cg/ciod5blZQ5a4uCEenNQjxyGuzygLdKUmee/bQ==", + "requires": { + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "csrf": "3.1.0", + "http-errors": "~1.7.3" + }, + "dependencies": { + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + } + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -1715,6 +1855,11 @@ "assert-plus": "^1.0.0" } }, + "dasherize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz", + "integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg=" + }, "data-urls": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", @@ -1913,6 +2058,11 @@ "webidl-conversions": "^4.0.2" } }, + "dont-sniff-mimetype": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.1.0.tgz", + "integrity": "sha512-ZjI4zqTaxveH2/tTlzS1wFp+7ncxNZaIEWYg3lzZRHkKf5zPT/MnEG6WL0BhHMJUabkh8GeU5NL5j+rEUCb7Ug==" + }, "dot-prop": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", @@ -1967,6 +2117,11 @@ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, + "enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -2659,6 +2814,11 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, "events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", @@ -2825,11 +2985,42 @@ } } }, + "express-session": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.1.tgz", + "integrity": "sha512-UbHwgqjxQZJiWRTMyhvWGvjBQduGCSBDhhZXYenziMFjxst5rMV+aJZ6hKPHZnPyHGsrqRICxtX8jtEbm/z36Q==", + "requires": { + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.0", + "uid-safe": "~2.1.5" + }, + "dependencies": { + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + } + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extend-shallow": { "version": "3.0.2", @@ -2952,6 +3143,16 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-text-encoding": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" + }, + "fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" + }, "fb-watchman": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", @@ -2961,6 +3162,11 @@ "bser": "^2.0.0" } }, + "fecha": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz", + "integrity": "sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==" + }, "figures": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz", @@ -3053,6 +3259,11 @@ "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", "dev": true }, + "fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, "follow-redirects": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", @@ -3707,6 +3918,64 @@ "wide-align": "^1.1.0" } }, + "gaxios": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.0.4.tgz", + "integrity": "sha512-97NmFuMETFQh6gqPUxkqjxRMjmY8aRKRMphIkgO/b90AbCt5wAVuXsp8oWjIXlLN2pIK/fsXD8edcM7ULkFMLg==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + }, + "dependencies": { + "agent-base": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", + "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", + "requires": { + "debug": "4" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "gcp-metadata": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.1.4.tgz", + "integrity": "sha512-5J/GIH0yWt/56R3dNaNWPGQ/zXsZOddYECfJaqxFWgrZ9HC2Kvc5vl9upOgUUHKzURjAVf2N+f6tEJiojqXUuA==", + "requires": { + "gaxios": "^3.0.0", + "json-bigint": "^1.0.0" + } + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -3783,6 +4052,93 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, + "google-auth-library": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.0.5.tgz", + "integrity": "sha512-Wj31lfTm2yR4g3WfOOB1Am1tt478Xq9OvzTPQJi17tn/I9R5IcsxjANBsE93nYmxYxtwDedhOdIb8l3vSPG49Q==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^3.0.0", + "gcp-metadata": "^4.1.0", + "gtoken": "^5.0.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "google-p12-pem": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.2.tgz", + "integrity": "sha512-tbjzndQvSIHGBLzHnhDs3cL4RBjLbLXc2pYvGH+imGVu5b4RMAttUTdnmW2UH0t11QeBTXZ7wlXPS7hrypO/tg==", + "requires": { + "node-forge": "^0.9.0" + } + }, + "googleapis": { + "version": "56.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-56.0.0.tgz", + "integrity": "sha512-qf8Xp7QV7h81nWzspRCMIEnFsG6aBrsomkl0H/578G8/8lIZ/tt+6yDexVTPsR6XDo/jhSb4rtN9462AmLhQxA==", + "requires": { + "google-auth-library": "^6.0.0", + "googleapis-common": "^4.4.0" + } + }, + "googleapis-common": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-4.4.0.tgz", + "integrity": "sha512-Bgrs8/1OZQFFIfVuX38L9t48rPAkVUXttZy6NzhhXxFOEMSHgfFIjxou7RIXOkBHxmx2pVwct9WjKkbnqMYImQ==", + "requires": { + "extend": "^3.0.2", + "gaxios": "^3.0.0", + "google-auth-library": "^6.0.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^8.0.0" + }, + "dependencies": { + "uuid": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz", + "integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==" + } + } + }, "got": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", @@ -3814,6 +4170,43 @@ "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", "dev": true }, + "gtoken": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.2.tgz", + "integrity": "sha512-lull70rHCTvRTmAt+R/6W5bTtx4MjHku7AwJwK5fGqhOmygcZud0nrZcX+QUNfBJwCzqy7S5i1Bc4NYnr5PMMA==", + "requires": { + "gaxios": "^3.0.0", + "google-p12-pem": "^3.0.0", + "jws": "^4.0.0", + "mime": "^2.2.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" + } + } + }, "handlebars": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz", @@ -3927,12 +4320,87 @@ } } }, + "helmet": { + "version": "3.23.3", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.23.3.tgz", + "integrity": "sha512-U3MeYdzPJQhtvqAVBPntVgAvNSOJyagwZwyKsFdyRa8TV3pOKVFljalPOCxbw5Wwf2kncGhmP0qHjyazIdNdSA==", + "requires": { + "depd": "2.0.0", + "dont-sniff-mimetype": "1.1.0", + "feature-policy": "0.3.0", + "helmet-crossdomain": "0.4.0", + "helmet-csp": "2.10.0", + "hide-powered-by": "1.1.0", + "hpkp": "2.0.0", + "hsts": "2.2.0", + "nocache": "2.1.0", + "referrer-policy": "1.2.0", + "x-xss-protection": "1.3.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, + "helmet-crossdomain": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz", + "integrity": "sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA==" + }, + "helmet-csp": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.10.0.tgz", + "integrity": "sha512-Rz953ZNEFk8sT2XvewXkYN0Ho4GEZdjAZy4stjiEQV3eN7GDxg1QKmYggH7otDyIA7uGA6XnUMVSgeJwbR5X+w==", + "requires": { + "bowser": "2.9.0", + "camelize": "1.0.0", + "content-security-policy-builder": "2.1.0", + "dasherize": "2.0.0" + } + }, + "hide-powered-by": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.1.0.tgz", + "integrity": "sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg==" + }, "hosted-git-info": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", "dev": true }, + "hpkp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hpkp/-/hpkp-2.0.0.tgz", + "integrity": "sha1-EOFCJk52IVpdMMROxD3mTe5tFnI=" + }, + "hpp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hpp/-/hpp-0.2.3.tgz", + "integrity": "sha512-4zDZypjQcxK/8pfFNR7jaON7zEUpXZxz4viyFmqjb3kWNWAHsLEUmWXcdn25c5l76ISvnD6hbOGO97cXUI3Ryw==", + "requires": { + "lodash": "^4.17.12", + "type-is": "^1.6.12" + } + }, + "hsts": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hsts/-/hsts-2.2.0.tgz", + "integrity": "sha512-ToaTnQ2TbJkochoVcdXYm4HOCliNozlviNsg+X2XQLQvZNI/kCHR9rZxVYpJB3UPcHz80PgxRyWQ7PdU1r+VBQ==", + "requires": { + "depd": "2.0.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, "html-encoding-sniffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", @@ -5189,6 +5657,14 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -5308,6 +5784,11 @@ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, + "kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, "latest-version": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", @@ -5364,8 +5845,7 @@ "lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash.includes": { "version": "4.3.0", @@ -5408,6 +5888,25 @@ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, + "logform": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz", + "integrity": "sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==", + "requires": { + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "triple-beam": "^1.3.0" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -5605,6 +6104,11 @@ "minimist": "0.0.8" } }, + "moment": { + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", + "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==" + }, "mongodb": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.3.3.tgz", @@ -5783,11 +6287,20 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "nocache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz", + "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" + }, "node-fetch": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", - "dev": true + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, + "node-forge": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz", + "integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==" }, "node-int64": { "version": "0.4.0", @@ -6058,6 +6571,14 @@ "wrappy": "1" } }, + "one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "requires": { + "fn.name": "1.x.x" + } + }, "onetime": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", @@ -6535,6 +7056,11 @@ "util.promisify": "^1.0.0" } }, + "referrer-policy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz", + "integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA==" + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -6747,6 +7273,11 @@ "glob": "^7.1.3" } }, + "rndm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz", + "integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w=" + }, "rsvp": { "version": "4.8.5", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", @@ -6975,6 +7506,21 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + } + } + }, "sisteransi": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.4.tgz", @@ -7373,6 +7919,11 @@ "tweetnacl": "~0.14.0" } }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, "stack-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", @@ -7657,6 +8208,11 @@ "require-main-filename": "^2.0.0" } }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -7793,12 +8349,22 @@ "punycode": "^2.1.0" } }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", "dev": true }, + "tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==" + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -7870,6 +8436,14 @@ } } }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, "undefsafe": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", @@ -8015,6 +8589,11 @@ "prepend-http": "^1.0.1" } }, + "url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=" + }, "urlgrey": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/urlgrey/-/urlgrey-0.4.4.tgz", @@ -8205,6 +8784,64 @@ } } }, + "winston": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz", + "integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==", + "requires": { + "@dabh/diagnostics": "^2.0.2", + "async": "^3.1.0", + "is-stream": "^2.0.0", + "logform": "^2.2.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.4.0" + }, + "dependencies": { + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "winston-transport": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz", + "integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==", + "requires": { + "readable-stream": "^2.3.7", + "triple-beam": "^1.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } + } + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -8296,6 +8933,11 @@ "async-limiter": "~1.0.0" } }, + "x-xss-protection": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.3.0.tgz", + "integrity": "sha512-kpyBI9TlVipZO4diReZMAHWtS0MMa/7Kgx8hwG/EuZLiA6sg4Ah/4TRdASHhRRN3boobzcYgFRUFSgHRge6Qhg==" + }, "xdg-basedir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", diff --git a/package.json b/package.json index dd8f1d4..f99e0e6 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@sendgrid/mail": "^7.0.0", + "app-root-path": "^3.0.0", "aws-sdk": "^2.691.0", "bcrypt": "^3.0.6", "body-parser": "^1.19.0", @@ -21,13 +22,16 @@ "dotenv": "^8.2.0", "ejs": "~2.6.1", "express": "^4.16.4", + "googleapis": "^56.0.0", "http-status-codes": "^1.4.0", "jsonwebtoken": "^8.5.1", + "moment": "^2.27.0", "mongoose": "^5.7.7", "morgan": "^1.9.1", "multer": "^1.4.2", "socket.io": "^2.3.0", - "validator": "^10.11.0" + "validator": "^10.11.0", + "winston": "^3.3.3" }, "jest": { "testEnvironment": "node", diff --git a/test/organisation.test.js b/test/organisation.test.js index 12f966b..f159266 100644 --- a/test/organisation.test.js +++ b/test/organisation.test.js @@ -108,14 +108,14 @@ describe('POST /org/', () => { .set('Authorization', `Bearer ${token}`) .send(testOrg) .expect(HttpStatus.CREATED) - orgId = response.body.org._id + orgId = response.body.orgData._id /** DB must be changed **/ - const org = await Organization.findById(response.body.org._id) + const org = await Organization.findById(response.body.orgData._id) expect(org).not.toBeNull() /** Check the response **/ expect(response.body).toMatchObject({ - org: { + orgData: { isArchived: false, _id: `${orgId}`, name: `${testOrg.name}`, diff --git a/test/proposal.test.js b/test/proposal.test.js index 1098d9f..5a0b70d 100644 --- a/test/proposal.test.js +++ b/test/proposal.test.js @@ -1,4 +1,4 @@ -const app = require('../app') +const app = require('../app').app const mongoose = require('mongoose') const jwt = require('jsonwebtoken') const HttpStatus = require('http-status-codes') @@ -195,3 +195,12 @@ test('Should return the proposal by the given Id', async (done) => { done() }) + +afterAll(async () => { + // avoid jest open handle error + await new Promise((resolve) => setTimeout(() => resolve(), 500)) + // close server + await server.close() + // Closing the DB connection allows Jest to exit successfully. + await mongoose.connection.close() +}) diff --git a/test/user.test.js b/test/user.test.js index 04534ec..f2df44a 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -185,6 +185,38 @@ test('Should not get profile for unauthenticated user', async () => { .expect(HttpStatus.UNAUTHORIZED) }) +/** Should update user profile */ +test('Should update profile or authenticated user', async () => { + await request(app) + .patch('/user/me') + .set('Authorization', `Bearer ${testUser.tokens[0].token}`) + .send({ + email: 'updated@example.com' + }) + .expect(HttpStatus.OK) +}) + +/** Should fail to make updates that are not allowed to user profile */ +test('Should be able to make only allowed updates to authenticated user', async () => { + await request(app) + .patch('/user/me') + .set('Authorization', `Bearer ${testUser.tokens[0].token}`) + .send({ + gender: 'Male' + }) + .expect(HttpStatus.BAD_REQUEST) +}) + +/** Should Fail updating profile of unauthenticate user */ +test('Should not update profile or unauthenticated user', async () => { + await request(app) + .patch('/user/me') + .send({ + email: 'updated@example.com' + }) + .expect(HttpStatus.UNAUTHORIZED) +}) + /** Delete authenticated user profile */ test('Should delete profile of authenticated user', async () => { await request(app) @@ -209,7 +241,7 @@ test('Should not delete profile of unauthenticated user', async () => { /** Forgot password request **/ test('Should send the request to change the password ', async () => { const response = await request(app) - .post('/user/password_reset') + .patch('/user/password_reset/request') .send({ email: `${testUser.email}` }) @@ -221,7 +253,7 @@ test('Should send the request to change the password ', async () => { /* Password update */ test('Should update the password ', async () => { await request(app) - .post(`/user/password_reset/${passwordToken}`) + .patch(`/user/password_reset/${passwordToken}`) .send({ password: 'newPassword', id: testUserId @@ -243,7 +275,7 @@ test('Should activate the account ', async (done) => { /* Get invite link */ test('Should generate an invite link and send', async () => { const response = await request(app) - .get('/user/invite') + .get('/user/invite?role=user') .set('Authorization', `Bearer ${testUser.tokens[0].token}`) .send() .expect(HttpStatus.OK) @@ -258,7 +290,7 @@ test('Should validate the invite link token ', async () => { await request(app) .get(`/user/invite/${inviteToken}`) .send() - .expect(HttpStatus.OK) + .expect(HttpStatus.MOVED_TEMPORARILY) }) /* Logout the user */