├── .gitignore ├── LICENSE ├── README.md └── server ├── .env.sample ├── README.md ├── TODO.md ├── now.json ├── package-lock.json ├── package.json └── src ├── api ├── notes.js └── users.js ├── app.js ├── app.test.js ├── auth ├── auth.controller.js ├── auth.middlewares.js ├── auth.model.js ├── auth.routes.js ├── auth.schema.js └── auth.test.js ├── controllers └── users.controller.js ├── db └── connection.js ├── index.js └── tasks └── createAdminUser.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # next.js build output 79 | .next 80 | 81 | # nuxt.js build output 82 | .nuxt 83 | 84 | # gatsby files 85 | .cache/ 86 | public 87 | 88 | # vuepress build output 89 | .vuepress/dist 90 | 91 | # Serverless directories 92 | .serverless/ 93 | 94 | # FuseBox cache 95 | .fusebox/ 96 | 97 | # DynamoDB Local files 98 | .dynamodb/ 99 | 100 | # TernJS port file 101 | .tern-port 102 | 103 | .DS_STORE -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Coding Garden with CJ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Auth For Newbs - Part 6+ 2 | 3 | This is a continuation of the [Auth For Newbs series on YouTube](https://www.youtube.com/playlist?list=PLM_i0obccy3tfAersmDaq7-WFqvooNOXf). In this continuation, we will work on the stretch goals and making the code more production ready. 4 | 5 | * [x] Route to list all users 6 | * GET /api/v1/users 7 | * [x] Route to update a user 8 | * PATCH /api/v1/users/:id 9 | * [x] Add a role property to users when created 10 | * Role will default 'user' 11 | * [x] Add a active property to users when created 12 | * Active will default true 13 | * [x] Seed the DB with an admin user 14 | * Insert user with role 'admin' 15 | * [x] Restrict GET /api/v1/users to only users with admin role 16 | * List all users 17 | * [x] Restrict PATCH /api/v1/users/:id to only users with admin role 18 | * Update a user 19 | * [x] Prevent inactive users from logging in 20 | * [ ] Route to create a user 21 | * POST /api/v1/users 22 | * [ ] Restrict POST /api/v1/users to only users with admin role 23 | * Create a user -------------------------------------------------------------------------------- /server/.env.sample: -------------------------------------------------------------------------------- 1 | TOKEN_SECRET=something-very-very-very-secure 2 | DEFAULT_ADMIN_PASSWORD=something-very-secure 3 | TEST_DB_URL=localhost/auth-from-scratch-test 4 | DB_URL=localhost/auth-from-scratch -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | ## Auth from scratch from CJ's coding garden 2 | 3 | ##### Tech: 4 | - Nodejs: Javascript engine for writing a web server on my computer 5 | - Expressjs: Framework for Nodejs to write middle ware, write routes, etc.. 6 | - @hapi/joi: validation package 7 | - monk: its like knex but for mongodb, easy way to talk to the db on your machine 8 | - bcrypt: hashing package to keep passwords secret in your db 9 | - volleyball: http logger that makes it easy to read/debug in the console as we work 10 | 11 | #### Backend: 12 | To start: navigate to ./server 13 | - $ npm run dev (uses nodemon so i dont have to refresh) 14 | - using postman to check apis -------------------------------------------------------------------------------- /server/TODO.md: -------------------------------------------------------------------------------- 1 | ## Backend Admin 2 | 3 | * [x] Route to list all users 4 | * GET /api/v1/users 5 | * [x] Route to update a user 6 | * PATCH /api/v1/users/:id 7 | * [x] Add a role property to users when created 8 | * Role will default 'user' 9 | * [x] Add a active property to users when created 10 | * Active will default true 11 | * [x] Seed the DB with an admin user 12 | * Insert user with role 'admin' 13 | * [x] Restrict GET /api/v1/users to only users with admin role 14 | * List all users 15 | * [x] Restrict PATCH /api/v1/users/:id to only users with admin role 16 | * Update a user 17 | * [ ] Restrict POST /api/v1/users to only users with admin role 18 | * Create a user 19 | * [x] Prevent inactive users from logging in 20 | 21 | ## Backend Admin pt2 22 | 23 | * [x] set up tests: mocha, chai, & super test 24 | * [x] create a test db 25 | * [x] setup linter file 26 | * [] MVC folder structure 27 | * [x] folder by feature 28 | * [x] controller file 29 | * [x] model file for validations and query logic 30 | * [x] routes file for basic descriptions for express routes 31 | * [x] test file inside each folder 32 | * [x] refactor some of the routes in to middle wares 33 | * [] deploy! 34 | * [] run the admin seed on deploy 35 | 36 | stretch? 37 | * [] storing token in a cookie 38 | * [] refresh tokens 39 | * [] pre commit hook 40 | 41 | mongo://username:password@cluster0-lpxdr.mongodb.net/test -------------------------------------------------------------------------------- /server/now.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "auth-from-scratch", 4 | "env": { 5 | "TOKEN_SECRET": "@auth-jwt-secret", 6 | "DEFAULT_ADMIN_PASSWORD": "@auth-admin-pwd", 7 | "DB_URL": "@auth-scratch-db" 8 | }, 9 | "builds": [ 10 | { 11 | "src": "src/index.js", 12 | "use": "@now/node-server" 13 | } 14 | ], 15 | "routes": [ 16 | { 17 | "src": "/.*", 18 | "dest": "src/index.js" 19 | } 20 | ], 21 | "alias": [ 22 | "auth-from-scratch" 23 | ] 24 | } -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "start": "node src/index.js", 8 | "dev": "nodemon src/index.js", 9 | "seed:admin": "node src/tasks/createAdminUser.js", 10 | "lint": "eslint src/", 11 | "test": "NODE_ENV=test mocha src/*.test.js src/**/*.test.js --watch", 12 | "heroku-postbuild": "npm run seed:admin" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "MIT", 17 | "dependencies": { 18 | "@hapi/joi": "^16.1.2", 19 | "bcryptjs": "^2.4.3", 20 | "cors": "^2.8.5", 21 | "dotenv": "^8.1.0", 22 | "express": "^4.17.1", 23 | "helmet": "^3.21.1", 24 | "jsonwebtoken": "^8.5.1", 25 | "monk": "^7.1.1", 26 | "volleyball": "^1.5.1" 27 | }, 28 | "devDependencies": { 29 | "chai": "^4.2.0", 30 | "eslint": "^6.5.1", 31 | "eslint-config-airbnb-base": "^14.0.0", 32 | "eslint-plugin-import": "^2.18.2", 33 | "mocha": "^6.2.1", 34 | "nodemon": "^1.19.2", 35 | "supertest": "^4.0.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /server/src/api/notes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const Joi = require('@hapi/joi'); 3 | 4 | const db = require('./../db/connection'); 5 | 6 | const notes = db.get('notes'); 7 | 8 | const schema = Joi.object({ 9 | title: Joi.string().trim().max(100).required(), 10 | note: Joi.string().trim().required(), 11 | }); 12 | 13 | const router = express.Router(); 14 | 15 | 16 | router.get('/', (req, res, next) => { 17 | // find notes with at current users id 18 | notes.find({ 19 | user_id: req.user._id, 20 | }).then((results) => { 21 | // respond with user specific results! 22 | res.json(results); 23 | }).catch(next); 24 | }); 25 | 26 | router.post('/', (req, res, next) => { 27 | const result = schema.validate(req.body); 28 | if (!result.error) { 29 | // create foreign key! 30 | const note = { 31 | ...req.body, 32 | user_id: req.user._id, 33 | }; 34 | // insert into db 35 | notes 36 | .insert(note) 37 | .then((createdNote) => { 38 | res.json(createdNote); 39 | }); 40 | } else { 41 | const error = new Error(result.error); 42 | res.status(422); 43 | next(error); 44 | } 45 | }); 46 | 47 | module.exports = router; 48 | -------------------------------------------------------------------------------- /server/src/api/users.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const controller = require('../controllers/users.controller'); 3 | 4 | const router = express.Router(); 5 | 6 | router.get('/', controller.list); 7 | router.patch('/:id', controller.updateOne); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /server/src/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const volleyball = require('volleyball'); 3 | const cors = require('cors'); 4 | const helmet = require('helmet'); 5 | 6 | require('dotenv').config(); 7 | 8 | const app = express(); 9 | const middleware = require('./auth/auth.middlewares'); 10 | const auth = require('./auth/auth.routes'); 11 | const notes = require('./api/notes'); 12 | const users = require('./api/users'); 13 | 14 | app.use(volleyball); 15 | app.use(cors({ 16 | origin: 'http://localhost:4200', 17 | })); 18 | app.use(express.json()); 19 | app.use(helmet()); 20 | app.use(middleware.checkTokenSetUser); 21 | 22 | app.get('/', (req, res) => { 23 | res.json({ 24 | message: 'Hello World!', 25 | user: req.user, 26 | }); 27 | }); 28 | 29 | app.use( 30 | '/auth', 31 | auth, 32 | ); 33 | app.use( 34 | '/api/v1/notes', 35 | middleware.isLoggedIn, 36 | notes, 37 | ); 38 | app.use( 39 | '/api/v1/users', 40 | middleware.isLoggedIn, 41 | middleware.isAdmin, 42 | users, 43 | ); 44 | 45 | function notFound(req, res, next) { 46 | res.status(404); 47 | const error = new Error(`Not Found - ${ req.originalUrl}`); 48 | next(error); 49 | } 50 | 51 | function errorHandler(err, req, res, next) { 52 | res.status(res.statusCode || 500); 53 | res.json({ 54 | message: err.message, 55 | stack: err.stack, 56 | }); 57 | } 58 | 59 | app.use(notFound); 60 | app.use(errorHandler); 61 | 62 | module.exports = app; 63 | -------------------------------------------------------------------------------- /server/src/app.test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | const { expect } = require('chai'); 3 | 4 | const app = require('./app'); 5 | 6 | describe('App - GET /', () => { 7 | it('should respond with a message', async () => { 8 | const response = await request(app) 9 | .get('/') 10 | .expect(200); 11 | expect(response.body.message).to.equal('Hello World!'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /server/src/auth/auth.controller.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require('bcryptjs'); 2 | const jwt = require('jsonwebtoken'); 3 | 4 | const users = require('./auth.model'); 5 | 6 | const createTokenSendResponse = (user, res, next) => { 7 | const payload = { 8 | _id: user._id, 9 | username: user.username, 10 | role: user.role, 11 | active: user.active, 12 | }; 13 | jwt.sign( 14 | payload, 15 | process.env.TOKEN_SECRET, { 16 | expiresIn: '1h', 17 | }, (err, token) => { 18 | if (err) { 19 | res.status(422); 20 | const error = Error('Unable to login'); 21 | next(error); 22 | } else { 23 | // login all good 24 | res.json({ token }); 25 | } 26 | }, 27 | ); 28 | }; 29 | 30 | const get = (req, res) => { 31 | res.json({ 32 | message: 'Hello Auth! 🔐', 33 | }); 34 | }; 35 | 36 | const signup = async (req, res, next) => { 37 | try { 38 | const hashed = await bcrypt.hash(req.body.password, 12); 39 | const newUser = { 40 | username: req.body.username, 41 | password: hashed, 42 | role: 'user', 43 | active: true, 44 | }; 45 | const insertedUser = await users.insert(newUser); 46 | createTokenSendResponse(insertedUser, res, next); 47 | } catch (error) { 48 | res.status(500); 49 | next(error); 50 | } 51 | }; 52 | 53 | const login = async (req, res, next) => { 54 | try { 55 | const result = await bcrypt.compare( 56 | req.body.password, 57 | req.loggingInUser.password, 58 | ); 59 | if (result) { 60 | createTokenSendResponse(req.loggingInUser, res, next); 61 | } else { 62 | res.status(422); 63 | throw new Error('Unable to login'); 64 | } 65 | } catch (error) { 66 | res.status(res.statusCode === 200 ? 500 : res.statusCode); 67 | next(error); 68 | } 69 | }; 70 | 71 | module.exports = { 72 | get, 73 | login, 74 | signup, 75 | }; 76 | -------------------------------------------------------------------------------- /server/src/auth/auth.middlewares.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | 3 | const schema = require('./auth.schema'); 4 | const users = require('./auth.model'); 5 | 6 | function checkTokenSetUser(req, res, next) { 7 | const authHeader = req.get('Authorization'); 8 | if (authHeader) { 9 | const token = authHeader.split(' ')[1]; 10 | if (token) { 11 | // use jwt lib to decode 12 | jwt.verify(token, process.env.TOKEN_SECRET, (error, user) => { 13 | if (error) { 14 | console.log(error); 15 | } 16 | req.user = user; 17 | next(); 18 | }); 19 | } else { 20 | next(); 21 | } 22 | } else { 23 | next(); 24 | } 25 | } 26 | 27 | function isLoggedIn(req, res, next) { 28 | if (req.user) { 29 | next(); 30 | } else { 31 | unAuthorized(res, next); 32 | } 33 | } 34 | 35 | function isAdmin(req, res, next) { 36 | if (req.user.role === 'admin') { 37 | next(); 38 | } else { 39 | unAuthorized(res, next); 40 | } 41 | } 42 | 43 | function unAuthorized(res, next) { 44 | const error = new Error('🚫 Un-Authorized 🚫'); 45 | res.status(401); 46 | next(error); 47 | } 48 | 49 | const validateUser = (defaultErrorMessage = '') => (req, res, next) => { 50 | const result = schema.validate(req.body); 51 | if (!result.error) { 52 | next(); 53 | } else { 54 | const error = defaultErrorMessage ? new Error(defaultErrorMessage) : result.error; 55 | res.status(422); 56 | next(error); 57 | } 58 | }; 59 | 60 | const findUser = (defaultLoginError, isError, errorCode = 422) => async (req, res, next) => { 61 | try { 62 | const user = await users.findOne({ 63 | username: req.body.username, 64 | }); 65 | if (isError(user)) { 66 | res.status(errorCode); 67 | next(new Error(defaultLoginError)); 68 | } else { 69 | req.loggingInUser = user; 70 | next(); 71 | } 72 | } catch (error) { 73 | res.status(500); 74 | next(error); 75 | } 76 | }; 77 | 78 | module.exports = { 79 | checkTokenSetUser, 80 | isLoggedIn, 81 | isAdmin, 82 | validateUser, 83 | findUser, 84 | }; 85 | -------------------------------------------------------------------------------- /server/src/auth/auth.model.js: -------------------------------------------------------------------------------- 1 | const db = require('./../db/connection'); 2 | 3 | const users = db.get('users'); 4 | users.createIndex('username', { unique: true }); 5 | 6 | module.exports = users; 7 | -------------------------------------------------------------------------------- /server/src/auth/auth.routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const controller = require('./auth.controller'); 4 | const middlewares = require('./auth.middlewares'); 5 | 6 | const router = express.Router(); 7 | // any route in here is pre-pended with /auth 8 | 9 | const defaultLoginError = 'Unable to login'; 10 | const signInError = 'That username is not unique. Please choose another one.'; 11 | 12 | router.get('/', controller.get); 13 | router.post( 14 | '/signup', 15 | middlewares.validateUser(), 16 | middlewares.findUser(signInError, (user) => user, 409), 17 | controller.signup, 18 | ); 19 | router.post( 20 | '/login', 21 | middlewares.validateUser(defaultLoginError), 22 | middlewares.findUser(defaultLoginError, (user) => !(user && user.active)), 23 | controller.login, 24 | ); 25 | 26 | module.exports = router; 27 | -------------------------------------------------------------------------------- /server/src/auth/auth.schema.js: -------------------------------------------------------------------------------- 1 | const Joi = require('@hapi/joi'); 2 | 3 | const schema = Joi.object({ 4 | username: Joi.string() 5 | .regex(/(^[a-zA-Z0-9_]*$)/) 6 | .min(2) 7 | .max(30) 8 | .required(), 9 | 10 | password: Joi.string() 11 | .trim() 12 | .min(10) 13 | .required(), 14 | }); 15 | 16 | module.exports = schema; 17 | -------------------------------------------------------------------------------- /server/src/auth/auth.test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | const { expect } = require('chai'); 3 | 4 | const app = require('./../app'); 5 | const db = require('./../db/connection'); 6 | 7 | const users = db.get('users'); 8 | 9 | const newUser = { 10 | username: 'testuser', 11 | password: '1234567890', 12 | }; 13 | 14 | describe('GET /auth', () => { 15 | it('should respond with a message', async () => { 16 | const response = await request(app) 17 | .get('/auth') 18 | .expect(200); 19 | expect(response.body.message).to.equal('Hello Auth! 🔐'); 20 | }); 21 | }); 22 | 23 | describe('POST /auth/signup', () => { 24 | before(async () => { 25 | await users.remove({}); 26 | }); 27 | 28 | it('should require a username', async () => { 29 | const response = await request(app) 30 | .post('/auth/signup') 31 | .send({}) 32 | .expect(422); 33 | expect(response.body.message).to.equal('"username" is required'); 34 | }); 35 | it('should require a password', async () => { 36 | const response = await request(app) 37 | .post('/auth/signup') 38 | .send({ username: 'testuser' }) 39 | .expect(422); 40 | expect(response.body.message).to.equal('"password" is required'); 41 | }); 42 | it('should create a new user', async () => { 43 | const response = await request(app) 44 | .post('/auth/signup') 45 | .send(newUser) 46 | .expect(200); 47 | expect(response.body).to.have.property('token'); 48 | }); 49 | it('should not allow a user with an existing username', async () => { 50 | const response = await request(app) 51 | .post('/auth/signup') 52 | .send(newUser) 53 | .expect(409); 54 | expect(response.body.message).to.equal('That username is not unique. Please choose another one.'); 55 | }); 56 | }); 57 | 58 | describe('POST /auth/login', () => { 59 | it('should require a username', async () => { 60 | const response = await request(app) 61 | .post('/auth/login') 62 | .send({}) 63 | .expect(422); 64 | expect(response.body.message).to.equal('Unable to login'); 65 | }); 66 | it('should require a password', async () => { 67 | const response = await request(app) 68 | .post('/auth/login') 69 | .send({ username: 'testuser' }) 70 | .expect(422); 71 | expect(response.body.message).to.equal('Unable to login'); 72 | }); 73 | it('should only allow valid users to login', async () => { 74 | const response = await request(app) 75 | .post('/auth/login') 76 | .send({ username: 'testuser', password: 'testtesttest' }) 77 | .expect(422); 78 | expect(response.body.message).to.equal('Unable to login'); 79 | }); 80 | it('should only allow valid users to login', async () => { 81 | const response = await request(app) 82 | .post('/auth/login') 83 | .send(newUser) 84 | .expect(200); 85 | expect(response.body).to.have.property('token'); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /server/src/controllers/users.controller.js: -------------------------------------------------------------------------------- 1 | const Joi = require('@hapi/joi'); 2 | const bcrypt = require('bcryptjs'); 3 | const db = require('../db/connection'); 4 | 5 | const users = db.get('users'); 6 | const schema = Joi.object({ 7 | username: Joi.string() 8 | .regex(/(^[a-zA-Z0-9_]*$)/) 9 | .min(2) 10 | .max(30), 11 | password: Joi.string() 12 | .trim() 13 | .min(10), 14 | role: Joi.string().valid('user', 'admin'), 15 | 16 | active: Joi.bool(), 17 | }); 18 | 19 | const list = async (req, res, next) => { 20 | try { 21 | const result = await users.find({}, '-password'); 22 | res.json(result); 23 | } catch (error) { 24 | next(error); 25 | } 26 | }; 27 | 28 | const updateOne = async (req, res, next) => { 29 | const { id: _id } = req.params; 30 | // validate id params 31 | try { 32 | // validate req body 33 | const result = schema.validate(req.body); 34 | if (!result.error) { 35 | // if valid: find user in db with given id 36 | const query = { _id }; 37 | const user = await users.findOne(query); 38 | if (user) { 39 | // update user in db 40 | const updatedUser = req.body; 41 | if (updatedUser.password) { 42 | updatedUser.password = await bcrypt.hash(updatedUser.password, 12); 43 | } 44 | const result = await users.findOneAndUpdate(query, { 45 | $set: updatedUser, 46 | }); 47 | // respond with user 48 | delete result.password; 49 | res.json(result); 50 | } else { 51 | // if not exists - send 404 (with user not found) 52 | next(); 53 | } 54 | } else { 55 | // if not valid - send an error with the reason 56 | res.status(422); 57 | throw new Error(result.error); 58 | } 59 | } catch (error) { 60 | next(error); 61 | } 62 | }; 63 | 64 | module.exports = { 65 | list, 66 | updateOne, 67 | }; 68 | -------------------------------------------------------------------------------- /server/src/db/connection.js: -------------------------------------------------------------------------------- 1 | const monk = require('monk'); 2 | 3 | let dbUrl = process.env.DB_URL; 4 | 5 | if (process.env.NODE_ENV === 'test') { 6 | dbUrl = process.env.TEST_DB_URL; 7 | } 8 | 9 | const db = monk(dbUrl); 10 | 11 | module.exports = db; 12 | -------------------------------------------------------------------------------- /server/src/index.js: -------------------------------------------------------------------------------- 1 | const app = require('./app'); 2 | 3 | const port = process.env.PORT || 5000; 4 | app.listen(port, () => { 5 | console.log('Listening on port', port); 6 | }); 7 | -------------------------------------------------------------------------------- /server/src/tasks/createAdminUser.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require('bcryptjs'); 2 | const dotenv = require('dotenv'); 3 | 4 | dotenv.config(); 5 | 6 | const db = require('../db/connection'); 7 | 8 | const users = db.get('users'); 9 | 10 | async function createAdminUser() { 11 | try { 12 | const user = await users.findOne({ role: 'admin' }); 13 | if (!user) { 14 | await users.insert({ 15 | username: 'Admin', 16 | password: await bcrypt.hash(process.env.DEFAULT_ADMIN_PASSWORD, 12), 17 | active: true, 18 | role: 'admin', 19 | }); 20 | console.log('Admin user created!'); 21 | } else { 22 | console.log('Admin user already exists!'); 23 | } 24 | } catch (error) { 25 | console.error(error); 26 | } finally { 27 | db.close(); 28 | } 29 | } 30 | 31 | createAdminUser(); 32 | --------------------------------------------------------------------------------