Skip to content

Instantly share code, notes, and snippets.

@hhamilto
Last active October 2, 2015 22:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hhamilto/d221df349bbc58f9708c to your computer and use it in GitHub Desktop.
Save hhamilto/d221df349bbc58f9708c to your computer and use it in GitHub Desktop.
// 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