Last active
October 2, 2015 22:36
-
-
Save hhamilto/d221df349bbc58f9708c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// I kept my implementation's external dependencies to only the | |
// core express module and standard node modules | |
express = require('express') | |
crypto = require('crypto') | |
http = require('http') | |
assert = require('assert') | |
app = express() | |
//parse form values | |
app.use(function(req,res,next){ | |
if(req.method == 'POST'){ | |
var bodyText = '' | |
req.on('data', function(data){ | |
bodyText += data.toString() | |
}) | |
req.on('end', function(){ | |
var body = {} | |
bodyText.split('&').forEach(function(pair){ | |
pair = pair.split('=').map(function(text){ | |
return decodeURIComponent(text.replace('+', ' ')) | |
}) | |
body[pair[0]] = pair[1] | |
}) | |
req.body = body | |
next() | |
}) | |
}else{ | |
next() | |
} | |
}) | |
// parse cookie values | |
app.use(function(req,res,next){ | |
req.cookies = {} | |
if(req.headers.cookie){ | |
req.headers.cookie.split('; ').forEach(function(crumb){ | |
var pair = crumb.split('=') | |
req.cookies[pair[0]] = pair[1] | |
}) | |
} | |
next() | |
}) | |
app.get('/signup', function(req,res){ | |
res.send('<html>'+ | |
'<body>'+ | |
'<h1>Sign up</h1>'+ | |
'<form method="POST" action="/signup">'+ | |
'<label for="username">Username: </label>'+ | |
'<input name="username" type="text" placeholder="username"/>'+ | |
'<label for="password">Password: </label>'+ | |
'<input name="password" type="text" placeholder="password"/>'+ | |
'<button type="submit">Sign Up</button>'+ | |
'</form>'+ | |
'</body>'+ | |
'</html>') | |
}) | |
users = {} | |
app.post('/signup', function(req,res){ | |
users[req.body.username] = { | |
username: req.body.username, | |
password: req.body.password | |
} | |
res.redirect('/') | |
}) | |
app.get('/login', function(req,res){ | |
res.send('<html>'+ | |
'<body>'+ | |
'<h1>Login</h1>'+ | |
'<form method="POST" action="/login">'+ | |
'<label for="username">Username: </label>'+ | |
'<input name="username" type="text" placeholder="username"/>'+ | |
'<label for="password">Password: </label>'+ | |
'<input name="password" type="text" placeholder="password"/>'+ | |
'<button type="submit">Login</button>'+ | |
'</form>'+ | |
'</body>'+ | |
'</html>') | |
}) | |
getSessionExpiration = function(){ | |
return Date.now() + 1000*60*60 //session expires in an hour | |
} | |
sessions = {} | |
app.post('/login', function(req,res){ | |
var user | |
if((user = users[req.body.username]) && user.password == req.body.password){ | |
var sessionId = crypto.randomBytes(8).toString('hex') | |
res.cookie('sessionId', sessionId) | |
var session = { | |
user, | |
sessionId, | |
expires: getSessionExpiration() | |
} | |
user.session = session | |
sessions[sessionId] = session | |
res.redirect('/') | |
}else{ | |
res.sendStatus(401) | |
} | |
}) | |
app.get('/', function(req,res){ | |
res.write('<html>'+ | |
'<body>') | |
if(!isLoggedIn(req)) | |
res.write('<p><a href="/signup">Sign up</a></p>'+ | |
'<a href="/login">log in</a></p>') | |
if(isLoggedIn(req)) | |
res.write('<p>Logged in as ' + getSession(req).user.username + '</p>' + | |
'<p><a href="/logout">log out</a></p>') | |
res.write('<h1>This is an unprotected page</h1>'+ | |
'<a href="/protected">Visit protected page</a>'+ | |
'</body>'+ | |
'</html>') | |
res.end() | |
}) | |
var getSession = function(req){ | |
return sessions[req.cookies.sessionId] | |
} | |
var isLoggedIn = function(req){ | |
return getSession(req) && getSession(req).expires > Date.now() | |
} | |
var loginCheck = function(req,res,next){ | |
var session | |
if(isLoggedIn(req)){ | |
getSession(req).expires = getSessionExpiration() | |
next() | |
}else{ | |
res.sendStatus(401) | |
} | |
} | |
app.get('/protected',loginCheck, function(req,res){ | |
res.write('<html>'+ | |
'<body>'+ | |
'<h1>Protected</h1>'+ | |
'<p> Hi. I\'m secret. </p>'+ | |
'</body>'+ | |
'</html>') | |
res.end() | |
}) | |
app.get('/logout',loginCheck, function(req,res){ | |
var session = getSession(req) | |
delete sessions[session.sessionId] | |
delete session.user.session | |
res.redirect('/') | |
}) | |
runTests = function(){ | |
var tests = [] | |
var noop = function(){} | |
//test that protected page is protected | |
tests.push(function(done){ | |
http.get('http://localhost:3000/protected', function(res){ | |
respBody = '' | |
res.on('data', noop) | |
res.on('end', function(){ | |
assert.equal(res.statusCode, 401) | |
done(true) | |
}) | |
}) | |
}) | |
//test that the public page is accessible | |
tests.push(function(done){ | |
http.get('http://localhost:3000/', function(res){ | |
res.on('data', noop) | |
res.on('end', function(){ | |
assert.equal(res.statusCode, 200) | |
done(true) | |
}) | |
}) | |
}) | |
//test that the signup page is accessible | |
tests.push(function(done){ | |
http.get('http://localhost:3000/signup', function(res){ | |
res.on('data', noop) | |
res.on('end', function(){ | |
assert.equal(res.statusCode, 200) | |
done(true) | |
}) | |
}) | |
}) | |
//test that the login page is accessible | |
tests.push(function(done){ | |
http.get('http://localhost:3000/login', function(res){ | |
res.on('data', noop) | |
res.on('end', function(){ | |
assert.equal(res.statusCode, 200) | |
done(true) | |
}) | |
}) | |
}) | |
//test that the sign up and login functions render customized index | |
tests.push(function(done){ | |
var req = http.request({ | |
port: 3000, | |
path: '/signup', | |
method:'POST', | |
}, function(res){ | |
var respBody = '' | |
res.on('data', noop) | |
res.on('end', function(){ | |
assert.notEqual(res.statusCode/100, 5)//no 500 class responses | |
assert.notEqual(res.statusCode/100, 4)//no 400 class responses | |
var req = http.request({ | |
port: 3000, | |
path: '/login', | |
method:'POST' | |
}, function(res){ | |
var cookie = res.headers['set-cookie'][0].split(';')[0] | |
var respBody = '' | |
res.on('data', noop) | |
res.on('end', function(){ | |
assert.notEqual(res.statusCode/100, 5)//no 500 class responses | |
assert.notEqual(res.statusCode/100, 4)//no 400 class responses | |
var req = http.request({ | |
port: 3000, | |
path: '/', | |
method:'GET', | |
headers: { | |
'Cookie': cookie | |
} | |
}, function(res){ | |
var respBody = '' | |
res.on('data', function(data){ | |
respBody += data | |
}) | |
res.on('end', function(){ | |
assert.equal(res.statusCode, 200) | |
assert.equal(/bob/.test(respBody), true) | |
done(true) | |
}) | |
}) | |
req.end() | |
}) | |
}) | |
req.write('username=bob&password=foo') | |
req.end() | |
}) | |
}) | |
req.write('username=bob&password=foo') | |
req.end() | |
}) | |
//test that index page does not contain name after logging out | |
tests.push(function(done){ | |
var req = http.request({ | |
port: 3000, | |
path: '/login', | |
method:'POST' | |
}, function(res){ | |
var cookie = res.headers['set-cookie'][0].split(';')[0] | |
var respBody = '' | |
res.on('data', noop) | |
res.on('end', function(){ | |
assert.notEqual(res.statusCode/100, 5)//no 500 class responses | |
assert.notEqual(res.statusCode/100, 4)//no 400 class responses | |
var req = http.request({ | |
port: 3000, | |
path: '/logout', | |
method:'GET', | |
headers: { | |
'Cookie': cookie | |
} | |
}, function(res){ | |
assert.notEqual(res.statusCode/100, 5)//no 500 class responses | |
assert.notEqual(res.statusCode/100, 4)//no 400 class responses | |
res.on('data', noop) | |
res.on('end', function(){ | |
var req = http.request({ | |
port: 3000, | |
path: '/', | |
method:'GET', | |
headers: { | |
'Cookie': cookie | |
} | |
}, function(res){ | |
var respBody = '' | |
res.on('data', function(data){ | |
respBody += data | |
}) | |
res.on('end', function(){ | |
assert.equal(res.statusCode, 200) | |
assert.equal(/bob/.test(respBody), false) | |
done(true) | |
}) | |
}) | |
req.end() | |
}) | |
}) | |
req.end() | |
}) | |
}) | |
req.write('username=bob&password=foo') | |
req.end() | |
}) | |
var count = tests.length | |
var countDown = function(){ | |
if(!--count){ | |
console.log("All tests passed.") | |
//server.close() | |
} | |
} | |
tests.forEach(function(test){ | |
test(function(result){ | |
if(!result) | |
console.log('Test failed.') | |
else | |
countDown() | |
}) | |
}) | |
} | |
server = http.createServer(app).listen(3000, runTests) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment