Skip to content

Instantly share code, notes, and snippets.

@nemtsov
Created September 18, 2019 20:08
Show Gist options
  • Save nemtsov/e73e0a96968c3dcaaa23f5afc4020b7f to your computer and use it in GitHub Desktop.
Save nemtsov/e73e0a96968c3dcaaa23f5afc4020b7f to your computer and use it in GitHub Desktop.
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