From 4c3a31c77f50b8a7f5266046652e695b3e16738d Mon Sep 17 00:00:00 2001 From: Mikayla Dobson <93477693+innocuous-symmetry@users.noreply.github.com> Date: Tue, 31 Jan 2023 19:06:15 -0600 Subject: [PATCH] setting up auth system --- server/controllers/AuthController.js | 91 ++++++++++++++++++++++++ server/controllers/ControllerResponse.js | 11 +++ server/controllers/ItemController.js | 24 +++++++ server/db/seed.js | 3 + server/db/sql/create/createUserTable.sql | 4 +- server/loaders/passport.js | 12 +++- server/models/Item.js | 22 +++++- server/models/User.js | 62 ++++++++++++++++ server/package.json | 2 +- server/routes/auth.js | 52 +++++++++++++- server/routes/index.js | 13 +++- server/routes/item.js | 21 ++++-- 12 files changed, 303 insertions(+), 14 deletions(-) create mode 100644 server/controllers/AuthController.js create mode 100644 server/controllers/ControllerResponse.js create mode 100644 server/controllers/ItemController.js diff --git a/server/controllers/AuthController.js b/server/controllers/AuthController.js new file mode 100644 index 0000000..6700f82 --- /dev/null +++ b/server/controllers/AuthController.js @@ -0,0 +1,91 @@ +const bcrypt = require('bcrypt'); + +const ControllerResponse = require('./ControllerResponse'); +const User = require('../models/User'); + +module.exports = class AuthController { + static async getOne(id) { + const user = await User.getOne(id); + + const ok = user !== null; + const data = ok ? user : ("No user found with id " + id); + const code = ok ? 200 : 404; + + return new ControllerResponse(ok, data, code); + } + + static async getOneByEmail(email) { + const user = await User.getOneByEmail(email); + + const ok = user !== null; + const data = ok ? user : ("No user found with email" + email); + const code = ok ? 200 : 404; + + return new ControllerResponse(ok, data, code); + } + + static async getAll() { + const list = await User.getAll(); + + const ok = list.length > 0; + const data = ok ? list : "No user data found"; + const code = ok ? 200 : 404; + + return new ControllerResponse(ok, data, code); + } + + static async create() { + + } + + /** + * + * @param {{ email: string, password: string }} userData - data to compare against encrypted DB entry + * @returns { ControllerResponse | null } controller response, or null failing all else + */ + static async login(userData) { + try { + const potentialUser = await AuthController.getOneByEmail(userData.email); + + if (potentialUser.ok) { + const match = await bcrypt.compare(userData.password, potentialUser.data.password); + return new ControllerResponse(match, (match ? potentialUser : "Invalid credentials"), (match ? 200 : 403)); + } else { + return new ControllerResponse(false, "No user found with these credentials", 404); + } + } catch (error) { + console.log(error); + } + + return null; + } + + static async logout() { + + } + + /** + * ## Register method + * + * @param {{ username: string, password: string, email: string }} data - provided user data + * @returns { ControllerResponse } controller response including status codes + */ + static async register(userData) { + try { + const potentialUser = await AuthController.getOneByEmail(userData.email); + if (potentialUser.ok) { + return new ControllerResponse(false, ("User already registered with email " + userData.email), 401); + } + + const salt = await bcrypt.genSalt(12); + const hash = await bcrypt.hash(userData.password, salt); + + const newUser = new User(userData.username, userData.email, hash); + const result = await User.create(newUser); + + return new ControllerResponse(result.rows.length > 0, result, (result.rows.length > 0 ? 201 : 400)); + } catch (error) { + console.log(error); + } + } +} \ No newline at end of file diff --git a/server/controllers/ControllerResponse.js b/server/controllers/ControllerResponse.js new file mode 100644 index 0000000..d67174d --- /dev/null +++ b/server/controllers/ControllerResponse.js @@ -0,0 +1,11 @@ +module.exports = class ControllerResponse { + ok; + data; + code; + + constructor(ok, data, code) { + this.ok = ok; + this.data = data; + this.code = code; + } +} \ No newline at end of file diff --git a/server/controllers/ItemController.js b/server/controllers/ItemController.js new file mode 100644 index 0000000..26cd5d5 --- /dev/null +++ b/server/controllers/ItemController.js @@ -0,0 +1,24 @@ +const Item = require('../models/Item'); +const ControllerResponse = require('./ControllerResponse'); + +module.exports = class ItemController { + static async getAll() { + const result = await Item.getAll(); + + const ok = result !== null; + const code = ok ? 200 : 404; + const data = ok ? result : "No items found"; + + return new ControllerResponse(ok, data, code); + } + + static async getOne(id) { + const result = await Item.getOne(id); + + const ok = result !== null; + const code = ok ? 200 : 404; + const data = ok ? result : ("No item found with ID " + id); + + return new ControllerResponse(ok, data, code); + } +} \ No newline at end of file diff --git a/server/db/seed.js b/server/db/seed.js index 62e6fcf..5914a51 100644 --- a/server/db/seed.js +++ b/server/db/seed.js @@ -9,6 +9,9 @@ const root = path.resolve(__dirname); async function seed() { console.clear(); + await pool.query(`DROP TABLE IF EXISTS item`); + await pool.query(`DROP TABLE IF EXISTS appuser`); + const createUserTable = fs.readFileSync(root + "/sql/create/createUserTable.sql").toString(); const createItemTable = fs.readFileSync(root + "/sql/create/createItemTable.sql").toString(); const populateItemTable = fs.readFileSync(root + "/sql/populate/populateItemTable.sql").toString(); diff --git a/server/db/sql/create/createUserTable.sql b/server/db/sql/create/createUserTable.sql index 6bc6099..5d5e412 100644 --- a/server/db/sql/create/createUserTable.sql +++ b/server/db/sql/create/createUserTable.sql @@ -3,6 +3,6 @@ CREATE TABLE IF NOT EXISTS appuser ( username VARCHAR NOT NULL, email VARCHAR UNIQUE, password VARCHAR NOT NULL, - created VARCHAR NOT NULL, - modified VARCHAR NOT NULL + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + modified TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); \ No newline at end of file diff --git a/server/loaders/passport.js b/server/loaders/passport.js index f942f3f..a7da541 100644 --- a/server/loaders/passport.js +++ b/server/loaders/passport.js @@ -1,5 +1,6 @@ const passport = require('passport'); const { Strategy } = require('passport-local'); +const AuthController = require('../controllers/authController'); async function passportLoader(app) { app.use(passport.initialize()); @@ -13,13 +14,20 @@ async function passportLoader(app) { done(null, user); }) - passport.use(new Strategy(async (email, password, done) => { + passport.use(new Strategy({ usernameField: "email", passwordField: "password" }, async (email, password, done) => { try { - console.log(email, password); + const response = await AuthController.login({ email: email, password: password }); + if (response && response.ok) { + return done(null, response.data.data); + } else { + return done(null, false); + } } catch (error) { return done(error); } })) + + return passport; } module.exports = passportLoader; \ No newline at end of file diff --git a/server/models/Item.js b/server/models/Item.js index b7f9587..608e10b 100644 --- a/server/models/Item.js +++ b/server/models/Item.js @@ -1,4 +1,6 @@ -class Item { +const pool = require('../db'); + +module.exports = class Item { name; description; created; @@ -12,7 +14,23 @@ class Item { this.modified = new Date(Date.now()).toDateString(); } - async getAll() { + static async getAll() { + const query = `SELECT * FROM item;` + const result = await pool.query(query); + if (result.rows.length) { + return result.rows; + } + return null; + } + + static async getOne(id) { + const query = `SELECT * FROM item WHERE id = $1`; + const result = await pool.query(query, [id]); + if (result.rows.length) { + return result.rows[0]; + } + + return null; } } \ No newline at end of file diff --git a/server/models/User.js b/server/models/User.js index b305f4f..6fbf794 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -1,3 +1,5 @@ +const pool = require('../db'); + module.exports = class User { username; email; @@ -12,4 +14,64 @@ module.exports = class User { this.created = new Date(Date.now()).toDateString(); this.modified = new Date(Date.now()).toDateString(); } + + presentAsArray() { + return [this.username, this.email, this.password]; + } + + static async getOne(id) { + const query = `SELECT * FROM appuser WHERE id = $1`; + const result = await pool.query(query, [id]); + if (result.rows.length) { + return result.rows[0]; + } + + return null; + } + + static async getOneByEmail(email) { + const query = `SELECT * FROM appuser WHERE email = $1`; + const result = await pool.query(query, [email]); + if (result.rows.length) { + return result.rows[0]; + } + + return null; + } + + static async getAll() { + const query = `SELECT * FROM appuser;`; + const result = await pool.query(query); + if (result.rows.length) { + return result.rows; + } + + return null; + } + + static async create(data) { + let result; + const query = `INSERT INTO appuser (username, email, password) VALUES ($1, $2, $3) RETURNING *`; + + if (!data instanceof User) { + const newUser = new User(data.username, data.email, data.password); + result = await pool.query(query, newUser.presentAsArray()); + } else { + result = await pool.query(query, data.presentAsArray()); + } + + return result.rows; + } + + static async login() { + + } + + static async logout() { + + } + + static async register() { + + } } \ No newline at end of file diff --git a/server/package.json b/server/package.json index 33760e1..e071c67 100644 --- a/server/package.json +++ b/server/package.json @@ -18,7 +18,7 @@ "dotenv": "^16.0.3", "express": "^4.18.2", "express-session": "^1.17.3", - "passport": "^0.6.0", + "passport": "^0.4.0", "passport-local": "^1.0.0", "pg": "^8.9.0" }, diff --git a/server/routes/auth.js b/server/routes/auth.js index d6a91b4..66b576d 100644 --- a/server/routes/auth.js +++ b/server/routes/auth.js @@ -1,5 +1,55 @@ -function authRoute(app, passport) { +const AuthController = require('../controllers/authController'); +const router = require('express').Router(); + +async function authRoute(app, passport) { + router.post('/register', async (req, res) => { + try { + const data = req.body; + const response = await AuthController.register(data); + + res.status(response.code).send(response.data); + } catch (error) { + console.log(error); + } + }) + + router.post('/login', async (req, res, next) => { + try { + const data = req.body; + const response = await AuthController.login(data); + + if (!response || !response.ok) { + res.status(response.code || 400).send(response.data || "Something went wrong"); + } else { + req.session.user = response.data; + req.session.save((err) => { + return next(err); + }) + + res.send(response.data); + } + } catch (error) { + next(error); + } + }) + + router.delete('/logout', async (req, res, next) => { + try { + req.session.destroy((err) => { + if (err) throw err; + req.logout((err) => { + if (err) return next(err); + }) + }) + + res.status(204).send({ ok: true }); + } catch (error) { + next(error); + } + }) + + return router; } module.exports = authRoute; \ No newline at end of file diff --git a/server/routes/index.js b/server/routes/index.js index 2872907..dfa2dbc 100644 --- a/server/routes/index.js +++ b/server/routes/index.js @@ -1,5 +1,16 @@ -async function routesLoader(app, passport) { +const authRoute = require("./auth"); +const itemRoute = require("./item"); +async function routesLoader(app, passport) { + const authRouter = await authRoute(app, passport); + const itemRouter = await itemRoute(app, passport); + + app.get('/', (req, res) => { + res.send(req.session); + }) + + app.use('/auth', authRouter); + app.use('/app', passport.authenticate('local'), itemRouter); } module.exports = routesLoader; \ No newline at end of file diff --git a/server/routes/item.js b/server/routes/item.js index 77b9346..b8d0a97 100644 --- a/server/routes/item.js +++ b/server/routes/item.js @@ -1,9 +1,20 @@ const router = require('express').Router(); +const ItemController = require('../controllers/ItemController'); function itemRoute(app, passport) { - app.use('/app/items', router); - - router.get('/', (req, res) => { - res.status(200).send('items'); + router.get('/item', async (req, res) => { + const response = await ItemController.getAll(); + const { data, code } = response; + res.status(code).send(data); }) -} \ No newline at end of file + + router.get('/item/:id', async (req, res) => { + const { id } = req.params; + const { data, code } = await ItemController.getOne(id); + res.status(code).send(data); + }) + + return router; +} + +module.exports = itemRoute; \ No newline at end of file