commit c87c7f98d340d73808f3174585ab471b5ef53c2d Author: hippoz Date: Mon Oct 5 20:36:03 2020 +0300 Initial commit diff --git a/api/v1/authfunctions.js b/api/v1/authfunctions.js new file mode 100755 index 0000000..1f9d088 --- /dev/null +++ b/api/v1/authfunctions.js @@ -0,0 +1,64 @@ +const User = require('../../models/User'); +const secret = require('../../secret'); +const config = require('../../config'); + +const jwt = require('jsonwebtoken'); + +const redirect = (res, status=401, url=undefined) => { + if (!url) { + res.status(status).json({ + error: true, + message: 'ERROR_ACCESS_DENIED' + }); + return; + } + res.redirect(url); +} + +function authenticateEndpoint(callback, url=undefined, minPermissionLevel=config.roleMap.RESTRICTED) { + return (req, res) => { + const token = req.cookies.token; + if (!token) { + redirect(res, 403, url); + return; + } + + jwt.verify(token, secret.jwtPrivateKey, {}, async (err, data) => { + if (err) { + redirect(res, 401, url); + return; + } + + if (!data) { + redirect(res, 401, url); + return + } + + if (!data.username) { + redirect(res, 401, url); + return; + } + + const user = await User.findByUsername(data.username); + + if (!user) { + redirect(res, 401, url); + return; + } + + let permissionLevel = config.roleMap[user.role]; + if (!permissionLevel) { + permissionLevel = 0; + } + + if (permissionLevel < minPermissionLevel) { + redirect(res, 401, url); + return; + } + + callback(req, res, user); + }); + }; +} + +module.exports = { authenticateEndpoint }; \ No newline at end of file diff --git a/api/v1/content.js b/api/v1/content.js new file mode 100755 index 0000000..25e29aa --- /dev/null +++ b/api/v1/content.js @@ -0,0 +1,140 @@ +const User = require('../../models/User'); +const Category = require('../../models/Category'); +const Post = require('../../models/Post'); +const config = require('../../config'); +const secret = require('../../secret'); + +const { authenticateEndpoint } = require('./authfunctions'); + +const mongoose = require('mongoose'); +const { body, query, param, validationResult } = require('express-validator'); +const express = require('express'); + +const app = express.Router(); +mongoose.connect(config.mongoUrl, {useNewUrlParser: true, useUnifiedTopology: true}); + +const rateLimit = require("express-rate-limit"); + +const createLimiter = rateLimit({ + windowMs: 2 * 60 * 1000, + max: 3, +}); + +app.post('/category/create', [ + createLimiter, + body('title').not().isEmpty().trim().isLength({ min: 3, max: 32 }).escape() +], authenticateEndpoint(async (req, res, user) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + res.status(400).json({ error: true, message: 'ERROR_REQUEST_INVALID_DATA', errors: errors.array() }); + return; + } + + const title = req.body.title; + const category = await Category.create({ + title: title, + creator: user._id, + posts: [] + }); + + res.status(200).json({ + error: false, + message: 'SUCCESS_CATEGORY_CREATED', + category: { + title: category.title, + creator: category.creator, + posts: category.posts + } + }); +}, undefined, config.roleMap.USER)); + +app.post('/post/create', [ + createLimiter, + body('category').not().isEmpty().trim().escape().isLength({ min: 24, max: 24 }), + body('title').not().isEmpty().trim().isLength({ min: 3, max: 32 }).escape(), + body('body').not().isEmpty().trim().isLength({ min: 3, max: 1000 }).escape(), +], authenticateEndpoint(async (req, res, user) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + res.status(400).json({ error: true, message: 'ERROR_REQUEST_INVALID_DATA', errors: errors.array() }); + return; + } + + const category = req.body.category; + const title = req.body.title; + const content = req.body.body; + + const post = new Post(); + post.title = title; + post.body = content; + post.creator = user._id; + post.category = category; + + const r = await Category.updateOne({ + _id: category + }, { + $push: { posts: post } + }); + + if (r.n < 1) { + res.status(404).json({ + error: true, + message: 'ERROR_CATEGORY_NOT_FOUND' + }); + return; + } + + res.status(200).json({ + error: false, + message: 'SUCCESS_POST_CREATED' + }); +}, undefined, config.roleMap.USER)); + +app.get('/category/:category/info', [ + param('category').not().isEmpty().trim().escape().isLength({ min: 24, max: 24 }) +], authenticateEndpoint(async (req, res, user) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + res.status(400).json({ error: true, message: 'ERROR_REQUEST_INVALID_DATA', errors: errors.array() }); + return; + } + + const categoryId = req.params.category; + const category = await Category.findById(categoryId).populate('posts.creator'); + + if (!category) { + res.status(404).json({ + error: true, + message: 'ERROR_CATEGORY_NOT_FOUND' + }); + return; + } + + res.status(200).json({ + error: false, + message: 'SUCCESS_CATEGORY_DATA_FETCHED', + category: { + title: category.title, + creator: category.creator, + posts: category.posts + } + }); +})); + +app.get('/category/list', authenticateEndpoint(async (req, res, user) => { + let count = parseInt(req.query.count); + if (!Number.isInteger(count)) { + count = 10; + } + + // TODO: This is probably not efficient + const categories = await Category.find().sort({ _id: -1 }).limit(count).select('-posts -__v').populate('creator', 'username _id'); + + res.status(200).json({ + error: false, + message: 'SUCCESS_CATEGORY_LIST_FETCHED', + categories + }); +})); + +module.exports = app; \ No newline at end of file diff --git a/api/v1/index.js b/api/v1/index.js new file mode 100755 index 0000000..b483573 --- /dev/null +++ b/api/v1/index.js @@ -0,0 +1,16 @@ +const usersAPI = require('./users'); +const contentAPI = require('./content'); + +const express = require('express'); + +const app = express.Router(); + +app.use('/users', usersAPI); +app.use('/content', contentAPI); + +app.get('/', (req, res) => { + // TODO: Add more checks for this, or maybe remove + res.json({ apiStatus: 'OK', apis: [ 'users', 'content' ] }); +}); + +module.exports = app; \ No newline at end of file diff --git a/api/v1/users.js b/api/v1/users.js new file mode 100755 index 0000000..665d231 --- /dev/null +++ b/api/v1/users.js @@ -0,0 +1,191 @@ +const User = require('../../models/User'); +const config = require('../../config'); +const secret = require('../../secret'); + +const { authenticateEndpoint } = require('./authfunctions'); + +// TODO: Might want to use something else (https://blog.benpri.me/blog/2019/01/13/why-you-shouldnt-be-using-bcrypt-and-scrypt/) +const bcrypt = require('bcrypt'); +const mongoose = require('mongoose'); +const { body, query, param, validationResult } = require('express-validator'); +const express = require('express'); +const jwt = require('jsonwebtoken'); + +const app = express.Router(); +mongoose.connect(config.mongoUrl, {useNewUrlParser: true, useUnifiedTopology: true}); + +const rateLimit = require("express-rate-limit"); + +const createAccountLimiter = rateLimit({ + windowMs: 60 * 60 * 1000, // 1 hour window + max: 5, // start blocking after 5 requests + message: "You are being rate limited" +}); + +app.post('/account/create', [ + createAccountLimiter, + body('username').not().isEmpty().trim().isLength({ min: 3, max: 32 }).isAlphanumeric(), + body('email').not().isEmpty().isEmail().normalizeEmail(), + body('password').not().isEmpty().isLength({ min: 8, max: 128 }) +], async (req, res) => { + try { + const errors = validationResult(req); + if (!errors.isEmpty()) { + res.status(400).json({ error: true, message: 'ERROR_REQUEST_INVALID_DATA', errors: errors.array() }); + return; + } + + const username = req.body.username; + + const existingUser = await User.findByUsername(username); + if (existingUser) { + res.status(400).json({ error: true, message: 'ERROR_REQUEST_USERNAME_EXISTS', errors: [{ value: username, msg: 'Username exists', param: 'username', location: 'body' }] }); + return; + } + + const unhashedPassword = req.body.password; + const email = req.body.email; + const startingRole = 'USER'; + + + const hashedPassword = await bcrypt.hash(unhashedPassword, config.bcryptRounds); + + const user = await User.create({ + username, + email, + password: hashedPassword, + role: startingRole + }); + + res.status(200).json({ + error: false, + message: 'SUCCESS_USER_CREATED', + user: { + _id: user._id, + username: user.username, + email: user.email, + role: user.role, + permissionLevel: config.roleMap[user.role] + } + }); + } catch (e) { + console.error('Internal server error', e); + res.status(500).json({ error: true, message: 'INTERNAL_SERVER_ERROR' }); + return; + } + +}); + +app.post('/token/create', [ + createAccountLimiter, + body('username').not().isEmpty().trim().isAlphanumeric(), + body('password').not().isEmpty() +], async (req, res) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + res.status(400).json({ error: true, message: 'ERROR_REQUEST_LOGIN_INVALID' }); + return; + } + + const username = req.body.username; + + const existingUser = await User.findByUsername(username); + if (!existingUser) { + res.status(403).json({ error: true, message: 'ERROR_REQUEST_LOGIN_INVALID' }); + return; + } + + const password = req.body.password; + + let passwordCheck; + try { + passwordCheck = await bcrypt.compare(password, existingUser.password); + } catch(e) { + passwordCheck = false; + } + if (!passwordCheck) { + res.status(403).json({ error: true, message: 'ERROR_REQUEST_LOGIN_INVALID' }); + return; + } + + jwt.sign({ username }, secret.jwtPrivateKey, { expiresIn: '3h' }, (err, token) => { + if (err) { + res.status(500).json({ + error: true, + message: 'INTERNAL_SERVER_ERROR' + }); + return; + } + + // TODO: Ugly fix for setting httponly cookies + if (req.body.alsoSetCookie) { + res.cookie('token', token, { + maxAge: 3 * 60 * 60 * 1000, httpOnly: true, domain: config.address, + }); + } + + res.status(200).json({ + error: false, + message: 'SUCCESS_TOKEN_CREATED', + user: { + _id: existingUser._id, + username: existingUser.username, + email: existingUser.email, + role: existingUser.role, + permissionLevel: config.roleMap[existingUser.role] + }, + token + }); + }); +}); + +app.get('/current/info', authenticateEndpoint((req, res, user) => { + res.status(200).json({ + error: false, + message: 'SUCCESS_USER_DATA_FETCHED', + user: { + _id: user._id, + username: user.username, + email: user.email, + role: user.role, + permissionLevel: config.roleMap[user.role] + }, + }); +}, undefined, 0)); + +app.get('/user/:userid/info', [ + param('userid').not().isEmpty().trim().escape().isLength({ min: 24, max: 24 }) +], authenticateEndpoint(async (req, res, user) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + res.status(400).json({ error: true, message: 'ERROR_REQUEST_INVALID_DATA', errors: errors.array() }); + return; + } + + const userid = req.params.userid; + if (!userid) { + res.sendStatus(400).json({ + error: true, + message: 'ERROR_REQUEST_INVALID_DATA' + }); + return; + } + const otherUser = await User.findById(userid); + + res.status(200).json({ + error: false, + message: 'SUCCESS_USER_DATA_FETCHED', + user: { + _id: otherUser._id, + username: otherUser.username, + role: otherUser.role + }, + }); +})); + +app.post('/browser/token/clear', authenticateEndpoint((req, res, user) => { + res.clearCookie('token'); + res.sendStatus(200); +})); + +module.exports = app; \ No newline at end of file diff --git a/apitest.rest b/apitest.rest new file mode 100755 index 0000000..a7f6798 --- /dev/null +++ b/apitest.rest @@ -0,0 +1,57 @@ +POST http://localhost:3000/api/v1/users/account/create +Content-Type: application/json + +{ + "username": "test", + "password": "testtesttest", + "email": "test@test.test" +} + +### + +POST http://localhost:3000/api/v1/users/token/create +Content-Type: application/json + +{ + "username": "test", + "password": "testtesttest" +} + +### + +GET http://localhost:3000/api/v1/users/current/info +Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJpYXQiOjE2MDA1MTA2MTEsImV4cCI6MTYwMDUyMTQxMX0.q85p94FLPR4fxZ4O5pmalEEjU9Hyr9js63u6LgoCQCw + +### + +POST http://localhost:3000/api/v1/content/category/create +Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJpYXQiOjE2MDA1MTA2MTEsImV4cCI6MTYwMDUyMTQxMX0.q85p94FLPR4fxZ4O5pmalEEjU9Hyr9js63u6LgoCQCw +Content-Type: application/json + +{ + "name": "testing1" +} + +### + +POST http://localhost:3000/api/v1/content/post/create +Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJpYXQiOjE2MDA1MTA2MTEsImV4cCI6MTYwMDUyMTQxMX0.q85p94FLPR4fxZ4O5pmalEEjU9Hyr9js63u6LgoCQCw +Content-Type: application/json + +{ + "category": "5f65e1f05c3cdd86400f43ec", + "title": "Test title", + "content": "Test content!!!" +} + +### + +GET http://localhost:3000/api/v1/content/category/5f65e1f05c3cdd86400f43ec/info +Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJpYXQiOjE2MDA1MTA2MTEsImV4cCI6MTYwMDUyMTQxMX0.q85p94FLPR4fxZ4O5pmalEEjU9Hyr9js63u6LgoCQCw +Content-Type: application/json + +### + +GET http://localhost:3000/api/v1/content/category/list +Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJpYXQiOjE2MDA1MTA2MTEsImV4cCI6MTYwMDUyMTQxMX0.q85p94FLPR4fxZ4O5pmalEEjU9Hyr9js63u6LgoCQCw +Content-Type: application/json \ No newline at end of file diff --git a/app/app.html b/app/app.html new file mode 100755 index 0000000..78998a6 --- /dev/null +++ b/app/app.html @@ -0,0 +1,138 @@ + + + + + + App + + + + + + + + + + + + +
+
+ + Create category + + + + + + + + + Close + Create + + + + + + Create post for {{ selection.category.title }} + + + + + + + + + + + + + + Close + Create + + + + + + {{ viewingProfile.username }} + + +

Role: {{ viewingProfile.role }}

+ + + Close + +
+
+ + +

Brainlet

+ + {{ loggedInUser.username }} + + Manage account + + +
+ + +

Browsing category: {{ selection.category.title }}

+

Browsing {{ selection.category.title }}

+ Back + Refresh +
+ +
+ + +
+ by {{ post.creator.username }} +
+ + + + + {{ button.text }} + +
+
+ + + + add + edit + + + + + add + Create a new post + + + + category + Create a new category + + + +
+ + {{ snackbarNotification }} + {{ snackbarButtonText }} + +
+ + \ No newline at end of file diff --git a/app/auth.html b/app/auth.html new file mode 100755 index 0000000..aa3f41b --- /dev/null +++ b/app/auth.html @@ -0,0 +1,71 @@ + + + + + + Auth + + + + + + + + + +
+
+ +

Brainlet

+ Home +
+
+
+ + +
{{ modeName }}
+
+ + +
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+
+ + + Log in instead + Sign up instead + + Sign up + Log in + + Log out + +
+ +
+ + + {{ snackbarNotification }} + {{ snackbarButtonText }} + +
+ + \ No newline at end of file diff --git a/app/resources/css/base.css b/app/resources/css/base.css new file mode 100755 index 0000000..e69de29 diff --git a/app/resources/js/app.js b/app/resources/js/app.js new file mode 100755 index 0000000..a85adf2 --- /dev/null +++ b/app/resources/js/app.js @@ -0,0 +1,357 @@ +Vue.use(VueMaterial.default); + +const getCreatePostError = (json) => { + switch (json.message) { + case 'ERROR_REQUEST_INVALID_DATA': { + switch (json.errors[0].param) { + case 'title': { + return 'Invalid title. Must be between 3 and 32 characters.'; + } + case 'body': { + return 'Invalid content. Must be between 3 and 1000 characters'; + } + case 'category': { + return 'Invalid category. Something went wrong.'; + } + default: { + return 'Invalid value sent to server. Something went wrong.'; + } + } + } + + case 'ERROR_CATEGORY_NOT_FOUND': { + return 'The category you tried to post to no longer exists.'; + } + + case 'ERROR_ACCESS_DENIED': { + return 'You are not allowed to perform this action.' + } + + default: { + return 'Unknown error. Something went wrong.'; + } + } +} + +const getCreateCategoryError = (json) => { + switch (json.message) { + case 'ERROR_REQUEST_INVALID_DATA': { + switch (json.errors[0].param) { + case 'title': { + return 'Invalid title. Title must be between 3 and 32 characters.'; + } + } + } + + case 'ERROR_ACCESS_DENIED': { + return 'You are not allowed to perform this action.' + } + + default: { + return 'Unknown error. Something went wrong.'; + } + } +} + +const app = new Vue({ + el: '#app', + data: { + showSnackbarNotification: false, + snackbarNotification: '', + snackbarNotificationDuration: 999999, + snackbarButtonText: 'Ok', + loggedInUser: {}, + showApp: false, + menuVisible: false, + selection: { + category: { + title: '', + browsing: false, + _id: undefined, + isCategory: false + }, + posts: [] + }, + cardButtons: [], + dialog: { + show: { + createPost: false, + createCategory: false + }, + text: { + createPost: { + title: '', + body: '' + }, + createCategory: { + title: '' + } + } + }, + viewingProfile: { + show: false, + _id: '', + username: '', + role: '' + } + }, + mounted: async function() { + const res = await fetch(`${window.location.origin}/api/v1/users/current/info`, { + method: 'GET', + headers: { + 'Accept': 'application/json', + }, + credentials: 'include' + }); + + if (res.status === 200) { + const json = await res.json(); + if (json.user.permissionLevel >= 1) { + this.loggedInUser = json.user; + this.showApp = true; + this.browseCategories(); + } else { + this.showApp = false; + this.snackbarEditButton('Manage', () => { + window.location.href = `${window.location.origin}/auth.html`; + this.resetSnackbarButton(); + this.showSnackbarNotification = false; + }); + this.notification('Your account does not have the required permissions to enter this page'); + } + } else { + this.showApp = false; + this.snackbarEditButton('Manage', () => { + window.location.href = `${window.location.origin}/auth.html`; + this.resetSnackbarButton(); + this.showSnackbarNotification = false; + }); + this.notification('You are not logged in or your session is invalid'); + } + }, + methods: { + navigateToAccountManager() { + window.location.href = `${window.location.origin}/auth.html`; + }, + snackbarButtonAction: function() { + this.showSnackbarNotification = false; + }, + snackbarButtonClick: function() { + this.snackbarButtonAction(); + }, + snackbarEditButton: function(buttonText="Ok", action) { + this.snackbarButtonText = buttonText; + this.snackbarButtonAction = action; + }, + resetSnackbarButton: function() { + this.snackbarButtonText = 'Ok'; + this.snackbarButtonAction = () => { + this.showSnackbarNotification = false; + }; + }, + notification: function(text) { + this.snackbarNotification = text; + this.showSnackbarNotification = true; + }, + button: function(text, click) { + this.cardButtons.push({ text, click }); + }, + refresh: function() { + if (this.selection.category.title === 'categories' && this.selection.category.isCategory === false) { + this.browseCategories(); + } else { + this.browse(this.selection.category); + } + }, + viewProfile: async function(id) { + // TODO: this just returns the username for now + const res = await fetch(`${window.location.origin}/api/v1/users/user/${id}/info`, { + method: 'GET', + headers: { + 'Accept': 'application/json', + }, + credentials: 'include' + }); + + if (res.status === 200) { + const json = await res.json(); + this.viewingProfile.username = json.user.username; + this.viewingProfile._id = json.user._id; + this.viewingProfile.role = json.user.role; + this.viewingProfile.show = true; + } else { + this.resetSnackbarButton(); + this.notification('Failed to fetch user data'); + } + }, + stopBrowsing: function() { + this.selection.category = { + title: '', + browsing: false, + _id: undefined, + isCategory: false + }; + this.selection.posts = []; + this.cardButtons = []; + }, + showCreatePostDialog: function() { + if (!this.selection.category.isCategory) { + this.resetSnackbarButton(); + this.notification('You are not in a category'); + return; + } + + this.dialog.show.createPost = true; + }, + createPost: async function() { + if (!this.selection.category.isCategory) { + this.resetSnackbarButton(); + this.notification('You are not in a category'); + return; + } + + const category = this.selection.category; + const input = this.dialog.text.createPost; + + const res = await fetch(`${window.location.origin}/api/v1/content/post/create`, { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + credentials: 'include', + body: JSON.stringify({ + category: category._id, + title: input.title, + body: input.body + }) + }); + + if (res.status !== 200) { + if (res.status === 401 || res.status === 403) { + this.resetSnackbarButton(); + this.notification('You are not allowed to do that'); + return; + } + if (res.status === 429) { + this.resetSnackbarButton(); + this.notification('Chill! You are posting too much!'); + return; + } + + const json = await res.json(); + this.resetSnackbarButton(); + this.notification(getCreatePostError(json)); + return; + } else { + this.resetSnackbarButton(); + this.notification('Successfully created post'); + this.dialog.show.createPost = false; + this.browse(this.selection.category); + return; + } + }, + createCategory: async function() { + const input = this.dialog.text.createCategory; + + const res = await fetch(`${window.location.origin}/api/v1/content/category/create`, { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + credentials: 'include', + body: JSON.stringify({ + title: input.title + }) + }); + + if (res.status !== 200) { + if (res.status === 401 || res.status === 403) { + this.resetSnackbarButton(); + this.notification('You are not allowed to do that'); + return; + } + if (res.status === 429) { + this.resetSnackbarButton(); + this.notification('Chill! You are posting too much!'); + return; + } + + const json = await res.json(); + this.resetSnackbarButton(); + this.notification(getCreateCategoryError(json)); + return; + } else { + this.resetSnackbarButton(); + this.notification('Successfully created category'); + this.dialog.show.createCategory = false; + this.browseCategories(); + return; + } + }, + browse: async function(category) { + const { _id, title } = category; + + const res = await fetch(`${window.location.origin}/api/v1/content/category/${_id}/info`, { + method: 'GET', + headers: { + 'Accept': 'application/json', + }, + credentials: 'include' + }); + + if (res.status === 200) { + const json = await res.json(); + + this.selection.category.title = title; + this.selection.category._id = _id; + this.selection.category.browsing = true; + this.selection.category.isCategory = true; + this.selection.posts = []; + + this.cardButtons = []; + + for (let i = 0; i < json.category.posts.length; i++) { + const v = json.category.posts[i]; + this.selection.posts.push({ title: v.title, body: v.body, _id: v._id, creator: v.creator }); + } + } else { + this.resetSnackbarButton(); + this.notification('Failed to fetch category'); + } + }, + browseCategories: async function() { + const res = await fetch(`${window.location.origin}/api/v1/content/category/list?count=50`, { + method: 'GET', + headers: { + 'Accept': 'application/json', + }, + credentials: 'include' + }); + + if (res.status === 200) { + const json = await res.json(); + + this.selection.category.title = 'categories'; + this.selection.category.browsing = true; + this.selection.category.isCategory = false; + this.selection.posts = []; + + this.cardButtons = []; + + this.button('View', (post) => { + this.browse(post); + }); + + for (let i = 0; i < json.categories.length; i++) { + const v = json.categories[i]; + this.selection.posts.push({ title: v.title, body: '', _id: v._id, creator: v.creator }); + } + } else { + this.resetSnackbarButton(); + this.notification('Failed to fetch category list'); + } + } + } +}); \ No newline at end of file diff --git a/app/resources/js/auth.js b/app/resources/js/auth.js new file mode 100755 index 0000000..5b79b8c --- /dev/null +++ b/app/resources/js/auth.js @@ -0,0 +1,227 @@ +Vue.use(VueMaterial.default); + +const getLoginMessageFromError = (json) => { + switch (json.message) { + case 'ERROR_REQUEST_LOGIN_INVALID': { + return 'Invalid username or password.'; + } + + case 'ERROR_ACCESS_DENIED': { + return 'You are not allowed to perform this action.' + } + + default: { + return 'Unknown error. Something went wrong.' + } + } +} + +const getSignupMessageFromError = (json) => { + switch (json.message) { + case 'ERROR_REQUEST_INVALID_DATA': { + + switch (json.errors[0].param) { + case 'username': { + return 'Invalid username. Username must be between 3 and 32 characters long, and be alphanumeric.'; + } + case 'password': { + return 'Invalid password. Password must be at least 8 characters long and at most 128 characters.'; + } + case 'email': { + return 'Invalid email.'; + } + + default: { + return 'Invalid value sent to server. Something went wrong.'; + } + } + + } + + case 'ERROR_ACCESS_DENIED': { + return 'You are not allowed to perform this action.' + } + + case 'ERROR_REQUEST_USERNAME_EXISTS': { + return 'That username is taken.'; + } + + default: { + return 'Unknown error. Something went wrong.' + } + } +}; + +const app = new Vue({ + el: '#app', + data: { + usernameInput: '', + emailInput: '', + passwordInput: '', + mode: 'SIGNUP', + showSnackbarNotification: false, + snackbarNotification: '', + snackbarNotificationDuration: 999999, + snackbarButtonText: 'Ok', + successfulLogin: false, + loggedInUser: {} + }, + mounted: async function() { + const res = await fetch(`${window.location.origin}/api/v1/users/current/info`, { + method: 'GET', + headers: { + 'Accept': 'application/json', + }, + credentials: 'include' + }); + + if (res.status === 200) { + const json = await res.json(); + if (json.user.permissionLevel >= 1) { + this.loggedInUser = json.user; + this.mode = 'MANAGE'; + } else { + this.resetSnackbarButton(); + this.notification('Your account has been suspended'); + this.successfulLogin = true; + } + } else { + this.mode = 'SIGNUP'; + } + }, + computed: { + modeName: function() { + switch (this.mode) { + case 'SIGNUP': { + return 'Sign up'; + } + case 'LOGIN': { + return 'Log in'; + } + case 'LOADING': { + return 'Loading...'; + } + case 'MANAGE': { + return this.loggedInUser.username || 'Unknown account'; + } + case 'NONE': { + return ''; + } + default: { + return 'Something went wrong.'; + } + } + } + }, + methods: { + snackbarButtonAction: function() { + this.showSnackbarNotification = false; + }, + snackbarButtonClick: function() { + this.snackbarButtonAction(); + }, + snackbarEditButton: function(buttonText="Ok", action) { + this.snackbarButtonText = buttonText; + this.snackbarButtonAction = action; + }, + resetSnackbarButton: function() { + this.snackbarButtonText = 'Ok'; + this.snackbarButtonAction = () => { + this.showSnackbarNotification = false; + }; + }, + notification: function(text) { + this.snackbarNotification = text; + this.showSnackbarNotification = true; + }, + performTokenRemoval: async function() { + const res = await fetch(`${window.location.origin}/api/v1/users/browser/token/clear`, { + method: 'POST', + credentials: 'include' + }); + + if (res.status === 200) { + this.loggedInUser = {}; + this.snackbarEditButton('Home', () => { + window.location.href = `${window.location.origin}`; + this.resetSnackbarButton(); + this.showSnackbarNotification = false; + }); + this.successfulLogin = true; + this.notification('Successfully logged out'); + } else { + this.resetSnackbarButton(); + this.notification('Could not log out'); + } + }, + navigateHome: function() { + window.location.href = `${window.location.origin}`; + }, + performTokenCreation: async function() { + const res = await fetch(`${window.location.origin}/api/v1/users/token/create`, { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + credentials: 'include', + body: JSON.stringify({ + username: this.usernameInput, + password: this.passwordInput, + alsoSetCookie: true + }) + }); + + const json = await res.json(); + + if (json.error || res.status !== 200) { + this.resetSnackbarButton(); + this.notification(getLoginMessageFromError(json)); + return; + } else { + document.cookie = `token=${json.token}; HTTPOnly=true; max-age=10800`; + this.successfulLogin = true; + this.snackbarEditButton('Home', () => { + this.navigateHome(); + this.resetSnackbarButton(); + this.showSnackbarNotification = false; + }); + this.notification(`Successfully logged into account "${json.user.username}"`); + this.loggedInUser = { username: json.user.username, _id: json.user._id }; + return; + } + }, + performAccountCreation: async function() { + const res = await fetch(`${window.location.origin}/api/v1/users/account/create`, { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + username: this.usernameInput, + email: this.emailInput, + password: this.passwordInput + }) + }); + + const json = await res.json(); + + console.log(json); + + if (json.error || res.status !== 200) { + this.resetSnackbarButton(); + this.notification(getSignupMessageFromError(json)); + return; + } else { + this.snackbarEditButton('Login', () => { + this.mode = 'LOGIN'; + this.resetSnackbarButton(); + this.showSnackbarNotification = false; + }); + this.notification(`Account "${json.user.username}" successfully created`); + return; + } + } + } +}); \ No newline at end of file diff --git a/config.js b/config.js new file mode 100755 index 0000000..6cd61e0 --- /dev/null +++ b/config.js @@ -0,0 +1,14 @@ +module.exports = { + ports: { + mainServerPort: 25, + }, + address: '188.25.251.46', + mongoUrl: 'mongodb://localhost:27017/app', + bcryptRounds: 10, + roleMap: { + 'BANNED': 0, + 'RESTRICTED': 1, + 'USER': 2, + 'ADMIN': 3 + } +}; \ No newline at end of file diff --git a/index.js b/index.js new file mode 100755 index 0000000..d2ae4d9 --- /dev/null +++ b/index.js @@ -0,0 +1,33 @@ +const config = require('./config'); +const apiRoute = require('./api/v1'); + +const express = require('express'); +const cookieParser = require('cookie-parser'); +const cors = require('cors') + +const { authenticateEndpoint } = require('./api/v1/authfunctions'); + +const app = express(); + +app.use(express.urlencoded({ extended: false })); +app.use(express.json()); +app.use(cookieParser()); +app.use(cors({ + origin: `http://${config.address}`, + credentials: true, + optionsSuccessStatus: 200 +})); +app.use('/api/v1', apiRoute); +app.use(express.static('app')); + +app.get('/', authenticateEndpoint((req, res, user) => { + res.redirect('/app.html'); +}, `/auth.html`)); + +app.get('/admin', (req, res) => { + res.send('Keanu chungus wholesome 100 reddit moment 😀i beat up a kid that said minecraft bad 😂and my doggo bit him so i gave him snaccos😉 and we watched pewdiepie together while in elon musk’s cyber truck 😳talking about how superior reddit memers are : “haha emojis bad” 😲i said and keanu reeves came outta nowhere and said “this is wholesome 100, updoot this wholesome boy” 😗so i got alot of updoots and edit: thanks for the gold kind stranger😣. but the kind stranger revealed himself to be baby yoda eating chiccy nuggies😨 and drinking choccy milk😎 so we went to the cinema to see our (communism funny) favorite movies avengers endgame😆 but then thor played fortnite and fortnite bad😡, so then i said “reality is often dissappointing” and then baby yoda replied r/unexpectedthanos and i replied by r/expectedthanos😖 for balance and then danny devito came to pick us up from the cinema😩 and all the insta normies and gay mods stood watching😵 ,as we,superior redditors went home with danny devito to suck on his magnum dong😫 but i said no homo and started sucking,not like those gay mods😮,then the next morning we woke up to MrBeast telling us to plant 69420 million trees😌, me, baby yoda and danny said nice, and then on our way to plant 69420 million trees😊 (nice) we saw a kid doing a tiktok so keanu reeves appeared and said “we have a kid to burn” and i replied “you’re breathtaking”😄 so i said “i need a weapon” and baby yoda gave me an RPG so i blew the kid (DESTRUCTION 100)😎 and posted it on r/memes and r/dankmemes and r/pewdiepiesubmissions and got 1000000000 updoots😘,i’m sure pewds will give me a big pp, then we shat on emoji users😂😂 and started dreaming about girls that will never like me😢 and posted a lie on r/teenagers about how i got a GF after my doggo died by the hands of fortnite players😳 so i exploited his death for updoots😜, but i watched the sunset with the wholesome gang😁 (keanu,danny,Mrbeast, pewds, spongebob,stefan karl , bob ross, steve irwin, baby yoda and other artists that reddit exploits them) [Everyone liked that] WHOLESOME 100 REDDIT 100🤡'); +}); + +app.listen(config.ports.mainServerPort, () => { + console.log(`Main server is listening on port ${config.ports.mainServerPort}`); +}); \ No newline at end of file diff --git a/models/Category.js b/models/Category.js new file mode 100755 index 0000000..70e7562 --- /dev/null +++ b/models/Category.js @@ -0,0 +1,10 @@ +const mongoose = require('mongoose'); +const Post = require('./Post'); + +const Category = mongoose.model('Category', { + title: String, + creator: {type: mongoose.Schema.Types.ObjectId, ref: 'User'}, + posts: [Post.schema] +}); + +module.exports = Category; \ No newline at end of file diff --git a/models/Post.js b/models/Post.js new file mode 100755 index 0000000..5bf1350 --- /dev/null +++ b/models/Post.js @@ -0,0 +1,10 @@ +const mongoose = require('mongoose'); + +const Post = mongoose.model('Post', { + title: String, + body: String, + creator: {type: mongoose.Schema.Types.ObjectId, ref: 'User'}, + categoryId: {type: mongoose.Schema.Types.ObjectId, ref: 'Category'} +}); + +module.exports = Post; \ No newline at end of file diff --git a/models/User.js b/models/User.js new file mode 100755 index 0000000..f0b7add --- /dev/null +++ b/models/User.js @@ -0,0 +1,14 @@ +const mongoose = require('mongoose'); + +const User = mongoose.model('User', { + username: String, + password: String, + email: String, + role: String +}); + +User.findByUsername = async function(username) { + return await User.findOne({ username }).exec(); +}; + +module.exports = User; \ No newline at end of file diff --git a/notes.txt b/notes.txt new file mode 100755 index 0000000..4a3f212 --- /dev/null +++ b/notes.txt @@ -0,0 +1,3 @@ +vulns: + - you can send a malformed request and express will send the full error + - 2 users can have the same name if one of the letters is uppercase \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100755 index 0000000..4e871ab --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1128 @@ +{ + "name": "dictionar", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.0.tgz", + "integrity": "sha512-jB0yCBl4W/kVHM2whjfyqnxTmOHkCX4kHEa5nYKSoGeYe8YrjTYTc87/6bwt1g8cmV0QrbhKriETg9jWtcREhg==", + "requires": { + "node-addon-api": "^3.0.0", + "node-pre-gyp": "0.15.0" + } + }, + "bl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "bson": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", + "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==" + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-parser": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz", + "integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==", + "requires": { + "cookie": "0.4.0", + "cookie-signature": "1.0.6" + } + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "denque": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", + "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "express-rate-limit": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-5.1.3.tgz", + "integrity": "sha512-TINcxve5510pXj4n9/1AMupkj3iWxl3JuZaWhCdYDlZeoCPqweGZrxbrlqTCFb1CT5wli7s8e2SH/Qz2c9GorA==" + }, + "express-validator": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.6.1.tgz", + "integrity": "sha512-+MrZKJ3eGYXkNF9p9Zf7MS7NkPJFg9MDYATU5c80Cf4F62JdLBIjWxy6481tRC0y1NnC9cgOw8FuN364bWaGhA==", + "requires": { + "lodash": "^4.17.19", + "validator": "^13.1.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "requires": { + "minipass": "^2.6.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.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==" + } + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "kareem": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz", + "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==" + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "requires": { + "minipass": "^2.9.0" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "mongodb": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.0.tgz", + "integrity": "sha512-/XWWub1mHZVoqEsUppE0GV7u9kanLvHxho6EvBxQbShXTKYF9trhZC2NzbulRGeG7xMJHD8IOWRcdKx5LPjAjQ==", + "requires": { + "bl": "^2.2.0", + "bson": "^1.1.4", + "denque": "^1.4.1", + "require_optional": "^1.0.1", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + } + }, + "mongoose": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.10.0.tgz", + "integrity": "sha512-5itAvBMVDG4+zTDtuLg/IyoTxEMgvpOSHnigQ9Cyh8LR4BEgMAChJj7JSaGkg+tr1AjCSY9DgSdU8bHqCOoxXg==", + "requires": { + "bson": "^1.1.4", + "kareem": "2.3.1", + "mongodb": "3.6.0", + "mongoose-legacy-pluralize": "1.0.2", + "mpath": "0.7.0", + "mquery": "3.2.2", + "ms": "2.1.2", + "regexp-clone": "1.0.0", + "safe-buffer": "5.2.1", + "sift": "7.0.1", + "sliced": "1.0.1" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "mongoose-legacy-pluralize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", + "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" + }, + "mpath": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.7.0.tgz", + "integrity": "sha512-Aiq04hILxhz1L+f7sjGyn7IxYzWm1zLNNXcfhDtx04kZ2Gk7uvFdgZ8ts1cWa/6d0TQmag2yR8zSGZUmp0tFNg==" + }, + "mquery": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz", + "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==", + "requires": { + "bluebird": "3.5.1", + "debug": "3.1.0", + "regexp-clone": "^1.0.0", + "safe-buffer": "5.1.2", + "sliced": "1.0.1" + }, + "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" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "needle": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz", + "integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==", + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "node-addon-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.0.tgz", + "integrity": "sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg==" + }, + "node-pre-gyp": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz", + "integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.3", + "needle": "^2.5.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4.4.2" + } + }, + "nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", + "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" + }, + "npm-packlist": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "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" + } + }, + "regexp-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", + "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" + }, + "require_optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", + "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "requires": { + "resolve-from": "^2.0.0", + "semver": "^5.1.0" + } + }, + "resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "sift": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz", + "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==" + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "sliced": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", + "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" + }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "optional": true, + "requires": { + "memory-pager": "^1.0.2" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "validator": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.1.1.tgz", + "integrity": "sha512-8GfPiwzzRoWTg7OV1zva1KvrSemuMkv07MA9TTl91hfhe+wKrsrgVN4H2QSFd/U/FhiU3iWPYVgvbsOGwhyFWw==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } +} diff --git a/package.json b/package.json new file mode 100755 index 0000000..c8b6a9e --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "dictionar", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "bcrypt": "^5.0.0", + "cookie-parser": "^1.4.5", + "cors": "^2.8.5", + "express": "^4.17.1", + "express-rate-limit": "^5.1.3", + "express-validator": "^6.6.1", + "jsonwebtoken": "^8.5.1", + "mongoose": "^5.10.0" + } +} diff --git a/secret.js.template b/secret.js.template new file mode 100755 index 0000000..2a3e345 --- /dev/null +++ b/secret.js.template @@ -0,0 +1,3 @@ +module.exports = { + jwtPrivateKey: 'KEY' +}; \ No newline at end of file