Database-based login/logout

This commit is contained in:
Timothy Warren 2014-09-24 17:58:53 -04:00
parent 02083af4a9
commit c5393b050e
8 changed files with 164 additions and 13 deletions

38
app.js
View File

@ -3,6 +3,7 @@
// ------------ Basic Dependencies ------------------------------------------- // ------------ Basic Dependencies -------------------------------------------
var express = require('express'), var express = require('express'),
session = require('express-session'), session = require('express-session'),
csrf = require('csurf'),
path = require('path'), path = require('path'),
favicon = require('serve-favicon'), favicon = require('serve-favicon'),
logger = require('morgan'), logger = require('morgan'),
@ -11,7 +12,7 @@ var express = require('express'),
requireDir = require('require-dir'), requireDir = require('require-dir'),
connection = require('express-myconnection'), connection = require('express-myconnection'),
bcrypt = require('bcrypt-nodejs'), bcrypt = require('bcrypt-nodejs'),
mysql = require('mysql'); mysql = require('mysql2');
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
var app = express(); var app = express();
@ -20,13 +21,13 @@ app.set('trust proxy', true); // Trust X-Forwarded-* headers
// Database connection // Database connection
app.use( app.use(
connection('mysql', { connection(mysql, {
host: 'localhost', host: 'localhost',
user: 'node', user: 'node',
password: 'node', password: 'node',
port: 3306, port: 3306,
database: 'node' database: 'node'
}, 'request') }, 'pool')
); );
// view engine setup // view engine setup
@ -42,7 +43,28 @@ app.use(logger('dev'));
app.use(bodyParser.json()); app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser()); app.use(cookieParser());
app.use(session({
resave: true,
saveUninitialized: true,
secret: 'j2uyc0hjh2;clkjang1ddojj'
}));
app.use(express.static(path.join(__dirname, 'public'), {redirect:false})); app.use(express.static(path.join(__dirname, 'public'), {redirect:false}));
app.use(csrf({
ignoreMethods: ['GET', 'HEAD', 'OPTIONS']
}));
//Check session for any pages that require authentication
app.use(function(err, req, res, next) {
if ( ! req.session.uid)
{
console.log("This should redirect to index!")
['/', '/login', '/logout'].forEach(function(item) {
if (req.path.match(item)) return next();
});
res.redirect(303, '/');
}
});
// Route mapping // Route mapping
// Routes are prefixed by the filename, // Routes are prefixed by the filename,
@ -54,6 +76,7 @@ Object.keys(routes).forEach(function(route) {
var path = (route != 'index') var path = (route != 'index')
? '/' + route ? '/' + route
: '/'; : '/';
app.use(path, routes[route]); app.use(path, routes[route]);
}); });
@ -66,6 +89,15 @@ app.use(function(req, res, next) {
// error handlers // error handlers
// csrf error handler
app.use(function(err, req, res, next) {
if (err.code !== 'EBADCSRFTOKEN') return next(err);
// Bad CSRF Token
res.status(403);
res.send('Session has expired, or has been tampered with.');
});
// development error handler // development error handler
// will print stacktrace // will print stacktrace
if (app.get('env') === 'development') { if (app.get('env') === 'development') {

View File

@ -16,7 +16,6 @@
"dustjs-linkedin":"*", "dustjs-linkedin":"*",
"dustjs-helpers":"*", "dustjs-helpers":"*",
"nodeunit":"*", "nodeunit":"*",
"mysql":"*",
"node-memcache":"*" "node-memcache":"*"
} }
} }

View File

@ -1,9 +1,73 @@
var express = require('express'); var express = require('express');
var router = express.Router(); var router = express.Router();
/* GET home page. */ /* GET Home / Login Form */
router.get('/', function(req, res) { router.get('/', function(req, res) {
res.render('index', { title: 'Express'}); var util = require('util');
var request = util.inspect(req, {depth: 2});
// If the user isn't logged in
if ( ! req.session.uid)
{
res.render('login', {
title: 'Node Task Manager',
csrfToken: req.csrfToken()
});
}
else
{
res.render('index', {
title: 'Node Task Manager',
user: req.session.username,
req: request
});
}
});
/* Login action */
router.post('/login', function(req, res) {
var bcrypt = require('bcrypt-nodejs');
var user = req.body.user,
pass = req.body.pass;
req.getConnection(function(err, connection) {
if (err) throw err;
var sql = " SELECT id, username, email, password, timezone, num_format " +
" FROM todo_user " +
" WHERE email = ? OR username = ? ";
// Find the username / email
connection.execute(sql, [user, user], function(err, rows, fields) {
if (err) throw err;
var user = rows[0];
// Verify the password hash
bcrypt.compare(pass, user.password, function(err, passRes) {
if (err) throw err;
// Password is good, set session data and redirect
if (passRes === true)
{
req.session.uid = user.id;
req.session.num_format = user.num_format;
req.session.username = user.username;
res.redirect(303, '/');
}
});
});
})
});
/* Logout action */
router.get('/logout', function(req, res) {
// Destroy the session, and redirect to the index page
req.session.destroy(function(err) {
res.redirect(303, '/');
});
}); });
module.exports = router; module.exports = router;

View File

@ -3,7 +3,23 @@ var router = express.Router();
/* GET list of tasks */ /* GET list of tasks */
router.get('/list', function(req, res) { router.get('/list', function(req, res) {
res.json([{}]); req.getConnection(function(err, connection) {
if (err) throw err;
var uid = req.session.uid;
if ( ! uid)
{
console.log("Redirect because of bad session in list route");
res.redirect('/');
return;
}
connection.execute('SELECT * from todo_task_view WHERE user_id=?', [uid], function(err, rows) {
if (err) throw err;
res.json(rows);
});
});
}); });

1
views/csrf.dust Normal file
View File

@ -0,0 +1 @@
<input type="hidden" name="_csrf" value="{csrfToken}" />

View File

@ -1,6 +1,6 @@
{>layout/} {>layout/}
{<content} {<content}
<h1>{title}</h1> <p>Hi, {user}. Welcome to {title}</p>
<p>Welcome to {title}</p> <pre>{req}</pre>
{/content} {/content}

View File

@ -2,17 +2,29 @@
<html> <html>
<head> <head>
<title>{title}</title> <title>{title}</title>
<link rel='stylesheet' href='/css/style.css' /> <link rel="stylesheet" href="/css/ink.min.css" />
<link rel="stylesheet" href="/css/ink-flex.min.css" />
<link rel="stylesheet" href="/css/font-awesome.min.css" />
</head> </head>
<body> <body>
<header class="ink-grid">
<h1>{title}</h1>
<nav class="ink-navigation">
<ul class="menu horizontal">
<li><a href="/logout">Logout</a></li>
</ul>
</nav>
</header>
<main class="ink-grid">
{+content} {+content}
This is the base content. This is the base content.
{/content} {/content}
</main>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="/js/native.history.js"></script>
</body> </body>
<!-- <!--
{session} {session}
--> -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="/js/native.history.js"></script>
</html> </html>

27
views/login.dust Normal file
View File

@ -0,0 +1,27 @@
{>layout/}
{<content}
<form class="ink-form column-group" action="/login" method="post">
<fieldset>
<legend>Login</legend>
<div class="control-group required">
<label for="user">Username / Email Address</label>
<div class="control">
<input type="text" name="user" id="user" required="required" />
</div>
<p class="tip">This field is required</p>
</div>
<div class="control-group required">
<label for="pass">Password</label>
<div class="control">
<input type="password" name="pass" id="pass" required="required" />
</div>
<p class="tip">This field is required</p>
</div>
<div class="control-group">
<input type="hidden" name="_csrf" value="{csrfToken}" />
<button type="submit">Login</button>
</div>
</fieldset>
</form>
{/content}