diff --git a/www/app/lib/auth.ts b/www/app/lib/auth.ts index 9169c694..375af3e4 100644 --- a/www/app/lib/auth.ts +++ b/www/app/lib/auth.ts @@ -1,26 +1,19 @@ -// import { kv } from "@vercel/kv"; -import Redlock, { ResourceLockedError } from "redlock"; import { AuthOptions } from "next-auth"; import AuthentikProvider from "next-auth/providers/authentik"; import { JWT } from "next-auth/jwt"; import { JWTWithAccessToken, CustomSession } from "./types"; -import Redis from "ioredis"; const PRETIMEOUT = 60; // seconds before token expires to refresh it -const DEFAULT_REDIS_KEY_TIMEOUT = 60 * 60 * 24 * 30; // 30 days (refresh token expires in 30 days) -const kv = new Redis(process.env.KV_URL || "", { - tls: {}, -}); -const redlock = new Redlock([kv], {}); -redlock.on("error", (error) => { - if (error instanceof ResourceLockedError) { - return; - } +// Simple in-memory cache for tokens (in production, consider using a proper cache solution) +const tokenCache = new Map< + string, + { token: JWTWithAccessToken; timestamp: number } +>(); +const TOKEN_CACHE_TTL = 60 * 60 * 24 * 30 * 1000; // 30 days in milliseconds - // Log all other errors. - console.error(error); -}); +// Simple lock mechanism to prevent concurrent token refreshes +const refreshLocks = new Map>(); export const authOptions: AuthOptions = { providers: [ @@ -51,12 +44,11 @@ export const authOptions: AuthOptions = { accessTokenExpires: expiresAt * 1000, refreshToken: account.refresh_token, }; - kv.set( - `token:${jwtToken.sub}`, - JSON.stringify(jwtToken), - "EX", - DEFAULT_REDIS_KEY_TIMEOUT, - ); + // Store in memory cache + tokenCache.set(`token:${jwtToken.sub}`, { + token: jwtToken, + timestamp: Date.now(), + }); return jwtToken; } @@ -65,7 +57,7 @@ export const authOptions: AuthOptions = { } // access token has expired, try to update it - return await redisLockedrefreshAccessToken(token); + return await lockedRefreshAccessToken(token); }, async session({ session, token }) { const extendedToken = token as JWTWithAccessToken; @@ -83,32 +75,51 @@ export const authOptions: AuthOptions = { }, }; -async function redisLockedrefreshAccessToken(token: JWT) { - return await redlock.using( - [token.sub as string, "jwt-refresh"], - 5000, - async () => { - const redisToken = await kv.get(`token:${token.sub}`); - const currentToken = JSON.parse( - redisToken as string, - ) as JWTWithAccessToken; +async function lockedRefreshAccessToken( + token: JWT, +): Promise { + const lockKey = `${token.sub}-refresh`; - // if there is multiple requests for the same token, it may already have been refreshed - if (Date.now() < currentToken.accessTokenExpires) { - return currentToken; + // Check if there's already a refresh in progress + const existingRefresh = refreshLocks.get(lockKey); + if (existingRefresh) { + return existingRefresh; + } + + // Create a new refresh promise + const refreshPromise = (async () => { + try { + // Check cache for recent token + const cached = tokenCache.get(`token:${token.sub}`); + if (cached) { + // Clean up old cache entries + if (Date.now() - cached.timestamp > TOKEN_CACHE_TTL) { + tokenCache.delete(`token:${token.sub}`); + } else if (Date.now() < cached.token.accessTokenExpires) { + // Token is still valid + return cached.token; + } } - // now really do the request + // Refresh the token + const currentToken = cached?.token || (token as JWTWithAccessToken); const newToken = await refreshAccessToken(currentToken); - await kv.set( - `token:${currentToken.sub}`, - JSON.stringify(newToken), - "EX", - DEFAULT_REDIS_KEY_TIMEOUT, - ); + + // Update cache + tokenCache.set(`token:${token.sub}`, { + token: newToken, + timestamp: Date.now(), + }); + return newToken; - }, - ); + } finally { + // Clean up the lock after a short delay + setTimeout(() => refreshLocks.delete(lockKey), 100); + } + })(); + + refreshLocks.set(lockKey, refreshPromise); + return refreshPromise; } async function refreshAccessToken(token: JWT): Promise { diff --git a/www/package.json b/www/package.json index b1bccb58..b509c8ff 100644 --- a/www/package.json +++ b/www/package.json @@ -19,14 +19,12 @@ "@sentry/nextjs": "^7.77.0", "@tanstack/react-query": "^5.85.5", "@vercel/edge-config": "^0.4.1", - "@vercel/kv": "^2.0.0", "@whereby.com/browser-sdk": "^3.3.4", "autoprefixer": "10.4.20", "axios": "^1.8.2", "eslint": "^9.33.0", "eslint-config-next": "^14.2.31", "fontawesome": "^5.6.3", - "ioredis": "^5.4.1", "jest-worker": "^29.6.2", "lucide-react": "^0.525.0", "next": "^14.2.30", @@ -44,7 +42,6 @@ "react-markdown": "^9.0.0", "react-qr-code": "^2.0.12", "react-select-search": "^4.1.7", - "redlock": "^5.0.0-beta.2", "sass": "^1.63.6", "simple-peer": "^9.11.1", "tailwindcss": "^3.3.2", diff --git a/www/pnpm-lock.yaml b/www/pnpm-lock.yaml index 59628629..9095e5e1 100644 --- a/www/pnpm-lock.yaml +++ b/www/pnpm-lock.yaml @@ -31,9 +31,6 @@ importers: "@vercel/edge-config": specifier: ^0.4.1 version: 0.4.1 - "@vercel/kv": - specifier: ^2.0.0 - version: 2.0.0 "@whereby.com/browser-sdk": specifier: ^3.3.4 version: 3.13.1(@types/react@18.2.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -52,9 +49,6 @@ importers: fontawesome: specifier: ^5.6.3 version: 5.6.3 - ioredis: - specifier: ^5.4.1 - version: 5.7.0 jest-worker: specifier: ^29.6.2 version: 29.7.0 @@ -106,9 +100,6 @@ importers: react-select-search: specifier: ^4.1.7 version: 4.1.8(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - redlock: - specifier: ^5.0.0-beta.2 - version: 5.0.0-beta.2 sass: specifier: ^1.63.6 version: 1.90.0 @@ -565,12 +556,6 @@ packages: integrity: sha512-p+Zh1sb6EfrfVaS86jlHGQ9HA66fJhV9x5LiE5vCbZtXEHAuhcmUZUdZ4WrFpUBfNalr2OkAJI5AcKEQF+Lebw==, } - "@ioredis/commands@1.3.0": - resolution: - { - integrity: sha512-M/T6Zewn7sDaBQEqIZ8Rb+i9y8qfGmq+5SDFSf9sA2lUZTmdDLVdOiQaeDp+Q4wElZ9HG1GAX5KhDaidp6LQsQ==, - } - "@isaacs/cliui@8.0.2": resolution: { @@ -1907,12 +1892,6 @@ packages: cpu: [x64] os: [win32] - "@upstash/redis@1.35.3": - resolution: - { - integrity: sha512-hSjv66NOuahW3MisRGlSgoszU2uONAY2l5Qo3Sae8OT3/Tng9K+2/cBRuyPBX8egwEGcNNCF9+r0V6grNnhL+w==, - } - "@vercel/build-utils@8.4.12": resolution: { @@ -1969,13 +1948,6 @@ packages: integrity: sha512-IPAVaALuGAzt2apvTtBs5tB+8zZRzn/yG3AGp8dFyCsw/v5YOuk0Q5s8Z3fayLvJbFpjrKtqRNDZzVJBBU3MrQ==, } - "@vercel/kv@2.0.0": - resolution: - { - integrity: sha512-zdVrhbzZBYo5d1Hfn4bKtqCeKf0FuzW8rSHauzQVMUgv1+1JOwof2mWcBuI+YMJy8s0G0oqAUfQ7HgUDzb8EbA==, - } - engines: { node: ">=14.6" } - "@vercel/next@4.3.18": resolution: { @@ -3053,13 +3025,6 @@ packages: } engines: { node: ">=6" } - cluster-key-slot@1.1.2: - resolution: - { - integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==, - } - engines: { node: ">=0.10.0" } - code-block-writer@10.1.1: resolution: { @@ -3304,13 +3269,6 @@ packages: integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==, } - denque@2.1.0: - resolution: - { - integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==, - } - engines: { node: ">=0.10" } - depd@1.1.2: resolution: { @@ -4549,13 +4507,6 @@ packages: } engines: { node: ">= 0.4" } - ioredis@5.7.0: - resolution: - { - integrity: sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==, - } - engines: { node: ">=12.22.0" } - ip-address@9.0.5: resolution: { @@ -5052,18 +5003,6 @@ packages: } engines: { node: ">=10" } - lodash.defaults@4.2.0: - resolution: - { - integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==, - } - - lodash.isarguments@3.1.0: - resolution: - { - integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==, - } - lodash.merge@4.6.2: resolution: { @@ -5542,12 +5481,6 @@ packages: sass: optional: true - node-abort-controller@3.1.1: - resolution: - { - integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==, - } - node-addon-api@7.1.1: resolution: { @@ -6348,27 +6281,6 @@ packages: } engines: { node: ">= 14.18.0" } - redis-errors@1.2.0: - resolution: - { - integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==, - } - engines: { node: ">=4" } - - redis-parser@3.0.0: - resolution: - { - integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==, - } - engines: { node: ">=4" } - - redlock@5.0.0-beta.2: - resolution: - { - integrity: sha512-2RDWXg5jgRptDrB1w9O/JgSZC0j7y4SlaXnor93H/UJm/QyDiFgBKNtrh0TI6oCXqYSaSoXxFh6Sd3VtYfhRXw==, - } - engines: { node: ">=12" } - redux-thunk@3.1.0: resolution: { @@ -6750,12 +6662,6 @@ packages: } engines: { node: ">=6" } - standard-as-callback@2.1.0: - resolution: - { - integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==, - } - stat-mode@0.3.0: resolution: { @@ -7246,12 +7152,6 @@ packages: } engines: { node: ">= 0.4" } - uncrypto@0.1.3: - resolution: - { - integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==, - } - undici-types@7.10.0: resolution: { @@ -8012,8 +7912,6 @@ snapshots: dependencies: "@swc/helpers": 0.5.17 - "@ioredis/commands@1.3.0": {} - "@isaacs/cliui@8.0.2": dependencies: string-width: 5.1.2 @@ -8841,10 +8739,6 @@ snapshots: "@unrs/resolver-binding-win32-x64-msvc@1.11.1": optional: true - "@upstash/redis@1.35.3": - dependencies: - uncrypto: 0.1.3 - "@vercel/build-utils@8.4.12": {} "@vercel/edge-config-fs@0.1.0": {} @@ -8901,10 +8795,6 @@ snapshots: "@vercel/static-config": 3.0.0 ts-morph: 12.0.0 - "@vercel/kv@2.0.0": - dependencies: - "@upstash/redis": 1.35.3 - "@vercel/next@4.3.18": dependencies: "@vercel/nft": 0.27.3 @@ -9900,8 +9790,6 @@ snapshots: clsx@2.1.1: {} - cluster-key-slot@1.1.2: {} - code-block-writer@10.1.1: {} color-convert@2.0.1: @@ -10022,8 +9910,6 @@ snapshots: delegates@1.0.0: {} - denque@2.1.0: {} - depd@1.1.2: {} dequal@2.0.3: {} @@ -10903,20 +10789,6 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 - ioredis@5.7.0: - dependencies: - "@ioredis/commands": 1.3.0 - cluster-key-slot: 1.1.2 - debug: 4.4.1(supports-color@9.4.0) - denque: 2.1.0 - lodash.defaults: 4.2.0 - lodash.isarguments: 3.1.0 - redis-errors: 1.2.0 - redis-parser: 3.0.0 - standard-as-callback: 2.1.0 - transitivePeerDependencies: - - supports-color - ip-address@9.0.5: dependencies: jsbn: 1.1.0 @@ -11206,10 +11078,6 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash.defaults@4.2.0: {} - - lodash.isarguments@3.1.0: {} - lodash.merge@4.6.2: {} longest-streak@3.1.0: {} @@ -11609,8 +11477,6 @@ snapshots: - "@babel/core" - babel-plugin-macros - node-abort-controller@3.1.1: {} - node-addon-api@7.1.1: optional: true @@ -12057,16 +11923,6 @@ snapshots: readdirp@4.1.2: {} - redis-errors@1.2.0: {} - - redis-parser@3.0.0: - dependencies: - redis-errors: 1.2.0 - - redlock@5.0.0-beta.2: - dependencies: - node-abort-controller: 3.1.1 - redux-thunk@3.1.0(redux@5.0.1): dependencies: redux: 5.0.1 @@ -12316,8 +12172,6 @@ snapshots: dependencies: type-fest: 0.7.1 - standard-as-callback@2.1.0: {} - stat-mode@0.3.0: {} statuses@1.5.0: {} @@ -12679,8 +12533,6 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 - uncrypto@0.1.3: {} - undici-types@7.10.0: {} undici@5.28.4: