Skip to content

Commit e308594

Browse files
refactor(auth): split session and admin contracts behind neutral types (#338)
1 parent c40452d commit e308594

47 files changed

Lines changed: 1090 additions & 445 deletions

Some content is hidden

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

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
2121
### OPTIONAL SERVER ENVIRONMENT VARIABLES
2222
### =================================
2323

24+
### Auth provider: supabase (default) or ory
25+
# AUTH_PROVIDER=supabase
26+
27+
### Ory Network SDK URL (required when AUTH_PROVIDER=ory)
28+
# ORY_SDK_URL=https://your-project.projects.oryapis.com
29+
2430
### Billing API URL (Required if NEXT_PUBLIC_INCLUDE_BILLING=1)
2531
# BILLING_API_URL=https://billing.e2b.dev
2632

src/app/(auth)/auth/cli/page.tsx

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { redirect } from 'next/navigation'
22
import { Suspense } from 'react'
33
import { AUTH_URLS, PROTECTED_URLS } from '@/configs/urls'
44
import { createUserTeamsRepository } from '@/core/modules/teams/user-teams-repository.server'
5+
import { auth } from '@/core/server/auth'
56
import { l, serializeErrorForLog } from '@/core/shared/clients/logger/logger'
6-
import { createClient } from '@/core/shared/clients/supabase/server'
77
import { encodedRedirect } from '@/lib/utils/auth'
88
import { generateE2BUserAccessToken } from '@/lib/utils/server'
99
import { Alert, AlertDescription, AlertTitle } from '@/ui/primitives/alert'
@@ -96,11 +96,7 @@ export default async function CLIAuthPage({
9696
searchParams: CLISearchParams
9797
}) {
9898
const { next, state, error } = await searchParams
99-
const supabase = await createClient()
100-
101-
const {
102-
data: { user },
103-
} = await supabase.auth.getUser()
99+
const authContext = await auth.getAuthContext()
104100

105101
if (state === 'success') {
106102
return <SuccessState />
@@ -111,7 +107,7 @@ export default async function CLIAuthPage({
111107
l.error(
112108
{
113109
key: 'cli_auth:invalid_redirect_url',
114-
user_id: user?.id,
110+
user_id: authContext?.user.id,
115111
context: {
116112
next,
117113
},
@@ -122,29 +118,25 @@ export default async function CLIAuthPage({
122118
}
123119

124120
// If user is not authenticated, redirect to sign in with return URL
125-
if (!user) {
121+
if (!authContext) {
126122
const searchParams = new URLSearchParams({
127123
returnTo: `${AUTH_URLS.CLI}?${new URLSearchParams({ next }).toString()}`,
128124
})
129125
redirect(`${AUTH_URLS.SIGN_IN}?${searchParams.toString()}`)
130126
}
131127

132128
// Handle CLI callback if authenticated
133-
if (!error && next && user) {
129+
if (!error && next && authContext) {
134130
try {
135-
const {
136-
data: { session },
137-
} = await supabase.auth.getSession()
138-
139-
if (!session?.access_token) {
140-
throw new Error('No provider access token found')
141-
}
142-
143-
if (!user.email) {
131+
if (!authContext.user.email) {
144132
throw new Error('No user email found')
145133
}
146134

147-
return await handleCLIAuth(next, user.email, session.access_token)
135+
return await handleCLIAuth(
136+
next,
137+
authContext.user.email,
138+
authContext.accessToken
139+
)
148140
} catch (err) {
149141
if (err instanceof Error && err.message.includes('NEXT_REDIRECT')) {
150142
throw err
@@ -154,7 +146,7 @@ export default async function CLIAuthPage({
154146
{
155147
key: 'cli_auth:unexpected_error',
156148
error: serializeErrorForLog(err),
157-
user_id: user?.id,
149+
user_id: authContext.user.id,
158150
context: {
159151
next,
160152
},

src/app/api/auth/callback/route.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { redirect } from 'next/navigation'
22
import { AUTH_URLS, PROTECTED_URLS } from '@/configs/urls'
3+
import { supabaseAuthFlows } from '@/core/server/auth/supabase/flows'
34
import { l, serializeErrorForLog } from '@/core/shared/clients/logger/logger'
4-
import { createClient } from '@/core/shared/clients/supabase/server'
55
import { encodedRedirect } from '@/lib/utils/auth'
66

77
export async function GET(request: Request) {
@@ -29,8 +29,7 @@ export async function GET(request: Request) {
2929
)
3030

3131
if (code) {
32-
const supabase = await createClient()
33-
const { data, error } = await supabase.auth.exchangeCodeForSession(code)
32+
const { data, error } = await supabaseAuthFlows.exchangeCodeForSession(code)
3433

3534
if (error) {
3635
l.error(

src/app/api/auth/email-callback/route.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { PROTECTED_URLS } from '@/configs/urls'
2-
import { createClient } from '@/core/shared/clients/supabase/server'
2+
import { supabaseAuthFlows } from '@/core/server/auth/supabase/flows'
3+
import { l, serializeErrorForLog } from '@/core/shared/clients/logger/logger'
34
import { encodedRedirect } from '@/lib/utils/auth'
45

56
export async function GET(request: Request) {
@@ -24,10 +25,21 @@ export async function GET(request: Request) {
2425
})
2526
}
2627

27-
const supabase = await createClient()
28-
const { error } = await supabase.auth.exchangeCodeForSession(code!)
28+
const { error } = await supabaseAuthFlows.exchangeCodeForSession(code)
2929

3030
if (error) {
31+
l.error(
32+
{
33+
key: 'email_callback:supabase_error',
34+
error: serializeErrorForLog(error),
35+
context: {
36+
error_code: error.code,
37+
error_status: error.status,
38+
},
39+
},
40+
`email callback supabase error: ${error.message}`
41+
)
42+
3143
return encodedRedirect('error', next, 'Failed to update E-Mail', {
3244
type: 'update_email',
3345
})

src/app/api/teams/[teamSlug]/metrics/route.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'server-cli-only'
22

33
import { toRouteErrorResponse } from '@/core/server/adapters/errors'
4-
import { getSessionInsecure } from '@/core/server/functions/auth/get-session'
4+
import { auth } from '@/core/server/auth'
55
import { getTeamMetricsCore } from '@/core/server/functions/sandboxes/get-team-metrics-core'
66
import { getTeamIdFromSlug } from '@/core/server/functions/team/get-team-id-from-slug'
77
import { l, serializeErrorForLog } from '@/core/shared/clients/logger/logger'
@@ -47,9 +47,9 @@ export async function POST(
4747

4848
const { start: startMs, end: endMs } = parsedInput.data
4949

50-
const session = await getSessionInsecure()
50+
const authContext = await auth.getAuthContext()
5151

52-
if (!session) {
52+
if (!authContext) {
5353
l.warn(
5454
{
5555
key: 'team_metrics_route_handler:unauthenticated',
@@ -63,7 +63,7 @@ export async function POST(
6363

6464
const teamIdResult = await getTeamIdFromSlug(
6565
teamSlug,
66-
session.access_token
66+
authContext.accessToken
6767
)
6868

6969
if (!teamIdResult.ok) {
@@ -77,7 +77,7 @@ export async function POST(
7777
{
7878
key: 'team_metrics_route_handler:forbidden_team',
7979
team_slug: teamSlug,
80-
user_id: session.user.id,
80+
user_id: authContext.user.id,
8181
},
8282
'team_metrics_route_handler: forbidden team'
8383
)
@@ -86,9 +86,9 @@ export async function POST(
8686
}
8787

8888
const result = await getTeamMetricsCore({
89-
accessToken: session.access_token,
89+
accessToken: authContext.accessToken,
9090
teamId,
91-
userId: session.user.id,
91+
userId: authContext.user.id,
9292
startMs,
9393
endMs,
9494
})

src/app/dashboard/(resolvers)/inspect/sandbox/[sandboxId]/route.ts

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import { SUPABASE_AUTH_HEADERS } from '@/configs/api'
44
import { COOKIE_KEYS } from '@/configs/cookies'
55
import { AUTH_URLS, PROTECTED_URLS } from '@/configs/urls'
66
import { createUserTeamsRepository } from '@/core/modules/teams/user-teams-repository.server'
7+
import { auth } from '@/core/server/auth'
78
import { infra } from '@/core/shared/clients/api'
89
import { l, serializeErrorForLog } from '@/core/shared/clients/logger/logger'
9-
import { createClient } from '@/core/shared/clients/supabase/server'
1010
import { SandboxIdSchema } from '@/core/shared/schemas/api'
1111
import { setTeamCookies } from '@/lib/utils/cookies'
1212

@@ -130,34 +130,18 @@ export async function GET(
130130
}
131131

132132
const sandboxId = parsedSandboxId.data
133-
const supabase = await createClient()
134-
const { data: userResponse, error: userError } =
135-
await supabase.auth.getUser()
133+
const authContext = await auth.getAuthContext()
136134

137-
if (userError || !userResponse.user) {
135+
if (!authContext) {
138136
l.info({
139137
key: 'inspect_sandbox:unauthenticated',
140138
sandbox_id: sandboxId,
141-
error: userError,
142139
})
143140
return redirectToSignInPage(request)
144141
}
145142

146-
const userId = userResponse.user.id
147-
const { data: sessionResponse, error: sessionError } =
148-
await supabase.auth.getSession()
149-
150-
if (sessionError || !sessionResponse.session) {
151-
l.warn({
152-
key: 'inspect_sandbox:session_error',
153-
user_id: userId,
154-
sandbox_id: sandboxId,
155-
error: sessionError,
156-
})
157-
return redirectToSignInPage(request)
158-
}
159-
160-
const accessToken = sessionResponse.session.access_token
143+
const userId = authContext.user.id
144+
const accessToken = authContext.accessToken
161145
const teamsResult = await createUserTeamsRepository({
162146
accessToken,
163147
}).listUserTeams()

src/app/dashboard/[teamSlug]/layout.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ import { COOKIE_KEYS } from '@/configs/cookies'
66
import { METADATA } from '@/configs/metadata'
77
import { AUTH_URLS } from '@/configs/urls'
88
import { DASHBOARD_TEAMS_LIST_QUERY_OPTIONS } from '@/core/application/teams/queries'
9-
import { getSessionInsecure } from '@/core/server/functions/auth/get-session'
10-
import getUserByToken from '@/core/server/functions/auth/get-user-by-token'
9+
import { auth } from '@/core/server/auth'
1110
import DashboardLayoutView from '@/features/dashboard/layouts/layout'
1211
import Sidebar from '@/features/dashboard/sidebar/sidebar'
1312
import { HydrateClient, prefetchAsync, trpc } from '@/trpc/server'
@@ -35,13 +34,12 @@ export default async function DashboardLayout({
3534
const cookieStore = await cookies()
3635
const { teamSlug } = await params
3736

38-
const session = await getSessionInsecure()
39-
const { error, data } = await getUserByToken(session?.access_token)
37+
const authContext = await auth.getAuthContext()
4038

4139
const sidebarState = cookieStore.get(COOKIE_KEYS.SIDEBAR_STATE)?.value
4240
const defaultOpen = sidebarState === 'true'
4341

44-
if (error || !data.user) {
42+
if (!authContext) {
4543
throw redirect(AUTH_URLS.SIGN_IN)
4644
}
4745

@@ -51,7 +49,7 @@ export default async function DashboardLayout({
5149

5250
return (
5351
<HydrateClient>
54-
<DashboardTeamGate teamSlug={teamSlug} user={data.user}>
52+
<DashboardTeamGate teamSlug={teamSlug} user={authContext.user}>
5553
<SidebarProvider
5654
defaultOpen={typeof sidebarState === 'undefined' ? true : defaultOpen}
5755
>

src/app/dashboard/[teamSlug]/team-gate.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
'use client'
22

3-
import type { User } from '@supabase/supabase-js'
43
import { useQuery } from '@tanstack/react-query'
54
import { DASHBOARD_TEAMS_LIST_QUERY_OPTIONS } from '@/core/application/teams/queries'
5+
import type { AuthUser } from '@/core/server/auth'
66
import { DashboardContextProvider } from '@/features/dashboard/context'
77
import LoadingLayout from '@/features/dashboard/loading-layout'
88
import { useTRPC } from '@/trpc/client'
99
import Unauthorized from '../unauthorized'
1010

1111
interface DashboardTeamGateProps {
1212
teamSlug: string
13-
user: User
13+
user: AuthUser
1414
children: React.ReactNode
1515
}
1616

src/app/dashboard/account/route.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,24 @@
11
import { type NextRequest, NextResponse } from 'next/server'
22
import { AUTH_URLS, PROTECTED_URLS } from '@/configs/urls'
3-
import { getSessionInsecure } from '@/core/server/functions/auth/get-session'
3+
import { auth } from '@/core/server/auth'
44
import { resolveUserTeam } from '@/core/server/functions/team/resolve-user-team'
5-
import { createClient } from '@/core/shared/clients/supabase/server'
65
import { encodedRedirect } from '@/lib/utils/auth'
76
import { setTeamCookies } from '@/lib/utils/cookies'
87

98
export async function GET(request: NextRequest) {
10-
const supabase = await createClient()
11-
const { data, error } = await supabase.auth.getUser()
9+
const authContext = await auth.getAuthContext()
1210

13-
if (error || !data.user) {
11+
if (!authContext) {
1412
return NextResponse.redirect(new URL(AUTH_URLS.SIGN_IN, request.url))
1513
}
1614

17-
const session = await getSessionInsecure(supabase)
18-
19-
if (!session) {
20-
return NextResponse.redirect(new URL(AUTH_URLS.SIGN_IN, request.url))
21-
}
22-
23-
const team = await resolveUserTeam(data.user.id, session.access_token)
15+
const team = await resolveUserTeam(
16+
authContext.user.id,
17+
authContext.accessToken
18+
)
2419

2520
if (!team) {
26-
await supabase.auth.signOut()
21+
await auth.signOut()
2722

2823
const signInUrl = new URL(AUTH_URLS.SIGN_IN, request.url)
2924

src/app/dashboard/route.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { type NextRequest, NextResponse } from 'next/server'
22
import { TAB_URL_MAP } from '@/configs/dashboard-tab-url-map'
33
import { AUTH_URLS, PROTECTED_URLS } from '@/configs/urls'
4-
import { getSessionInsecure } from '@/core/server/functions/auth/get-session'
4+
import { auth } from '@/core/server/auth'
55
import { resolveUserTeam } from '@/core/server/functions/team/resolve-user-team'
6-
import { createClient } from '@/core/shared/clients/supabase/server'
76
import { encodedRedirect } from '@/lib/utils/auth'
87
import { setTeamCookies } from '@/lib/utils/cookies'
98

@@ -23,24 +22,19 @@ export async function GET(request: NextRequest) {
2322
const searchParams = request.nextUrl.searchParams
2423
const tab = searchParams.get('tab')
2524

26-
const supabase = await createClient()
25+
const authContext = await auth.getAuthContext()
2726

28-
const { data, error } = await supabase.auth.getUser()
29-
30-
if (error || !data.user) {
31-
return NextResponse.redirect(new URL('/sign-in', request.url))
32-
}
33-
34-
const session = await getSessionInsecure(supabase)
35-
36-
if (!session) {
27+
if (!authContext) {
3728
return NextResponse.redirect(new URL('/sign-in', request.url))
3829
}
3930

40-
const team = await resolveUserTeam(data.user.id, session.access_token)
31+
const team = await resolveUserTeam(
32+
authContext.user.id,
33+
authContext.accessToken
34+
)
4135

4236
if (!team) {
43-
await supabase.auth.signOut()
37+
await auth.signOut()
4438

4539
const signInUrl = new URL(AUTH_URLS.SIGN_IN, request.url)
4640

0 commit comments

Comments
 (0)