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;