Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0ccff91
Add global timeout for JWT expiration
nickbeaird Sep 11, 2020
0e94576
Parse cookies in the backend
nickbeaird Sep 11, 2020
8a81204
Verify token provided to user in email
nickbeaird Sep 11, 2020
ef521f7
Add hard code redirect for localhost
nickbeaird Sep 11, 2020
72fb856
Verify token on the backend
nickbeaird Sep 11, 2020
16ba7d1
Verify token from the front end
nickbeaird Sep 11, 2020
0b30e8c
Remove firebase
nickbeaird Sep 11, 2020
948778e
Remove console statements
nickbeaird Sep 11, 2020
6cc9e21
Update README for testing changes
nickbeaird Sep 11, 2020
bea4391
Remove firebase reference on login
nickbeaird Sep 12, 2020
3debfcc
Merge branch 'development' into magic_link_2
nickbeaird Oct 4, 2020
f66b27e
Fix merge conflict
nickbeaird Oct 4, 2020
ca21716
Fix imports
nickbeaird Oct 4, 2020
9169b16
Merge branch 'development' into magic_link_2
nickbeaird Oct 12, 2020
aa07003
Update yarn lock for merge
nickbeaird Oct 12, 2020
1e172b2
Send email for localhost to nginx container
nickbeaird Oct 12, 2020
bd62a8a
Allow development on localhost or docker
nickbeaird Oct 12, 2020
7096409
Remove docker commit workflow
nickbeaird Oct 13, 2020
f3d0f1d
Change workflow name
nickbeaird Oct 13, 2020
dfd11dc
Merge branch 'development' into magic_link_2
nickbeaird Oct 13, 2020
3a8d28f
Fix Github actions
nickbeaird Oct 13, 2020
96bbd7c
Fix github actions
nickbeaird Oct 13, 2020
1e85373
Fix github actions
nickbeaird Oct 13, 2020
e74809d
Fix Github Actions
nickbeaird Oct 13, 2020
b77cd6a
Fix merge actions
nickbeaird Oct 13, 2020
f89c4e4
Update github actions naming
nickbeaird Oct 13, 2020
3f666c9
Try merging worklfow
nickbeaird Oct 13, 2020
eb08fc5
Stop validators from removing periods
nickbeaird Oct 13, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 22 additions & 23 deletions .github/workflows/main.yaml → .github/workflows/all-merges.yaml
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
name: Build VMRS App
on:
push:
branches: [development]
pull_request:
branches: [development]

jobs:
test-and-push-docker:
push-docker-container:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true
steps:
- uses: actions/checkout@v2
- name: Build the backend container
run: docker-compose -f docker-compose-ci.yaml build backend
- name: Run backend test suite
run: docker-compose -f docker-compose-ci.yaml run --rm backend yarn run test --testPathIgnorePatterns=routers
- name: Build the frontend container
run: docker-compose build client
- name: Run frontend test suite
run: docker-compose run --rm client yarn run test
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
Expand All @@ -27,17 +18,8 @@ jobs:
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push backend
uses: docker/build-push-action@v2
with:
context: ./backend
file: ./backend/Dockerfile.api
target: api-production
platforms: linux/amd64
push: true
tags: |
vrmsdeploy/vrms:backend
- name: Build and push client
id: client_build
uses: docker/build-push-action@v2
with:
context: ./client
Expand All @@ -48,14 +30,31 @@ jobs:
push: true
tags: |
vrmsdeploy/vrms:client
- name: Client digest
run: echo ${{ steps.client_build.outputs.digest }}
- name: Build and push backend
id: backend_build
uses: docker/build-push-action@v2
with:
context: ./backend
file: ./backend/Dockerfile.api
platforms: linux/amd64
target: api-production
stdin_open: true
push: true
tags: |
vrmsdeploy/vrms:backend
- name: Backend digest
run: echo ${{ steps.backend_build.outputs.digest }}
- name: Build and push nginx
uses: docker/build-push-action@v2
id: nginx_build
with:
context: ./nginx
file: ./nginx/Dockerfile.nginx
platforms: linux/amd64
push: true
tags: |
vrmsdeploy/vrms:nginx
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
- name: Nginx digest
run: echo ${{ steps.nginx_build.outputs.digest }}
18 changes: 18 additions & 0 deletions .github/workflows/all-prs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Build VMRS App
on:
pull_request:
branches: [development]

jobs:
run-unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build the backend container
run: docker-compose -f docker-compose-ci.yaml build backend
- name: Run backend test suite
run: docker-compose -f docker-compose-ci.yaml run --rm backend yarn run test --testPathIgnorePatterns=routers
- name: Build the frontend container
run: docker-compose build client
- name: Run frontend test suite
run: docker-compose run --rm client yarn run test
27 changes: 7 additions & 20 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,19 @@ You will need to be in the backend directory for this to work.

To maintain idempotent tests, we have opted to use in memory test databases. Jest, like
most test runners, has hooks or methods for you to call before or after tests. We can
call the `beforeAll` and `afterAll` methods setup and tear down our database before each
test.
can setup our db and tear it down by importing the `setupDB` module.

```js
// You will need to require the db-handler file.
const dbHandler = require("./db-handler");
const { setupDB } = require("../setup-test");

// Call the dbHanlder methods in your beforeAll and afterAll methods.
beforeAll(async () => await dbHandler.connect());
afterAll(async () => await dbHandler.closeDatabase());
// You will need to name the in memory DB for this test.
setupDB("api-auth");
```

In addition to hooks to call after the file is completed, Jest also has hooks/methods to
call before and after each test case. **At this time, the `dbHandler` method for clearing
the database does not work, so you can remove each collection manually if needed.

```js
// You will need to require the db-handler file.
const dbHandler = require("./db-handler");

// You will need to get the Model.
const Event = require("../models/event.model.js");

// Then you can delete the Collection in the db beforeAll or afterAll.
afterEach(async () => await Event.remove({}));
```
If you are unsure of where to start, then find a test that does something similar to your
aims. Copy, tweak, and run that test until you have your desired outcome. Also make sure
to give your test it's own name.

### Unit Tests

Expand Down
4 changes: 4 additions & 0 deletions backend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const cron = require("node-cron");
const fetch = require("node-fetch");
const morgan = require("morgan");
const path = require("path");
const cookieParser = require("cookie-parser");

require("dotenv").config();

Expand All @@ -17,6 +18,9 @@ const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

// Used to save JWT token from MagicLink
app.use(cookieParser());

// HTTP Request Logger
app.use(morgan("dev"));

Expand Down
1 change: 1 addition & 0 deletions backend/config/auth.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ module.exports = {
SECRET:
'c0d7d0716e4cecffe9dcc77ff90476d98f5aace08ea40f5516bd982b06401021191f0f24cd6759f7d8ca41b64f68d0b3ad19417453bddfd1dbe8fcb197245079',
CUSTOM_REQUEST_HEADER: process.env.CUSTOM_REQUEST_HEADER,
TOKEN_EXPIRATION_SEC: 900,
};
/* eslint-enable */
4 changes: 2 additions & 2 deletions backend/controllers/email.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ async function mailServer(email, token) {
});
}
const encodedToken = encodeURIComponent(token);
const emailLink = `https://tinyurl.com/2drxdk/auth/me?token=${encodedToken}`;
const emailLink = `https://tinyurl.com/nyqxd/handleauth?token=${encodedToken}&signIn=true`;
const encodedUri = encodeURI(emailLink);
const mailOptions = {
from: EMAIL_ACCOUNT,
Expand All @@ -54,7 +54,7 @@ async function mailServer(email, token) {
html: `<a href=${encodedUri}>
LOGIN HERE
</a>`,
text: `Magic link: ${encodedUri}`,
text: `Magic link: ${emailLink}`,
};

const localhostEmail = async () => {
Expand Down
40 changes: 33 additions & 7 deletions backend/controllers/user.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ const DB = require('../models');

const User = DB.user;


function generateAccessToken(user) {
// expires after half and hour (1800 seconds = 30 minutes)
return jwt.sign({ id: user.id }, CONFIG.SECRET, { expiresIn: '1800s' });
return jwt.sign({ id: user.id, role: user.accessLevel }, CONFIG.SECRET, {
expiresIn: `${CONFIG.TOKEN_EXPIRATION_SEC}s`,
});
}

function createUser(req, res) {
Expand All @@ -28,9 +29,8 @@ function createUser(req, res) {
user.save((err, usr) => {
if (err) {
return res.status(500).send({ message: err });
}
return res.status(200).send({ message: 'User was registered successfully!' });

}
return res.status(200).send({ message: 'User was registered successfully!' });
});

const jsonToken = generateAccessToken(user);
Expand All @@ -57,10 +57,34 @@ function signin(req, res) {
});
}

function verifySignIn(req, res) {
let token = req.headers['x-access-token'] || req.headers['authorization'];

if (!token) {
return res.status(403).send({ message: 'Auth token is not supplied' });
}
if (token.startsWith('Bearer ')) {
// Remove Bearer from string
token = token.slice(7, token.length);
}

jwt.verify(token, CONFIG.SECRET, (err, decoded) => {
if (err) {
return res.status(401).send({ message: err });
}
res.cookie('token', token, { httpOnly: true });
res.sendStatus(200);
});
}

function verifyMe(req, res) {
res.send(200);
}

async function validateCreateUserAPICall(req, res, next) {
await body('name.firstName').not().isEmpty().trim().escape().run(req);
await body('name.lastName').not().isEmpty().trim().escape().run(req);
await body('email', 'Invalid email').exists().isEmail().normalizeEmail().run(req);
await body('email', 'Invalid email').exists().isEmail().normalizeEmail({ gmail_remove_dots: false }).run(req);

// Finds the validation errors in this request and wraps them in an object with handy functions
const errors = validationResult(req);
Expand All @@ -72,7 +96,7 @@ async function validateCreateUserAPICall(req, res, next) {
}

async function validateSigninUserAPICall(req, res, next) {
await body('email', 'Invalid email').exists().isEmail().normalizeEmail().run(req);
await body('email', 'Invalid email').exists().isEmail().normalizeEmail({ gmail_remove_dots: false }).run(req);

// Finds the validation errors in this request and wraps them in an object with handy functions
const errors = validationResult(req);
Expand All @@ -88,6 +112,8 @@ const userController = {
validateSigninUserAPICall,
createUser,
signin,
verifySignIn,
verifyMe,
};

module.exports = userController;
24 changes: 21 additions & 3 deletions backend/middleware/authJwt.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,41 @@ const jwt = require('jsonwebtoken');
const CONFIG = require('../config/auth.config.js');

function verifyToken(req, res, next) {
const token = req.headers['x-access-token'];
let token = req.headers['x-access-token'] || req.headers['authorization'];

if (!token) {
return res.status(403).send({ message: 'No token provided!' });
return res.status(403).send({ message: 'Auth token is not supplied' });
}
if (token.startsWith('Bearer ')) {
// Remove Bearer from string
token = token.slice(7, token.length);
}

jwt.verify(token, CONFIG.SECRET, (err, decoded) => {
if (err) {
return res.status(401).send({ message: 'Unauthorized!' });
return res.status(401).send({ message: err });
}
res.cookie('token', token, { httpOnly: true });
req.userId = decoded.id;
return next();
});
return next();
}

function verifyCookie(req, res, next) {
jwt.verify(req.cookies.token, CONFIG.SECRET, (err, decoded) => {
if (err) {
return res.status(401).send({ message: err });
}
req.userId = decoded.id;
req.role = decoded.accessLevel;

next();
});
}

const authJwt = {
verifyToken,
verifyCookie,
};
module.exports = authJwt;
4 changes: 2 additions & 2 deletions backend/middleware/verifyUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function checkDuplicateEmail(req, res, next) {
});
}

function isAdmin(req, res, next) {
function isAdminByEmail(req, res, next) {
User.findOne({ email: req.body.email }).then((user) => {
if (!user) {
res.status(400).send({
Expand All @@ -38,7 +38,7 @@ function isAdmin(req, res, next) {

const verifyUser = {
checkDuplicateEmail,
isAdmin,
isAdminByEmail,
};

module.exports = verifyUser;
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@slack/bolt": "^2.2.3",
"async": "^3.2.0",
"body-parser": "^1.19.0",
"cookie-parser": "^1.4.5",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
Expand Down
12 changes: 8 additions & 4 deletions backend/routers/auth.router.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const express = require('express');
const { verifyUser } = require('../middleware');
const { authJwt, verifyUser } = require('../middleware');
const userController = require('../controllers/user.controller');

const router = express.Router();
Expand All @@ -17,9 +17,13 @@ router.post(
);

router.post(
'/signin',
[userController.validateSigninUserAPICall, verifyUser.isAdmin],
userController.signin,
"/signin",
[userController.validateSigninUserAPICall, verifyUser.isAdminByEmail],
userController.signin
);

router.post("/verify-signin", userController.verifySignIn);

router.post('/me', [authJwt.verifyCookie], userController.verifyMe);

module.exports = router;
Loading