diff --git a/package-lock.json b/package-lock.json index a58415e5c1..c7d7a3ea0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1306,6 +1306,11 @@ "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", "dev": true }, + "flru": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/flru/-/flru-1.0.2.tgz", + "integrity": "sha512-kWyh8ADvHBFz6ua5xYOPnUroZTT/bwWfrCeL0Wj1dzG4/YOmOcfJ99W8dOVyyynJN35rZ9aCOtHChqQovV7yog==" + }, "foreground-child": { "version": "1.5.6", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", diff --git a/package.json b/package.json index 8adaf5b639..a8570f5e47 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "instrument": true }, "dependencies": { - "cookie": "^0.4.0" + "cookie": "^0.4.0", + "flru": "^1.0.2" } } diff --git a/site/src/routes/auth/callback.js b/site/src/routes/auth/callback.js index 4635a624ef..7979b1c21f 100644 --- a/site/src/routes/auth/callback.js +++ b/site/src/routes/auth/callback.js @@ -3,8 +3,7 @@ import devalue from 'devalue'; import * as cookie from 'cookie'; import * as httpie from 'httpie'; import { parse, stringify } from 'querystring'; -import { find, query } from '../../utils/db.js'; -import { to_user } from '../../utils/auth'; +import { sanitize_user, create_user, create_session } from '../../utils/auth'; import { oauth, secure, client_id, client_secret } from './_config.js'; export async function get(req, res) { @@ -25,21 +24,8 @@ export async function get(req, res) { } }); - const { id: uid, name, avatar_url, login } = r2.data; - - // Upsert `users` table - const [user] = await query(` - insert into users(uid, name, username, avatar, github_token) - values ($1, $2, $3, $4, $5) on conflict (uid) do update - set (name, username, avatar, github_token, updated_at) = ($2, $3, $4, $5, now()) - returning * - `, [uid, name, login, avatar_url, access_token]); - - const session = await find(` - insert into sessions(user_id) - values ($1) - returning * - `, [user.id]); + const user = await create_user(r2.data, access_token); + const session = await create_session(user); res.writeHead(200, { 'Set-Cookie': cookie.serialize('sid', session.uid, { @@ -54,7 +40,7 @@ export async function get(req, res) { res.end(` `); diff --git a/site/src/routes/auth/logout.js b/site/src/routes/auth/logout.js index 2a6ec6f8ba..7e132f0a41 100644 --- a/site/src/routes/auth/logout.js +++ b/site/src/routes/auth/logout.js @@ -1,12 +1,10 @@ import send from '@polka/send'; import * as cookie from 'cookie'; -import { query } from '../../utils/db.js'; import { secure } from './_config.js'; +import { delete_session } from '../../utils/auth.js'; export async function get(req, res) { - await query(` - delete from sessions where uid = $1 - `, [req.cookies.sid]); + await delete_session(req.cookies.sid); send(res, 200, '', { 'Set-Cookie': cookie.serialize('sid', '', { diff --git a/site/src/server.js b/site/src/server.js index 6cca2b17b0..2425b51f54 100644 --- a/site/src/server.js +++ b/site/src/server.js @@ -2,9 +2,7 @@ import polka from 'polka'; import send from '@polka/send'; import sirv from 'sirv'; import * as sapper from '@sapper/server'; -import * as cookie from 'cookie'; -import { find } from './utils/db'; -import { to_user } from './utils/auth'; +import { sanitize_user, authenticate } from './utils/auth'; const { PORT = 3000 } = process.env; @@ -17,24 +15,8 @@ const app = polka({ }); app.use( - // authenticate user - async (req, res, next) => { - if (req.headers.cookie) { - req.cookies = cookie.parse(req.headers.cookie); - if (req.cookies.sid) { - req.user = await find(` - select users.id, users.uid, users.username, users.name, users.avatar - from sessions - left join users on sessions.user_id = users.id - where sessions.uid = $1 and expiry > now() - `, [req.cookies.sid]); - } - } - - next(); - }, + authenticate(), - // serve static files sirv('static', { dev: process.env.NODE_ENV === 'development', setHeaders(res) { @@ -43,10 +25,9 @@ app.use( } }), - // run Sapper sapper.middleware({ session: req => ({ - user: to_user(req.user) + user: sanitize_user(req.user) }) }) ); diff --git a/site/src/utils/auth.js b/site/src/utils/auth.js index 5fc1153735..1f7ccaf867 100644 --- a/site/src/utils/auth.js +++ b/site/src/utils/auth.js @@ -1,6 +1,65 @@ -export const to_user = obj => obj && ({ +import * as cookie from 'cookie'; +import flru from 'flru'; +import { find, query } from './db'; + +export const sanitize_user = obj => obj && ({ uid: obj.uid, username: obj.username, name: obj.name, avatar: obj.avatar -}); \ No newline at end of file +}); + +const session_cache = flru(1000); + +export const create_user = async (gh_user, access_token) => { + return await find(` + insert into users(uid, name, username, avatar, github_token) + values ($1, $2, $3, $4, $5) on conflict (uid) do update + set (name, username, avatar, github_token, updated_at) = ($2, $3, $4, $5, now()) + returning id, uid, username, name, avatar + `, [gh_user.id, gh_user.name, gh_user.login, gh_user.avatar_url, access_token]); +}; + +export const create_session = async user => { + const session = await find(` + insert into sessions(user_id) + values ($1) + returning uid + `, [user.id]); + + session_cache.set(session.uid, user); + + return session; +}; + +export const delete_session = async sid => { + await query(`delete from sessions where uid = $1`, [sid]); + session_cache.set(sid, null); +}; + +const get_user = async sid => { + if (!sid) return null; + + if (!session_cache.has(sid)) { + session_cache.set(sid, await find(` + select users.id, users.uid, users.username, users.name, users.avatar + from sessions + left join users on sessions.user_id = users.id + where sessions.uid = $1 and expiry > now() + `, [sid])); + } + + return session_cache.get(sid); +}; + +export const authenticate = () => { + // this is a convenient time to clear out expired sessions + query(`delete from sessions where expiry < now()`); + + return async (req, res, next) => { + req.cookies = cookie.parse(req.headers.cookie || ''); + req.user = await get_user(req.cookies.sid); + + next(); + }; +}; \ No newline at end of file