Skip to content

Commit 217d2be

Browse files
fix(api): align v2 permissions and resource behavior
1 parent bd91ab7 commit 217d2be

86 files changed

Lines changed: 416 additions & 444 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/files/[fileId]/content/route.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ describe('PUT /api/v2/files/[fileId]/content', () => {
115115

116116
const response = await callPut('{not-json')
117117

118-
expect(response.status).toBe(404)
118+
expect(response.status).toBe(403)
119119
expect(mocks.admit).toHaveBeenCalledWith(auth.principal, FILE_ID)
120120
expect(mocks.updateContent).not.toHaveBeenCalled()
121121
})

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const PUT = defineV2JsonRoute({
1919
auth: v2ApiKeyAuth,
2020
operation: fileOperations.updateContent,
2121
rateLimit: v2RateLimits.publicApi,
22-
errorPolicy: v2FileErrorPolicies.concealResourceAuthorization,
22+
errorPolicy: v2FileErrorPolicies.default,
2323
parseOptions: {
2424
invalidJsonResponse: () => v2Error('BAD_REQUEST', 'Request body must be valid JSON'),
2525
maxBodyBytes: MAX_WORKSPACE_FILE_INLINE_BODY_BYTES,

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,13 @@ describe('GET /api/v2/files/[fileId]/metadata', () => {
113113
expect(mocks.readMetadata).not.toHaveBeenCalled()
114114
})
115115

116-
it('conceals an authorization failure as not found', async () => {
116+
it('returns forbidden for an authorization failure', async () => {
117117
mocks.readMetadata.mockRejectedValue(new NoWorkspaceAccessError())
118118

119119
const response = await callGet(`workspaceId=${WORKSPACE_ID}`)
120120

121-
expect(response.status).toBe(404)
122-
expect((await response.json()).error.code).toBe('NOT_FOUND')
121+
expect(response.status).toBe(403)
122+
expect((await response.json()).error.code).toBe('FORBIDDEN')
123123
})
124124

125125
it('returns the v2 metadata projection through the shared use case', async () => {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const GET = defineV2JsonRoute({
1414
auth: v2ApiKeyAuth,
1515
operation: fileOperations.readMetadata,
1616
rateLimit: v2RateLimits.publicApi,
17-
errorPolicy: v2FileErrorPolicies.concealResourceAuthorization,
17+
errorPolicy: v2FileErrorPolicies.default,
1818
mapInput: ({ params, query }) => ({
1919
fileId: params.fileId,
2020
assertedWorkspaceId: query.workspaceId,

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,16 +141,16 @@ describe('v2 single-file routes', () => {
141141
})
142142
})
143143

144-
it('conceals download authorization failures', async () => {
144+
it('returns forbidden for download authorization failures', async () => {
145145
mocks.download.mockRejectedValue(new NoWorkspaceAccessError())
146146

147147
const response = await GET(
148148
new NextRequest(`http://localhost:3000/api/v2/files/${FILE_ID}?workspaceId=${WORKSPACE_ID}`),
149149
context
150150
)
151151

152-
expect(response.status).toBe(404)
153-
expect((await response.json()).error.code).toBe('NOT_FOUND')
152+
expect(response.status).toBe(403)
153+
expect((await response.json()).error.code).toBe('FORBIDDEN')
154154
})
155155

156156
it('renames through the shared use case and v2 presenter', async () => {
@@ -170,7 +170,7 @@ describe('v2 single-file routes', () => {
170170
})
171171
})
172172

173-
it('maps rename conflicts and conceals absent workspace access', async () => {
173+
it('maps rename conflicts and returns forbidden for absent workspace access', async () => {
174174
mocks.rename.mockRejectedValueOnce(new OrchestrationError('conflict', 'Name exists'))
175175
const conflict = await PATCH(
176176
new NextRequest(`http://localhost:3000/api/v2/files/${FILE_ID}`, {
@@ -183,15 +183,15 @@ describe('v2 single-file routes', () => {
183183
expect(conflict.status).toBe(409)
184184

185185
mocks.rename.mockRejectedValueOnce(new NoWorkspaceAccessError())
186-
const concealed = await PATCH(
186+
const forbidden = await PATCH(
187187
new NextRequest(`http://localhost:3000/api/v2/files/${FILE_ID}`, {
188188
method: 'PATCH',
189189
headers: { 'Content-Type': 'application/json' },
190190
body: JSON.stringify({ workspaceId: WORKSPACE_ID, name: 'renamed.csv' }),
191191
}),
192192
context
193193
)
194-
expect(concealed.status).toBe(404)
194+
expect(forbidden.status).toBe(403)
195195
})
196196

197197
it('returns forbidden when the current workspace role cannot rename the file', async () => {

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const GET = defineV2BinaryRoute({
3333
auth: v2ApiKeyAuth,
3434
operation: fileOperations.download,
3535
rateLimit: v2RateLimits.publicApi,
36-
errorPolicy: v2FileErrorPolicies.concealResourceAuthorization,
36+
errorPolicy: v2FileErrorPolicies.default,
3737
mapInput: ({ params, query }) => ({
3838
fileId: params.fileId,
3939
assertedWorkspaceId: query.workspaceId,
@@ -59,7 +59,7 @@ export const PATCH = defineV2JsonRoute({
5959
auth: v2ApiKeyAuth,
6060
operation: fileOperations.rename,
6161
rateLimit: v2RateLimits.publicApi,
62-
errorPolicy: v2FileErrorPolicies.concealResourceAuthorization,
62+
errorPolicy: v2FileErrorPolicies.default,
6363
mapInput: ({ params, body }) => ({
6464
fileId: params.fileId,
6565
assertedWorkspaceId: body.workspaceId,
@@ -81,7 +81,7 @@ export const DELETE = defineV2JsonRoute({
8181
auth: v2ApiKeyAuth,
8282
operation: fileOperations.delete,
8383
rateLimit: v2RateLimits.publicApi,
84-
errorPolicy: v2FileErrorPolicies.concealResourceAuthorization,
84+
errorPolicy: v2FileErrorPolicies.default,
8585
mapInput: ({ params, query }) => ({
8686
fileId: params.fileId,
8787
assertedWorkspaceId: query.workspaceId,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export const GET = defineV2JsonRoute({
1515
auth: v2ApiKeyAuth,
1616
operation: fileOperations.readShare,
1717
rateLimit: v2RateLimits.publicApi,
18-
errorPolicy: v2FileErrorPolicies.concealResourceAuthorization,
18+
errorPolicy: v2FileErrorPolicies.default,
1919
mapInput: ({ params, query }) => ({
2020
fileId: params.fileId,
2121
assertedWorkspaceId: query.workspaceId,
@@ -29,7 +29,7 @@ export const PUT = defineV2JsonRoute({
2929
auth: v2ApiKeyAuth,
3030
operation: fileOperations.updateShare,
3131
rateLimit: v2RateLimits.publicApi,
32-
errorPolicy: v2FileErrorPolicies.concealResourceAuthorization,
32+
errorPolicy: v2FileErrorPolicies.default,
3333
mapInput: ({ params, body }) => ({
3434
fileId: params.fileId,
3535
assertedWorkspaceId: body.workspaceId,

apps/sim/app/api/v2/files/uploads/utils.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,15 @@ function uploadStatus(status: string): V2UploadStatus {
3939
import type { Principal } from '@sim/auth/principal'
4040
import type { NextRequest, NextResponse } from 'next/server'
4141
import { authenticateV2ApiKey } from '@/lib/api/server/routes/v2-api-key-auth'
42-
import { v2CaughtOrchestrationError, v2Error } from '@/app/api/v2/lib/response'
42+
import { v2CaughtOrchestrationError } from '@/app/api/v2/lib/response'
4343

4444
/** Re-authenticates the API key for each upload control leg. */
4545
export async function authenticateUploadPrincipal(request: NextRequest): Promise<Principal> {
4646
const auth = await authenticateV2ApiKey(request.headers.get('x-api-key'))
4747
return auth.principal
4848
}
4949

50-
/** Resource-ID upload controls conceal authorization failures as absence. */
50+
/** Renders upload-control application failures without rewriting authorization status. */
5151
export function v2UploadControlError(error: unknown): NextResponse | null {
52-
const response = v2CaughtOrchestrationError(error)
53-
if (!response) return null
54-
return response.status === 403 ? v2Error('NOT_FOUND', 'Upload session not found') : response
52+
return v2CaughtOrchestrationError(error)
5553
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const GET = defineV2JsonRoute({
3333
auth: v2ApiKeyAuth,
3434
operation: knowledgeOperations.readDocument,
3535
rateLimit: v2RateLimits.publicApi,
36-
errorPolicy: v2KnowledgeErrorPolicies.concealKnowledgeBaseAuthorization,
36+
errorPolicy: v2KnowledgeErrorPolicies.default,
3737
mapInput: ({ params, query }) => ({
3838
knowledgeBaseId: params.id,
3939
documentId: params.documentId,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export const GET = defineV2JsonRoute({
7171
auth: v2ApiKeyAuth,
7272
operation: knowledgeOperations.listDocuments,
7373
rateLimit: v2RateLimits.publicApi,
74-
errorPolicy: v2KnowledgeErrorPolicies.concealKnowledgeBaseAuthorization,
74+
errorPolicy: v2KnowledgeErrorPolicies.default,
7575
mapInput: ({ params, query }) => {
7676
const decodedCursor = query.cursor ? decodeCursor<{ offset: number }>(query.cursor) : null
7777
if (

0 commit comments

Comments
 (0)