Skip to content

Commit 833e700

Browse files
waleedlatif1waleed
authored andcommitted
feat(sessions): add redis as priority option for session data (#1592)
* feat(sessions): add redis as priority option for session data * update chat client otp
1 parent 2d49892 commit 833e700

File tree

3 files changed

+129
-181
lines changed

3 files changed

+129
-181
lines changed

apps/sim/app/api/chat/[identifier]/otp/route.ts

Lines changed: 35 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { z } from 'zod'
66
import { renderOTPEmail } from '@/components/emails/render-email'
77
import { sendEmail } from '@/lib/email/mailer'
88
import { createLogger } from '@/lib/logs/console/logger'
9-
import { getRedisClient, markMessageAsProcessed, releaseLock } from '@/lib/redis'
9+
import { getRedisClient } from '@/lib/redis'
1010
import { generateRequestId } from '@/lib/utils'
1111
import { addCorsHeaders, setChatAuthCookie } from '@/app/api/chat/utils'
1212
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
@@ -21,83 +21,52 @@ function generateOTP() {
2121
// We use 15 minutes (900 seconds) expiry for OTPs
2222
const OTP_EXPIRY = 15 * 60
2323

24-
// Store OTP in Redis
25-
async function storeOTP(email: string, chatId: string, otp: string): Promise<void> {
24+
async function storeOTP(email: string, chatId: string, otp: string): Promise<boolean> {
2625
const key = `otp:${email}:${chatId}`
2726
const redis = getRedisClient()
2827

29-
if (redis) {
30-
// Use Redis if available
31-
await redis.set(key, otp, 'EX', OTP_EXPIRY)
32-
} else {
33-
// Use the existing function as fallback to mark that an OTP exists
34-
await markMessageAsProcessed(key, OTP_EXPIRY)
28+
if (!redis) {
29+
logger.warn('Redis not available, OTP functionality requires Redis')
30+
return false
31+
}
3532

36-
// For the fallback case, we need to handle storing the OTP value separately
37-
// since markMessageAsProcessed only stores "1"
38-
const valueKey = `${key}:value`
39-
try {
40-
// Access the in-memory cache directly - hacky but works for fallback
41-
const inMemoryCache = (global as any).inMemoryCache
42-
if (inMemoryCache) {
43-
const fullKey = `processed:${valueKey}`
44-
const expiry = OTP_EXPIRY ? Date.now() + OTP_EXPIRY * 1000 : null
45-
inMemoryCache.set(fullKey, { value: otp, expiry })
46-
}
47-
} catch (error) {
48-
logger.error('Error storing OTP in fallback cache:', error)
49-
}
33+
try {
34+
await redis.set(key, otp, 'EX', OTP_EXPIRY)
35+
return true
36+
} catch (error) {
37+
logger.error('Error storing OTP in Redis:', error)
38+
return false
5039
}
5140
}
5241

53-
// Get OTP from Redis
5442
async function getOTP(email: string, chatId: string): Promise<string | null> {
5543
const key = `otp:${email}:${chatId}`
5644
const redis = getRedisClient()
5745

58-
if (redis) {
59-
// Use Redis if available
60-
return await redis.get(key)
46+
if (!redis) {
47+
return null
6148
}
62-
// Use the existing function as fallback - check if it exists
63-
const exists = await new Promise((resolve) => {
64-
try {
65-
// Check the in-memory cache directly - hacky but works for fallback
66-
const inMemoryCache = (global as any).inMemoryCache
67-
const fullKey = `processed:${key}`
68-
const cacheEntry = inMemoryCache?.get(fullKey)
69-
resolve(!!cacheEntry)
70-
} catch {
71-
resolve(false)
72-
}
73-
})
74-
75-
if (!exists) return null
7649

77-
// Try to get the value key
78-
const valueKey = `${key}:value`
7950
try {
80-
const inMemoryCache = (global as any).inMemoryCache
81-
const fullKey = `processed:${valueKey}`
82-
const cacheEntry = inMemoryCache?.get(fullKey)
83-
return cacheEntry?.value || null
84-
} catch {
51+
return await redis.get(key)
52+
} catch (error) {
53+
logger.error('Error getting OTP from Redis:', error)
8554
return null
8655
}
8756
}
8857

89-
// Delete OTP from Redis
9058
async function deleteOTP(email: string, chatId: string): Promise<void> {
9159
const key = `otp:${email}:${chatId}`
9260
const redis = getRedisClient()
9361

94-
if (redis) {
95-
// Use Redis if available
62+
if (!redis) {
63+
return
64+
}
65+
66+
try {
9667
await redis.del(key)
97-
} else {
98-
// Use the existing function as fallback
99-
await releaseLock(`processed:${key}`)
100-
await releaseLock(`processed:${key}:value`)
68+
} catch (error) {
69+
logger.error('Error deleting OTP from Redis:', error)
10170
}
10271
}
10372

@@ -177,7 +146,17 @@ export async function POST(
177146

178147
const otp = generateOTP()
179148

180-
await storeOTP(email, deployment.id, otp)
149+
const stored = await storeOTP(email, deployment.id, otp)
150+
if (!stored) {
151+
logger.error(`[${requestId}] Failed to store OTP - Redis unavailable`)
152+
return addCorsHeaders(
153+
createErrorResponse(
154+
'Email verification temporarily unavailable, please try again later',
155+
503
156+
),
157+
request
158+
)
159+
}
181160

182161
const emailHtml = await renderOTPEmail(
183162
otp,

apps/sim/lib/auth.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { quickValidateEmail } from '@/lib/email/validation'
4444
import { env, isTruthy } from '@/lib/env'
4545
import { isBillingEnabled, isEmailVerificationEnabled } from '@/lib/environment'
4646
import { createLogger } from '@/lib/logs/console/logger'
47+
import { getRedisClient } from '@/lib/redis'
4748
import { SSO_TRUSTED_PROVIDERS } from './sso/consts'
4849

4950
const logger = createLogger('Auth')
@@ -59,6 +60,40 @@ if (validStripeKey) {
5960
})
6061
}
6162

63+
// Configure Redis secondary storage for session data (optional)
64+
const redis = getRedisClient()
65+
const redisSecondaryStorage = redis
66+
? {
67+
get: async (key: string) => {
68+
try {
69+
const value = await redis.get(key)
70+
return value
71+
} catch (error) {
72+
logger.error('Redis get error in secondaryStorage', { key, error })
73+
return null
74+
}
75+
},
76+
set: async (key: string, value: string, ttl?: number) => {
77+
try {
78+
if (ttl) {
79+
await redis.set(key, value, 'EX', ttl)
80+
} else {
81+
await redis.set(key, value)
82+
}
83+
} catch (error) {
84+
logger.error('Redis set error in secondaryStorage', { key, ttl, error })
85+
}
86+
},
87+
delete: async (key: string) => {
88+
try {
89+
await redis.del(key)
90+
} catch (error) {
91+
logger.error('Redis delete error in secondaryStorage', { key, error })
92+
}
93+
},
94+
}
95+
: undefined
96+
6297
export const auth = betterAuth({
6398
baseURL: getBaseURL(),
6499
trustedOrigins: [
@@ -69,6 +104,8 @@ export const auth = betterAuth({
69104
provider: 'pg',
70105
schema,
71106
}),
107+
// Conditionally add secondaryStorage only if Redis is available
108+
...(redisSecondaryStorage ? { secondaryStorage: redisSecondaryStorage } : {}),
72109
session: {
73110
cookieCache: {
74111
enabled: true,

0 commit comments

Comments
 (0)