Skip to content

Commit 8f0ef58

Browse files
v0.5.7: combobox selectors, usage indicator, workflow loading race condition, other improvements
2 parents 3058e35 + 33ca148 commit 8f0ef58

File tree

80 files changed

+3242
-10938
lines changed

Some content is hidden

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

80 files changed

+3242
-10938
lines changed

apps/docs/app/[lang]/[[...slug]]/page.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -198,15 +198,17 @@ export default async function Page(props: { params: Promise<{ slug?: string[]; l
198198
component: <CustomFooter />,
199199
}}
200200
>
201-
<div className='relative'>
201+
<div className='relative mt-6 sm:mt-0'>
202202
<div className='absolute top-1 right-0 flex items-center gap-2'>
203-
<CopyPageButton
204-
content={`# ${page.data.title}
203+
<div className='hidden sm:flex'>
204+
<CopyPageButton
205+
content={`# ${page.data.title}
205206
206207
${page.data.description || ''}
207208
208209
${page.data.content || ''}`}
209-
/>
210+
/>
211+
</div>
210212
<PageNavigationArrows previous={neighbours?.previous} next={neighbours?.next} />
211213
</div>
212214
<DocsTitle>{page.data.title}</DocsTitle>

apps/docs/components/docs-layout/sidebar-components.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export function SidebarFolder({
6969
</Link>
7070
<button
7171
onClick={() => setOpen(!open)}
72-
className='rounded p-1 transition-colors hover:bg-gray-100/60 dark:hover:bg-gray-800/40'
72+
className='cursor-pointer rounded p-1 transition-colors hover:bg-gray-100/60 dark:hover:bg-gray-800/40'
7373
aria-label={open ? 'Collapse' : 'Expand'}
7474
>
7575
<ChevronRight
@@ -84,7 +84,7 @@ export function SidebarFolder({
8484
<button
8585
onClick={() => setOpen(!open)}
8686
className={cn(
87-
'flex w-full items-center justify-between rounded-md px-2.5 py-1.5 text-left font-medium text-[13px] leading-tight transition-colors',
87+
'flex w-full cursor-pointer items-center justify-between rounded-md px-2.5 py-1.5 text-left font-medium text-[13px] leading-tight transition-colors',
8888
'hover:bg-gray-100/60 dark:hover:bg-gray-800/40',
8989
'text-gray-800 dark:text-gray-200'
9090
)}

apps/docs/components/ui/code-block.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export function CodeBlock(props: React.ComponentProps<typeof FumadocsCodeBlock>)
3030
if (pre) handleCopy(pre.textContent || '')
3131
}}
3232
className={cn(
33-
'rounded-md p-2 transition-all',
33+
'cursor-pointer rounded-md p-2 transition-all',
3434
'border border-border bg-background/80 hover:bg-muted',
3535
'backdrop-blur-sm'
3636
)}

apps/docs/components/ui/copy-page-button.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function CopyPageButton({ content }: CopyPageButtonProps) {
2323
return (
2424
<button
2525
onClick={handleCopy}
26-
className='flex items-center gap-1.5 rounded-lg border border-border/40 bg-background px-2.5 py-2 text-muted-foreground/60 text-sm leading-none transition-all hover:border-border hover:bg-accent/50 hover:text-muted-foreground'
26+
className='flex cursor-pointer items-center gap-1.5 rounded-lg border border-border/40 bg-background px-2.5 py-2 text-muted-foreground/60 text-sm leading-none transition-all hover:border-border hover:bg-accent/50 hover:text-muted-foreground'
2727
aria-label={copied ? 'Copied to clipboard' : 'Copy page content'}
2828
>
2929
{copied ? (

apps/docs/components/ui/language-dropdown.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export function LanguageDropdown() {
8282
aria-haspopup='listbox'
8383
aria-expanded={isOpen}
8484
aria-controls='language-menu'
85-
className='flex items-center gap-1.5 rounded-xl px-3 py-2 font-normal text-[0.9375rem] text-foreground/60 leading-[1.4] transition-colors hover:bg-foreground/8 hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring'
85+
className='flex cursor-pointer items-center gap-1.5 rounded-xl px-3 py-2 font-normal text-[0.9375rem] text-foreground/60 leading-[1.4] transition-colors hover:bg-foreground/8 hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring'
8686
style={{
8787
fontFamily:
8888
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
@@ -110,7 +110,7 @@ export function LanguageDropdown() {
110110
}}
111111
role='option'
112112
aria-selected={currentLang === code}
113-
className={`flex w-full items-center gap-3 px-3 py-3 text-base transition-colors first:rounded-t-xl last:rounded-b-xl hover:bg-muted/80 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring md:gap-2 md:px-2.5 md:py-2 md:text-sm ${
113+
className={`flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-base transition-colors first:rounded-t-xl last:rounded-b-xl hover:bg-muted/80 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring md:gap-2 md:px-2.5 md:py-2 md:text-sm ${
114114
currentLang === code ? 'bg-muted/60 font-medium text-primary' : 'text-foreground'
115115
}`}
116116
>

apps/docs/components/ui/search-trigger.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function SearchTrigger() {
1515
return (
1616
<button
1717
type='button'
18-
className='flex h-10 w-[460px] items-center gap-2 rounded-xl border border-border/50 px-3 py-2 text-sm backdrop-blur-xl transition-colors hover:border-border'
18+
className='flex h-10 w-[460px] cursor-pointer items-center gap-2 rounded-xl border border-border/50 px-3 py-2 text-sm backdrop-blur-xl transition-colors hover:border-border'
1919
style={{
2020
backgroundColor: 'hsla(0, 0%, 5%, 0.85)',
2121
backdropFilter: 'blur(33px) saturate(180%)',

apps/docs/components/ui/theme-toggle.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export function ThemeToggle() {
1414

1515
if (!mounted) {
1616
return (
17-
<button className='flex items-center justify-center rounded-md p-1 text-muted-foreground'>
17+
<button className='flex cursor-pointer items-center justify-center rounded-md p-1 text-muted-foreground'>
1818
<Moon className='h-4 w-4' />
1919
</button>
2020
)
@@ -23,7 +23,7 @@ export function ThemeToggle() {
2323
return (
2424
<button
2525
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
26-
className='flex items-center justify-center rounded-md p-1 text-muted-foreground transition-colors hover:text-foreground'
26+
className='flex cursor-pointer items-center justify-center rounded-md p-1 text-muted-foreground transition-colors hover:text-foreground'
2727
aria-label='Toggle theme'
2828
>
2929
{theme === 'dark' ? <Moon className='h-4 w-4' /> : <Sun className='h-4 w-4' />}

apps/sim/app/(landing)/components/nav/nav.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ interface NavProps {
2020
}
2121

2222
export default function Nav({ hideAuthButtons = false, variant = 'landing' }: NavProps = {}) {
23-
const [githubStars, setGithubStars] = useState('18k')
23+
const [githubStars, setGithubStars] = useState('18.5k')
2424
const [isHovered, setIsHovered] = useState(false)
2525
const [isLoginHovered, setIsLoginHovered] = useState(false)
2626
const router = useRouter()

apps/sim/app/api/auth/oauth/microsoft/file/route.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
import { db } from '@sim/db'
2-
import { account } from '@sim/db/schema'
3-
import { eq } from 'drizzle-orm'
41
import { type NextRequest, NextResponse } from 'next/server'
5-
import { getSession } from '@/lib/auth'
2+
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
63
import { createLogger } from '@/lib/logs/console/logger'
74
import { validateMicrosoftGraphId } from '@/lib/security/input-validation'
85
import { generateRequestId } from '@/lib/utils'
9-
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
6+
import { getCredential, refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
107

118
export const dynamic = 'force-dynamic'
129

@@ -15,15 +12,10 @@ const logger = createLogger('MicrosoftFileAPI')
1512
export async function GET(request: NextRequest) {
1613
const requestId = generateRequestId()
1714
try {
18-
const session = await getSession()
19-
20-
if (!session?.user?.id) {
21-
return NextResponse.json({ error: 'User not authenticated' }, { status: 401 })
22-
}
23-
2415
const { searchParams } = new URL(request.url)
2516
const credentialId = searchParams.get('credentialId')
2617
const fileId = searchParams.get('fileId')
18+
const workflowId = searchParams.get('workflowId') || undefined
2719

2820
if (!credentialId || !fileId) {
2921
return NextResponse.json({ error: 'Credential ID and File ID are required' }, { status: 400 })
@@ -35,19 +27,27 @@ export async function GET(request: NextRequest) {
3527
return NextResponse.json({ error: fileIdValidation.error }, { status: 400 })
3628
}
3729

38-
const credentials = await db.select().from(account).where(eq(account.id, credentialId)).limit(1)
30+
const authz = await authorizeCredentialUse(request, {
31+
credentialId,
32+
workflowId,
33+
requireWorkflowIdForInternal: false,
34+
})
3935

40-
if (!credentials.length) {
41-
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
36+
if (!authz.ok || !authz.credentialOwnerUserId) {
37+
const status = authz.error === 'Credential not found' ? 404 : 403
38+
return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status })
4239
}
4340

44-
const credential = credentials[0]
45-
46-
if (credential.userId !== session.user.id) {
47-
return NextResponse.json({ error: 'Unauthorized' }, { status: 403 })
41+
const credential = await getCredential(requestId, credentialId, authz.credentialOwnerUserId)
42+
if (!credential) {
43+
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
4844
}
4945

50-
const accessToken = await refreshAccessTokenIfNeeded(credentialId, session.user.id, requestId)
46+
const accessToken = await refreshAccessTokenIfNeeded(
47+
credentialId,
48+
authz.credentialOwnerUserId,
49+
requestId
50+
)
5151

5252
if (!accessToken) {
5353
return NextResponse.json({ error: 'Failed to obtain valid access token' }, { status: 401 })

apps/sim/app/api/auth/oauth/microsoft/files/route.ts

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
import { db } from '@sim/db'
2-
import { account } from '@sim/db/schema'
3-
import { eq } from 'drizzle-orm'
41
import { type NextRequest, NextResponse } from 'next/server'
5-
import { getSession } from '@/lib/auth'
2+
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
63
import { createLogger } from '@/lib/logs/console/logger'
74
import { generateRequestId } from '@/lib/utils'
8-
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
5+
import { getCredential, refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
96

107
export const dynamic = 'force-dynamic'
118

@@ -18,46 +15,39 @@ export async function GET(request: NextRequest) {
1815
const requestId = generateRequestId()
1916

2017
try {
21-
// Get the session
22-
const session = await getSession()
23-
24-
// Check if the user is authenticated
25-
if (!session?.user?.id) {
26-
logger.warn(`[${requestId}] Unauthenticated request rejected`)
27-
return NextResponse.json({ error: 'User not authenticated' }, { status: 401 })
28-
}
29-
3018
// Get the credential ID from the query params
3119
const { searchParams } = new URL(request.url)
3220
const credentialId = searchParams.get('credentialId')
3321
const query = searchParams.get('query') || ''
22+
const workflowId = searchParams.get('workflowId') || undefined
3423

3524
if (!credentialId) {
3625
logger.warn(`[${requestId}] Missing credential ID`)
3726
return NextResponse.json({ error: 'Credential ID is required' }, { status: 400 })
3827
}
3928

40-
// Get the credential from the database
41-
const credentials = await db.select().from(account).where(eq(account.id, credentialId)).limit(1)
29+
const authz = await authorizeCredentialUse(request, {
30+
credentialId,
31+
workflowId,
32+
requireWorkflowIdForInternal: false,
33+
})
4234

43-
if (!credentials.length) {
44-
logger.warn(`[${requestId}] Credential not found`, { credentialId })
45-
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
35+
if (!authz.ok || !authz.credentialOwnerUserId) {
36+
const status = authz.error === 'Credential not found' ? 404 : 403
37+
return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status })
4638
}
4739

48-
const credential = credentials[0]
49-
50-
// Check if the credential belongs to the user
51-
if (credential.userId !== session.user.id) {
52-
logger.warn(`[${requestId}] Unauthorized credential access attempt`, {
53-
credentialUserId: credential.userId,
54-
requestUserId: session.user.id,
55-
})
56-
return NextResponse.json({ error: 'Unauthorized' }, { status: 403 })
40+
const credential = await getCredential(requestId, credentialId, authz.credentialOwnerUserId)
41+
if (!credential) {
42+
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
5743
}
5844

5945
// Refresh access token if needed using the utility function
60-
const accessToken = await refreshAccessTokenIfNeeded(credentialId, session.user.id, requestId)
46+
const accessToken = await refreshAccessTokenIfNeeded(
47+
credentialId,
48+
authz.credentialOwnerUserId,
49+
requestId
50+
)
6151

6252
if (!accessToken) {
6353
return NextResponse.json({ error: 'Failed to obtain valid access token' }, { status: 401 })

0 commit comments

Comments
 (0)