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
8 changes: 5 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
"cmdk": "^1.0.4",
"date-fns": "^4.1.0",
"date-fns-tz": "^3.2.0",
"e2b": "^2.14.0",
"e2b": "^2.27.1",
"echarts": "^6.0.0",
"echarts-for-react": "^3.0.2",
"fast-xml-parser": "^5.3.5",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,53 @@
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
import { COOKIE_KEYS } from '@/configs/cookies'
import { AUTH_URLS, PROTECTED_URLS } from '@/configs/urls'
import { auth } from '@/core/server/auth'
import { getTeamIdFromSlug } from '@/core/server/functions/team/get-team-id-from-slug'
import { createSandboxManagementAuth } from '@/core/shared/sandbox-management-auth.server'
import SandboxInspectView from '@/features/dashboard/sandbox/inspect/view'

const DEFAULT_ROOT_PATH = '/home/user'

export default async function SandboxInspectPage() {
const cookieStore = await cookies()
interface SandboxInspectPageProps {
params: Promise<{
sandboxId: string
teamSlug: string
}>
}
Comment thread
ben-fornefeld marked this conversation as resolved.

export default async function SandboxInspectPage({
params,
}: SandboxInspectPageProps) {
const [{ teamSlug }, cookieStore, authContext] = await Promise.all([
params,
cookies(),
auth.getAuthContext(),
])

if (!authContext) {
redirect(AUTH_URLS.SIGN_IN)
}

const teamId = await getTeamIdFromSlug(teamSlug, authContext.accessToken)
if (!teamId.ok) {
throw new Error('Failed to resolve team for sandbox filesystem')
}
if (!teamId.data) {
redirect(PROTECTED_URLS.DASHBOARD)
Comment thread
ben-fornefeld marked this conversation as resolved.
}

const rootPath =
cookieStore.get(COOKIE_KEYS.SANDBOX_INSPECT_ROOT_PATH)?.value ||
DEFAULT_ROOT_PATH

return <SandboxInspectView rootPath={rootPath} />
return (
<SandboxInspectView
rootPath={rootPath}
sandboxManagementAuth={createSandboxManagementAuth(
authContext,
teamId.data
)}
/>
)
}
2 changes: 1 addition & 1 deletion src/app/dashboard/[teamSlug]/team-gate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useQuery } from '@tanstack/react-query'
import { DASHBOARD_TEAMS_LIST_QUERY_OPTIONS } from '@/core/application/teams/queries'
import { DASHBOARD_USER_PROFILE_QUERY_OPTIONS } from '@/core/application/user/queries'
import type { AuthUser } from '@/core/server/auth'
import type { AuthUser } from '@/core/modules/auth/models'
import { DashboardContextProvider } from '@/features/dashboard/context'
import LoadingLayout from '@/features/dashboard/loading-layout'
import { useTRPC } from '@/trpc/client'
Expand Down
6 changes: 5 additions & 1 deletion src/app/dashboard/terminal/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { auth } from '@/core/server/auth'
import { resolveUserTeam } from '@/core/server/functions/team/resolve-user-team'
import { infra } from '@/core/shared/clients/api'
import { createSandboxManagementAuth } from '@/core/shared/sandbox-management-auth.server'
import { SandboxIdSchema } from '@/core/shared/schemas/api'
import DashboardTerminal from '@/features/dashboard/terminal/dashboard-terminal'
import { normalizeTerminalTemplate } from '@/features/dashboard/terminal/template'
Expand Down Expand Up @@ -110,7 +111,10 @@ export default async function TerminalPage({
initialCommand={command}
initialSandboxId={terminalSandboxId}
initialTemplate={terminalTemplate}
teamId={team.id}
sandboxManagementAuth={createSandboxManagementAuth(
authContext,
team.id
)}
/>
</main>
)
Expand Down
10 changes: 10 additions & 0 deletions src/core/modules/auth/models.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import z from 'zod'
import { httpUrlSchema } from '@/core/shared/schemas/url'

export type AuthUser = {
id: string
email: string | null
name: string | null
avatarUrl: string | null
providers: string[]
canChangeEmail: boolean
canChangePassword: boolean
}

export const OtpTypeSchema = z.enum([
'signup',
'recovery',
Expand Down
2 changes: 1 addition & 1 deletion src/core/server/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@ export function createAuthForHeaders(headers: Headers): AuthProvider {
: createSupabaseAuthForHeaders(headers)
}

export type { AuthUser } from '@/core/modules/auth/models'
export type { AuthAdmin } from './admin'
export type { AuthUser } from './types'
12 changes: 3 additions & 9 deletions src/core/server/auth/types.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
export type AuthUser = {
id: string
email: string | null
name: string | null
avatarUrl: string | null
providers: string[]
canChangeEmail: boolean
canChangePassword: boolean
}
import type { AuthUser } from '@/core/modules/auth/models'

export type { AuthUser } from '@/core/modules/auth/models'

export type AuthContext = {
user: AuthUser
Expand Down
15 changes: 15 additions & 0 deletions src/core/shared/sandbox-management-auth.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'server-only'

import { SUPABASE_AUTH_HEADERS } from '@/configs/api'
import type { AuthContext } from '@/core/server/auth/types'
import type { SandboxManagementAuth } from './sandbox-management-auth'

export function createSandboxManagementAuth(
authContext: AuthContext,
teamId: string
): SandboxManagementAuth {
return {
headers: SUPABASE_AUTH_HEADERS(authContext.accessToken, teamId),
userId: authContext.user.id,
}
}
4 changes: 4 additions & 0 deletions src/core/shared/sandbox-management-auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface SandboxManagementAuth {
userId: string
headers: Record<string, string>
}
2 changes: 1 addition & 1 deletion src/features/dashboard/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import { createContext, type ReactNode, useContext, useEffect } from 'react'
import { useDebounceCallback } from 'usehooks-ts'
import type { AuthUser } from '@/core/modules/auth/models'
import type { TeamModel } from '@/core/modules/teams/models'
import type { AuthUser } from '@/core/server/auth'

interface DashboardContextValue {
team: TeamModel
Expand Down
19 changes: 5 additions & 14 deletions src/features/dashboard/sandbox/inspect/context.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use client'

import Sandbox from 'e2b'
import { useRouter } from 'next/navigation'
import type { ReactNode } from 'react'
import {
createContext,
Expand All @@ -11,9 +10,7 @@ import {
useMemo,
useRef,
} from 'react'
import { SUPABASE_AUTH_HEADERS } from '@/configs/api'
import { AUTH_URLS } from '@/configs/urls'
import { supabase } from '@/core/shared/clients/supabase/client'
import type { SandboxManagementAuth } from '@/core/shared/sandbox-management-auth'
import { useSandboxInspectAnalytics } from '@/lib/hooks/use-analytics'
import { getParentPath, normalizePath } from '@/lib/utils/filesystem'
import { useDashboard } from '../../context'
Expand All @@ -34,11 +31,13 @@ const SandboxInspectContext = createContext<SandboxInspectContextValue | null>(
interface SandboxInspectProviderProps {
children: ReactNode
rootPath: string
sandboxManagementAuth: SandboxManagementAuth
}

export default function SandboxInspectProvider({
children,
rootPath,
sandboxManagementAuth,
}: SandboxInspectProviderProps) {
const { team } = useDashboard()
const teamId = team.id
Expand All @@ -47,7 +46,6 @@ export default function SandboxInspectProvider({
const storeRef = useRef<FilesystemStore | null>(null)
const sandboxManagerRef = useRef<SandboxManager | null>(null)

const router = useRouter()
const { trackInteraction } = useSandboxInspectAnalytics()

// ---------- synchronous store initialisation ----------
Expand Down Expand Up @@ -181,19 +179,12 @@ export default function SandboxInspectProvider({
sandboxManagerRef.current.stopWatching()
}

const { data } = await supabase.auth.getSession()

if (!data || !data.session) {
router.replace(AUTH_URLS.SIGN_IN)
return
}

const sandbox = await Sandbox.connect(sandboxInfo.sandboxID, {
domain: process.env.NEXT_PUBLIC_E2B_DOMAIN,
// Keep inspect connections from extending sandbox TTL via SDK default connect timeout.
timeoutMs: 1_000,
headers: {
...SUPABASE_AUTH_HEADERS(data.session.access_token, teamId),
...sandboxManagementAuth.headers,
},
})

Expand All @@ -209,7 +200,7 @@ export default function SandboxInspectProvider({
team_id: teamId,
root_path: rootPath,
})
}, [sandboxInfo, teamId, rootPath, trackInteraction, router])
}, [sandboxInfo, teamId, rootPath, trackInteraction, sandboxManagementAuth])

// handle sandbox connection / disconnection
useEffect(() => {
Expand Down
8 changes: 7 additions & 1 deletion src/features/dashboard/sandbox/inspect/view.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client'

import { SANDBOX_INSPECT_MINIMUM_ENVD_VERSION } from '@/configs/versioning'
import type { SandboxManagementAuth } from '@/core/shared/sandbox-management-auth'
import SandboxInspectProvider from '@/features/dashboard/sandbox/inspect/context'
import SandboxInspectFilesystem from '@/features/dashboard/sandbox/inspect/filesystem'
import SandboxInspectViewer from '@/features/dashboard/sandbox/inspect/viewer'
Expand All @@ -11,10 +12,12 @@ import SandboxInspectIncompatible from './incompatible'

interface SandboxInspectViewProps {
rootPath: string
sandboxManagementAuth: SandboxManagementAuth
}

export default function SandboxInspectView({
rootPath,
sandboxManagementAuth,
}: SandboxInspectViewProps) {
const { teamSlug } =
useRouteParams<'/dashboard/[teamSlug]/sandboxes/[sandboxId]'>()
Expand All @@ -39,7 +42,10 @@ export default function SandboxInspectView({
}

return (
<SandboxInspectProvider rootPath={rootPath}>
<SandboxInspectProvider
rootPath={rootPath}
sandboxManagementAuth={sandboxManagementAuth}
>
<div className="flex min-h-0 flex-1 gap-4 overflow-hidden p-3 md:p-6">
<SandboxInspectFilesystem rootPath={rootPath} />
<SandboxInspectViewer />
Expand Down
9 changes: 5 additions & 4 deletions src/features/dashboard/terminal/dashboard-terminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Terminal as XTerm } from '@xterm/xterm'
import type Sandbox from 'e2b'
import type { CommandHandle } from 'e2b'
import { useCallback, useEffect, useRef, useState } from 'react'
import type { SandboxManagementAuth } from '@/core/shared/sandbox-management-auth'
import {
DEFAULT_COLS,
DEFAULT_CWD,
Expand Down Expand Up @@ -38,15 +39,15 @@ interface DashboardTerminalProps {
initialCommand?: string
initialSandboxId?: string
initialTemplate?: string
teamId: string
sandboxManagementAuth: SandboxManagementAuth
}

export default function DashboardTerminal({
autoStart = false,
initialCommand = '',
initialSandboxId,
initialTemplate,
teamId,
sandboxManagementAuth,
}: DashboardTerminalProps) {
const [status, setStatus] = useState<TerminalStatus>('idle')
const [activeSandboxId, setActiveSandboxId] = useState<string>()
Expand Down Expand Up @@ -200,8 +201,8 @@ export default function DashboardTerminal({
const { sandbox } = await openTerminalSandbox({
forceNewSandbox: options.forceNewSandbox,
onStatus: appendOutput,
sandboxManagementAuth,
sandboxId: options.sandboxId,
teamId,
template: nextTemplate,
})

Expand Down Expand Up @@ -270,7 +271,7 @@ export default function DashboardTerminal({
disconnectTerminal,
resizeTerminal,
runCommand,
teamId,
sandboxManagementAuth,
template,
updateTerminalUrl,
]
Expand Down
18 changes: 4 additions & 14 deletions src/features/dashboard/terminal/sandbox-session.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Sandbox from 'e2b'
import { SUPABASE_AUTH_HEADERS } from '@/configs/api'
import { supabase } from '@/core/shared/clients/supabase/client'
import type { SandboxManagementAuth } from '@/core/shared/sandbox-management-auth'
import { TERMINAL_SANDBOX_TIMEOUT_MS } from './constants'
import {
clearStoredTerminalSession,
Expand All @@ -11,26 +10,19 @@ import {
interface OpenTerminalSandboxOptions {
forceNewSandbox?: boolean
onStatus: (message: string) => void
sandboxManagementAuth: SandboxManagementAuth
sandboxId?: string
teamId: string
template: string
}

export async function openTerminalSandbox({
forceNewSandbox = false,
onStatus,
sandboxManagementAuth,
sandboxId,
teamId,
template,
}: OpenTerminalSandboxOptions) {
const { data } = await supabase.auth.getSession()

if (!data.session) {
throw new Error('You need to sign in before opening a terminal.')
}

const userId = data.session.user.id
const headers = SUPABASE_AUTH_HEADERS(data.session.access_token, teamId)
Comment thread
ben-fornefeld marked this conversation as resolved.
const { headers, userId } = sandboxManagementAuth
Comment thread
ben-fornefeld marked this conversation as resolved.

if (sandboxId) {
onStatus(`Connecting to terminal sandbox ${sandboxId}...\r\n`)
Expand Down Expand Up @@ -100,8 +92,6 @@ function createTerminalSandbox({
template: string
userId: string
}) {
// The browser SDK sends the signed-in user's Supabase token so E2B can
// authorize sandbox ownership without a dashboard proxy endpoint.
return Sandbox.create(template, {
domain: process.env.NEXT_PUBLIC_E2B_DOMAIN,
timeoutMs: TERMINAL_SANDBOX_TIMEOUT_MS,
Expand Down
Loading
Loading