From c3e1caa1ecab9b7d2e392b64ead640a928ff7c3c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 1 Feb 2026 16:42:15 +0000 Subject: [PATCH 1/8] cache-auth-permission-lookups Co-authored-by: me --- app/utils/auth.server.ts | 38 ++++++++++++++++++++++++++++--- app/utils/permissions.server.ts | 40 +++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/app/utils/auth.server.ts b/app/utils/auth.server.ts index f785ea88d..0fe8f4e19 100644 --- a/app/utils/auth.server.ts +++ b/app/utils/auth.server.ts @@ -1,6 +1,8 @@ import crypto from 'node:crypto' +import { remember } from '@epic-web/remember' import { type Connection, type Password, type User } from '@prisma/client' import bcrypt from 'bcryptjs' +import { LRUCache } from 'lru-cache' import { redirect } from 'react-router' import { Authenticator } from 'remix-auth' import { safeRedirect } from 'remix-utils/safe-redirect' @@ -19,6 +21,32 @@ export const sessionKey = 'sessionId' export const authenticator = new Authenticator() +const sessionUserIdCache = remember( + 'session-user-id-cache', + () => new LRUCache({ max: 10000 }), +) + +function getCachedSessionUserId(sessionId: string) { + const cached = sessionUserIdCache.get(sessionId) + if (!cached) return null + if (cached.expiresAt <= Date.now()) { + sessionUserIdCache.delete(sessionId) + return null + } + return cached.userId +} + +function cacheSessionUserId( + sessionId: string, + userId: string, + expirationDate: Date, +) { + const expiresAt = expirationDate.getTime() + const ttl = expiresAt - Date.now() + if (ttl <= 0) return + sessionUserIdCache.set(sessionId, { userId, expiresAt }, { ttl }) +} + for (const [providerName, provider] of Object.entries(providers)) { const strategy = provider.getAuthStrategy() if (strategy) { @@ -32,17 +60,20 @@ export async function getUserId(request: Request) { ) const sessionId = authSession.get(sessionKey) if (!sessionId) return null + const cachedUserId = getCachedSessionUserId(sessionId) + if (cachedUserId) return cachedUserId const session = await prisma.session.findUnique({ - select: { userId: true }, - where: { id: sessionId, expirationDate: { gt: new Date() } }, + select: { userId: true, expirationDate: true }, + where: { id: sessionId }, }) - if (!session?.userId) { + if (!session || session.expirationDate <= new Date()) { throw redirect('/', { headers: { 'set-cookie': await authSessionStorage.destroySession(authSession), }, }) } + cacheSessionUserId(sessionId, session.userId, session.expirationDate) return session.userId } @@ -217,6 +248,7 @@ export async function logout( // if this fails, we still need to delete the session from the user's browser // and it doesn't do any harm staying in the db anyway. if (sessionId) { + sessionUserIdCache.delete(sessionId) // the .catch is important because that's what triggers the query. // learn more about PrismaPromise: https://www.prisma.io/docs/orm/reference/prisma-client-reference#prismapromise-behavior void prisma.session.deleteMany({ where: { id: sessionId } }).catch(() => {}) diff --git a/app/utils/permissions.server.ts b/app/utils/permissions.server.ts index 1c1d92f45..2d0efc34d 100644 --- a/app/utils/permissions.server.ts +++ b/app/utils/permissions.server.ts @@ -1,14 +1,39 @@ +import { remember } from '@epic-web/remember' +import { LRUCache } from 'lru-cache' import { data } from 'react-router' import { requireUserId } from './auth.server.ts' import { prisma } from './db.server.ts' import { type PermissionString, parsePermissionString } from './user.ts' +const permissionCheckCache = remember( + 'permission-check-cache', + () => new LRUCache({ max: 10000, ttl: 1000 * 60 * 2 }), +) + +const roleCheckCache = remember( + 'role-check-cache', + () => new LRUCache({ max: 10000, ttl: 1000 * 60 * 2 }), +) + export async function requireUserWithPermission( request: Request, permission: PermissionString, ) { const userId = await requireUserId(request) const permissionData = parsePermissionString(permission) + const cacheKey = `${userId}:${permission}` + const cached = permissionCheckCache.get(cacheKey) + if (cached === true) return userId + if (cached === false) { + throw data( + { + error: 'Unauthorized', + requiredPermission: permissionData, + message: `Unauthorized: required permissions: ${permission}`, + }, + { status: 403 }, + ) + } const user = await prisma.user.findFirst({ select: { id: true }, where: { @@ -27,6 +52,7 @@ export async function requireUserWithPermission( }, }, }) + permissionCheckCache.set(cacheKey, Boolean(user)) if (!user) { throw data( { @@ -42,10 +68,24 @@ export async function requireUserWithPermission( export async function requireUserWithRole(request: Request, name: string) { const userId = await requireUserId(request) + const cacheKey = `${userId}:${name}` + const cached = roleCheckCache.get(cacheKey) + if (cached === true) return userId + if (cached === false) { + throw data( + { + error: 'Unauthorized', + requiredRole: name, + message: `Unauthorized: required role: ${name}`, + }, + { status: 403 }, + ) + } const user = await prisma.user.findFirst({ select: { id: true }, where: { id: userId, roles: { some: { name } } }, }) + roleCheckCache.set(cacheKey, Boolean(user)) if (!user) { throw data( { From 348b96ff6cad9e423a3d692e844ffc95e2953003 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 1 Feb 2026 16:52:43 +0000 Subject: [PATCH 2/8] use-cachified-for-auth-cache Co-authored-by: me --- app/utils/auth.server.ts | 75 ++++++++++++++------------ app/utils/permissions.server.ts | 94 +++++++++++++-------------------- 2 files changed, 80 insertions(+), 89 deletions(-) diff --git a/app/utils/auth.server.ts b/app/utils/auth.server.ts index 0fe8f4e19..997bafdb1 100644 --- a/app/utils/auth.server.ts +++ b/app/utils/auth.server.ts @@ -1,11 +1,10 @@ import crypto from 'node:crypto' -import { remember } from '@epic-web/remember' import { type Connection, type Password, type User } from '@prisma/client' import bcrypt from 'bcryptjs' -import { LRUCache } from 'lru-cache' import { redirect } from 'react-router' import { Authenticator } from 'remix-auth' import { safeRedirect } from 'remix-utils/safe-redirect' +import { cachified, lruCache } from './cache.server.ts' import { providers } from './connections.server.ts' import { prisma } from './db.server.ts' import { combineHeaders, downloadFile } from './misc.tsx' @@ -21,30 +20,37 @@ export const sessionKey = 'sessionId' export const authenticator = new Authenticator() -const sessionUserIdCache = remember( - 'session-user-id-cache', - () => new LRUCache({ max: 10000 }), -) +const sessionCacheKey = (sessionId: string) => `session-user-id:${sessionId}` -function getCachedSessionUserId(sessionId: string) { - const cached = sessionUserIdCache.get(sessionId) - if (!cached) return null - if (cached.expiresAt <= Date.now()) { - sessionUserIdCache.delete(sessionId) - return null - } - return cached.userId +type SessionUserIdCacheEntry = { + userId: string + expirationDate: string } -function cacheSessionUserId( - sessionId: string, - userId: string, - expirationDate: Date, -) { - const expiresAt = expirationDate.getTime() - const ttl = expiresAt - Date.now() - if (ttl <= 0) return - sessionUserIdCache.set(sessionId, { userId, expiresAt }, { ttl }) +async function getCachedSessionEntry(sessionId: string) { + return cachified({ + key: sessionCacheKey(sessionId), + cache: lruCache, + ttl: SESSION_EXPIRATION_TIME, + async getFreshValue(context) { + const session = await prisma.session.findUnique({ + select: { userId: true, expirationDate: true }, + where: { id: sessionId }, + }) + if (!session) return null + const now = Date.now() + const expiresAt = session.expirationDate.getTime() + if (expiresAt <= now) { + context.metadata.ttl = 0 + return null + } + context.metadata.ttl = expiresAt - now + return { + userId: session.userId, + expirationDate: session.expirationDate.toISOString(), + } + }, + }) } for (const [providerName, provider] of Object.entries(providers)) { @@ -60,21 +66,24 @@ export async function getUserId(request: Request) { ) const sessionId = authSession.get(sessionKey) if (!sessionId) return null - const cachedUserId = getCachedSessionUserId(sessionId) - if (cachedUserId) return cachedUserId - const session = await prisma.session.findUnique({ - select: { userId: true, expirationDate: true }, - where: { id: sessionId }, - }) - if (!session || session.expirationDate <= new Date()) { + const cachedSession = await getCachedSessionEntry(sessionId) + if (!cachedSession) { + throw redirect('/', { + headers: { + 'set-cookie': await authSessionStorage.destroySession(authSession), + }, + }) + } + const expirationDate = new Date(cachedSession.expirationDate) + if (expirationDate <= new Date()) { + lruCache.delete(sessionCacheKey(sessionId)) throw redirect('/', { headers: { 'set-cookie': await authSessionStorage.destroySession(authSession), }, }) } - cacheSessionUserId(sessionId, session.userId, session.expirationDate) - return session.userId + return cachedSession.userId } export async function requireUserId( @@ -248,7 +257,7 @@ export async function logout( // if this fails, we still need to delete the session from the user's browser // and it doesn't do any harm staying in the db anyway. if (sessionId) { - sessionUserIdCache.delete(sessionId) + lruCache.delete(sessionCacheKey(sessionId)) // the .catch is important because that's what triggers the query. // learn more about PrismaPromise: https://www.prisma.io/docs/orm/reference/prisma-client-reference#prismapromise-behavior void prisma.session.deleteMany({ where: { id: sessionId } }).catch(() => {}) diff --git a/app/utils/permissions.server.ts b/app/utils/permissions.server.ts index 2d0efc34d..eca9956cb 100644 --- a/app/utils/permissions.server.ts +++ b/app/utils/permissions.server.ts @@ -1,19 +1,13 @@ -import { remember } from '@epic-web/remember' -import { LRUCache } from 'lru-cache' import { data } from 'react-router' +import { cachified, lruCache } from './cache.server.ts' import { requireUserId } from './auth.server.ts' import { prisma } from './db.server.ts' import { type PermissionString, parsePermissionString } from './user.ts' -const permissionCheckCache = remember( - 'permission-check-cache', - () => new LRUCache({ max: 10000, ttl: 1000 * 60 * 2 }), -) - -const roleCheckCache = remember( - 'role-check-cache', - () => new LRUCache({ max: 10000, ttl: 1000 * 60 * 2 }), -) +const permissionCacheKey = (userId: string, permission: PermissionString) => + `permission-check:${userId}:${permission}` +const roleCacheKey = (userId: string, name: string) => + `role-check:${userId}:${name}` export async function requireUserWithPermission( request: Request, @@ -21,39 +15,33 @@ export async function requireUserWithPermission( ) { const userId = await requireUserId(request) const permissionData = parsePermissionString(permission) - const cacheKey = `${userId}:${permission}` - const cached = permissionCheckCache.get(cacheKey) - if (cached === true) return userId - if (cached === false) { - throw data( - { - error: 'Unauthorized', - requiredPermission: permissionData, - message: `Unauthorized: required permissions: ${permission}`, - }, - { status: 403 }, - ) - } - const user = await prisma.user.findFirst({ - select: { id: true }, - where: { - id: userId, - roles: { - some: { - permissions: { + const allowed = await cachified({ + key: permissionCacheKey(userId, permission), + cache: lruCache, + ttl: 1000 * 60 * 2, + async getFreshValue() { + const user = await prisma.user.findFirst({ + select: { id: true }, + where: { + id: userId, + roles: { some: { - ...permissionData, - access: permissionData.access - ? { in: permissionData.access } - : undefined, + permissions: { + some: { + ...permissionData, + access: permissionData.access + ? { in: permissionData.access } + : undefined, + }, + }, }, }, }, - }, + }) + return Boolean(user) }, }) - permissionCheckCache.set(cacheKey, Boolean(user)) - if (!user) { + if (!allowed) { throw data( { error: 'Unauthorized', @@ -68,25 +56,19 @@ export async function requireUserWithPermission( export async function requireUserWithRole(request: Request, name: string) { const userId = await requireUserId(request) - const cacheKey = `${userId}:${name}` - const cached = roleCheckCache.get(cacheKey) - if (cached === true) return userId - if (cached === false) { - throw data( - { - error: 'Unauthorized', - requiredRole: name, - message: `Unauthorized: required role: ${name}`, - }, - { status: 403 }, - ) - } - const user = await prisma.user.findFirst({ - select: { id: true }, - where: { id: userId, roles: { some: { name } } }, + const allowed = await cachified({ + key: roleCacheKey(userId, name), + cache: lruCache, + ttl: 1000 * 60 * 2, + async getFreshValue() { + const user = await prisma.user.findFirst({ + select: { id: true }, + where: { id: userId, roles: { some: { name } } }, + }) + return Boolean(user) + }, }) - roleCheckCache.set(cacheKey, Boolean(user)) - if (!user) { + if (!allowed) { throw data( { error: 'Unauthorized', From a1df756207d99a908256e50a0983262794bec287 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 1 Feb 2026 17:02:27 +0000 Subject: [PATCH 3/8] fix-permission-cache-return Co-authored-by: me --- app/utils/permissions.server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/utils/permissions.server.ts b/app/utils/permissions.server.ts index eca9956cb..48bd1b6ba 100644 --- a/app/utils/permissions.server.ts +++ b/app/utils/permissions.server.ts @@ -51,7 +51,7 @@ export async function requireUserWithPermission( { status: 403 }, ) } - return user.id + return userId } export async function requireUserWithRole(request: Request, name: string) { @@ -78,5 +78,5 @@ export async function requireUserWithRole(request: Request, name: string) { { status: 403 }, ) } - return user.id + return userId } From 5f9dcb394d406f52248b76b379a3b8bb81505773 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 1 Feb 2026 17:11:14 +0000 Subject: [PATCH 4/8] add-ci-safety-agent-rule Co-authored-by: me --- .cursor/rules/ci-safety.mdc | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .cursor/rules/ci-safety.mdc diff --git a/.cursor/rules/ci-safety.mdc b/.cursor/rules/ci-safety.mdc new file mode 100644 index 000000000..ff77f0b84 --- /dev/null +++ b/.cursor/rules/ci-safety.mdc @@ -0,0 +1,12 @@ +--- +description: CI safety checks before shipping +globs: "*" +alwaysApply: true +--- +### CI safety checklist + +- Run `npm run typecheck` after TypeScript changes. +- Run `npx prisma generate --sql` if Prisma SQL imports fail. +- Run `npm run test:e2e:run` for auth/session/permission changes. +- If a check cannot run due to environment limits, document the reason and + avoid declaring the change ready without CI confirmation. From e4a9215c7ce87f047a86026e79930e8027d1e92b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 1 Feb 2026 17:11:56 +0000 Subject: [PATCH 5/8] Fix session cache invalidation --- app/routes/settings/profile/index.tsx | 16 +++++++++++++++- app/utils/auth.server.ts | 13 ++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/app/routes/settings/profile/index.tsx b/app/routes/settings/profile/index.tsx index 777471053..7091a53da 100644 --- a/app/routes/settings/profile/index.tsx +++ b/app/routes/settings/profile/index.tsx @@ -9,7 +9,11 @@ import { ErrorList, Field } from '#app/components/forms.tsx' import { Button } from '#app/components/ui/button.tsx' import { Icon } from '#app/components/ui/icon.tsx' import { StatusButton } from '#app/components/ui/status-button.tsx' -import { requireUserId, sessionKey } from '#app/utils/auth.server.ts' +import { + invalidateSessionCache, + requireUserId, + sessionKey, +} from '#app/utils/auth.server.ts' import { prisma } from '#app/utils/db.server.ts' import { getUserImgSrc, useDoubleCheck } from '#app/utils/misc.tsx' import { authSessionStorage } from '#app/utils/session.server.ts' @@ -287,12 +291,22 @@ async function signOutOfSessionsAction({ request, userId }: ProfileActionArgs) { sessionId, 'You must be authenticated to sign out of other sessions', ) + const sessionsToInvalidate = await prisma.session.findMany({ + select: { id: true }, + where: { + userId, + id: { not: sessionId }, + }, + }) await prisma.session.deleteMany({ where: { userId, id: { not: sessionId }, }, }) + for (const session of sessionsToInvalidate) { + invalidateSessionCache(session.id) + } return { status: 'success' } as const } diff --git a/app/utils/auth.server.ts b/app/utils/auth.server.ts index 997bafdb1..0ca1e4f0a 100644 --- a/app/utils/auth.server.ts +++ b/app/utils/auth.server.ts @@ -22,6 +22,10 @@ export const authenticator = new Authenticator() const sessionCacheKey = (sessionId: string) => `session-user-id:${sessionId}` +export function invalidateSessionCache(sessionId: string) { + lruCache.delete(sessionCacheKey(sessionId)) +} + type SessionUserIdCacheEntry = { userId: string expirationDate: string @@ -37,7 +41,10 @@ async function getCachedSessionEntry(sessionId: string) { select: { userId: true, expirationDate: true }, where: { id: sessionId }, }) - if (!session) return null + if (!session) { + context.metadata.ttl = 0 + return null + } const now = Date.now() const expiresAt = session.expirationDate.getTime() if (expiresAt <= now) { @@ -76,7 +83,7 @@ export async function getUserId(request: Request) { } const expirationDate = new Date(cachedSession.expirationDate) if (expirationDate <= new Date()) { - lruCache.delete(sessionCacheKey(sessionId)) + invalidateSessionCache(sessionId) throw redirect('/', { headers: { 'set-cookie': await authSessionStorage.destroySession(authSession), @@ -257,7 +264,7 @@ export async function logout( // if this fails, we still need to delete the session from the user's browser // and it doesn't do any harm staying in the db anyway. if (sessionId) { - lruCache.delete(sessionCacheKey(sessionId)) + invalidateSessionCache(sessionId) // the .catch is important because that's what triggers the query. // learn more about PrismaPromise: https://www.prisma.io/docs/orm/reference/prisma-client-reference#prismapromise-behavior void prisma.session.deleteMany({ where: { id: sessionId } }).catch(() => {}) From b978e33802e0ebfc76eaee82eb3586f68576b01f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 1 Feb 2026 17:30:26 +0000 Subject: [PATCH 6/8] Fix auth cache invalidation --- app/routes/settings/profile/index.tsx | 13 ++++++++++--- app/utils/auth.server.ts | 18 +++++++++--------- app/utils/permissions.server.ts | 17 ++++++++++++++--- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/app/routes/settings/profile/index.tsx b/app/routes/settings/profile/index.tsx index 7091a53da..b8fb940d8 100644 --- a/app/routes/settings/profile/index.tsx +++ b/app/routes/settings/profile/index.tsx @@ -304,9 +304,9 @@ async function signOutOfSessionsAction({ request, userId }: ProfileActionArgs) { id: { not: sessionId }, }, }) - for (const session of sessionsToInvalidate) { - invalidateSessionCache(session.id) - } + await Promise.all( + sessionsToInvalidate.map((session) => invalidateSessionCache(session.id)), + ) return { status: 'success' } as const } @@ -351,7 +351,14 @@ function SignOutOfSessions({ } async function deleteDataAction({ userId }: ProfileActionArgs) { + const sessionsToInvalidate = await prisma.session.findMany({ + select: { id: true }, + where: { userId }, + }) await prisma.user.delete({ where: { id: userId } }) + await Promise.all( + sessionsToInvalidate.map((session) => invalidateSessionCache(session.id)), + ) return redirectWithToast('/', { type: 'success', title: 'Data Deleted', diff --git a/app/utils/auth.server.ts b/app/utils/auth.server.ts index 0ca1e4f0a..a5d0f9b07 100644 --- a/app/utils/auth.server.ts +++ b/app/utils/auth.server.ts @@ -4,7 +4,7 @@ import bcrypt from 'bcryptjs' import { redirect } from 'react-router' import { Authenticator } from 'remix-auth' import { safeRedirect } from 'remix-utils/safe-redirect' -import { cachified, lruCache } from './cache.server.ts' +import { cache, cachified } from './cache.server.ts' import { providers } from './connections.server.ts' import { prisma } from './db.server.ts' import { combineHeaders, downloadFile } from './misc.tsx' @@ -22,8 +22,8 @@ export const authenticator = new Authenticator() const sessionCacheKey = (sessionId: string) => `session-user-id:${sessionId}` -export function invalidateSessionCache(sessionId: string) { - lruCache.delete(sessionCacheKey(sessionId)) +export async function invalidateSessionCache(sessionId: string) { + await cache.delete(sessionCacheKey(sessionId)) } type SessionUserIdCacheEntry = { @@ -34,7 +34,7 @@ type SessionUserIdCacheEntry = { async function getCachedSessionEntry(sessionId: string) { return cachified({ key: sessionCacheKey(sessionId), - cache: lruCache, + cache, ttl: SESSION_EXPIRATION_TIME, async getFreshValue(context) { const session = await prisma.session.findUnique({ @@ -83,7 +83,7 @@ export async function getUserId(request: Request) { } const expirationDate = new Date(cachedSession.expirationDate) if (expirationDate <= new Date()) { - invalidateSessionCache(sessionId) + await invalidateSessionCache(sessionId) throw redirect('/', { headers: { 'set-cookie': await authSessionStorage.destroySession(authSession), @@ -264,10 +264,10 @@ export async function logout( // if this fails, we still need to delete the session from the user's browser // and it doesn't do any harm staying in the db anyway. if (sessionId) { - invalidateSessionCache(sessionId) - // the .catch is important because that's what triggers the query. - // learn more about PrismaPromise: https://www.prisma.io/docs/orm/reference/prisma-client-reference#prismapromise-behavior - void prisma.session.deleteMany({ where: { id: sessionId } }).catch(() => {}) + await prisma.session + .deleteMany({ where: { id: sessionId } }) + .catch(() => {}) + await invalidateSessionCache(sessionId) } throw redirect(safeRedirect(redirectTo), { ...responseInit, diff --git a/app/utils/permissions.server.ts b/app/utils/permissions.server.ts index 48bd1b6ba..4b276ed0f 100644 --- a/app/utils/permissions.server.ts +++ b/app/utils/permissions.server.ts @@ -1,5 +1,5 @@ import { data } from 'react-router' -import { cachified, lruCache } from './cache.server.ts' +import { cache, cachified } from './cache.server.ts' import { requireUserId } from './auth.server.ts' import { prisma } from './db.server.ts' import { type PermissionString, parsePermissionString } from './user.ts' @@ -9,6 +9,17 @@ const permissionCacheKey = (userId: string, permission: PermissionString) => const roleCacheKey = (userId: string, name: string) => `role-check:${userId}:${name}` +export async function invalidatePermissionCache( + userId: string, + permission: PermissionString, +) { + await cache.delete(permissionCacheKey(userId, permission)) +} + +export async function invalidateRoleCache(userId: string, name: string) { + await cache.delete(roleCacheKey(userId, name)) +} + export async function requireUserWithPermission( request: Request, permission: PermissionString, @@ -17,7 +28,7 @@ export async function requireUserWithPermission( const permissionData = parsePermissionString(permission) const allowed = await cachified({ key: permissionCacheKey(userId, permission), - cache: lruCache, + cache, ttl: 1000 * 60 * 2, async getFreshValue() { const user = await prisma.user.findFirst({ @@ -58,7 +69,7 @@ export async function requireUserWithRole(request: Request, name: string) { const userId = await requireUserId(request) const allowed = await cachified({ key: roleCacheKey(userId, name), - cache: lruCache, + cache, ttl: 1000 * 60 * 2, async getFreshValue() { const user = await prisma.user.findFirst({ From 18e941d956470205005089c0baa13e9325702cb0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 1 Feb 2026 17:41:25 +0000 Subject: [PATCH 7/8] Handle logout cache invalidation errors --- app/utils/auth.server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/utils/auth.server.ts b/app/utils/auth.server.ts index a5d0f9b07..c54d71741 100644 --- a/app/utils/auth.server.ts +++ b/app/utils/auth.server.ts @@ -267,7 +267,7 @@ export async function logout( await prisma.session .deleteMany({ where: { id: sessionId } }) .catch(() => {}) - await invalidateSessionCache(sessionId) + await invalidateSessionCache(sessionId).catch(() => {}) } throw redirect(safeRedirect(redirectTo), { ...responseInit, From c6e3f135916fd1028cacf8bf8d1b20ac636618ee Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 1 Feb 2026 18:00:15 +0000 Subject: [PATCH 8/8] Allow cached false values --- app/utils/cache.server.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/utils/cache.server.ts b/app/utils/cache.server.ts index 9f0ff6601..71a776fdf 100644 --- a/app/utils/cache.server.ts +++ b/app/utils/cache.server.ts @@ -152,7 +152,6 @@ export const cache: CachifiedCache = { }) if (!parsedEntry.success) return null const { metadata, value } = parsedEntry.data - if (!value) return null return { metadata, value } }, async set(key, entry) {