Created
September 18, 2019 20:08
-
-
Save nemtsov/e73e0a96968c3dcaaa23f5afc4020b7f 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
const bcrypt = require('bcryptjs'); | |
const crypto = require('crypto'); | |
const http = require('http'); | |
const querystring = require('querystring'); | |
const { promisify } = require('util'); | |
const randomBytes = promisify(crypto.randomBytes); | |
const ONE_YEAR_IN_SEC = 365 * 24 * 60 * 60; | |
const users = []; | |
const sessions = []; | |
async function createUser(email, plaintextPassword) { | |
const user = { | |
email, | |
hashedPassword: await bcrypt.hash(plaintextPassword, 10) | |
}; | |
users.push(user); | |
return user; | |
} | |
async function findUserByEmail(email, plaintextPassword) { | |
const user = users.find(maybeUser => maybeUser.email === email); | |
if (!user) return null; | |
const isValidPassword = await bcrypt.compare( | |
plaintextPassword, | |
user.hashedPassword | |
); | |
return isValidPassword ? user : null; | |
} | |
async function createSession(user) { | |
const session = { | |
id: (await randomBytes(32)).toString('hex'), | |
isValid: true, | |
user | |
}; | |
sessions.push(session); | |
return session; | |
} | |
async function findSession(sessionId) { | |
return sessions.find(session => session.isValid && session.id === sessionId); | |
} | |
async function invalidateSession(sessionId) { | |
const session = await findSession(sessionId); | |
if (!session) return; | |
session.isValid = false; | |
} | |
function getRequestBody(req) { | |
return new Promise((resolve, reject) => { | |
const chunks = []; | |
req.on('data', chunk => chunks.push(chunk)); | |
req.on('end', () => resolve(querystring.parse(chunks.join()))); | |
req.on('error', err => reject(err)); | |
}); | |
} | |
function getRequestCookie(req) { | |
return querystring.parse(req.headers.cookie); | |
} | |
http | |
.createServer(async (req, res) => { | |
const sessionId = getRequestCookie(req).sid; | |
const currentSession = await findSession(sessionId); | |
switch (req.url) { | |
case '/': { | |
res.setHeader('content-type', 'text/html'); | |
res.end( | |
currentSession | |
? ` | |
Welcome, ${currentSession.user.email}! | | |
<a href="/auth/logout">Log Out</a> | |
` | |
: ` | |
<a href="/signup">Sign Up</a> | | |
<a href="/login">Log In</a> | |
` | |
); | |
break; | |
} | |
case '/signup': { | |
res.setHeader('content-type', 'text/html'); | |
res.end(` | |
<form method="post" action="/auth/signup"> | |
<label>Email: <input type="email" name="email"></label> | |
<label>Password: <input type="password" name="password"></label> | |
<button type="submit">Sign Up</button> | |
or <a href="/login">Log In</a> | |
</form> | |
`); | |
break; | |
} | |
case '/login': { | |
res.setHeader('content-type', 'text/html'); | |
res.end(` | |
<form method="post" action="/auth/login"> | |
<label>Email: <input type="email" name="email"></label> | |
<label>Password: <input type="password" name="password"></label> | |
<button type="submit">Log In</button> | |
or <a href="/signup">Sign Up</a> | |
</form> | |
`); | |
break; | |
} | |
case '/auth/login': { | |
res.statusCode = 302; | |
const body = await getRequestBody(req); | |
if (!body.email || !body.password) { | |
res.setHeader('location', '/login'); | |
res.end(); | |
break; | |
} | |
const user = await findUserByEmail(body.email, body.password); | |
if (!user) { | |
res.setHeader('location', '/login'); | |
res.end(); | |
break; | |
} | |
const newSession = await createSession(user); | |
res.setHeader( | |
'set-cookie', | |
`sid=${newSession.id}; Path=/; Max-Age=${ONE_YEAR_IN_SEC}; HttpOnly; SameSite=Strict` | |
); | |
res.setHeader('location', '/'); | |
res.end(); | |
break; | |
} | |
case '/auth/signup': { | |
res.statusCode = 302; | |
const body = await getRequestBody(req); | |
if (!body.email || !body.password) { | |
res.setHeader('location', '/signup'); | |
res.end(); | |
break; | |
} | |
const user = await createUser(body.email, body.password); | |
const newSession = await createSession(user); | |
res.setHeader( | |
'set-cookie', | |
`sid=${newSession.id}; Path=/; Max-Age=${ONE_YEAR_IN_SEC}; HttpOnly; SameSite=Strict` | |
); | |
res.setHeader('location', '/'); | |
res.end(); | |
break; | |
} | |
case '/auth/logout': { | |
await invalidateSession(currentSession.id); | |
res.statusCode = 302; | |
res.setHeader('set-cookie', `sid=; Path=/; Max-Age=0`); | |
res.setHeader('location', '/'); | |
res.end(); | |
break; | |
} | |
default: { | |
res.statusCode = 404; | |
res.end(); | |
} | |
} | |
}) | |
.listen(3000); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment