Skip to content
Permalink
Browse files
added login project
  • Loading branch information
aa7401 committed Oct 7, 2019
1 parent f24e9df commit c593b3eac8055a67e35e19f884d158c4caaeac87
Show file tree
Hide file tree
Showing 10 changed files with 371 additions and 2 deletions.
@@ -12,9 +12,9 @@ Before you start you need to pull any _upstream changes_. Detailed instructions

## 1 Modularisation

The first step you will need to do is to split your code up to make it easier to understand. Take a look at the end of the `index.js` routes file.
The first step you will need to do is to split your code up to make it easier to understand. Take a look at the `06_code_quality/login` project.

Start by updating your routes file by copying over the `modules/accounts.js` file from the lab materials and making sure you import it into your `index.js` file by adding the following statement just below where all the other modules are imported:
Notice the line where we import the module.

```javascript
const accounts = require('modules/accounts')
@@ -0,0 +1,147 @@
#!/usr/bin/env node

/**
* Routes File
*/

'use strict'

/* MODULE IMPORTS */
const Koa = require('koa')
const Router = require('koa-router')
const views = require('koa-views')
const staticDir = require('koa-static')
const bodyParser = require('koa-bodyparser')
const koaBody = require('koa-body')({multipart: true, uploadDir: '.'})
const session = require('koa-session')
const sqlite = require('sqlite-async')
const bcrypt = require('bcrypt-promise')
const fs = require('fs-extra')
const mime = require('mime-types')
//const jimp = require('jimp')

/* IMPORT CUSTOM MODULES */
const accounts = require('./modules/accounts')

const app = new Koa()
const router = new Router()

/* CONFIGURING THE MIDDLEWARE */
app.keys = ['darkSecret']
app.use(staticDir('public'))
app.use(bodyParser())
app.use(session(app))
app.use(views(`${__dirname}/views`, { extension: 'handlebars' }, {map: { handlebars: 'handlebars' }}))

const port = 8080
const saltRounds = 10

/**
* The secure home page.
*
* @name Home Page
* @route {GET} /
* @authentication This route requires cookie-based authentication.
*/
router.get('/', async ctx => {
try {
if(ctx.session.authorised !== true) return ctx.redirect('/login?msg=you need to log in')
const data = {}
if(ctx.query.msg) data.msg = ctx.query.msg
await ctx.render('index')
} catch(err) {
await ctx.render('error', {message: err.message})
}
})

/**
* The user registration page.
*
* @name Register Page
* @route {GET} /register
*/
router.get('/register', async ctx => await ctx.render('register'))

/**
* The script to process new user registrations.
*
* @name Register Script
* @route {POST} /register
*/
router.post('/register', koaBody, async ctx => {
try {
const body = ctx.request.body
console.log(body)
// PROCESSING FILE
const {path, type} = ctx.request.files.avatar
const fileExtension = mime.extension(type)
console.log(`path: ${path}`)
console.log(`type: ${type}`)
console.log(`fileExtension: ${fileExtension}`)
await fs.copy(path, 'public/avatars/avatar.png')
// ENCRYPTING PASSWORD AND BUILDING SQL
body.pass = await bcrypt.hash(body.pass, saltRounds)
const sql = `INSERT INTO users(user, pass) VALUES("${body.user}", "${body.pass}")`
console.log(sql)
// DATABASE COMMANDS
const db = await sqlite.open('./website.db')
await db.run(sql)
await db.close()
// REDIRECTING USER TO HOME PAGE
ctx.redirect(`/?msg=new user "${body.name}" added`)
} catch(err) {
await ctx.render('error', {message: err.message})
}
})

router.get('/login', async ctx => {
const data = {}
if(ctx.query.msg) data.msg = ctx.query.msg
if(ctx.query.user) data.user = ctx.query.user
await ctx.render('login', data)
})

router.post('/login', async ctx => {
try {
const body = ctx.request.body
const db = await sqlite.open('./website.db')
// DOES THE USERNAME EXIST?
const records = await db.get(`SELECT count(id) AS count FROM users WHERE user="${body.user}";`)
if(!records.count) return ctx.redirect('/login?msg=invalid%20username')
const record = await db.get(`SELECT pass FROM users WHERE user = "${body.user}";`)
await db.close()
// DOES THE PASSWORD MATCH?
const valid = await bcrypt.compare(body.pass, record.pass)
if(valid == false) return ctx.redirect(`/login?user=${body.user}&msg=invalid%20password`)
// WE HAVE A VALID USERNAME AND PASSWORD
ctx.session.authorised = true
return ctx.redirect('/?msg=you are now logged in...')
} catch(err) {
await ctx.render('error', {message: err.message})
}
})

// router.post('/login', async ctx => { // 19 lines reduced to 10!
// const body = ctx.request.body
// try {
// await accounts.checkCredentials(body.user, body.pass)
// ctx.session.authorised = true
// return ctx.redirect('/?msg=you are now logged in...')
// } catch(err) {
// return ctx.redirect(`/login?user=${body.user}&msg=${err.message}`)
// }
// })

router.get('/logout', async ctx => {
ctx.session.authorised = null;
ctx.redirect('/')
})

app.use(router.routes())
module.exports = app.listen(port, async() => {
// MAKE SURE WE HAVE A DATABASE WITH THE CORRECT SCHEMA
const db = await sqlite.open('./website.db')
await db.run('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, user TEXT, pass TEXT);')
await db.close()
console.log(`listening on port ${port}`)
})
@@ -0,0 +1,81 @@
#!/usr/bin/env node

/**
* Accounts module
* @module modules/accounts
*/

'use strict'

var sqlite = require('sqlite-async');
let bcrypt = require('bcrypt-promise');

/**
* This is a generic function that opens the database, executes a query,
* closes the database connection and returns the data.
* @param {String} query - The SQL statement to execute.
* @returns {Object} - the date returned by the query.
*/
async function runSQL(query) {
try {
console.log(query)
let DBName = "./website.db";
const db = await sqlite.open(DBName);
const data = await db.all(query);
await db.close();
if(data.length === 1) return data[0]
return data;
} catch(err) {
throw err
}
}

module.exports.checkCredentials = async(username, password)=> {
try {
var records = await runSQL(`SELECT count(id) AS count FROM users WHERE user="${username}";`);
if(!records.count) throw new Error("invalid username")
const record = await runSQL(`SELECT pass FROM users WHERE user = "${username}";`)
const valid = await bcrypt.compare(password, record.pass)
if(valid == false) throw new Error(`invalid password`)
return true
} catch(err) {
throw err
}
}



/* ----------------------------- STUB FUNCTIONS ----------------------------- */

/**
* This function checks the database to see if a username already exists in
* the database. If it detects a duplicate it throws an exception.
* @param {String} username - The username to check.
* @returns {boolean} - returns true if the username does not exist.
* @throws {Error} - throws an error if the username already exists.
*/
async function checkNoDuplicateUsername (username) {
return true
}

/**
* This function takes data from an uploaded image and saves it to the `avatars` directory. The file name will be the username.
* @param {String} path - the location of the uploaded image
* @param {String} mimeType - the mime type of the uploaded file.
* @returns {boolean} - returns true if the image is valid and is saved.
* @throws {TypeError} - throws an error if the file is not a png or jpg image.
*/
async function saveImage(path, mimetype) {
return true
}

/**
* This function adds new users to the database.
* @param {String} username - The username to to add.
* @param {String} password - The password to add.
* @returns {boolean} - returns true if the username does not exist.
* @throws {Error} - throws an error if the new user account has been created.
*/
module.exports.addUser = async(username, password) => {
return true;
}
@@ -0,0 +1,58 @@

{
"name": "10_auth",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"acceptance": "jest --coverage --detectOpenHandles",
"jsdoc": "node_modules/.bin/jsdoc -c jsdoc.conf",
"linter": "node_modules/.bin/eslint .",
"test": "jest --coverage --detectOpenHandles",
"unit": "node_modules/.bin/jest --coverage --runInBand tests/unit/"
},
"jest": {
"testEnvironment": "node",
"verbose": true,
"collectCoverage": true,
"coverageDirectory": "docs/coverage/",
"coverageThreshold": {
"global": {
"branches": 0,
"functions": 0,
"lines": 0,
"statements": 0
}
}
},
"author": "",
"license": "ISC",
"dependencies": {
"bcrypt": "^3.0.3",
"bcrypt-promise": "^2.0.0",
"fs-extra": "^7.0.1",
"handlebars": "^4.1.2",
"jimp": "^0.6.0",
"koa": "^2.6.2",
"koa-body": "^4.0.8",
"koa-bodyparser": "^4.2.1",
"koa-router": "^7.4.0",
"koa-session": "^5.10.1",
"koa-static": "^5.0.0",
"koa-views": "^6.1.5",
"mime-types": "^2.1.22",
"sqlite-async": "^1.0.11"
},
"devDependencies": {
"eslint": "^5.15.2",
"handlebars-validate": "^0.1.2",
"html-validator-cli": "^6.0.0",
"http-status-codes": "^1.3.2",
"jest": "^24.1.0",
"jsdoc": "^3.5.5",
"jsdoc-route-plugin": "^0.1.0",
"puppeteer": "^1.12.2",
"site-validator-cli": "^1.0.1",
"supertest": "^4.0.2"
}
}
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -0,0 +1,11 @@
body {
font-family: Arial, Helvetica, sans-serif;
}

.msg {
border: 1px solid red;
font-weight: bold;
color: red;
padding: 1em;
}

@@ -0,0 +1,15 @@
<!doctype html>

<html lang="en">
<head>
<meta charset="utf-8">
<title>ERROR</title>
<meta name="description" content="The HTML5 Template">
<meta name="author" content="Joe Bloggs">
<link href="style.css" type="text/css" rel="stylesheet" />
</head>
<body>
<h1>An Error Has Occurred</h1>
<h2>{{message}}</h2>
</body>
</html>
@@ -0,0 +1,14 @@
<!doctype html>

<html lang="en">
<head>
<meta charset="utf-8">
<title>Home Page</title>
<meta name="description" content="form to add new books">
<meta name="author" content="Mark Tyers">
<link href="style.css" type="text/css" rel="stylesheet" />
</head>
<body>
<h1>Home</h1>
<p>This is a secure page. Users need to have a valid account and be logged in to see it.</p>
</html>
@@ -0,0 +1,23 @@

<!doctype html>

<html lang="en">
<head>
<meta charset="utf-8">
<title>Log In</title>
<meta name="description" content="form to add new books">
<meta name="author" content="Mark Tyers">
<link href="style.css" type="text/css" rel="stylesheet" />
</head>
<body>
<h1>Log In</h1>
{{#if msg}}
<p class="msg">{{msg}}</p>
{{/if}}
<form action="/login" method="post">
<p>Username:<br /><input type="text" name="user" placeholder="your username" value="{{user}}" autofocus></p>
<p>Password:<br /><input type="password" name="pass" placeholder="your password" value=""></p>
<p><input type="submit" value="Log in"></p>
</form>
</body>
</html>
@@ -0,0 +1,20 @@
<!doctype html>

<html lang="en">
<head>
<meta charset="utf-8">
<title>Create an Account</title>
<meta name="description" content="form to add new books">
<meta name="author" content="Mark Tyers">
<link href="style.css" type="text/css" rel="stylesheet" />
</head>
<body>
<h1>Create an Account</h1>
<form action="/register" enctype="multipart/form-data" method="post">
<p>Username:<br /><input type="text" name="user" placeholder="your preferred username (no spaces)" value=""></p>
<p>Password:<br /><input type="password" name="pass" placeholder="your chosen password" value=""></p>
<p>Profile Picture:<br /><input type="file" name="avatar"></p>
<p><input type="submit" value="Create"></p>
</form>
</body>
</html>

0 comments on commit c593b3e

Please sign in to comment.