diff --git a/web/bun.lockb b/web/bun.lockb index a61af02..fa1cd4a 100755 Binary files a/web/bun.lockb and b/web/bun.lockb differ diff --git a/web/docker-compose.yml b/web/docker-compose.yml new file mode 100644 index 0000000..f7187b0 --- /dev/null +++ b/web/docker-compose.yml @@ -0,0 +1,20 @@ +version: "3.9" + +services: + redis: + image: "redis:7.2-alpine" + restart: unless-stopped + command: --save 1 1 --loglevel warning --maxmemory-policy noeviction + volumes: + - redis-data:/data + ports: + - "6379:6379" + healthcheck: + test: ["CMD-SHELL", "redis-cli", "ping"] + interval: 10s + timeout: 10s + retries: 3 + start_period: 5s + +volumes: + redis-data: diff --git a/web/package.json b/web/package.json index 281719c..45521b7 100644 --- a/web/package.json +++ b/web/package.json @@ -15,6 +15,7 @@ "@farcaster/auth-kit": "0.2.0", "@farcaster/core": "^0.13.4", "date-fns": "^3.3.1", + "ioredis": "^5.3.2", "linkify-it": "^5.0.0", "linkify-react": "^4.1.3", "next": "14.1.0", diff --git a/web/src/app/api/auth/sign-in/route.ts b/web/src/app/api/auth/sign-in/route.ts index 2577e4f..561c10b 100644 --- a/web/src/app/api/auth/sign-in/route.ts +++ b/web/src/app/api/auth/sign-in/route.ts @@ -32,7 +32,7 @@ export async function POST(request: Request) { const token = randomUUID(); const fid = verifyResult.fid.toString(); - setCurrentUser({ token, fid }); + await setCurrentUser({ token, fid }); return NextResponse.json({ fid, token }); } diff --git a/web/src/app/api/auth/sign-out/route.ts b/web/src/app/api/auth/sign-out/route.ts index e9e7c78..5da70e1 100644 --- a/web/src/app/api/auth/sign-out/route.ts +++ b/web/src/app/api/auth/sign-out/route.ts @@ -2,6 +2,6 @@ import { clearCurrentUser } from '@lib/auth/clearCurrentUser'; import { NextResponse } from 'next/server'; export async function POST() { - clearCurrentUser(); + await clearCurrentUser(); return NextResponse.json({ success: true }); } diff --git a/web/src/components/logout/Logout.tsx b/web/src/components/logout/Logout.tsx index 8536877..6f231f6 100644 --- a/web/src/components/logout/Logout.tsx +++ b/web/src/components/logout/Logout.tsx @@ -1,4 +1,4 @@ -'use client'; +"use client"; export default function Logout() { return ( @@ -6,7 +6,7 @@ export default function Logout() { type="button" className="bg-fc-purple cursor-pointer rounded px-4 py-2 text-white" onClick={async () => { - 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; +}