diff --git a/packages/backend/src/graphql/context.ts b/packages/backend/src/graphql/context.ts index 0d5f1b0..4e1edf3 100644 --- a/packages/backend/src/graphql/context.ts +++ b/packages/backend/src/graphql/context.ts @@ -1,9 +1,11 @@ import type { YogaInitialContext } from "graphql-yoga"; import type { FastifyRequest, FastifyReply } from "fastify"; import type { Context } from "./builder"; -import { getSession } from "../lib/auth"; +import { getSession, setSession } from "../lib/auth"; import { db } from "../db/connection"; import { redis } from "../lib/auth"; +import { users } from "../db/schema/index"; +import { eq } from "drizzle-orm"; type ServerContext = { req: FastifyRequest; @@ -25,7 +27,30 @@ export async function buildContext(ctx: YogaInitialContext & ServerContext): Pro ); const token = cookies["session_token"]; - const currentUser = token ? await getSession(token) : null; + let currentUser = null; + + if (token) { + const session = await getSession(token); // also slides TTL + if (session) { + const dbInstance = ctx.db || db; + const [dbUser] = await dbInstance.select().from(users).where(eq(users.id, session.id)).limit(1); + if (dbUser) { + currentUser = { + id: dbUser.id, + email: dbUser.email, + role: (dbUser.role === "admin" ? "viewer" : dbUser.role) as "model" | "viewer", + is_admin: dbUser.is_admin, + first_name: dbUser.first_name, + last_name: dbUser.last_name, + artist_name: dbUser.artist_name, + slug: dbUser.slug, + avatar: dbUser.avatar, + }; + // Refresh cached session with up-to-date data + await setSession(token, currentUser); + } + } + } return { db: ctx.db || db, diff --git a/packages/backend/src/graphql/resolvers/auth.ts b/packages/backend/src/graphql/resolvers/auth.ts index 1a818df..25eb578 100644 --- a/packages/backend/src/graphql/resolvers/auth.ts +++ b/packages/backend/src/graphql/resolvers/auth.ts @@ -45,7 +45,7 @@ builder.mutationField("login", (t) => // Set session cookie const isProduction = process.env.NODE_ENV === "production"; - const cookieValue = `session_token=${token}; HttpOnly; Path=/; SameSite=Lax; Max-Age=86400${isProduction ? "; Secure" : ""}`; + const cookieValue = `session_token=${token}; HttpOnly; Path=/; SameSite=Strict; Max-Age=86400${isProduction ? "; Secure" : ""}`; (ctx.reply as any).header?.("Set-Cookie", cookieValue); // For graphql-yoga response @@ -74,7 +74,8 @@ builder.mutationField("logout", (t) => await deleteSession(token); } // Clear cookie - const cookieValue = "session_token=; HttpOnly; Path=/; Max-Age=0"; + const isProduction = process.env.NODE_ENV === "production"; + const cookieValue = `session_token=; HttpOnly; Path=/; SameSite=Strict; Max-Age=0${isProduction ? "; Secure" : ""}`; (ctx.reply as any).header?.("Set-Cookie", cookieValue); return true; }, diff --git a/packages/backend/src/lib/auth.ts b/packages/backend/src/lib/auth.ts index e847d50..83e0829 100644 --- a/packages/backend/src/lib/auth.ts +++ b/packages/backend/src/lib/auth.ts @@ -21,6 +21,8 @@ export async function setSession(token: string, user: SessionUser): Promise { const data = await redis.get(`session:${token}`); if (!data) return null; + // Slide the expiration window on every access + await redis.expire(`session:${token}`, 86400); return JSON.parse(data) as SessionUser; }