colocate all auth/db code, use LRU cache to avoid unnecessary db hits

pull/3420/head
Richard Harris 5 years ago
parent 3c5d46dc0b
commit 37b8848dac

5
package-lock.json generated

@ -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",

@ -100,6 +100,7 @@
"instrument": true
},
"dependencies": {
"cookie": "^0.4.0"
"cookie": "^0.4.0",
"flru": "^1.0.2"
}
}

@ -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(`
<script>
window.opener.postMessage({
user: ${devalue(to_user(user))}
user: ${devalue(sanitize_user(user))}
}, window.location.origin);
</script>
`);

@ -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', '', {

@ -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)
})
})
);

@ -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
});
});
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();
};
};
Loading…
Cancel
Save