瀏覽代碼

master first commit

Artem 3 年之前
當前提交
eb9f10e0bf

+ 13 - 0
.eslintrc.js

@@ -0,0 +1,13 @@
+module.exports = {
+    "env": {
+        "commonjs": true,
+        "es2021": true,
+        "node": true
+    },
+    "extends": "eslint:recommended",
+    "parserOptions": {
+        "ecmaVersion": 12
+    },
+    "rules": {
+    }
+};

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+node_modules

+ 9 - 0
README.md

@@ -0,0 +1,9 @@
+## how to run ?
+
+```bash
+npm start
+```
+
+## our db in
+
+You can found all your data inside `db` folder.

+ 5 - 0
db/data.json

@@ -0,0 +1,5 @@
+{
+  "werty": {
+    "222": "qwewqw"
+  }
+}

+ 16 - 0
db/users.json

@@ -0,0 +1,16 @@
+[
+  {
+    "email": "test@text.com",
+    "password": "$2a$10$id7VjzFhI2ycyLD9VUtjYus16QfN14CPwP2vN/5glOmzRz7SPzPE.",
+    "id": "3c078003-14e4-47b6-9a7f-935b1a3fe7c1",
+    "picture": "https://gravatar.com/avatar/2efeccd8672424f7ce19eee82b7155fa?d=identicon"
+  },
+  {
+    "email": "qwerty2@test.com",
+    "password": "$2a$10$sLKK1TA1B/pxDJvg50FuyOJ20GvIkprrtkytZ4fqP6KoyRMEvhT0y",
+    "createAt": "2021-09-04T19:54:25.176Z",
+    "updateAt": "2021-09-04T19:54:25.176Z",
+    "id": "7c07375d-1158-421d-97a4-f58440a95b2c",
+    "picture": "https://gravatar.com/avatar/2efeccd8672424f7ce19eee82b7155fa?d=identicon"
+  }
+]

+ 23 - 0
logs/errors.log

@@ -0,0 +1,23 @@
+2021-09-04 19:52:48:5248 error: uncaughtException: Route.post() requires a callback function but got a [object Undefined]
+Error: Route.post() requires a callback function but got a [object Undefined]
+    at Route.<computed> [as post] (/Users/artemstepanov/Documents/a-level/test-back-end/node_modules/express/lib/router/route.js:202:15)
+    at Function.proto.<computed> [as post] (/Users/artemstepanov/Documents/a-level/test-back-end/node_modules/express/lib/router/index.js:510:19)
+    at Object.<anonymous> (/Users/artemstepanov/Documents/a-level/test-back-end/src/routes/auth/index.js:9:8)
+    at Module._compile (internal/modules/cjs/loader.js:1063:30)
+    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
+    at Module.load (internal/modules/cjs/loader.js:928:32)
+    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
+    at Module.require (internal/modules/cjs/loader.js:952:19)
+    at require (internal/modules/cjs/helpers.js:88:18)
+    at Object.<anonymous> (/Users/artemstepanov/Documents/a-level/test-back-end/src/routes/index.js:3:14)
+    at Module._compile (internal/modules/cjs/loader.js:1063:30)
+    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
+    at Module.load (internal/modules/cjs/loader.js:928:32)
+    at Function.Module._load (internal/modules/cjs/loader.js:769:14)
+    at Module.require (internal/modules/cjs/loader.js:952:19)
+    at require (internal/modules/cjs/helpers.js:88:18) undefined
+2021-09-04 22:48:55:4855 error: URL: undefined/register, Method: POST, ErrorMessage: User already exist undefined
+2021-09-04 22:52:28:5228 error: URL: undefined/register, Method: POST, ErrorMessage: The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received undefined undefined
+2021-09-04 22:53:22:5322 error: URL: undefined/register, Method: POST, ErrorMessage: The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received undefined undefined
+2021-09-04 23:04:05:45 error: URL: undefined/login, Method: POST, ErrorMessage: Cannot set headers after they are sent to the client undefined
+2021-09-04 23:04:40:440 error: URL: undefined/login, Method: POST, ErrorMessage: Cannot set headers after they are sent to the client undefined

文件差異過大導致無法顯示
+ 5970 - 0
package-lock.json


+ 30 - 0
package.json

@@ -0,0 +1,30 @@
+{
+  "name": "test-back-end",
+  "version": "1.0.0",
+  "description": "",
+  "main": "src/index.js",
+  "scripts": {
+    "start": "nodemon ."
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "bcryptjs": "^2.4.3",
+    "bluebird": "^3.7.2",
+    "crypto": "^1.0.1",
+    "express": "^4.17.1",
+    "joi": "^17.4.2",
+    "jsonwebtoken": "^8.5.1",
+    "passport": "^0.4.1",
+    "passport-http": "^0.3.0",
+    "passport-http-bearer": "^1.0.1",
+    "passport-jwt": "^4.0.0",
+    "uuidv4": "^6.2.12",
+    "winston": "^3.3.3"
+  },
+  "devDependencies": {
+    "eslint": "^7.32.0",
+    "nodemon": "^2.0.12"
+  }
+}

+ 5 - 0
src/config.js

@@ -0,0 +1,5 @@
+module.exports = {
+  PORT: 8000,
+  jwtSecret: 'some-bla-bla-secret',
+  masterKey: 'super-secret-master-key'
+}

+ 17 - 0
src/index.js

@@ -0,0 +1,17 @@
+const express = require('express');
+
+const { passport } = require('./services/passport');
+const router = require('./routes');
+const { PORT } = require('./config');
+
+const app = express();
+
+app.use(express.urlencoded({ extended: true }));
+app.use(express.json());
+
+app.use(passport.initialize());
+app.use(router);
+
+app.listen(PORT, () => {
+  console.log(`Example app listening at http://localhost:${PORT}`)
+})

+ 30 - 0
src/routes/auth/controller.js

@@ -0,0 +1,30 @@
+const { sign } = require('../../services/jwt');
+const { success } = require('../../services/response');
+const { loggerError } = require('../../services/logger');
+const { addNewUserToDB, getAllUsersDataFromDB } = require('../../utils/data-handlers');
+
+
+const login = ({ user, ...other }, res, next) =>
+  sign(user)
+    .then((token) => ({ token, user }))
+    .then(success(res, 201))
+    .catch(loggerError(next, other))
+
+const register = async ({ body, ...other }, res, next) => {
+  const { email, password } = body;
+  try {
+    const allUsers = await getAllUsersDataFromDB();
+    const isUserAlreadyExist = allUsers.some(el => el.email === email);
+    if (isUserAlreadyExist) return res.status(403).send({ message: 'User already exists'})
+
+    const newUser = await addNewUserToDB({ email, password });
+    return success(res, 201)(newUser.view)
+  } catch (error) {
+    return loggerError(next, other)(error)
+  }
+}
+
+module.exports = {
+  login,
+  register
+}

+ 11 - 0
src/routes/auth/index.js

@@ -0,0 +1,11 @@
+const { Router } = require('express'); 
+const { login, register } = require('./controller');
+const { password, master } = require('../../services/passport');
+const { joiMiddleware, newUserSchema } = require('../../services/joi');
+
+const router = new Router()
+
+router.post('/login', password(), login)
+router.post('/register', master(), joiMiddleware(newUserSchema), register)
+
+module.exports = router;

+ 26 - 0
src/routes/datas/controller.js

@@ -0,0 +1,26 @@
+const { success } = require('../../services/response');
+const { loggerError } = require('../../services/logger');
+const { getData, setData } = require('../../utils/data-handlers');
+
+const set = async ({ body, ...other }, res, next) => {
+  try {
+    const data = await setData(body);
+    return success(res, 201)(data);
+  } catch (error) {
+    return loggerError(next, other)(error);
+  }
+}
+
+const get = async (req, res, next) => {
+  try {
+    const data = await getData();
+    return success(res, 200)(data);
+  } catch (error) {
+    return loggerError(next, req)(error);
+  }
+}
+
+module.exports = {
+  set,
+  get
+}

+ 10 - 0
src/routes/datas/index.js

@@ -0,0 +1,10 @@
+const { Router } = require('express'); 
+const { get, set } = require('./controller');
+const { token } = require('../../services/passport');
+
+const router = new Router();
+
+router.get('/', token({ required: true }), get);
+router.post('/', token({ required: true }), set);
+
+module.exports = router;

+ 12 - 0
src/routes/index.js

@@ -0,0 +1,12 @@
+const { Router } = require('express');
+const users = require('./users');
+const auth = require('./auth');
+const data = require('./datas');
+
+const router = new Router()
+
+router.use('/auth', auth);
+router.use('/users', users);
+router.use('/data', data);
+
+module.exports = router;

+ 5 - 0
src/routes/users/controller.js

@@ -0,0 +1,5 @@
+const showMe = ({ user }, res) => res.status(200).json(user.view)
+
+module.exports = {
+  showMe
+}

+ 10 - 0
src/routes/users/index.js

@@ -0,0 +1,10 @@
+const { Router } = require('express'); 
+
+const { token } = require('../../services/passport');
+const { showMe } = require('./controller');
+
+const router = new Router();
+
+router.get('/me', token({ required: true }), showMe)
+
+module.exports = router;

+ 36 - 0
src/routes/users/model.js

@@ -0,0 +1,36 @@
+const { uuid } = require('uuidv4');
+const crypto = require('crypto');
+const bcrypt = require('bcryptjs');
+
+class User {
+  constructor({ email, password, createAt, updateAt, id, picture }, createMode = true) {
+    const timeStamp = new Date().toISOString();
+    const hash = crypto.createHash('md5').update(email).digest('hex');
+    const salt = bcrypt.genSaltSync(10);
+    
+    this.email = email;
+    this.password = createMode ? bcrypt.hashSync(password, salt) : password;
+    this.createAt = createMode ? timeStamp : createAt;
+    this.updateAt = createMode ? timeStamp : updateAt;
+    this.id = createMode ? uuid() : id;
+    this.picture = createMode ? `https://gravatar.com/avatar/${hash}?d=identicon` : picture;
+  }
+
+  get view() {
+    return {
+      email: this.email,
+      createAt: this.createAt,
+      updateAt: this.updateAt,
+      id: this.id,
+      picture: this.picture,
+    }
+  }
+
+  authenticate(password) {
+    return bcrypt.compareSync(password, this.password);
+  } 
+}
+
+module.exports = {
+  User
+}

+ 25 - 0
src/services/joi/index.js

@@ -0,0 +1,25 @@
+const Joi = require('joi');
+
+const newUserSchema = Joi.object().keys({ 
+  password: Joi.string().min(6).required(),
+  email: Joi.string().email().required(),
+});
+
+const joiMiddleware = (schema) => { 
+  return (req, res, next) => { 
+  const { error } = schema.validate(req.body); 
+
+  if (!error) { 
+    return next(); 
+  } else { 
+    const { details } = error; 
+    const message = details.map(i => i.message).join(',');
+
+   return res.status(422).json({ error: message }) } 
+  } 
+}
+
+module.exports = {
+  newUserSchema,
+  joiMiddleware
+};

+ 23 - 0
src/services/jwt/index.js

@@ -0,0 +1,23 @@
+const jwt = require('jsonwebtoken')
+const Promise = require('bluebird')
+const { jwtSecret } = require('../../config');
+
+const jwtSign = Promise.promisify(jwt.sign)
+const jwtVerify = Promise.promisify(jwt.verify)
+
+const sign = (user, options, method = jwtSign) => {
+  const fieldsForJWT = ['id'];
+  const jwtObject = fieldsForJWT.reduce((acc, elem) => ({ ...acc, [elem]: user[elem] }), {})
+
+  return method(jwtObject, jwtSecret, options)
+}
+
+const signSync = (id, options) => sign(id, options, jwt.sign)
+
+const verify = (token) => jwtVerify(token, jwtSecret)
+
+module.exports = {
+  signSync,
+  verify,
+  sign,
+}

+ 42 - 0
src/services/logger/index.js

@@ -0,0 +1,42 @@
+
+const { createLogger, transports, format } = require('winston');
+
+const { combine, printf, timestamp } = format
+
+const options = {
+  error: {
+    level: 'error',
+    filename: './logs/errors.log',
+    handleExceptions: true,
+    json: true,
+    maxsize: 5242880,
+    maxFiles: 5
+  }
+}
+
+const logger = createLogger({
+  format: combine(
+    timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
+    printf(info => `${info.timestamp} ${info.level}: ${info.message} ${info.location}`)
+  ),
+  transports: [
+    new transports.File(options.error),
+    new transports.Console()
+  ]
+})
+
+const loggerError = (next, data) => (err) => {
+  if (data) {
+    const { headers, method, url } = data
+
+    logger.error(`URL: ${headers.referer}${url}, Method: ${method}, ErrorMessage: ${err.message}`)
+  } else {
+    logger.error(err)
+  }
+
+  return next(err)
+}
+
+module.exports = {
+  loggerError
+}

+ 82 - 0
src/services/passport/index.js

@@ -0,0 +1,82 @@
+const passport = require('passport');
+const { BasicStrategy } = require('passport-http');
+const { Strategy: BearerStrategy } = require('passport-http-bearer');
+const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
+
+const { masterKey, jwtSecret } = require('../../config');
+const { getAllUsersDataFromDB } = require('../../utils/data-handlers');
+
+const { User } = require('../../routes/users/model');
+
+const password = () => (req, res, next) =>
+  passport.authenticate('password', { session: false }, (err, user) => {
+    if (err && err.param) {
+      return res.status(400).json(err)
+    } else if (err || !user) {
+      return res.status(401).json(err)
+    }
+    req.logIn(user, { session: false }, (err) => {
+      if (err) return res.status(401).end()
+      next()
+    })
+  })(req, res, next)
+
+const master = () =>
+  passport.authenticate('master', { session: false })
+
+const token = ({ required } = {}) => (req, res, next) =>
+  passport.authenticate('token', { session: false }, (err, user) => {
+    if (err || (required && !user)) {
+      return res.status(401).end()
+    }
+    req.logIn(user, { session: false }, (err) => {
+      if (err) return res.status(401).end()
+      next()
+    })
+  })(req, res, next)
+
+passport.use('password', new BasicStrategy((email, password, done) => {
+  const allUsers = getAllUsersDataFromDB();
+
+  const userRecord = allUsers.find(el => el.email === email);
+  if (!userRecord) return done({ message: 'Wrong credentials' });
+
+  const user = new User(userRecord, false);
+  const isPasswordValid = user.authenticate(password);
+  if (!isPasswordValid) return done({ message: 'Wrong credentials' });
+
+  return done(null, user);
+}))
+
+passport.use('master', new BearerStrategy((token, done) => {
+  if (token === masterKey) {
+    done(null, {})
+  } else {
+    done(null, false)
+  }
+}))
+
+passport.use('token', new JwtStrategy({
+  secretOrKey: jwtSecret,
+  jwtFromRequest: ExtractJwt.fromExtractors([
+    ExtractJwt.fromUrlQueryParameter('access_token'),
+    ExtractJwt.fromBodyField('access_token'),
+    ExtractJwt.fromAuthHeaderWithScheme('Bearer')
+  ])
+}, ({ id }, done) => {
+  const allUsers = getAllUsersDataFromDB();
+
+  const userRecord = allUsers.find(el => el.id === id);
+  if (!userRecord) return done({ message: 'Invalid token' });
+
+  const user = new User(userRecord, false);
+  return done(null, user)
+}))
+
+
+module.exports = {
+  password,
+  master,
+  token,
+  passport
+}

+ 28 - 0
src/services/response/index.js

@@ -0,0 +1,28 @@
+const success = (res, status) => (entity) => {
+  if (entity) {
+    res.status(status || 200).json(entity)
+  }
+  return null
+}
+
+const notFound = (res) => (entity) => {
+  if (entity) {
+    return entity
+  }
+  res.status(404).end()
+  return null
+}
+
+const ignoreNotFound = (res) => (entity) => {
+  if (entity) {
+    return entity
+  }
+  res.status(414).end()
+  return null
+}
+
+module.exports = {
+  success,
+  notFound,
+  ignoreNotFound
+}

+ 36 - 0
src/utils/data-handlers.js

@@ -0,0 +1,36 @@
+const path = require('path');
+const fs = require('fs');
+
+const { User } = require('../routes/users/model');
+
+const pathToUsersJSON = path.join(__dirname, '..', '..', 'db', 'users.json');
+const pathToDataJSON = path.join(__dirname, '..', '..', 'db', 'data.json');
+
+const getAllUsersDataFromDB = () => {
+  const rawData = fs.readFileSync(pathToUsersJSON);
+
+  const oldArray = JSON.parse(rawData);
+  return oldArray.map(el => new User(el, false))
+}
+
+const setDataToFile = (p, d) => fs.writeFileSync(p, JSON.stringify(d, null, 2));
+
+module.exports = {
+  getAllUsersDataFromDB,
+  addNewUserToDB: async (data) => {
+    const newUser = new User(data);
+
+    const oldDataFromJSON = getAllUsersDataFromDB();
+    const newData = oldDataFromJSON.concat(newUser);
+    setDataToFile(pathToUsersJSON, newData);
+    return newUser;
+  },
+  getData: async () => {
+    const rawData = fs.readFileSync(pathToDataJSON);
+    return JSON.parse(rawData);
+  },
+  setData: async (data) => {
+    setDataToFile(pathToDataJSON, data);
+    return data;
+  },
+}