Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
253161a
feat(mult-credentials): progress
icecrasher321 Feb 11, 2026
7314675
checkpoint
icecrasher321 Feb 12, 2026
508772c
make it autoselect personal secret when create secret is clicked
icecrasher321 Feb 12, 2026
aefa281
improve collaborative UX
icecrasher321 Feb 12, 2026
319768c
remove add member ui for workspace secrets
icecrasher321 Feb 12, 2026
622023d
bulk entry of .env
icecrasher321 Feb 13, 2026
5e19226
promote to workspace secret
icecrasher321 Feb 13, 2026
8ed8a5a
more ux improvmeent
icecrasher321 Feb 13, 2026
bdd1483
share with workspace for oauth
icecrasher321 Feb 13, 2026
17710b3
remove new badge
icecrasher321 Feb 13, 2026
77bb048
share button
icecrasher321 Feb 13, 2026
dcf40be
copilot + oauth name comflict
icecrasher321 Feb 13, 2026
fa32b9e
reconnect option to connect diff account
icecrasher321 Feb 13, 2026
ff13b1f
remove credential no access marker
icecrasher321 Feb 13, 2026
3ad0f62
canonical credential id entry
icecrasher321 Feb 13, 2026
084ff9c
remove migration to prep stagin migration
icecrasher321 Feb 13, 2026
7092c88
Merge remote-tracking branch 'origin/staging' into feat/mult-credenti…
icecrasher321 Feb 13, 2026
93826cb
migration readded
icecrasher321 Feb 13, 2026
d70a5d4
backfill improvements
icecrasher321 Feb 14, 2026
ea42e64
run lint
icecrasher321 Feb 14, 2026
08b908f
fix tests
icecrasher321 Feb 14, 2026
6053050
remove unused code
icecrasher321 Feb 14, 2026
cd1ccf1
autoselect provider when connecting from block
icecrasher321 Feb 14, 2026
41cdca2
address bugbot comments
icecrasher321 Feb 14, 2026
3769da8
remove some dead code
icecrasher321 Feb 14, 2026
d235d74
more permissions stuff
icecrasher321 Feb 14, 2026
140f870
remove more unused code
icecrasher321 Feb 14, 2026
9584b99
address bugbot
icecrasher321 Feb 14, 2026
d8bbd7e
add filter
icecrasher321 Feb 14, 2026
6c3f3a4
remove migration to prep migration
icecrasher321 Feb 18, 2026
80282c3
Merge remote-tracking branch 'origin/staging' into feat/mult-credenti…
icecrasher321 Feb 18, 2026
ed9c35f
fix migration
icecrasher321 Feb 18, 2026
9b20e76
fix migration issues
icecrasher321 Feb 18, 2026
3f6fa6b
remove migration prep merge
icecrasher321 Feb 20, 2026
c50d194
Merge remote-tracking branch 'origin/staging' into feat/mult-credenti…
icecrasher321 Feb 20, 2026
c2edb50
readd migration
icecrasher321 Feb 20, 2026
abae19c
include user tables triggers
icecrasher321 Feb 20, 2026
c53fcb8
extract shared code
icecrasher321 Feb 20, 2026
34ca49a
fix
icecrasher321 Feb 20, 2026
4f5500f
fix tx issue
icecrasher321 Feb 20, 2026
1e4d255
remove migration to prep merge
icecrasher321 Feb 23, 2026
23e24b4
Merge branch 'staging' into feat/mult-credentials-rv
icecrasher321 Feb 23, 2026
94d5268
readd migration
icecrasher321 Feb 23, 2026
cc790aa
fix agent tool input
icecrasher321 Feb 23, 2026
ef358d0
agent with tool input deletion case
icecrasher321 Feb 23, 2026
35d6e17
fix credential subblock saving
icecrasher321 Feb 23, 2026
d117400
remove dead code
icecrasher321 Feb 23, 2026
e23ef7c
fix tests
icecrasher321 Feb 23, 2026
4e1d607
address bugbot comments
icecrasher321 Feb 23, 2026
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
8 changes: 3 additions & 5 deletions apps/sim/app/api/auth/accounts/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { db } from '@sim/db'
import { account } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { and, desc, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'

Expand Down Expand Up @@ -31,15 +31,13 @@ export async function GET(request: NextRequest) {
})
.from(account)
.where(and(...whereConditions))

// Use the user's email as the display name (consistent with credential selector)
const userEmail = session.user.email
.orderBy(desc(account.updatedAt))

const accountsWithDisplayName = accounts.map((acc) => ({
id: acc.id,
accountId: acc.accountId,
providerId: acc.providerId,
displayName: userEmail || acc.providerId,
displayName: acc.accountId || acc.providerId,
}))

return NextResponse.json({ accounts: accountsWithDisplayName })
Expand Down
120 changes: 3 additions & 117 deletions apps/sim/app/api/auth/oauth/credentials/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,6 @@ describe('OAuth Credentials API Route', () => {
eq: vi.fn((field, value) => ({ field, value, type: 'eq' })),
}))

vi.doMock('jwt-decode', () => ({
jwtDecode: vi.fn(),
}))

vi.doMock('@sim/logger', () => ({
createLogger: vi.fn().mockReturnValue(mockLogger),
}))
Expand All @@ -84,64 +80,6 @@ describe('OAuth Credentials API Route', () => {
vi.clearAllMocks()
})

it('should return credentials successfully', async () => {
mockGetSession.mockResolvedValueOnce({
user: { id: 'user-123' },
})

mockParseProvider.mockReturnValueOnce({
baseProvider: 'google',
})

const mockAccounts = [
{
id: 'credential-1',
userId: 'user-123',
providerId: 'google-email',
accountId: 'test@example.com',
updatedAt: new Date('2024-01-01'),
idToken: null,
},
{
id: 'credential-2',
userId: 'user-123',
providerId: 'google-default',
accountId: 'user-id',
updatedAt: new Date('2024-01-02'),
idToken: null,
},
]

mockDb.select.mockReturnValueOnce(mockDb)
mockDb.from.mockReturnValueOnce(mockDb)
mockDb.where.mockResolvedValueOnce(mockAccounts)

mockDb.select.mockReturnValueOnce(mockDb)
mockDb.from.mockReturnValueOnce(mockDb)
mockDb.where.mockReturnValueOnce(mockDb)
mockDb.limit.mockResolvedValueOnce([{ email: 'user@example.com' }])

const req = createMockRequestWithQuery('GET', '?provider=google-email')

const { GET } = await import('@/app/api/auth/oauth/credentials/route')

const response = await GET(req)
const data = await response.json()

expect(response.status).toBe(200)
expect(data.credentials).toHaveLength(2)
expect(data.credentials[0]).toMatchObject({
id: 'credential-1',
provider: 'google-email',
isDefault: false,
})
expect(data.credentials[1]).toMatchObject({
id: 'credential-2',
provider: 'google-default',
isDefault: true,
})
})

it('should handle unauthenticated user', async () => {
mockGetSession.mockResolvedValueOnce(null)

Expand Down Expand Up @@ -198,71 +136,19 @@ describe('OAuth Credentials API Route', () => {
expect(data.credentials).toHaveLength(0)
})

it('should decode ID token for display name', async () => {
const { jwtDecode } = await import('jwt-decode')
const mockJwtDecode = jwtDecode as any

it('should return empty credentials when no workspace context', async () => {
mockGetSession.mockResolvedValueOnce({
user: { id: 'user-123' },
})

mockParseProvider.mockReturnValueOnce({
baseProvider: 'google',
})

const mockAccounts = [
{
id: 'credential-1',
userId: 'user-123',
providerId: 'google-default',
accountId: 'google-user-id',
updatedAt: new Date('2024-01-01'),
idToken: 'mock-jwt-token',
},
]

mockJwtDecode.mockReturnValueOnce({
email: 'decoded@example.com',
name: 'Decoded User',
})

mockDb.select.mockReturnValueOnce(mockDb)
mockDb.from.mockReturnValueOnce(mockDb)
mockDb.where.mockResolvedValueOnce(mockAccounts)

const req = createMockRequestWithQuery('GET', '?provider=google')
const req = createMockRequestWithQuery('GET', '?provider=google-email')

const { GET } = await import('@/app/api/auth/oauth/credentials/route')

const response = await GET(req)
const data = await response.json()

expect(response.status).toBe(200)
expect(data.credentials[0].name).toBe('decoded@example.com')
})

it('should handle database error', async () => {
mockGetSession.mockResolvedValueOnce({
user: { id: 'user-123' },
})

mockParseProvider.mockReturnValueOnce({
baseProvider: 'google',
})

mockDb.select.mockReturnValueOnce(mockDb)
mockDb.from.mockReturnValueOnce(mockDb)
mockDb.where.mockRejectedValueOnce(new Error('Database error'))

const req = createMockRequestWithQuery('GET', '?provider=google')

const { GET } = await import('@/app/api/auth/oauth/credentials/route')

const response = await GET(req)
const data = await response.json()

expect(response.status).toBe(500)
expect(data.error).toBe('Internal server error')
expect(mockLogger.error).toHaveBeenCalled()
expect(data.credentials).toHaveLength(0)
})
})
Loading