backend | autentikaatio | middlewares

Notes-demo 2 (3/3)

Middleware backend

Viimeinen versio backendistä notesdemolle jossa otettu käyttöön middlewaret.

Asenna ensimmäiseksi express

npx express-generator --no-view --git

Asenna tämän jälkeen tarvittavat kirjastot

npm install
npm install cors --save
npm install nodemon --save-dev
npm install dotenv --save
npm install mysql2 --save
npm install knex --save
npm install bcryptjs --save
npm install jsonwebtoken --save
npm install ajv --

Varmista, että .gitignore sisältää ainakin seuraavat tiedostot:

node_modules/
*.env
build/

Lisää .env:

.env
PORT=3001
DB_USER=root
DB_PASS=mypass123
DB_HOST=localhost
DB_PORT=3306
DB_TYPE=mysql2
DB_DATABASE=notesdemo_db
SECRET=tosisalainensalasanainen

DEBUG=notesmiddleware:*

Ota dotenv käyttöön app.js-tiedostossa:

require('dotenv').config()

Lisää kansio utils ja sinne tiedosto config.

config.js
let PORT = process.env.PORT
let SECRET = process.env.SECRET

let DATABASE_OPTIONS = {
    client: process.env.DB_TYPE,
    connection: {
        host: process.env.DB_HOST,
        user: process.env.DB_USER,
        password: process.env.DB_PASS,
        database: process.env.DB_DATABASE
    }
}

module.exports = {
  DATABASE_OPTIONS,
  PORT,
  SECRET
}

Lisää scripts tiedostoon package.json:

"startdev": "nodemon ./bin/www"  

Käynnistä backend:

npm run startdev

Lisää kansioon utils tiedosto dbConnection.

dbConnection.js
const config = require('./config.js')
const options = config.DATABASE_OPTIONS;
const db = require('knex')(options)
module.exports = db;

Routers

login.js
var express = require('express');
var router = express.Router();
const knex = require('../utils/dbConnection');
const config = require('../utils/config.js')

const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')

router.post('/', (req, res, next) => {
    const user = req.body;
    console.log(user);

    knex('users').select('*').where('username', '=', user.username)
        .then((dbuser) => {
            if (dbuser.length == 0) {
                return res.status(401).json(
                    { error: "invalid username or password" }
                )
            }
            const tempUser = dbuser[0];
            bcrypt.compare(user.password, tempUser.password)
                .then((passwordCorrect) => {
                    if (!passwordCorrect) {
                        return res.status(401).json(
                            { error: "invalid username or password" }
                        )
                    } 

                    const userForToken = {
                        username: tempUser.username,
                        id: tempUser.id
                    } 

                    const token = jwt.sign(userForToken, config.SECRET)

                    res.status(200).send({
                        token,
                        username: tempUser.username,
                        role: "regularuser"
                    })
                })
        })
        .catch((err) => {
            res.status(500).json(
                { error: err }
            )
        })
})

module.exports = router;
register.js
var express = require('express');
var router = express.Router();
const knex = require('../utils/dbConnection');

const bcrypt = require('bcryptjs')

router.post('/', (req, res, next) => {
    const user = req.body;
    const saltRounds = 10;
    console.log(user);

    bcrypt.hash(user.password, saltRounds)
        .then((passwordHash) => {
            const newUser = {
                username: user.username,
                password: passwordHash,
                email: user.email
            }
    
            knex('users').insert(newUser)
                .then(() => {
                    res.status(204).end()
                })
                .catch((err) => {
                    console.log(err);
                    res.status(500).json(
                        { error: err }
                    )
                })
        })
})

module.exports = router;

Reititys notes-tiedoille

notesRouter.js
var express = require('express');
var router = express.Router();
const knex = require('../utils/dbConnection');

router.get('/', (req, res, next) => {
    const decodedTokenId = res.locals.auth.userId; 

    knex('notes').select('*').where('user_id', '=', decodedTokenId)
        .then((rows) => {
            res.json(rows);
        })
        .catch((err) => {
            console.log('SELECT * NOTES failed')
            res.status(500).json(
                { error: err }
            )
        })
})

router.post('/', (req, res, next) => {
    const note = req.body;
    console.log(note);

    note.user_id = res.locals.auth.userId; 

    const newNote = {
        content: note.content,
        important: note.important,
        date: new Date(note.date),
        user_id: note.user_id 
    }

    knex('notes').insert(newNote)
        .then(id_arr => {
            console.log(id_arr);
            note.id = id_arr[0];
            res.json(note);
        })
        .catch((err) => {
            console.log(err);
            res.status(500).json(
                { error: err }
            )
        })
})

router.delete('/:id', (req, res, next) => {
    const id = req.params.id;
    console.log(id);

    const decodedTokenId = res.locals.auth.userId; 

    knex('notes').where('user_id', "=", decodedTokenId).andWhere('id', '=', id).del()
        .then(status => {
            console.log("deleted ok")
            res.status(204).end();
        })
        .catch((err) => {
            console.log(err);
            res.status(500).json(
                { error: err }
            )
        })
})

router.put('/:id', (req, res, next) => {
    const id = req.params.id;
    const note = req.body;

    const decodedTokenId = res.locals.auth.userId; 

    const updatedNote = {
        content: note.content,
        important: note.important,
        date: new Date(note.date)
        }

    knex('notes').update(updatedNote).where('user_id', "=", decodedTokenId)
    .andWhere('id', '=', id)
        .then((response) => {
            console.log(response)
            res.status(204).end();
        })
        .catch((err) => {
            console.log(err);
            res.status(500).json(
                { error: err }
            )
        })

})

module.exports = router;

Middlewares

auth.js

Lisää kansio middleware ja sinne tiedosto auth.js

const jwt = require('jsonwebtoken')
const config = require('../utils/config')

const getTokenFrom = req => {
    const authorization = req.get('authorization');
    if (authorization && authorization.toLowerCase().startsWith('bearer ')) {
        return authorization.substring(7)
    } else {
        return null
    }
}

const isAuthenticated = (req, res, next) => {
    const token = getTokenFrom(req);
    console.log(token);

    if (!token) {
        return res.status(401).json(
            { error: "auth token missing" }
        )
    }

    let decodedToken = null;

    try {
        decodedToken = jwt.verify(token, config.SECRET);
    }
    catch (error) {
        console.log("jwt error")
    }

    if (!decodedToken || !decodedToken.id) {
        return res.status(401).json(
            { error: "invalid token" }
        )
    }
    res.locals.auth = { userId: decodedToken.id }; 
    next(); 
}

module.exports = isAuthenticated;

Ota käyttöön app.js-tiedostolla

var isAuthenticated = require('./middleware/auth');

Muuta routerit

app.use('/', indexRouter);
app.use('/notes', isAuthenticated, notesRouter);
app.use('/login', loginRouter);
app.use('/register', registerRouter);

Lisää cors

const cors = require('cors')
app.use(cors())

Tietojen validointi scheman avulla

Lisää middleware-kansioon tiedosto validate.js

validate.js
const Ajv = require('ajv');
var ajv = new Ajv(); // options can be passed, e.g. {allErrors: true}

const validateSchema = (schema) => {
    return function (req, res, next) {
        console.log("starting middleware2")
        const reqmethod = req.method;
        if (reqmethod === "POST" || reqmethod === "PUT") {
            const body = req.body;
            var validate = ajv.compile(schema);
            var valid = validate(body);
            if (!valid) {
                console.log(validate.errors);
                return res.status(401).json(
                    { error: "check json-data" })
            } else {
                next();
            }
        }
        else {
            next();
        }
    }
}

module.exports = validateSchema;

Tee uusi kansio schemas ja luo sinne tiedosto userSchema.json

userSchema.json
{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "user",
    "type": "object",
    "properties": {
        "email": {
            "type": "string",
            "pattern": "^\\S+@\\S+\\.\\S+$",
            "minLength": 5,
            "maxLength": 50
        },
        "username": {
            "type": "string",
            "minLength": 6,
            "maxLength": 32
        },
        "password": {
            "type": "string",
            "minLength": 8,
            "maxLength": 32
        },
        "phonenumber": {
            "type": "string",
            "pattern": "\\+(9[976]\\d|8[987530]\\d|6[987]\\d|5[90]\\d|42\\d|3[875]\\d|2[98654321]\\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)\\d{1,14}$",
            "minLength": 8,
            "maxLength": 32
        }
    },
    "required": [
        "username",
        "password"
    ]
}
notesSchema.json
{
    "$schema": "http://json-schema.org/draft-07/schema#",
    "title": "note",
    "type": "object",
    "properties": {
        "content": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
        },
        "date": {
            "type": "string",
            "pattern": "\\b[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3}Z\\b"
        },
        "important": {
            "type": "boolean"
        },
        "user_id": {
            "type": "integer"
        }
    },
    "required": [
        "content",
        "date",
        "important"
    ]
}

Ota käyttöön app.js-tiedostolla

var userSchema = require('./schemas/userSchema.json');
var notesSchema = require('./schemas/notesSchema.json');
var validateSchema = require('./middleware/validate');

app.use('/register', validateSchema(userSchema), registerRouter);
app.use('/login', loginRouter);
app.use('/notes', isAuthenticated, validateSchema(notesSchema), notesRouter);