From f6b5bdfc3cfbb7cc5925df62d1e677ab5a7587dc Mon Sep 17 00:00:00 2001 From: horsefacts Date: Thu, 1 Feb 2024 11:25:48 -0500 Subject: [PATCH] feat: redis-backed sessions --- web/bun.lockb | Bin 194581 -> 197992 bytes web/docker-compose.yml | 20 ++++++++++++++++++ web/package.json | 1 + web/src/app/api/auth/sign-in/route.ts | 2 +- web/src/app/api/auth/sign-out/route.ts | 2 +- web/src/components/logout/Logout.tsx | 4 ++-- web/src/lib/auth/clearCurrentUser.ts | 11 +++++----- web/src/lib/auth/getCurrentUser.ts | 5 +++-- web/src/lib/auth/setCurrentUser.ts | 7 ++++--- web/src/lib/auth/shared.ts | 3 +-- web/src/lib/redis/client.ts | 3 +++ web/src/lib/redis/sessions.ts | 28 +++++++++++++++++++++++++ 12 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 web/docker-compose.yml create mode 100644 web/src/lib/redis/client.ts create mode 100644 web/src/lib/redis/sessions.ts diff --git a/web/bun.lockb b/web/bun.lockb index a61af02183360a8f51ad3171a11b549968b57cc3..fa1cd4a489c52ec1c8c1f131a74f8d44dfb4b676 100755 GIT binary patch delta 34779 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

ggV>o%W{nWKTSdp&kp4lWaEv%1&?$qK2c{ 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 y^j}5zEd1G_ { - await fetch('/api/auth/sign-out'); + await fetch("/api/auth/sign-out", { method: "POST" }); window.location.reload(); }} > diff --git a/web/src/lib/auth/clearCurrentUser.ts b/web/src/lib/auth/clearCurrentUser.ts index d83abe4..3151ac5 100644 --- a/web/src/lib/auth/clearCurrentUser.ts +++ b/web/src/lib/auth/clearCurrentUser.ts @@ -1,14 +1,15 @@ -import { cookies } from 'next/headers'; +import { clearSessionToken } from "@lib/redis/sessions"; +import { cookies } from "next/headers"; -import { getTokenFromCookieOrHeader } from './getTokenFromCookieOrHeader'; -import { tokenKey, tokens } from './shared'; +import { getTokenFromCookieOrHeader } from "./getTokenFromCookieOrHeader"; +import { tokenKey } from "./shared"; -export function clearCurrentUser() { +export async function clearCurrentUser() { const token = getTokenFromCookieOrHeader(); cookies().delete(tokenKey); if (token) { - delete tokens[token]; + await clearSessionToken({ token }); } } diff --git a/web/src/lib/auth/getCurrentUser.ts b/web/src/lib/auth/getCurrentUser.ts index 631d0d5..4650c67 100644 --- a/web/src/lib/auth/getCurrentUser.ts +++ b/web/src/lib/auth/getCurrentUser.ts @@ -1,8 +1,8 @@ +import { getSessionByToken } from '@lib/redis/sessions'; import { getProfile } from '@lib/services/user'; import { User } from '@shared/types/models'; import { getTokenFromCookieOrHeader } from './getTokenFromCookieOrHeader'; -import { tokens } from './shared'; export async function getCurrentUser(): Promise { const token = getTokenFromCookieOrHeader(); @@ -11,7 +11,8 @@ export async function getCurrentUser(): Promise { return null; } - const fid = tokens[token]; + const res = await getSessionByToken({ token }); + const { fid } = res; if (!fid) { return null; diff --git a/web/src/lib/auth/setCurrentUser.ts b/web/src/lib/auth/setCurrentUser.ts index 786d71b..0bb216e 100644 --- a/web/src/lib/auth/setCurrentUser.ts +++ b/web/src/lib/auth/setCurrentUser.ts @@ -1,8 +1,9 @@ +import { setSessionToken } from '@lib/redis/sessions'; import { cookies } from 'next/headers'; -import { tokenKey, tokens } from './shared'; +import { tokenKey } from './shared'; -export function setCurrentUser({ token, fid }: { token: string; fid: string }) { +export async function setCurrentUser({ token, fid }: { token: string; fid: string }) { cookies().set(tokenKey, token, { secure: true }); - tokens[token] = fid; + return setSessionToken({ token, fid }); } diff --git a/web/src/lib/auth/shared.ts b/web/src/lib/auth/shared.ts index aea4acc..ddd1ef3 100644 --- a/web/src/lib/auth/shared.ts +++ b/web/src/lib/auth/shared.ts @@ -1,2 +1 @@ -export const tokenKey = 'token'; -export const tokens: Record = {}; +export const tokenKey = "token"; diff --git a/web/src/lib/redis/client.ts b/web/src/lib/redis/client.ts new file mode 100644 index 0000000..2f11271 --- /dev/null +++ b/web/src/lib/redis/client.ts @@ -0,0 +1,3 @@ +import Redis from 'ioredis'; + +export const redis = new Redis(process.env.REDIS_URL ?? "redis://localhost:6379"); diff --git a/web/src/lib/redis/sessions.ts b/web/src/lib/redis/sessions.ts new file mode 100644 index 0000000..f2d02b6 --- /dev/null +++ b/web/src/lib/redis/sessions.ts @@ -0,0 +1,28 @@ +import { tokenKey } from "@lib/auth/shared"; + +import { redis } from "./client"; + +const ttl = 60 * 60 * 24 * 30; + +interface Session { + fid?: string; +} + +export async function setSessionToken({ + token, + fid, +}: { + token: string; + fid: string; +}) { + return redis.set(`${tokenKey}:${token}`, JSON.stringify({ fid }), "EX", ttl); +} + +export async function clearSessionToken({ token }: { token: string }) { + return redis.del(`${tokenKey}:${token}`); +} + +export async function getSessionByToken({ token }: { token: string }) { + const session = (await redis.get(`${tokenKey}:${token}`)) ?? "{}"; + return JSON.parse(session) as Session; +}