diff --git a/api/.env b/api/.env index fd742b60..0d946d18 100644 --- a/api/.env +++ b/api/.env @@ -5,4 +5,5 @@ NEO4J_PASSWORD=letmein GRAPHQL_LISTEN_PORT=4001 API_ENDPOINT=http://localhost:4001/ -JWT_SECRET=You-sh0uld_Change-Th1s \ No newline at end of file +JWT_SECRET=You-sh0uld_Change-Th1s +ENCRYPTION_SALT_ROUNDS=12 \ No newline at end of file diff --git a/api/.eslintrc.json b/api/.eslintrc.json index 7987b8c6..7879b3e4 100644 --- a/api/.eslintrc.json +++ b/api/.eslintrc.json @@ -13,7 +13,8 @@ "sourceType": "module" }, "rules": { - "no-unused-vars": "off" + "no-unused-vars": "off", + "require-atomic-updates": "off" }, "parser": "babel-eslint" } diff --git a/api/package-lock.json b/api/package-lock.json index 78a39999..63521f77 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -1170,8 +1170,7 @@ "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "accepts": { "version": "1.3.7", @@ -1736,8 +1735,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", @@ -1799,6 +1797,22 @@ "resolved": "https://registry.npmjs.org/batch-cluster/-/batch-cluster-5.6.7.tgz", "integrity": "sha512-IyXDIg9XoRlfqXJr2mXem44plYswk8c4wERfeXuZ16fXf8PqZkLRXsuJtuWOBSEqO1Xo4vQ2zK6irYiEpA8VGA==" }, + "bcrypt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-3.0.6.tgz", + "integrity": "sha512-taA5bCTfXe7FUjKroKky9EXpdhkVvhE5owfxfLYodbrAR1Ul3juLmIQmIQBK4L9a5BuUcE6cqmwT+Da20lF9tg==", + "requires": { + "nan": "2.13.2", + "node-pre-gyp": "0.12.0" + }, + "dependencies": { + "nan": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", + "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==" + } + } + }, "binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", @@ -1869,7 +1883,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2205,8 +2218,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "configstore": { "version": "3.1.2", @@ -3325,8 +3337,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "1.2.9", @@ -3958,7 +3969,6 @@ "version": "7.1.4", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4317,6 +4327,14 @@ "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", "dev": true }, + "ignore-walk": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "requires": { + "minimatch": "^3.0.4" + } + }, "image-extensions": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/image-extensions/-/image-extensions-1.1.0.tgz", @@ -4367,7 +4385,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -5385,7 +5402,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5494,6 +5510,26 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "needle": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", + "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", + "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" + } + } + } + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -5553,6 +5589,34 @@ "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=" }, + "node-pre-gyp": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz", + "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "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" + }, + "dependencies": { + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + } + } + }, "node-releases": { "version": "1.1.26", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.26.tgz", @@ -5623,6 +5687,20 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "npm-bundled": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==" + }, + "npm-packlist": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.4.tgz", + "integrity": "sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw==", + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -5794,8 +5872,16 @@ "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=", - "dev": true + "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" + } }, "output-file-sync": { "version": "2.0.1", @@ -5909,8 +5995,7 @@ "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=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", @@ -6437,7 +6522,6 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, "requires": { "glob": "^7.1.3" } @@ -6491,6 +6575,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "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.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", diff --git a/api/package.json b/api/package.json index 1a65aa03..8fe43700 100644 --- a/api/package.json +++ b/api/package.json @@ -22,6 +22,7 @@ "apollo-link-http": "^1.5.14", "apollo-server": "^2.8.1", "babel-runtime": "^6.26.0", + "bcrypt": "^3.0.6", "body-parser": "^1.19.0", "core-js": "^3.2.0", "dotenv": "^8.0.0", diff --git a/api/src/config.js b/api/src/config.js index 3466df5b..01be2053 100644 --- a/api/src/config.js +++ b/api/src/config.js @@ -1,6 +1,15 @@ import path from 'path' +// set environment variables from ../.env +require('dotenv').config() + +let encryptionSaltRounds = 10 +if (parseInt(process.env.ENCRYPTION_SALT_ROUNDS)) { + encryptionSaltRounds = parseInt(process.env.ENCRYPTION_SALT_ROUNDS) +} + export default { cachePath: process.env.PHOTO_CACHE || path.resolve(__dirname, 'cache'), host: new URL(process.env.API_ENDPOINT || 'http://localhost:4001/'), + encryptionSaltRounds, } diff --git a/api/src/index.js b/api/src/index.js index 28449018..6fe506ae 100644 --- a/api/src/index.js +++ b/api/src/index.js @@ -3,7 +3,6 @@ import express from 'express' import bodyParser from 'body-parser' import cors from 'cors' import { v1 as neo4j } from 'neo4j-driver' -import dotenv from 'dotenv' import http from 'http' import PhotoScanner from './scanner/Scanner' import _ from 'lodash' @@ -11,9 +10,6 @@ import config from './config' import { getUserFromToken, getTokenFromBearer } from './token' -// set environment variables from ../.env -dotenv.config() - const app = express() app.use(bodyParser.json()) app.use(cors()) diff --git a/api/src/resolvers/users.js b/api/src/resolvers/users.js index ae7c43b7..8311df94 100644 --- a/api/src/resolvers/users.js +++ b/api/src/resolvers/users.js @@ -1,18 +1,20 @@ import jwt from 'jsonwebtoken' import generateID from '../id-generator' import fs from 'fs-extra' +import bcrypt from 'bcrypt' import { neo4jgraphql } from 'neo4j-graphql-js' +import config from '../config' const Mutation = { async authorizeUser(root, args, ctx, info) { console.log('Authorize user') - let { username, password } = args + let { username } = args let session = ctx.driver.session() let result = await session.run( - 'MATCH (usr:User {username: {username}, password: {password} }) RETURN usr.id, usr.admin', - { username, password } + 'MATCH (user:User {username: {username}}) RETURN user', + { username } ) if (result.records.length == 0) { @@ -25,16 +27,23 @@ const Mutation = { const record = result.records[0] - const userId = record.get('usr.id') - const userAdmin = record.get('usr.admin') + const user = record.get('user').properties + + if ((await bcrypt.compare(args.password, user.password)) == false) { + return { + success: false, + status: 'Username or password was invalid', + token: null, + } + } let roles = [] - if (userAdmin) { + if (user.admin) { roles.push('admin') } - const token = jwt.sign({ id: userId, roles }, process.env.JWT_SECRET) + const token = jwt.sign({ id: user.id, roles }, process.env.JWT_SECRET) return { success: true, @@ -43,7 +52,7 @@ const Mutation = { } }, async registerUser(root, args, ctx, info) { - let { username, password, rootPath } = args + let { username, rootPath } = args let session = ctx.driver.session() let findResult = await session.run( @@ -67,9 +76,14 @@ const Mutation = { } } + const hashedPassword = await bcrypt.hash( + args.password, + config.encryptionSaltRounds + ) + const registerResult = await session.run( 'CREATE (n:User { username: {username}, password: {password}, id: {id}, admin: false, rootPath: {rootPath} }) return n.id', - { username, password, id: generateID(), rootPath } + { username, password: hashedPassword, id: generateID(), rootPath } ) let id = registerResult.records[0].get('n.id') @@ -91,6 +105,12 @@ const Mutation = { } } + if (args.password) + args.password = await bcrypt.hash( + args.password, + config.encryptionSaltRounds + ) + return neo4jgraphql(root, args, ctx, info) }, async createUser(root, args, ctx, info) { @@ -103,6 +123,12 @@ const Mutation = { // eslint-disable-next-line require-atomic-updates args.id = generateID() + if (args.password) + args.password = await bcrypt.hash( + args.password, + config.encryptionSaltRounds + ) + return neo4jgraphql(root, args, ctx, info) }, async changeUserPassword(root, args, ctx, info) { @@ -110,11 +136,16 @@ const Mutation = { const session = ctx.driver.session() + const hashedPassword = await bcrypt.hash( + newPassword, + config.encryptionSaltRounds + ) + await session.run( - `MATCH (u:User { id: {id} }) SET u.password = {newPassword}`, + `MATCH (u:User { id: {id} }) SET u.password = {password}`, { id, - newPassword, + password: hashedPassword, } )