From 5a406be06e9637d9d492173be271b524b9554f5b Mon Sep 17 00:00:00 2001 From: andrzej Date: Mon, 27 May 2024 16:12:11 +0200 Subject: [PATCH] translate tutorial to node/sql framework --- auth-routes/loginUser.mjs | 52 ---- auth/auth.mjs | 77 +++++ objects/Endpoints.mjs | 62 ++++- package-lock.json | 19 ++ package.json | 1 + test/endpoints.test.mjs | 572 ++++++++++++++++++-------------------- 6 files changed, 414 insertions(+), 369 deletions(-) delete mode 100644 auth-routes/loginUser.mjs create mode 100644 auth/auth.mjs diff --git a/auth-routes/loginUser.mjs b/auth-routes/loginUser.mjs deleted file mode 100644 index bd03458..0000000 --- a/auth-routes/loginUser.mjs +++ /dev/null @@ -1,52 +0,0 @@ -import jwt from "jsonwebtoken"; -import passport from "passport"; -import jwtSecret from "../config/jwtConfig"; -import { db } from "../db.mjs"; -import logger from "../logger.mjs"; - -module.exports = (app) => { - app.post("/loginUser", (req, res, next) => { - passport.authenticate("login", (err, users, info) => { - if (err) { - logger.error(`error ${err}`); - } - if (info !== undefined) { - logger.error(info.message); - if (info.message === "bad username") { - res.status(401).send(info.message); - } else { - res.status(403).send(info.message); - } - } else { - req.logIn(users, async () => { - let user = await db("users") - .select("*") - .where({ username: req.body.username }); - user = user[0]; - const token = jwt.sign({ id: user.id }, jwtSecret.secret, { - expiresIn: 60 * 60, - }); - res.status(200).send({ - auth: true, - token, - message: "user found & logged in", - }); - // User.findOne({ - // where: { - // username: req.body.username, - // }, - // }).then((user) => { - // const token = jwt.sign({ id: user.id }, jwtSecret.secret, { - // expiresIn: 60 * 60, - // }); - // res.status(200).send({ - // auth: true, - // token, - // message: "user found & logged in", - // }); - // }); - }); - } - })(req, res, next); - }); -}; diff --git a/auth/auth.mjs b/auth/auth.mjs new file mode 100644 index 0000000..a85efbd --- /dev/null +++ b/auth/auth.mjs @@ -0,0 +1,77 @@ +import passport from "passport"; +import * as passportLocal from "passport-local"; +import { db } from "../db.mjs"; +import logger from "../logger.mjs"; +import bcrypt from "bcrypt"; +//This code saves the information provided by the user to the database, and then sends the user information to the next middleware if successful. +passport.use( + "signup", + new localStrategy( + { + usernameField: "email", + passwordField: "password", + }, + async (email, password, done) => { + try { + const user = await db("users").insert({ email, password }); + return done(null, user); + } catch (error) { + done(error); + } + }, + ), +); + +async function isValidPwd(user, pwd) { + return bcrypt.compare(pwd, user.password); +} + +passport.use( + "login", + new localStrategy( + { + usernameField: "email", + passwordField: "password", + }, + async (email, password, done) => { + try { + const user = await db("users").select("*").where({ email }); + + if (user.length === 0) { + return done(null, false, { message: "User not found" }); + } + user = user[0]; + + const validate = await isValidPwd(user, password); + + if (!validate) { + return done(null, false, { message: "Wrong Password" }); + } + + return done(null, user, { message: "Logged in Successfully" }); + } catch (error) { + return done(error); + } + }, + ), +); +// ... + +const JWTstrategy = require("passport-jwt").Strategy; +const ExtractJWT = require("passport-jwt").ExtractJwt; +//This code uses passport-jwt to extract the JWT from the query parameter. It then verifies that this token has been signed with the secret or key set during logging in (TOP_SECRET). If the token is valid, the user details are passed to the next middleware. +passport.use( + new JWTstrategy( + { + secretOrKey: "TOP_SECRET", + jwtFromRequest: ExtractJWT.fromUrlQueryParameter("secret_token"), + }, + async (token, done) => { + try { + return done(null, token.user); + } catch (error) { + done(error); + } + }, + ), +); diff --git a/objects/Endpoints.mjs b/objects/Endpoints.mjs index 6cfb12b..95871ef 100644 --- a/objects/Endpoints.mjs +++ b/objects/Endpoints.mjs @@ -39,26 +39,60 @@ export const getEndpoints = (dbObject) => { return router; }; -export const postEndpoints = (db, data) => { +export const protectedEndpoints = (db, data) => { const router = express.Router(); - protectedEndpoint(router, Story, "create", "insert", db, data); - protectedEndpoint(router, Story, "edit", "update", db, data); - protectedEndpoint(router, Story, "delete", "update", db, data); - protectedEndpoint(router, Submission, "create", "insert", db, data); - protectedEndpoint(router, Submission, "edit", "update", db, data); - protectedEndpoint(router, Submission, "delete", "update", db, data); - protectedEndpoint(router, Publication, "create", "insert", db, data); - protectedEndpoint(router, Publication, "edit", "update", db, data); - protectedEndpoint(router, Publication, "delete", "del", db, data); + writeEndpoint(router, Story, "create", "insert", db, data); + writeEndpoint(router, Story, "edit", "update", db, data); + writeEndpoint(router, Story, "delete", "update", db, data); + writeEndpoint(router, Submission, "create", "insert", db, data); + writeEndpoint(router, Submission, "edit", "update", db, data); + writeEndpoint(router, Submission, "delete", "update", db, data); + writeEndpoint(router, Publication, "create", "insert", db, data); + writeEndpoint(router, Publication, "edit", "update", db, data); + writeEndpoint(router, Publication, "delete", "del", db, data); + //Auth endpoints + router.post( + "/signup", + passport.authenticate("signup", { session: false }), + async (req, res) => { + res.json({ + message: "Signup successful", + user: req.user, + }); + }, + ); + router.post("/login", async (req, res, next) => { + passport.authenticate("login", async (err, user, info) => { + try { + if (err || !user) { + const error = new Error("An error occurred."); + + return next(error); + } + + req.login(user, { session: false }, async (error) => { + if (error) return next(error); + + const body = { _id: user._id, email: user.email }; + const token = require("jsonwebtoken").sign( + { user: body }, + "TOP_SECRET", + ); + + return res.json({ token }); + }); + } catch (error) { + return next(error); + } + })(req, res, next); + }); return router; }; -const protectedEndpoint = (router, Entity, path, method, db, data) => { +const writeEndpoint = (router, Entity, path, method, db, data) => { router.post( `/${Entity.name.toLowerCase()}/${path}`, - passport.authenticate("jwt", { session: false }, (_, res) => { - res.json({ message: "protected endpoint" }); - }), + passport.authenticate("jwt", { session: false }), async (req, res) => { try { logger.trace({ data: req.body }, "POST request received"); diff --git a/package-lock.json b/package-lock.json index 14fb04a..0dc5f9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "GPL-3.0-or-later", "dependencies": { + "bcrypt": "^5.1.1", "body-parser": "^1.20.2", "chai": "^4.3.8", "chai-as-promised": "^7.1.1", @@ -595,6 +596,24 @@ } ] }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/bcrypt/node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", diff --git a/package.json b/package.json index 18a9336..7be0f17 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "author": "Andrzej Stepien", "license": "GPL-3.0-or-later", "dependencies": { + "bcrypt": "^5.1.1", "body-parser": "^1.20.2", "chai": "^4.3.8", "chai-as-promised": "^7.1.1", diff --git a/test/endpoints.test.mjs b/test/endpoints.test.mjs index cf9c02b..429e34b 100644 --- a/test/endpoints.test.mjs +++ b/test/endpoints.test.mjs @@ -1,311 +1,277 @@ -import {describe} from "mocha"; -import chai, { expect } from "chai"; +import { describe } from "mocha"; +import chai, { expect } from "chai"; import bodyParser from "body-parser"; -import express from 'express' +import express from "express"; import chaiHttp from "chai-http"; import { testDb as db } from "../db.mjs"; import { Data } from "../objects/Data.mjs"; import { beforeEach, afterEach } from "mocha"; -import { postEndpoints, getEndpoints } from "../objects/Endpoints.mjs"; +import { protectedEndpoints, getEndpoints } from "../objects/Endpoints.mjs"; -chai.use(chaiHttp) -const app = express() -const data = new Data(db) -await data.init() -app.use(bodyParser.json()) -app.use('/api',getEndpoints(data)) -app.use('/api', postEndpoints(db,data)) +chai.use(chaiHttp); +const app = express(); +const data = new Data(db); +await data.init(); +app.use(bodyParser.json()); +app.use("/api", getEndpoints(data)); +app.use("/api", protectedEndpoints(db, data)); +describe("testing endpoints...", async function () { + describe("Testing GET endpoints", async function () { + describe("GET stories", async function () { + it("should return a status code of 200 and an array", async function () { + const res = await chai.request(app).get("/api/stories"); + expect(res).to.have.status(200); + expect(res.body).to.be.a("array"); + }); + }); + describe("GET submissions", async function () { + it("should return a status code of 200 and an array", async function () { + const res = await chai.request(app).get("/api/submissions"); + expect(res).to.have.status(200); + expect(res.body).to.be.a("array"); + }); + }); + describe("GET publications", async function () { + it("should return a status code of 200 and an array", async function () { + const res = await chai.request(app).get("/api/publications"); + expect(res).to.have.status(200); + expect(res.body).to.be.a("array"); + }); + }); + }); + describe("testing /create endpoints", async function () { + describe("/story/create", async function () { + const goodData = { + title: "#test", + word_count: 111, + deleted: 0, + }; + const badData = { + title: 1, + word_count: "not a number", + }; + afterEach(async function () { + await db("stories").where("title", goodData.title).del(); + }); + it("should return 200 if a valid request is made", async function () { + const res = await chai + .request(app) + .post("/api/story/create") + .send(goodData); + expect(res).to.have.status(200); + }); + it("should return 400 if an invalid request is made", async function () { + const res = await chai + .request(app) + .post("/api/story/create") + .send(badData); + expect(res).to.have.status(400); + }); + it("the new entry should exist in the database", async function () { + await chai.request(app).post("/api/story/create").send(goodData); + const res = await db("stories") + .select("*") + .where("title", goodData.title); + expect(res[0].title).to.eql(goodData.title); + }); + }); + describe("/publication/create", async function () { + const goodData = { + title: "#test", + link: "www.internet.com", + deleted: 0, + }; + const badData = { + title: 1, + link: 1, + }; + afterEach(async function () { + await db("pubs").where("title", goodData.title).del(); + }); + it("should return 200 if a valid request is made", async function () { + const res = await chai + .request(app) + .post("/api/publication/create") + .send(goodData); + expect(res).to.have.status(200); + }); + it("should return 400 if an invalid request is made", async function () { + const res = await chai + .request(app) + .post("/api/publication/create") + .send(badData); + expect(res).to.have.status(400); + }); + it("the new entry should exist in the database", async function () { + await chai.request(app).post("/api/publication/create").send(goodData); + const res = await db("pubs").select("*").where("title", goodData.title); + expect(res[0].title).to.eql(goodData.title); + }); + }); + describe("/submission/create", async function () { + const goodData = { + story_id: 1, + pub_id: 1, + response_id: 1, + date_submitted: "1066-01-01", + date_responded: "1066-01-01", + }; + const badData = { + story_id: "string", + pub_id: 1, + response_id: 1, + date_submitted: "1066-01-01", + date_responded: "1066-01-01", + }; + afterEach(async function () { + await db("subs").where("date_submitted", goodData.date_submitted).del(); + }); + it("should return 200 if a valid request is made", async function () { + const res = await chai + .request(app) + .post("/api/submission/create") + .send(goodData); + expect(res).to.have.status(200); + }); + it("should return 400 if an invalid request is made", async function () { + const res = await chai + .request(app) + .post("/api/submission/create") + .send(badData); + expect(res).to.have.status(400); + }); + it("the new entry should exist in the database", async function () { + await chai.request(app).post("/api/submission/create").send(goodData); + const res = await db("subs") + .select("*") + .where("date_submitted", goodData.date_submitted); + expect(res[0].date_responded).to.eql(goodData.date_responded); + }); + }); + }); + describe("testing /edit endpoints", async function () { + describe("/story/edit", async function () { + const goodData = { + id: 1, + title: "#test", + word_count: 111, + deleted: 0, + }; + const badData = { + id: "string", + }; + let prev = {}; + beforeEach(async function () { + prev = await db("stories").select("*").where("id", 1); + prev = prev[0]; + }); + afterEach(async function () { + await db("stories").where("id", 1).update(prev); + }); + it("should return 200 when sent valid data", async function () { + const res = await chai + .request(app) + .post("/api/story/edit") + .send(goodData); + expect(res).to.have.status(200); + }); + it("should return 400 when sent invalid data", async function () { + const res = await chai + .request(app) + .post("/api/story/edit") + .send(badData); + expect(res).to.have.status(400); + }); + it("the edit should be reflected in the database", async function () { + await chai.request(app).post("/api/story/edit").send(goodData); + const res = await db("stories").select("*").where("id", goodData.id); + expect(res[0]).to.eql(goodData); + }); + }); + describe("/publication/edit", async function () { + const goodData = { + id: 1, + title: "#test", + link: "link", + query_after_days: 90, + deleted: 0, + }; + const badData = { + id: "string", + }; + let prev = {}; + beforeEach(async function () { + prev = await db("pubs").select("*").where("id", 1); + prev = prev[0]; + }); + afterEach(async function () { + await db("pubs").where("id", 1).update(prev); + }); + it("should return 200 when sent valid data", async function () { + const res = await chai + .request(app) + .post("/api/publication/edit") + .send(goodData); + expect(res).to.have.status(200); + }); + it("should return 400 when sent invalid data", async function () { + const res = await chai + .request(app) + .post("/api/publication/edit") + .send(badData); + expect(res).to.have.status(400); + }); + it("the edit should be reflected in the database", async function () { + await chai.request(app).post("/api/publication/edit").send(goodData); + const res = await db("pubs").select("*").where("id", goodData.id); + expect(res[0]).to.eql(goodData); + }); + }); + describe("/submission/edit", async function () { + const goodData = { + id: 1, + story_id: 1, + pub_id: 1, + response_id: 1, + date_submitted: "1066-01-01", + date_responded: "1066-01-01", + }; + const badData = { + story_id: "string", + pub_id: 1, + response_id: 1, + date_submitted: "1066-01-01", + date_responded: "1066-01-01", + }; + let prev = {}; + beforeEach(async function () { + prev = await db("subs").select("*").where("id", 1); + prev = prev[0]; + }); + afterEach(async function () { + await db("subs").where("id", 1).update(prev); + }); + it("should return 200 when sent valid data", async function () { + const res = await chai + .request(app) + .post("/api/submission/edit") + .send(goodData); + expect(res).to.have.status(200); + }); + it("should return 400 when sent invalid data", async function () { + const res = await chai + .request(app) + .post("/api/submission/edit") + .send(badData); + expect(res).to.have.status(400); + }); + it("the edit should be reflected in the database", async function () { + await chai.request(app).post("/api/submission/edit").send(goodData); + const res = await db("subs").select("*").where("id", goodData.id); + expect(res[0]).to.eql(goodData); + }); + }); + }); +}); -describe("testing endpoints...",async function(){ -describe("Testing GET endpoints", async function(){ - describe("GET stories",async function(){ - it("should return a status code of 200 and an array", async function(){ - const res = await chai.request(app).get('/api/stories') - expect(res).to.have.status(200) - expect(res.body).to.be.a('array') - }) - }) - describe("GET submissions",async function(){ - it("should return a status code of 200 and an array", async function(){ - const res = await chai.request(app).get('/api/submissions') - expect(res).to.have.status(200) - expect(res.body).to.be.a('array') - }) - }) - describe("GET publications",async function(){ - it("should return a status code of 200 and an array", async function(){ - const res = await chai.request(app).get('/api/publications') - expect(res).to.have.status(200) - expect(res.body).to.be.a('array') - }) - }) -}) -describe("testing /create endpoints", async function(){ - - describe("/story/create",async function(){ - const goodData = { - title:"#test", - word_count:111, - deleted:0 - } - const badData = { - title:1, - word_count:"not a number" - } - afterEach(async function(){ - await db('stories') - .where('title',goodData.title) - .del() - }) - it("should return 200 if a valid request is made",async function(){ - const res = await chai.request(app) - .post('/api/story/create') - .send(goodData) - expect(res).to.have.status(200) - }) - it("should return 400 if an invalid request is made",async function(){ - const res = await chai.request(app) - .post('/api/story/create') - .send(badData) - expect(res).to.have.status(400) - }) - it("the new entry should exist in the database",async function(){ - await chai.request(app) - .post('/api/story/create') - .send(goodData) - const res = await db('stories') - .select('*') - .where('title',goodData.title) - expect(res[0].title).to.eql(goodData.title) - }) - - }) - describe("/publication/create",async function(){ - const goodData = { - title:"#test", - link:"www.internet.com", - deleted:0 - } - const badData = { - title:1, - link:1 - } - afterEach(async function(){ - await db('pubs') - .where('title',goodData.title) - .del() - }) - it("should return 200 if a valid request is made",async function(){ - const res = await chai.request(app) - .post('/api/publication/create') - .send(goodData) - expect(res).to.have.status(200) - }) - it("should return 400 if an invalid request is made",async function(){ - const res = await chai.request(app) - .post('/api/publication/create') - .send(badData) - expect(res).to.have.status(400) - }) - it("the new entry should exist in the database",async function(){ - await chai.request(app) - .post('/api/publication/create') - .send(goodData) - const res = await db('pubs') - .select('*') - .where('title',goodData.title) - expect(res[0].title).to.eql(goodData.title) - }) - - }) - describe("/submission/create",async function(){ - const goodData = { - story_id:1, - pub_id:1, - response_id:1, - date_submitted:"1066-01-01", - date_responded:"1066-01-01" - } - const badData = { - story_id:"string", - pub_id:1, - response_id:1, - date_submitted:"1066-01-01", - date_responded:"1066-01-01" - } - afterEach(async function(){ - await db('subs') - .where('date_submitted',goodData.date_submitted) - .del() - }) - it("should return 200 if a valid request is made",async function(){ - const res = await chai.request(app) - .post('/api/submission/create') - .send(goodData) - expect(res).to.have.status(200) - }) - it("should return 400 if an invalid request is made",async function(){ - const res = await chai.request(app) - .post('/api/submission/create') - .send(badData) - expect(res).to.have.status(400) - }) - it("the new entry should exist in the database",async function(){ - await chai.request(app) - .post('/api/submission/create') - .send(goodData) - const res = await db('subs') - .select('*') - .where('date_submitted',goodData.date_submitted) - expect(res[0].date_responded).to.eql(goodData.date_responded) - }) - - }) -}) -describe("testing /edit endpoints",async function(){ - describe("/story/edit",async function(){ - const goodData = { - id:1, - title:"#test", - word_count:111, - deleted:0 - } - const badData = { - id:"string" - } - let prev = {} - beforeEach(async function(){ - prev = await db('stories') - .select('*') - .where('id',1) - prev = prev[0] - }) - afterEach(async function(){ - await db('stories') - .where('id',1) - .update(prev) - }) - it("should return 200 when sent valid data",async function(){ - const res = await chai.request(app) - .post('/api/story/edit') - .send(goodData) - expect(res).to.have.status(200) - }) - it("should return 400 when sent invalid data",async function(){ - const res = await chai.request(app) - .post('/api/story/edit') - .send(badData) - expect(res).to.have.status(400) - }) - it("the edit should be reflected in the database",async function(){ - await chai.request(app) - .post('/api/story/edit') - .send(goodData) - const res = await db('stories'). - select('*') - .where('id',goodData.id) - expect(res[0]).to.eql(goodData) - }) - - }) - describe("/publication/edit",async function(){ - const goodData = { - id:1, - title:"#test", - link:"link", - query_after_days:90, - deleted:0 - } - const badData = { - id:"string" - } - let prev = {} - beforeEach(async function(){ - prev = await db('pubs') - .select('*') - .where('id',1) - prev = prev[0] - }) - afterEach(async function(){ - await db('pubs') - .where('id',1) - .update(prev) - }) - it("should return 200 when sent valid data",async function(){ - const res = await chai.request(app) - .post('/api/publication/edit') - .send(goodData) - expect(res).to.have.status(200) - }) - it("should return 400 when sent invalid data",async function(){ - const res = await chai.request(app) - .post('/api/publication/edit') - .send(badData) - expect(res).to.have.status(400) - }) - it("the edit should be reflected in the database",async function(){ - await chai.request(app) - .post('/api/publication/edit') - .send(goodData) - const res = await db('pubs'). - select('*') - .where('id',goodData.id) - expect(res[0]).to.eql(goodData) - }) - - }) - describe("/submission/edit",async function(){ - const goodData = { - id:1, - story_id:1, - pub_id:1, - response_id:1, - date_submitted:"1066-01-01", - date_responded:"1066-01-01" - } - const badData = { - story_id:"string", - pub_id:1, - response_id:1, - date_submitted:"1066-01-01", - date_responded:"1066-01-01" - } - let prev = {} - beforeEach(async function(){ - prev = await db('subs') - .select('*') - .where('id',1) - prev = prev[0] - }) - afterEach(async function(){ - await db('subs') - .where('id',1) - .update(prev) - }) - it("should return 200 when sent valid data",async function(){ - const res = await chai.request(app) - .post('/api/submission/edit') - .send(goodData) - expect(res).to.have.status(200) - }) - it("should return 400 when sent invalid data",async function(){ - const res = await chai.request(app) - .post('/api/submission/edit') - .send(badData) - expect(res).to.have.status(400) - }) - it("the edit should be reflected in the database",async function(){ - await chai.request(app) - .post('/api/submission/edit') - .send(goodData) - const res = await db('subs'). - select('*') - .where('id',goodData.id) - expect(res[0]).to.eql(goodData) - }) - - }) -}) - -}) \ No newline at end of file