Skip to content

Commit 92ee9fe

Browse files
committed
fix(chat): close SSO GET cookie replay and add eligibility rate limit
- Skip chat_auth cookie validation for SSO in GET handler (replay vector for pre-fix cookies) - Route SSO GET through getSession() instead of always returning auth_required_sso so post-IdP config fetch works - Add per-IP rate limiting to /api/chat/[identifier]/sso to prevent allowlist enumeration
1 parent 7a03262 commit 92ee9fe

3 files changed

Lines changed: 30 additions & 6 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ export const GET = withRouteHandler(
360360

361361
if (
362362
deployment.authType !== 'public' &&
363+
deployment.authType !== 'sso' &&
363364
authCookie &&
364365
validateAuthToken(authCookie.value, deployment.id, deployment.password)
365366
) {

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import { and, eq, isNull } from 'drizzle-orm'
55
import type { NextRequest } from 'next/server'
66
import { chatSSOContract } from '@/lib/api/contracts/chats'
77
import { parseRequest } from '@/lib/api/server'
8+
import type { TokenBucketConfig } from '@/lib/core/rate-limiter'
9+
import { RateLimiter } from '@/lib/core/rate-limiter'
810
import { addCorsHeaders, isEmailAllowed } from '@/lib/core/security/deployment'
9-
import { generateRequestId } from '@/lib/core/utils/request'
11+
import { generateRequestId, getClientIp } from '@/lib/core/utils/request'
1012
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
1113
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
1214

@@ -15,10 +17,35 @@ const logger = createLogger('ChatSSOAPI')
1517
export const dynamic = 'force-dynamic'
1618
export const runtime = 'nodejs'
1719

20+
const rateLimiter = new RateLimiter()
21+
22+
const SSO_IP_RATE_LIMIT: TokenBucketConfig = {
23+
maxTokens: 20,
24+
refillRate: 20,
25+
refillIntervalMs: 15 * 60_000,
26+
}
27+
1828
export const POST = withRouteHandler(
1929
async (request: NextRequest, context: { params: Promise<{ identifier: string }> }) => {
2030
const requestId = generateRequestId()
2131

32+
const ip = getClientIp(request)
33+
if (ip !== 'unknown') {
34+
const ipRateLimit = await rateLimiter.checkRateLimitDirect(
35+
`chat-sso:ip:${ip}`,
36+
SSO_IP_RATE_LIMIT
37+
)
38+
if (!ipRateLimit.allowed) {
39+
logger.warn(`[${requestId}] SSO eligibility rate limit exceeded from ${ip}`)
40+
const retryAfter = Math.ceil(
41+
(ipRateLimit.retryAfterMs ?? SSO_IP_RATE_LIMIT.refillIntervalMs) / 1000
42+
)
43+
const response = createErrorResponse('Too many requests. Please try again later.', 429)
44+
response.headers.set('Retry-After', String(retryAfter))
45+
return addCorsHeaders(response, request)
46+
}
47+
}
48+
2249
const parsed = await parseRequest(chatSSOContract, request, context)
2350
if (!parsed.success) return parsed.response
2451

apps/sim/app/api/chat/utils.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,8 @@ export async function validateChatAuth(
175175
}
176176

177177
if (authType === 'sso') {
178-
if (request.method === 'GET') {
179-
return { authorized: false, error: 'auth_required_sso' }
180-
}
181-
182178
try {
183-
if (!parsedBody) {
179+
if (request.method !== 'GET' && !parsedBody) {
184180
return { authorized: false, error: 'SSO authentication is required' }
185181
}
186182

0 commit comments

Comments
 (0)