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
15 changes: 13 additions & 2 deletions apps/desktop/e2e/source-connect.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ test('source authorization returns to its desktop screen and refreshes live', as
const ok = attempts.get(state) === session && url.searchParams.has('code')
attempts.delete(state)
if (ok) installed = true
redirect(`/credential-groups/slack-complete?state=${state}&ok=${ok}`)
const reason =
url.searchParams.get('error') === 'session_expired' ? '&reason=signin_required' : ''
redirect(`/credential-groups/slack-complete?state=${state}&ok=${ok}${reason}`)
return
}
if (
Expand Down Expand Up @@ -224,7 +226,7 @@ test('source authorization returns to its desktop screen and refreshes live', as
if (path === '/provider') {
const state = url.searchParams.get('state') ?? ''
response.end(
`<!doctype html><a href="/api/knowledge/slack/oauth/callback?state=${state}&code=fixture">Authorize</a><a href="/api/knowledge/slack/oauth/callback?state=${state}&error=denied">Cancel</a>`
`<!doctype html><a href="/api/knowledge/slack/oauth/callback?state=${state}&code=fixture">Authorize</a><a href="/api/knowledge/slack/oauth/callback?state=${state}&error=denied">Cancel</a><a href="/api/knowledge/slack/oauth/callback?state=${state}&error=session_expired">Session expired</a>`
)
return
}
Expand Down Expand Up @@ -389,6 +391,15 @@ test('source authorization returns to its desktop screen and refreshes live', as
await expect(page.getByLabel('Enrollment error')).toHaveText('')
expect(page.url()).toBe(`${origin}/home`)
})
await check('browser sign-in failures retain recovery guidance on desktop', async () => {
await page.getByRole('button', { name: 'Connect Slack' }).click()
await expect.poll(async () => (await opened()).length).toBe(8)
await external.goto((await opened())[7])
await external.getByRole('link', { name: 'Session expired' }).click()
await expect(page.getByLabel('Connection')).toHaveText('error')
await expect(page.getByRole('alert')).toContainText('Sign in to Sim in your browser')
expect(page.url()).toBe(`${origin}/home`)
})
await page.screenshot({ path: test.info().outputPath('source-connect-desktop.png') })
} finally {
mkdirSync(dirname(reportPath), { recursive: true })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ describe('Slack OAuth callback', () => {
const response = await GET(request('state=state&code=code'))
expect(response.status).toBe(303)
expect(response.headers.get('location')).toBe(
'https://www.sim.ai/credential-groups/slack-complete?state=state&ok=false'
'https://www.sim.ai/credential-groups/slack-complete?state=state&ok=false&reason=signin_required'
)
expect(m.authenticate).not.toHaveBeenCalled()
expect(m.complete).not.toHaveBeenCalled()
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/app/api/knowledge/slack/oauth/callback/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export const GET = withRouteHandler(async (request) => {
const url = new URL('/credential-groups/slack-complete', getBaseUrl())
url.searchParams.set('state', callbackState)
url.searchParams.set('ok', 'false')
if (error instanceof InternalUnauthenticatedError)
url.searchParams.set('reason', 'signin_required')
return NextResponse.redirect(url, {
status: 303,
headers: { 'Cache-Control': 'no-store', 'Referrer-Policy': 'no-referrer' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function CredentialGroupCompletionHandoff({
completionId,
failure,
}: CredentialGroupCompletionHandoffProps) {
const started = useRef(false)
const started = useRef<boolean>(false)
useEffect(() => {
if (started.current) return
started.current = true
Expand Down
15 changes: 10 additions & 5 deletions apps/sim/app/credential-groups/slack-complete/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default async function SlackCompletePage({ searchParams }: SlackCompleteP
const scalar = (key: string) =>
typeof params[key] === 'string' && params[key].length <= 512 ? params[key] : undefined
const ok = params.ok === 'true'
const signInRequired = !ok && params.reason === 'signin_required'
const mode = params.mode === 'managed' ? 'managed' : 'search'
const organizationId = scalar('organizationId')
return (
Expand All @@ -25,7 +26,9 @@ export default async function SlackCompletePage({ searchParams }: SlackCompleteP
description={
ok
? 'Your connection is ready. You can return to Sim.'
: 'Authorization did not complete. Return to Sim and try connecting again.'
: signInRequired
? 'Sign in to Sim in your browser, then return to Sim and restart Slack setup.'
: 'Authorization did not complete. Return to Sim and try connecting again.'
}
>
<SlackCompletion
Expand All @@ -39,12 +42,14 @@ export default async function SlackCompletePage({ searchParams }: SlackCompleteP
/>
<ChipLink
href={
organizationId
? organizationRoutes(organizationId).settingsSection('search-slack')
: APP_ENTRY_PATH
signInRequired
? '/login'
: organizationId
? organizationRoutes(organizationId).settingsSection('search-slack')
: APP_ENTRY_PATH
}
>
Return to Sim
{signInRequired ? 'Sign in to Sim' : 'Return to Sim'}
</ChipLink>
</DesktopHandoffShell>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function SlackCompletion({
credentialGroupId,
slackBotCredentialId,
}: SlackCompletionProps) {
const started = useRef(false)
const started = useRef<boolean>(false)
useEffect(() => {
if (started.current || !state) return
started.current = true
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/desktop/connect/source-connect-launcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface SourceConnectLauncherProps {
}

export function SourceConnectLauncher({ requestId, state, port }: SourceConnectLauncherProps) {
const started = useRef(false)
const started = useRef<boolean>(false)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
if (started.current) return
Expand Down
6 changes: 5 additions & 1 deletion apps/sim/lib/desktop/source-browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,11 @@ export async function finishDesktopSourceBrowser(
return false
sessionStorage.removeItem(STORAGE_KEY)
const url = new URL(buildConnectCompletePath(context.state, context.port), window.location.origin)
if (completion.error) url.searchParams.set('error', 'connection_failed')
if (completion.error)
url.searchParams.set(
'error',
completion.error === 'signin_required' ? 'signin_required' : 'connection_failed'
)
else if (context.github) {
try {
const result = await requestJson(readGitHubSearchSetupContract, { query: context.github })
Expand Down
4 changes: 3 additions & 1 deletion apps/sim/lib/desktop/source-connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ export async function connectDesktopSource(
new Error(
result?.error === 'cancelled' || result?.error === 'superseded'
? 'Connection canceled. You can try again.'
: 'Connection did not complete. Try connecting again.'
: result?.error === 'signin_required'
? 'Sign in to Sim in your browser, then try connecting again.'
: 'Connection did not complete. Try connecting again.'
)
)
}
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/scripts/fixtures/desktop-source-connect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function SourceConnectFixture() {
}

function BrowserLauncher() {
const started = useRef(false)
const started = useRef<boolean>(false)
const [error, setError] = useState('')
useEffect(() => {
if (started.current) return
Expand Down Expand Up @@ -99,6 +99,7 @@ const content =
mode='search'
ok={params.get('ok') === 'true'}
state={params.get('state') ?? undefined}
reason={params.get('reason') ?? undefined}
/>
) : (
<SourceConnectFixture />
Expand Down
Loading