Merge pull request #36 from bluewave-labs/feat/login-route

Added login route, updated readme
This commit is contained in:
Veysel
2024-05-20 18:44:29 -04:00
committed by GitHub
6 changed files with 278 additions and 56 deletions

235
README.md
View File

@@ -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
<details>
<summary><code>User</code></summary>
| 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 |
</details>
<details>
<summary><code>Monitor</code></summary>
| 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 |
</details>
---
##### GET /api/v1/monitor/:id
<details>
<summary><code>POST</code> <b>/api/v1/auth/register</b></summary>
###### 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
}
}
```
</details>
---
##### GET /api/v1/monitors/user/:userId
<details>
<summary><code>POST</code> <b>/api/v1/auth/login</b></summary>
| 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
}
}
```
</details>
---
<details>
<summary><code>GET</code> <b>/api/v1/monitors</b></summary>
##### Method/Headers
> | Method/Headers | Value |
> | -------------- | ----- |
> | Method | GET |
##### Response Payload
> | Type | Notes |
> | ---------------- | ----------------- |
> | `Array[Monitor]` | Array of monitors |
</details>
---
<details>
<summary><code>GET</code> <b>/api/v1/monitor/{id}</b></summary>
###### Method/Headers
> | Method/Headers | Value |
> | -------------- | ----- |
> | Method | GET |
###### Response Payload
> | Type | Notes |
> | --------- | --------------------------------------------------- |
> | `Monitor` | Single monitor with the id in the request parameter |
</details>
---
<details>
<summary><code>GET</code> <b>/api/v1/monitors/user/{userId}</b></summary>
###### Method/Headers
> | Method/Headers | Value |
> | -------------- | ----- |
> | Method | GET |
###### Request Payload
> | Type | Notes |
> | ---------------- | ------------------------------------------------------------------ |
> | `Array[Monitor]` | Array of monitors created by user with userId specified in request |
</details>
## Contributors

View File

@@ -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 };

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;
module.exports = router;