Beginning the Budget App - Finishing the First Route
I realised that returning the username with the JWT is not useful to the rest of the app - I must return the user_id
instead. This can then be used in future requests.
Changing JWT
So first I updated getUserAuthData to also retrieve the user_id:
database/database.js
getUserAuthData(username) {
return this.knex
.select('user_id', 'app_access')
.from('user')
.where('username', username)
.first()
.then((result) => {
if (result) {
return result;
} else {
return null;
}})
.catch((error) => { console.error(error) })
}
Then ensure that the jwt is signed with the user_id instead of username:
routes/general/authentication.js
if (result) {
let userData = await db.getUserAuthData(req.body.username);
if (userData.app_access) {
let token = jwt.sign(
{ user_id: userData.user_id },
secret,
{ expiresIn: '24h' }
);
...
}
...
}
With that change, when the authHandler verifies the jwt, the user_id can be found in the req.decoded.user_id
variable in the subsequent routes.
handlers/authHandler.js
var checkToken = function(req, res, next) {
let token = req.headers['x-access-token'] || req.headers['authorization'];
if (token) {
if (token.startsWith('Bearer ')) {
// Remove Bearer from string
token = token.slice(7, token.length);
}
jwt.verify(token, secret, (err, decoded) => {
if (err) {
res.statusCode = 401;
return next('Authentication failed! Credentials are invalid');
} else {
req.decoded = decoded;
next();
}
});
} else {
res.statusCode = 400;
return next('Authentication failed! Please check the request');
}
};
Creating the first Budget route
With these changes I could go ahead and create the first budget route at /api/budget/transactions/
routes/budget/budget.js
const express = require('express');
const router = express.Router();
const Database = require('../../database/database');
require('dotenv').config({path: './.env'});
const dbURL = process.env['DB_URL'];
const db = new Database(dbURL);
router.get('/api/budget/transactions/', async (req, res, next) => {
try {
res.setHeader('content-type', 'application/json');
let transactions = await db.getAllTransactions(req.decoded.user_id);
res.statusCode = 200;
res.json({
success: true,
transactions: transactions
});
} catch (error) {
return next(error);
}
});
module.exports = router;
Now after logging in I can send a get request with the appropriate auth header to recieve all of the transactions on the account (for now just one):
{
"success": true,
"transactions": [
{
"date": "2020-04-23T14:00:00.000Z",
"amount": 500,
"notes": "Rent April",
"cleared": 0,
"category_id": 1,
"category_name": "Food",
"account_id": 1,
"account_name": null
}
]
}
Route test
Finally I created the test for this route - success! Now 7 passing tests!
const request = require('supertest');
const app = require('../index.js');
const chai = require('chai');
const chaiHttp = require('chai-http');
const { expect } = chai;
chai.use(chaiHttp);
describe('GET /api/budget/transactions/ SUCCESS', function() {
var token = null;
before(function(done) {
request(app)
.post('/login')
.send({ username: 'testuser@email.com', password: 'password' })
.end(function(err, res) {
token = res.body.token;
done();
});
});
it('Respond with success', function(done) {
request(app)
.get('/api/budget/transactions/')
.set("Authorization", "Bearer " + token)
.set({'Accept': 'application/json'})
.end(function(err, res) {
expect(res).to.exist;
expect(res.statusCode).to.equal(200);
expect(res.body.success).to.equal(true);
expect(res.body.transactions).to.exist;
done();
})
});
});
The next step is to look at creating a front-end for this budget app to display it nicely instead of JSON objects!