Skip to content

Commit 53ea6a8

Browse files
feat(api): gate the whole /api/v2 surface behind one flag; UI stays on v1
Every v2 route now runs exactly one check immediately after auth — v2ApiGateError — and answers 404 when the `v2-api` flag is off, so the surface is invisible until it is deliberately rolled out. The gate is keyed on userId only: a workspace/org-keyed check would have to read membership for a caller-supplied id before authorization runs, and its 404-vs-403 split would leak cohort membership (the trap the per-domain table gate worked around by running late). The two executions routes inherit it from the shared access resolver; the tables-specific gate is removed so no route checks twice. `tables-v2-api` stays, now gating only the internal predicate-grammar route /api/table/[tableId]/query — note v2 tables routes move to the unified flag, so enabling them is a `v2-api` decision now. Reverts the deploy modal, copilot handlers, and api_trigger example to the v1 execute endpoint: v1 works unchanged, and the UI must not advertise a surface most users would get a 404 from. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CiHhAk2R1NryaS3R8n2yFz
1 parent 35d306d commit 53ea6a8

45 files changed

Lines changed: 364 additions & 188 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/sim/app/api/v2/audit-logs/[id]/route.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { resolveEnterpriseAuditAccess } from '@/app/api/v1/audit-logs/auth'
1212
import { formatAuditLogEntry } from '@/app/api/v1/audit-logs/format'
1313
import { buildOrgScopeCondition, getOrgWorkspaceIds } from '@/app/api/v1/audit-logs/query'
1414
import { checkRateLimit } from '@/app/api/v1/middleware'
15+
import { v2ApiGateError } from '@/app/api/v2/lib/gate'
1516
import { v2Data, v2Error, v2RateLimitError, v2ValidationError } from '@/app/api/v2/lib/response'
1617

1718
const logger = createLogger('V2AuditLogDetailAPI')
@@ -38,6 +39,9 @@ export const GET = withRouteHandler(
3839

3940
const userId = rateLimit.userId!
4041

42+
const gate = await v2ApiGateError(userId)
43+
if (gate) return gate
44+
4145
const authResult = await resolveEnterpriseAuditAccess(userId)
4246
if (!authResult.success) return v2Error('FORBIDDEN', authResult.message)
4347

apps/sim/app/api/v2/audit-logs/route.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
queryAuditLogs,
1515
} from '@/app/api/v1/audit-logs/query'
1616
import { checkRateLimit } from '@/app/api/v1/middleware'
17+
import { v2ApiGateError } from '@/app/api/v2/lib/gate'
1718
import {
1819
v2CursorList,
1920
v2Error,
@@ -44,6 +45,9 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
4445

4546
const userId = rateLimit.userId!
4647

48+
const gate = await v2ApiGateError(userId)
49+
if (gate) return gate
50+
4751
const authResult = await resolveEnterpriseAuditAccess(userId)
4852
if (!authResult.success) return v2Error('FORBIDDEN', authResult.message)
4953

apps/sim/app/api/v2/billing/usage/logs/route.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ vi.mock('@/lib/billing/core/usage-log', () => ({
2020
getUsageCreditsByLogId: mockGetUsageCreditsByLogId,
2121
}))
2222

23+
vi.mock('@/app/api/v2/lib/gate', () => ({
24+
v2ApiGateError: vi.fn().mockResolvedValue(null),
25+
}))
26+
2327
import { GET } from '@/app/api/v2/billing/usage/logs/route'
2428

2529
const RATE_LIMIT_OK = {

apps/sim/app/api/v2/billing/usage/logs/route.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
1313
import { resolveDateRange } from '@/app/api/users/me/usage-logs/shared'
1414
import { checkRateLimit } from '@/app/api/v1/middleware'
1515
import { v2BillingWorkspaceFilter } from '@/app/api/v2/billing/utils'
16+
import { v2ApiGateError } from '@/app/api/v2/lib/gate'
1617
import {
1718
v2CursorList,
1819
v2Error,
@@ -38,6 +39,10 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
3839
if (!rateLimit.allowed) return v2RateLimitError(rateLimit)
3940

4041
const userId = rateLimit.userId!
42+
43+
const gate = await v2ApiGateError(userId)
44+
if (gate) return gate
45+
4146
const parsed = await parseRequest(
4247
v2ListUsageLogsContract,
4348
request,

apps/sim/app/api/v2/billing/usage/route.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ vi.mock('@/lib/billing/core/usage-log', () => ({
3535
getUserUsageLogs: mockGetUserUsageLogs,
3636
}))
3737

38+
vi.mock('@/app/api/v2/lib/gate', () => ({
39+
v2ApiGateError: vi.fn().mockResolvedValue(null),
40+
}))
41+
3842
import { GET } from '@/app/api/v2/billing/usage/route'
3943

4044
const RATE_LIMIT_OK = {

apps/sim/app/api/v2/billing/usage/route.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { generateRequestId } from '@/lib/core/utils/request'
1111
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
1212
import { checkRateLimit } from '@/app/api/v1/middleware'
1313
import { v2BillingWorkspaceFilter } from '@/app/api/v2/billing/utils'
14+
import { v2ApiGateError } from '@/app/api/v2/lib/gate'
1415
import { v2Data, v2Error, v2RateLimitError, v2ValidationError } from '@/app/api/v2/lib/response'
1516

1617
const logger = createLogger('V2BillingUsageAPI')
@@ -32,6 +33,10 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
3233
if (!rateLimit.allowed) return v2RateLimitError(rateLimit)
3334

3435
const userId = rateLimit.userId!
36+
37+
const gate = await v2ApiGateError(userId)
38+
if (gate) return gate
39+
3540
const parsed = await parseRequest(
3641
v2GetUsageSummaryContract,
3742
request,

apps/sim/app/api/v2/files/[fileId]/route.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
77
import { fetchWorkspaceFileBuffer, getWorkspaceFile } from '@/lib/uploads/contexts/workspace'
88
import { performDeleteWorkspaceFileItems } from '@/lib/workspace-files/orchestration'
99
import { checkRateLimit, resolveWorkspaceAccess } from '@/app/api/v1/middleware'
10+
import { v2ApiGateError } from '@/app/api/v2/lib/gate'
1011
import type { V2ErrorCode } from '@/app/api/v2/lib/response'
1112
import {
1213
rateLimitHeaders,
@@ -39,6 +40,10 @@ export const GET = withRouteHandler(async (request: NextRequest, context: FileRo
3940
if (!rateLimit.allowed) return v2RateLimitError(rateLimit)
4041

4142
const userId = rateLimit.userId!
43+
44+
const gate = await v2ApiGateError(userId)
45+
if (gate) return gate
46+
4247
const parsed = await parseRequest(v2DownloadFileContract, request, context, {
4348
validationErrorResponse: v2ValidationError,
4449
})
@@ -84,6 +89,10 @@ export const DELETE = withRouteHandler(async (request: NextRequest, context: Fil
8489
if (!rateLimit.allowed) return v2RateLimitError(rateLimit)
8590

8691
const userId = rateLimit.userId!
92+
93+
const gate = await v2ApiGateError(userId)
94+
if (gate) return gate
95+
8796
const parsed = await parseRequest(v2DeleteFileContract, request, context, {
8897
validationErrorResponse: v2ValidationError,
8998
})

apps/sim/app/api/v2/files/route.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
uploadWorkspaceFile,
2222
} from '@/lib/uploads/contexts/workspace'
2323
import { checkRateLimit, resolveWorkspaceAccess } from '@/app/api/v1/middleware'
24+
import { v2ApiGateError } from '@/app/api/v2/lib/gate'
2425
import {
2526
decodeCursor,
2627
encodeCursor,
@@ -65,6 +66,10 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
6566
if (!rateLimit.allowed) return v2RateLimitError(rateLimit)
6667

6768
const userId = rateLimit.userId!
69+
70+
const gate = await v2ApiGateError(userId)
71+
if (gate) return gate
72+
6873
const parsed = await parseRequest(
6974
v2ListFilesContract,
7075
request,
@@ -130,6 +135,10 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
130135
if (!rateLimit.allowed) return v2RateLimitError(rateLimit)
131136

132137
const userId = rateLimit.userId!
138+
139+
const gate = await v2ApiGateError(userId)
140+
if (gate) return gate
141+
133142
const parsed = await parseRequest(
134143
v2UploadFileContract,
135144
request,

apps/sim/app/api/v2/knowledge/[id]/documents/[documentId]/route.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { deleteDocument } from '@/lib/knowledge/documents/service'
1717
import type { KnowledgeBaseWithCounts } from '@/lib/knowledge/types'
1818
import { resolveKnowledgeBase, serializeDate } from '@/app/api/v1/knowledge/utils'
1919
import { checkRateLimit, type RateLimitResult } from '@/app/api/v1/middleware'
20+
import { v2ApiGateError } from '@/app/api/v2/lib/gate'
2021
import { v2Data, v2Error, v2RateLimitError, v2ValidationError } from '@/app/api/v2/lib/response'
2122

2223
const logger = createLogger('V2KnowledgeDocumentDetailAPI')
@@ -59,6 +60,10 @@ export const GET = withRouteHandler(
5960
if (!rateLimit.allowed) return v2RateLimitError(rateLimit)
6061

6162
const userId = rateLimit.userId!
63+
64+
const gate = await v2ApiGateError(userId)
65+
if (gate) return gate
66+
6267
const parsed = await parseRequest(v2GetKnowledgeDocumentContract, request, context, {
6368
validationErrorResponse: v2ValidationError,
6469
})
@@ -151,6 +156,10 @@ export const DELETE = withRouteHandler(
151156
if (!rateLimit.allowed) return v2RateLimitError(rateLimit)
152157

153158
const userId = rateLimit.userId!
159+
160+
const gate = await v2ApiGateError(userId)
161+
if (gate) return gate
162+
154163
const parsed = await parseRequest(v2DeleteKnowledgeDocumentContract, request, context, {
155164
validationErrorResponse: v2ValidationError,
156165
})

apps/sim/app/api/v2/knowledge/[id]/documents/route.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { uploadWorkspaceFile } from '@/lib/uploads/contexts/workspace'
3232
import { validateFileType } from '@/lib/uploads/utils/validation'
3333
import { resolveKnowledgeBase, serializeDate } from '@/app/api/v1/knowledge/utils'
3434
import { checkRateLimit, type RateLimitResult } from '@/app/api/v1/middleware'
35+
import { v2ApiGateError } from '@/app/api/v2/lib/gate'
3536
import {
3637
decodeCursor,
3738
encodeCursor,
@@ -84,6 +85,10 @@ export const GET = withRouteHandler(async (request: NextRequest, context: Docume
8485
if (!rateLimit.allowed) return v2RateLimitError(rateLimit)
8586

8687
const userId = rateLimit.userId!
88+
89+
const gate = await v2ApiGateError(userId)
90+
if (gate) return gate
91+
8792
const parsed = await parseRequest(v2ListKnowledgeDocumentsContract, request, context, {
8893
validationErrorResponse: v2ValidationError,
8994
})
@@ -161,6 +166,10 @@ export const POST = withRouteHandler(
161166
if (!rateLimit.allowed) return v2RateLimitError(rateLimit)
162167

163168
const userId = rateLimit.userId!
169+
170+
const gate = await v2ApiGateError(userId)
171+
if (gate) return gate
172+
164173
const parsed = await parseRequest(v2UploadKnowledgeDocumentContract, request, context, {
165174
validationErrorResponse: v2ValidationError,
166175
})

0 commit comments

Comments
 (0)