Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions apps/sim/app/api/auth/instagram/authorize/route.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createMockRequest } from '@sim/testing'
import { createRouteContext } from '@sim/testing/helpers/http'
import { authMockFns } from '@sim/testing/mocks/auth.mock'
import { oauthUtilsMock, oauthUtilsMockFns } from '@sim/testing/mocks/oauth-utils.mock'
import { urlsMockFns } from '@sim/testing/mocks/urls.mock'
Expand Down Expand Up @@ -45,7 +46,7 @@ describe('Instagram authorize route', () => {
'https://sim.test/api/auth/instagram/authorize?workspaceId=workspace-1&draftId=draft-exact'
)

const response = await GET(request)
const response = await GET(request, createRouteContext({}))

expect(response.status).toBe(307)
expect(response.headers.get('set-cookie')).toContain(
Expand All @@ -54,6 +55,20 @@ describe('Instagram authorize route', () => {
expect(mocks.createCredentialConnection).not.toHaveBeenCalled()
})

it('refuses a cross-site workspace launch before creating a connection draft', async () => {
const request = createMockRequest(
'GET',
undefined,
{ 'sec-fetch-site': 'cross-site' },
'https://sim.test/api/auth/instagram/authorize?workspaceId=workspace-1'
)

const response = await GET(request, createRouteContext({}))

expect(response.status).toBe(403)
expect(mocks.createCredentialConnection).not.toHaveBeenCalled()
})

it('returns a conflict when a different connection intent is already active', async () => {
mocks.createCredentialConnection.mockRejectedValue(
new OrchestrationError(
Expand All @@ -68,7 +83,7 @@ describe('Instagram authorize route', () => {
'https://sim.test/api/auth/instagram/authorize?workspaceId=workspace-1'
)

const response = await GET(request)
const response = await GET(request, createRouteContext({}))

expect(response.status).toBe(409)
await expect(response.json()).resolves.toEqual({
Expand Down
7 changes: 7 additions & 0 deletions apps/sim/app/api/auth/instagram/authorize/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { parseRequest } from '@/lib/api/server'
import { getSession } from '@/lib/auth'
import { requireConfiguredOAuthClient } from '@/lib/core/config/env-capabilities.server'
import { asOrchestrationError } from '@/lib/core/orchestration/types'
import { isCrossSiteSessionRequest } from '@/lib/core/security/same-origin'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { isSameOrigin } from '@/lib/core/utils/validation'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
Expand Down Expand Up @@ -41,6 +42,12 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
let credentialDraftId = draftId

if (workspaceId && !draftId) {
// The query alone picks the workspace, so a cross-site page must not be
// able to start this launch with the viewer's session cookie.
if (isCrossSiteSessionRequest(request)) {
logger.warn('Rejected cross-site Instagram workspace launch', { userId: session.user.id })
return NextResponse.json({ error: 'Cross-site launch refused' }, { status: 403 })
}
try {
const connection = await createCredentialConnection.execute({
principal: { kind: 'session', userId: session.user.id, sessionId },
Expand Down
3 changes: 0 additions & 3 deletions scripts/check-test-types.baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
"apps/sim/app/api/auth/forget-password/route.test.ts": {
"TS2554": 8
},
"apps/sim/app/api/auth/instagram/authorize/route.test.ts": {
"TS2554": 2
},
"apps/sim/app/api/auth/oauth/credentials/route.test.ts": {
"TS2322": 1,
"TS2345": 2,
Expand Down
Loading