diff --git a/apps/backend/package.json b/apps/backend/package.json index 0267ee5..649b054 100644 --- a/apps/backend/package.json +++ b/apps/backend/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "bcrypt": "^6.0.0", + "connect-redis": "^10.0.0", "cors": "^2.8.6", "database": "workspace:*", "dotenv": "^17.4.2", @@ -30,6 +31,7 @@ "express-session": "^1.19.0", "passport": "^0.7.0", "passport-local": "^1.0.0", + "redis": "^6.2.1", "zod": "^4.5.4" } } diff --git a/apps/backend/src/index.ts b/apps/backend/src/index.ts index ee55f52..9c581fa 100644 --- a/apps/backend/src/index.ts +++ b/apps/backend/src/index.ts @@ -8,6 +8,8 @@ import session from "express-session"; import passport from "passport"; import bcrypt from "bcrypt"; import env from "./utils/env.js"; +import { RedisStore } from "connect-redis"; +import { createClient, RedisClient } from "redis"; import { Strategy as LocalStrategy } from "passport-local"; import { prisma } from "database"; import { ZodError } from "zod"; @@ -16,6 +18,10 @@ import { ZodError } from "zod"; import usersRoute from "./routers/users.js"; import postsRoute from "./routers/posts.js"; +// redis client for session mgmt +const redisClient = createClient({ url: env.REDIS_URL }); +redisClient.connect().catch(console.error); + const app = express(); app.use(express.urlencoded({ extended: false })); app.use(express.json()); @@ -58,9 +64,16 @@ passport.deserializeUser(async (id: number, done) => { app.use( session({ + store: new RedisStore({ client: redisClient, prefix: "sess:" }), secret: env.SESSION_SECRET, resave: false, saveUninitialized: false, + cookie: { + httpOnly: true, + sameSite: "lax", + secure: env.NODE_ENV === "production", + maxAge: 1000 * 60 * 60 * 24 * 7, + }, }), ); app.use(passport.session()); diff --git a/apps/backend/src/utils/env.ts b/apps/backend/src/utils/env.ts index 30f692e..3dd66ac 100644 --- a/apps/backend/src/utils/env.ts +++ b/apps/backend/src/utils/env.ts @@ -6,6 +6,7 @@ const env = z.object({ PORT: z.coerce.number().default(3000), DATABASE_URL: z.string().default("file:../../dev.db"), SESSION_SECRET: z.string(), + REDIS_URL: z.string().default("redis://localhost:6379"), }); export default env.parse(process.env); diff --git a/apps/frontend/src/components/layout.tsx b/apps/frontend/src/components/layout.tsx index 59fe358..9c2c96b 100644 --- a/apps/frontend/src/components/layout.tsx +++ b/apps/frontend/src/components/layout.tsx @@ -20,6 +20,17 @@ export default function Layout({
{children}
+
+ +
); } diff --git a/apps/frontend/src/main.tsx b/apps/frontend/src/main.tsx index e21f152..d84d746 100644 --- a/apps/frontend/src/main.tsx +++ b/apps/frontend/src/main.tsx @@ -1,4 +1,3 @@ -import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; import { AuthProvider } from "./providers/AuthProvider"; @@ -33,9 +32,7 @@ const router = createBrowserRouter([ ]); createRoot(document.getElementById("root")!).render( - - - - - , + + + , ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e059bd6..c52d70d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -212,6 +212,9 @@ importers: bcrypt: specifier: ^6.0.0 version: 6.0.0 + connect-redis: + specifier: ^10.0.0 + version: 10.0.0(express-session@1.19.0)(redis@6.2.1) cors: specifier: ^2.8.6 version: 2.8.6 @@ -233,6 +236,9 @@ importers: passport-local: specifier: ^1.0.0 version: 1.0.0 + redis: + specifier: ^6.2.1 + version: 6.2.1 zod: specifier: ^4.5.4 version: 4.5.4 @@ -823,6 +829,42 @@ packages: '@types/react': optional: true + '@redis/bloom@6.2.1': + resolution: {integrity: sha512-huQgNLaCIZfQ9SeLn4q9124uOUd8HbZDYHwwUzNcRgHqCHiHKl2dDxMqJCeWh8cMqZAoWuHR8XnWbDMIf+o7ag==} + engines: {node: '>= 20.0.0'} + peerDependencies: + '@redis/client': ^6.2.1 + + '@redis/client@6.2.1': + resolution: {integrity: sha512-LzxBY7SIBvvJiyCgcaJZZakE3fJrZZ++i24+EDW9fKpCl68D35uJcKFpZZwCfOoG9WZTbyZlMzMeM0gtOAMU9Q==} + engines: {node: '>= 20.0.0'} + peerDependencies: + '@node-rs/xxhash': ^1.1.0 + '@opentelemetry/api': '>=1 <2' + peerDependenciesMeta: + '@node-rs/xxhash': + optional: true + '@opentelemetry/api': + optional: true + + '@redis/json@6.2.1': + resolution: {integrity: sha512-AFIUJ8Gj0DaaSBHYuSt8+O0oYWM+50OK1c0OmodB7XERIA8+BbyV3O4v76f9iccWasd1/7qjfZTpuzexUaZtrQ==} + engines: {node: '>= 20.0.0'} + peerDependencies: + '@redis/client': ^6.2.1 + + '@redis/search@6.2.1': + resolution: {integrity: sha512-2vfOAOyYFE7UUw3sBBlkqqruBtOUS4HRY5MtW4hp83llrwvtrTE4r22CEqXddlV+54zkLxBE4nmsIJ/dpezQrQ==} + engines: {node: '>= 20.0.0'} + peerDependencies: + '@redis/client': ^6.2.1 + + '@redis/time-series@6.2.1': + resolution: {integrity: sha512-kiYniph04dJOole+L359B6C9E+jYS2uDP7hca6Onj0xF38ZIpyxARO0Iq0W4ZRn1e8Q6vqW00QFZVSMRA/2Ijw==} + engines: {node: '>= 20.0.0'} + peerDependencies: + '@redis/client': ^6.2.1 + '@rolldown/binding-android-arm-eabi@1.2.7': resolution: {integrity: sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1517,6 +1559,10 @@ packages: classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} @@ -1526,6 +1572,13 @@ packages: confbox@0.3.1: resolution: {integrity: sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==} + connect-redis@10.0.0: + resolution: {integrity: sha512-x6EotIY20pvocamMtcOsgxFzYufAMfipE+rjWa0cZMeSx+3K0yoHi4Nv4sfMlgF+Z5KnUbMFxyCx9pDZP39p0Q==} + engines: {node: '>=22'} + peerDependencies: + express-session: '>=1' + redis: '>=5' + content-disposition@1.1.0: resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} engines: {node: '>=18'} @@ -2636,6 +2689,10 @@ packages: resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} engines: {node: '>= 20.19.0'} + redis@6.2.1: + resolution: {integrity: sha512-Z9VHtgYs48PiQC77X9O2Er8Hj4T+5BtFjT91/vi5Is1D04N72cA946ZslM1ImJw8ZctFBZWAVjM7S5wJNeHMpg==} + engines: {node: '>= 20.0.0'} + remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} @@ -3428,6 +3485,26 @@ snapshots: optionalDependencies: '@types/react': 19.2.18 + '@redis/bloom@6.2.1(@redis/client@6.2.1)': + dependencies: + '@redis/client': 6.2.1 + + '@redis/client@6.2.1': + dependencies: + cluster-key-slot: 1.1.2 + + '@redis/json@6.2.1(@redis/client@6.2.1)': + dependencies: + '@redis/client': 6.2.1 + + '@redis/search@6.2.1(@redis/client@6.2.1)': + dependencies: + '@redis/client': 6.2.1 + + '@redis/time-series@6.2.1(@redis/client@6.2.1)': + dependencies: + '@redis/client': 6.2.1 + '@rolldown/binding-android-arm-eabi@1.2.7': optional: true @@ -4085,12 +4162,19 @@ snapshots: classnames@2.5.1: {} + cluster-key-slot@1.1.2: {} + comma-separated-tokens@2.0.3: {} confbox@0.2.4: {} confbox@0.3.1: {} + connect-redis@10.0.0(express-session@1.19.0)(redis@6.2.1): + dependencies: + express-session: 1.19.0 + redis: 6.2.1 + content-disposition@1.1.0: {} content-type@1.0.5: {} @@ -5303,6 +5387,17 @@ snapshots: readdirp@5.1.1: {} + redis@6.2.1: + dependencies: + '@redis/bloom': 6.2.1(@redis/client@6.2.1) + '@redis/client': 6.2.1 + '@redis/json': 6.2.1(@redis/client@6.2.1) + '@redis/search': 6.2.1(@redis/client@6.2.1) + '@redis/time-series': 6.2.1(@redis/client@6.2.1) + transitivePeerDependencies: + - '@node-rs/xxhash' + - '@opentelemetry/api' + remark-parse@11.0.0: dependencies: '@types/mdast': 4.0.4