From d30014b432c92145f5ae35ca209b99842e3eb2c6 Mon Sep 17 00:00:00 2001 From: Vic Date: Fri, 20 May 2022 04:55:14 +0200 Subject: [PATCH] added user model and users handler --- ...15830-shelf.js => 20220520025053-shelf.js} | 4 +- migrations/sqls/20220519015830-shelf-down.sql | 2 - migrations/sqls/20220520025053-shelf-down.sql | 2 + ...elf-up.sql => 20220520025053-shelf-up.sql} | 0 package.json | 2 + src/handlers/products.ts | 8 +- src/handlers/users.ts | 129 ++++++++++++++++++ src/models/user.ts | 4 +- src/server.ts | 3 +- yarn.lock | 94 ++++++++++++- 10 files changed, 236 insertions(+), 12 deletions(-) rename migrations/{20220519015830-shelf.js => 20220520025053-shelf.js} (89%) delete mode 100644 migrations/sqls/20220519015830-shelf-down.sql create mode 100644 migrations/sqls/20220520025053-shelf-down.sql rename migrations/sqls/{20220519015830-shelf-up.sql => 20220520025053-shelf-up.sql} (100%) create mode 100644 src/handlers/users.ts diff --git a/migrations/20220519015830-shelf.js b/migrations/20220520025053-shelf.js similarity index 89% rename from migrations/20220519015830-shelf.js rename to migrations/20220520025053-shelf.js index 2a7df9f..d7d22d5 100644 --- a/migrations/20220519015830-shelf.js +++ b/migrations/20220520025053-shelf.js @@ -19,7 +19,7 @@ exports.setup = function(options, seedLink) { }; exports.up = function(db) { - var filePath = path.join(__dirname, 'sqls', '20220519015830-shelf-up.sql'); + var filePath = path.join(__dirname, 'sqls', '20220520025053-shelf-up.sql'); return new Promise( function( resolve, reject ) { fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){ if (err) return reject(err); @@ -34,7 +34,7 @@ exports.up = function(db) { }; exports.down = function(db) { - var filePath = path.join(__dirname, 'sqls', '20220519015830-shelf-down.sql'); + var filePath = path.join(__dirname, 'sqls', '20220520025053-shelf-down.sql'); return new Promise( function( resolve, reject ) { fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){ if (err) return reject(err); diff --git a/migrations/sqls/20220519015830-shelf-down.sql b/migrations/sqls/20220519015830-shelf-down.sql deleted file mode 100644 index 86f58f7..0000000 --- a/migrations/sqls/20220519015830-shelf-down.sql +++ /dev/null @@ -1,2 +0,0 @@ -drop TABLE products; -DROP TABLE users; \ No newline at end of file diff --git a/migrations/sqls/20220520025053-shelf-down.sql b/migrations/sqls/20220520025053-shelf-down.sql new file mode 100644 index 0000000..06a5a87 --- /dev/null +++ b/migrations/sqls/20220520025053-shelf-down.sql @@ -0,0 +1,2 @@ +DROP TABLE products; +DROP TABLE users; \ No newline at end of file diff --git a/migrations/sqls/20220519015830-shelf-up.sql b/migrations/sqls/20220520025053-shelf-up.sql similarity index 100% rename from migrations/sqls/20220519015830-shelf-up.sql rename to migrations/sqls/20220520025053-shelf-up.sql diff --git a/package.json b/package.json index 298c197..d17e00f 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "cross-env": "^7.0.3", "dotenv": "^16.0.0", "express": "^4.17.1", + "jsonwebtoken": "^8.5.1", "lodash": "^4.17.21", "nodemon": "^2.0.15", "pg": "^8.5.1", @@ -32,6 +33,7 @@ "@types/bcrypt": "^5.0.0", "@types/express": "^4.17.9", "@types/jasmine": "^3.6.3", + "@types/jsonwebtoken": "^8.5.8", "@types/pg": "^7.14.7", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.10.1", diff --git a/src/handlers/products.ts b/src/handlers/products.ts index a98a095..3739b1b 100644 --- a/src/handlers/products.ts +++ b/src/handlers/products.ts @@ -1,4 +1,4 @@ -import express, {Request, Response} from 'express' +import express, { Request, Response } from 'express' import { Product, ProductStore } from '../models/product' const productRoutes = (app: express.Application) => { @@ -13,7 +13,7 @@ const store = new ProductStore() const index = async (req: Request, res: Response) => { try { - const products = await store.index(); + const products = await store.index() res.json(products); console.log('working products index') } catch (err) { @@ -43,7 +43,7 @@ const create = async (req: Request, res: Response) => { price: req.body.price, } - const newProduct = await store.create(productInfo); + const newProduct = await store.create(productInfo) res.json(newProduct) } catch (err) { res.status(400) @@ -54,7 +54,7 @@ const create = async (req: Request, res: Response) => { const update = async (req: Request, res: Response) => { try { - const product = await store.update(req.body); + const product = await store.update(req.body) res.json({ name: req.body.name, price: req.body.price, diff --git a/src/handlers/users.ts b/src/handlers/users.ts new file mode 100644 index 0000000..b0065f9 --- /dev/null +++ b/src/handlers/users.ts @@ -0,0 +1,129 @@ +import express, { Request, Response, NextFunction } from 'express' +import { User, UserStore } from '../models/user' +import jwt, {Secret} from 'jsonwebtoken' + +const SECRET = process.env.TOKEN_SECRET as Secret + +const userRoutes = (app: express.Application) => { + app.get('/users', index) + app.get('/users/:id', read) + app.post('/users/create', verifyAuthToken, create) + app.put('/users/:id', verifyAuthToken, update) + app.delete('/users/:id', verifyAuthToken, destroy) + app.post("/users/auth", authenticate) +} + +const store = new UserStore() + +const verifyAuthToken = (req: Request, res: Response, next: NextFunction) => { + if (!req.headers.authorization) { + res.status(401) + res.json("Access denied, invalid token") + + return false + } + + try { + //const authorizationHeader = req.headers.authorization + const token = req.headers.authorization.split(" ")[1] + const decoded = jwt.verify(token, SECRET) + next() + } catch (err) { + res.status(401) + res.json("Access denied, invalid token") + } +} + +let verifyUserToken = (user: User | null) => { + return jwt.sign({ user }, SECRET) +} + + + +const index = async (req: Request, res: Response) => { + try { + const users = await store.index() + res.json(users); + } catch (err) { + res.status(400) + res.json(err) + } +} + +const read = async (req: Request, res: Response) => { + try { + const user = await store.read(parseInt(req.params.id)) + res.json(user) + } catch (err) { + res.status(400) + res.json(err) + } +} + + + +const create = async (req: Request, res: Response) => { + + const userInfo: User = { + firstName: req.body.fristName, + lastName: req.body.lastName, + username: req.body.username, + password: req.body.password + } + + try { + const newUser = await store.create(userInfo) + res.json(verifyUserToken(newUser)) + } catch(err) { + res.status(400) + res.json(err) + } +} + +const update = async (req: Request, res: Response) => { + try { + const user = await store.update(req.body) + res.json({ + firstName: req.body.fristName, + lastName: req.body.lastName, + username: req.body.username, + password: req.body.password + }) + } catch (err) { + res.status(400) + res.json(err) + } +} + + +const destroy = async (req: Request, res: Response) => { + try { + const deleted = await store.delete(req.body.id) + res.json(deleted) + } catch (err) { + res.status(400) + res.json(err) + } +} + +const authenticate = async (req: Request, res: Response) => { + const userInfo: User = { + username: req.body.username, + password: req.body.password + } + + if (userInfo.username === undefined || userInfo.password === undefined) { + res.status(400) + } + + try { + const auth: User | null = await store.authenticate(userInfo.username, userInfo.password) + res.json(verifyUserToken(auth)) + } catch(err) { + res.status(401) + res.json(err) + } +} + + +export default userRoutes \ No newline at end of file diff --git a/src/models/user.ts b/src/models/user.ts index 2d96eac..4f55a9b 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -7,8 +7,8 @@ const saltRounds = SALT_ROUNDS export type User = { id?: number; - firstName: string; - lastName: string; + firstName?: string; + lastName?: string; username: string; password: string; } diff --git a/src/server.ts b/src/server.ts index e5ca115..9fb35ca 100644 --- a/src/server.ts +++ b/src/server.ts @@ -2,6 +2,7 @@ import express, { Request, Response } from 'express' import bodyParser from 'body-parser' import productRoutes from './handlers/products' +import userRoutes from './handlers/users' const app: express.Application = express() const address: string = "0.0.0.0:3000" @@ -14,7 +15,7 @@ app.get('/', function (req: Request, res: Response) { }) productRoutes(app) - +userRoutes(app) // Start express server app.listen(port, function () { diff --git a/yarn.lock b/yarn.lock index fb0e124..34d04e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -161,6 +161,13 @@ "resolved" "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz" "version" "7.0.11" +"@types/jsonwebtoken@^8.5.8": + "integrity" "sha512-zm6xBQpFDIDM6o9r6HSgDeIcLy82TKWctCXEPbJJcXb5AKmi5BNNdLXneixK4lplX3PqIVcwLBCGE/kAGnlD4A==" + "resolved" "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.8.tgz" + "version" "8.5.8" + dependencies: + "@types/node" "*" + "@types/mime@^1": "integrity" "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" "resolved" "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz" @@ -537,6 +544,11 @@ dependencies: "fill-range" "^7.0.1" +"buffer-equal-constant-time@1.0.1": + "integrity" "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + "resolved" "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz" + "version" "1.0.1" + "buffer-from@^1.0.0": "integrity" "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" "resolved" "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" @@ -999,6 +1011,13 @@ "resolved" "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz" "version" "0.1.4" +"ecdsa-sig-formatter@1.0.11": + "integrity" "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==" + "resolved" "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz" + "version" "1.0.11" + dependencies: + "safe-buffer" "^5.0.1" + "ee-first@1.1.1": "integrity" "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" "resolved" "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" @@ -1821,6 +1840,39 @@ "resolved" "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" "version" "1.0.1" +"jsonwebtoken@^8.5.1": + "integrity" "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==" + "resolved" "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz" + "version" "8.5.1" + dependencies: + "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" + +"jwa@^1.4.1": + "integrity" "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==" + "resolved" "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz" + "version" "1.4.1" + dependencies: + "buffer-equal-constant-time" "1.0.1" + "ecdsa-sig-formatter" "1.0.11" + "safe-buffer" "^5.0.1" + +"jws@^3.2.2": + "integrity" "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==" + "resolved" "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz" + "version" "3.2.2" + dependencies: + "jwa" "^1.4.1" + "safe-buffer" "^5.0.1" + "keyv@^3.0.0": "integrity" "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==" "resolved" "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz" @@ -1855,11 +1907,46 @@ "resolved" "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz" "version" "4.2.0" +"lodash.includes@^4.3.0": + "integrity" "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + "resolved" "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz" + "version" "4.3.0" + +"lodash.isboolean@^3.0.3": + "integrity" "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + "resolved" "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz" + "version" "3.0.3" + +"lodash.isinteger@^4.0.4": + "integrity" "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + "resolved" "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz" + "version" "4.0.4" + +"lodash.isnumber@^3.0.3": + "integrity" "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + "resolved" "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz" + "version" "3.0.3" + +"lodash.isplainobject@^4.0.6": + "integrity" "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + "resolved" "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz" + "version" "4.0.6" + +"lodash.isstring@^4.0.1": + "integrity" "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + "resolved" "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz" + "version" "4.0.1" + "lodash.merge@^4.6.2": "integrity" "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" "resolved" "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" "version" "4.6.2" +"lodash.once@^4.0.0": + "integrity" "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + "resolved" "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz" + "version" "4.1.1" + "lodash.truncate@^4.4.2": "integrity" "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=" "resolved" "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz" @@ -2563,7 +2650,7 @@ dependencies: "queue-microtask" "^1.2.2" -"safe-buffer@~5.2.0", "safe-buffer@5.2.1": +"safe-buffer@^5.0.1", "safe-buffer@~5.2.0", "safe-buffer@5.2.1": "integrity" "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" "resolved" "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" "version" "5.2.1" @@ -2590,6 +2677,11 @@ "resolved" "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" "version" "5.7.1" +"semver@^5.6.0": + "integrity" "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "resolved" "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" + "version" "5.7.1" + "semver@^5.7.1": "integrity" "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" "resolved" "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz"