diff --git a/README.md b/README.md index 3397a324d..777ed19ab 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,11 @@ BlueWave uptime monitoring application - Clone this repository to your local machine +1. [Installation (Client)](#client) +1. [Installation (Server)](#server) +1. [Configuration(Server)](#config-server) +1. [Endpoints](#endpoints) + --- ### Client @@ -30,14 +35,14 @@ BlueWave uptime monitoring application --- -#### Configuration +#### Configuration {#config-server} Configure the server with the following environmental variables -| ENV Varialbe Name | Required/Optional | Type | Description | -| -------------------- | ----------------- | ------ | --------------------------------- | -| DB_CONNECTION_STRING | Required | string | Specfies URL for MongoDB Database | -| MAILERSEND_API_KEY | Required | string | Specfies API KEY for MailerSend service | +| ENV Varialbe Name | Required/Optional | Type | Description | +| -------------------- | ----------------- | ------ | ---------------------------------------------------- | +| DB_CONNECTION_STRING | Required | string | Specfies URL for MongoDB Database | +| MAILERSEND_API_KEY | Required | string | Specfies API KEY for MailerSend service | | SYSTEM_EMAIL_ADDRESS | Required | string | Specfies System email to be used in emailing service | --- @@ -64,9 +69,28 @@ Example: {success: true, msg: "Successful Request", data: {test: testData}} ``` +--- + ##### Data Types -###### Monitor +
+User + +| Name | Type | Notes | +| ------------- | --------- | --------------------- | +| firstname | `string` | First name | +| lastname | `string` | Last name | +| email | `string` | User's email | +| profilePicUrl | `string` | URL to User's picture | +| isActive | `boolean` | Default to `true` | +| isVerified | `boolean` | Default to `false` | +| updated_at | `Date` | Last update time | +| created_at | `Date` | Time created at | + +
+ +
+Monitor | Name | Type | Notes | | ----------- | --------- | ---------------------------------------- | @@ -79,51 +103,186 @@ Example: | updatedAt | `Date` | Last time the monitor was updated | | CreatedAt | `Date` | When the monitor was updated | ---- - -##### GET /api/v1/monitors - -###### Response - -| Status Code | Type | Description | -| ----------- | --------------------- | -------------------------------- | -| 200 | Response with Payload | Response with a list of monitors | - -###### Payload - -| Type | Notes | -| ---------------- | ----------------- | -| `Array[Monitor]` | Array of monitors | +
--- -##### GET /api/v1/monitor/:id +
+POST /api/v1/auth/register -###### Response +##### Method/Headers -| Status Code | Type | Description | -| ----------- | --------------------- | ------------------------------ | -| 200 | Response with Payload | Response with a single monitor | +> | Method/Headers | Value | +> | -------------- | ---------------- | +> | Method | POST | +> | content-type | application/json | -###### Payload +##### Body -| Type | Notes | -| --------- | --------------------------------------------------- | -| `Monitor` | Single monitor with the id in the request parameter | +> | Name | Type | Notes | +> | --------- | -------- | ------------------- | +> | firstname | `string` | | +> | lastname | `string` | +> | email | `string` | Valid email address | +> | password | `string` | Min 8 chars | + +##### Response Payload + +> | Type | Notes | +> | ------ | ---------- | +> | `User` | User model | + +##### Sample CURL request + +``` +curl --request POST \ + --url http://localhost:5000/api/v1/auth/register \ + --header 'Content-Type: application/json' \ + --data '{ + "firstname" : "User First Name", + "lastname": "User Last Name", + "email" : "user@gmail.com", + "password": "user_password" +}' +``` + +##### Sample Response + +```json +{ + "success": true, + "msg": "User created}", + "data": { + "_id": "6645079aae0b439371913972", + "firstname": "User First Name", + "lastname": "User Last Name", + "email": "user@gmail.com", + "isActive": true, + "isVerified": false, + "updated_at": "2024-05-15T19:06:02.720Z", + "created_at": "2024-05-15T19:06:02.720Z", + "__v": 0 + } +} +``` + +
--- -##### GET /api/v1/monitors/user/:userId +
+POST /api/v1/auth/login -| Status Code | Type | Description | -| ----------- | --------------------- | -------------------------------- | -| 200 | Response with Payload | Response with a list of monitors | +##### Method/Headers -###### Payload +> | Method/Headers | Value | +> | -------------- | ---------------- | +> | Method | POST | +> | content-type | application/json | -| Type | Notes | -| ---------------- | ------------------------------------------------------------------ | -| `Array[Monitor]` | Array of monitors created by user with userId specified in request | +##### Body + +> | Name | Type | Notes | +> | -------- | -------- | ------------------- | +> | email | `string` | Valid email address | +> | password | `string` | Min 8 chars | + +##### Response Payload + +> | Type | Notes | +> | ------ | ---------- | +> | `User` | User model | + +##### Sample CURL request + +``` +curl --request POST \ + --url http://localhost:5000/api/v1/auth/login \ + --header 'Content-Type: application/json' \ + --data '{ + "email" : "user@gmail.com", + "password": "user_password" +}' +``` + +##### Sample response + +```json +{ + "success": true, + "msg": "Found user", + "data": { + "_id": "6644fb9bae0b439371913969", + "firstname": "User First Name", + "lastname": "User Last Name", + "email": "user@gmail.com", + "isActive": true, + "isVerified": false, + "updated_at": "2024-05-15T18:14:51.049Z", + "created_at": "2024-05-15T18:14:51.049Z", + "__v": 0 + } +} +``` + +
+ +--- + +
+GET /api/v1/monitors + +##### Method/Headers + +> | Method/Headers | Value | +> | -------------- | ----- | +> | Method | GET | + +##### Response Payload + +> | Type | Notes | +> | ---------------- | ----------------- | +> | `Array[Monitor]` | Array of monitors | + +
+ +--- + +
+GET /api/v1/monitor/{id} + +###### Method/Headers + +> | Method/Headers | Value | +> | -------------- | ----- | +> | Method | GET | + +###### Response Payload + +> | Type | Notes | +> | --------- | --------------------------------------------------- | +> | `Monitor` | Single monitor with the id in the request parameter | + +
+ +--- + +
+GET /api/v1/monitors/user/{userId} + +###### Method/Headers + +> | Method/Headers | Value | +> | -------------- | ----- | +> | Method | GET | + +###### Request Payload + +> | Type | Notes | +> | ---------------- | ------------------------------------------------------------------ | +> | `Array[Monitor]` | Array of monitors created by user with userId specified in request | + +
## Contributors diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index eba12db7d..e8fdc739e 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -1,7 +1,7 @@ const express = require("express"); const UserModel = require("../models/user"); -const { registerValidation } = require("../validation/joi"); -const logger = require('../utils/logger') +const { registerValidation, loginValidation } = require("../validation/joi"); +const logger = require("../utils/logger"); /** * @function @@ -23,28 +23,73 @@ const registerController = async (req, res) => { try { const isUser = await req.db.getUserByEmail(req, res); if (isUser) { - logger.warning("User already exists!", { "service": "auth", "userId": isUser._id }); + logger.error("User already exists!", { + service: "auth", + userId: isUser._id, + }); return res .status(400) .json({ success: false, msg: "User already exists!" }); } } catch (error) { - logger.error(error.message, { "service": "auth" }); + logger.error(error.message, { service: "auth" }); return res.status(500).json({ success: false, msg: error.message }); } - try { // Create a new user const newUser = await req.db.insertUser(req, res); // TODO: Send an email to user // Will add this later - logger.info("New user created!", { "service": "auth", "userId": newUser._id }); - return res.json({ success: true, msg: "User created}", data: newUser }); + logger.info("New user created!", { service: "auth", userId: newUser._id }); + return res + .status(200) + .json({ success: true, msg: "User created}", data: newUser }); } catch (error) { - logger.error(error.message, { "service": "auth" }); + logger.error(error.message, { service: "auth" }); return res.status(500).json({ success: false, msg: error.message }); } }; -module.exports = { registerController }; +// ************************** +// Handles logging in a user +// Returns a user at the moment, but will likely return a JWT in the future +// ************************** +const loginController = async (req, res) => { + try { + // Validate input + await loginValidation.validateAsync(req.body); + + // Check if user exists + const user = await req.db.getUserByEmail(req, res); + + // If user not found, throw an error + if (!user) { + throw new Error("User not found!"); + } + // Compare password + const match = await user.comparePassword(req.body.password); + console.log(user); + if (!match) { + throw new Error("Password does not match!"); + } + + // Copy user to remove password. More memory, but better than mutating user? + // We can move this to the DB layer if we want + // Probably not neccessary at all as we'll likely return a JWT instead of a user + const userWithoutPassword = { ...user._doc }; + delete userWithoutPassword.password; + + // Happy path, return user + // In the future we'll probably return a JWT instead of a user + return res + .status(200) + .json({ success: true, msg: "Found user", data: userWithoutPassword }); + } catch (error) { + // Anything else should be an error + logger.error(error.message, { service: "auth" }); + return res.status(500).json({ success: false, msg: error.message }); + } +}; + +module.exports = { registerController, loginController }; diff --git a/Server/db/FakeDb.js b/Server/db/FakeDb.js index 8cf05cf89..a21b66ffd 100644 --- a/Server/db/FakeDb.js +++ b/Server/db/FakeDb.js @@ -22,6 +22,7 @@ const Monitor = require("../models/Monitor"); const UserModel = require("../models/user"); +const bcrypt = require("bcrypt"); const FAKE_MONITOR_DATA = []; const USERS = []; @@ -52,6 +53,8 @@ const connect = async () => { const insertUser = async (req, res) => { try { const newUser = new UserModel({ ...req.body }); + const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait + newUser.password = await bcrypt.hash(newUser.password, salt); // hash is also async, need to eitehr await or use hashSync USERS.push(newUser); return newUser; } catch (error) { diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js index e3edbb7e2..b6a126ab8 100644 --- a/Server/db/MongoDB.js +++ b/Server/db/MongoDB.js @@ -1,6 +1,7 @@ const Monitor = require("../models/Monitor"); const mongoose = require("mongoose"); const UserModel = require("../models/user"); +const user = require("../models/user"); const connect = async () => { try { @@ -14,18 +15,22 @@ const connect = async () => { const insertUser = async (req, res) => { try { - const newUser = await UserModel.create({ ...req.body }).select('-password'); - return newUser; + const newUser = new UserModel({ ...req.body }); + await newUser.save(); + return await UserModel.findOne({ _id: newUser._id }).select("-password"); // .select() doesn't work with create, need to save then find } catch (error) { throw error; } }; +// Gets a user by Email. Not sure if we'll ever need this except for login. +// If not needed except for login, we can move password comparison here const getUserByEmail = async (req, res) => { try { // Returns null if no user is found - const user = await UserModel.findOne({ email: req.body.email }).select('-password'); - return user; + // Need the password to be able to compare, removed .select() + // We can strip the hash before returing the user + return await UserModel.findOne({ email: req.body.email }); } catch (error) { throw error; } diff --git a/Server/models/user.js b/Server/models/user.js index 822c303d8..02d0d53b3 100644 --- a/Server/models/user.js +++ b/Server/models/user.js @@ -45,7 +45,13 @@ UserSchema.pre("save", async function (next) { next(); } const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait - this.password = bcrypt.hash(this.password, salt); + this.password = await bcrypt.hash(this.password, salt); // hash is also async, need to eitehr await or use hashSync + next(); }); +UserSchema.methods.comparePassword = function (submittedPassword) { + console.log(submittedPassword, this.password); + return bcrypt.compare(submittedPassword, this.password); +}; + module.exports = mongoose.model("User", UserSchema); diff --git a/Server/routes/authRoute.js b/Server/routes/authRoute.js index c778927e5..48a173b92 100644 --- a/Server/routes/authRoute.js +++ b/Server/routes/authRoute.js @@ -1,6 +1,10 @@ -const router = require('express').Router(); -const { registerController } = require('../controllers/authController') +const router = require("express").Router(); +const { + registerController, + loginController, +} = require("../controllers/authController"); -router.post('/register', registerController) +router.post("/register", registerController); +router.post("/login", loginController); -module.exports = router; \ No newline at end of file +module.exports = router;