Skip to content

Commit 3b72705

Browse files
rmorehigclaude
andcommitted
fix: support token auth in middleware and improve workspace detection
- Add token/host header check in middleware to allow public mode requests - Update /api/auth to return authenticated:true when token headers present - Decode JWT to extract workspace name as fallback in /api/config - Pass token/host headers when checking auth status in use-login - Use devMode: false for token-based client to avoid branch mode Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 6836526 commit 3b72705

6 files changed

Lines changed: 62 additions & 7 deletions

File tree

dashboard/app/api/auth/route.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NextResponse } from 'next/server'
1+
import { NextRequest, NextResponse } from 'next/server'
22
import { cookies } from 'next/headers'
33
import { createHash } from 'crypto'
44

@@ -31,12 +31,19 @@ function validateSession(sessionToken: string): boolean {
3131
}
3232

3333
// GET - Check auth status
34-
export async function GET() {
34+
export async function GET(request: NextRequest) {
3535
// If auth is disabled, always return authenticated
3636
if (process.env.DISABLE_AUTH === 'true') {
3737
return NextResponse.json({ authenticated: true })
3838
}
3939

40+
// Check for token auth from headers (passed from URL params by client)
41+
const headerToken = request.headers.get('X-Tinybird-Token')
42+
const headerHost = request.headers.get('X-Tinybird-Host')
43+
if (headerToken && headerHost) {
44+
return NextResponse.json({ authenticated: true })
45+
}
46+
4047
const cookieStore = await cookies()
4148
const sessionCookie = cookieStore.get(SESSION_COOKIE_NAME)
4249

dashboard/app/api/config/route.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,23 @@ interface TinybirdRegionResponse {
1313
api_host: string
1414
}
1515

16-
// Cache for regions to avoid repeated API calls
16+
// Try to extract workspace name from JWT token payload
17+
function extractWorkspaceNameFromToken(token: string): string | null {
18+
try {
19+
const payload = token.split('.')[1]
20+
if (!payload) return null
21+
const base64 =
22+
payload.replace(/-/g, '+').replace(/_/g, '/') +
23+
'==='.slice((payload.length + 3) % 4)
24+
const json = Buffer.from(base64, 'base64').toString()
25+
const data = JSON.parse(json)
26+
// Skip 'frontend_jwt' as it's just a token label, not workspace name
27+
const name = data.workspace_name || (data.name !== 'frontend_jwt' ? data.name : null)
28+
return name || null
29+
} catch {
30+
return null
31+
}
32+
}
1733

1834
async function fetchTinybirdRegions(): Promise<TinybirdRegionResponse[]> {
1935
try {
@@ -93,6 +109,11 @@ export async function GET(request: NextRequest) {
93109
workspaceName = tinybirdWorkspace?.name || null
94110
}
95111

112+
// If still no workspace name and we have a token, try to decode it
113+
if (!workspaceName && headerToken) {
114+
workspaceName = extractWorkspaceNameFromToken(headerToken)
115+
}
116+
96117
const workspace =
97118
configured && host && regionInfo
98119
? {

dashboard/lib/hooks/use-login.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,16 @@ export function getStoredCredentials(): StoredCredentials | null {
2929
}
3030
}
3131

32-
const fetcher = (url: string) => fetch(url).then(res => res.json())
32+
function createFetcher(token: string | null, host: string | null) {
33+
return (url: string) => {
34+
const headers: HeadersInit = {}
35+
if (token && host) {
36+
headers['X-Tinybird-Token'] = token
37+
headers['X-Tinybird-Host'] = host
38+
}
39+
return fetch(url, { headers }).then(res => res.json())
40+
}
41+
}
3342

3443
export function useLogin() {
3544
const searchParams = useSearchParams()
@@ -39,6 +48,9 @@ export function useLogin() {
3948
const host = searchParams?.get('host')
4049
const hasTokenAuth = !!token && !!host
4150

51+
// Create fetcher with token/host to pass as headers
52+
const fetcher = createFetcher(token, host)
53+
4254
// Check server session auth
4355
const { data, error, isLoading: isSessionLoading, mutate } = useSWR(
4456
'/api/auth',

dashboard/lib/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export function getServerClient() {
4444
}
4545

4646
export function createClientWithCredentials(token: string, host: string) {
47-
return createAnalyticsClient({ token, baseUrl: host })
47+
return createAnalyticsClient({ token, baseUrl: host, devMode: false })
4848
}
4949

5050
export async function getWorkspace(): Promise<TinybirdWorkspace | null> {

dashboard/middleware.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ export function middleware(request: NextRequest) {
2424
return NextResponse.next()
2525
}
2626

27+
// Check for token auth from headers (public mode)
28+
const headerToken = request.headers.get('X-Tinybird-Token')
29+
const headerHost = request.headers.get('X-Tinybird-Host')
30+
if (headerToken && headerHost) {
31+
return NextResponse.next()
32+
}
33+
2734
// Check for session cookie
2835
const sessionCookie = request.cookies.get(SESSION_COOKIE_NAME)
2936

tinybird/src/client.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,19 +109,27 @@ const __configDir = dirname(fileURLToPath(import.meta.url));
109109
interface CreateAnalyticsClientOptions {
110110
token?: string;
111111
baseUrl?: string;
112+
devMode?: boolean | "branch";
112113
}
113114

114115
/**
115116
* Create a Tinybird client with custom configuration
116117
*/
117118
export function createAnalyticsClient(options?: CreateAnalyticsClientOptions) {
118-
return createTinybirdClient({
119+
const clientOptions: Parameters<typeof createTinybirdClient>[0] = {
119120
datasources,
120121
pipes,
121122
configDir: __configDir,
122123
baseUrl: options?.baseUrl ?? process.env.TINYBIRD_HOST,
123124
token: options?.token ?? process.env.TINYBIRD_TOKEN,
124-
});
125+
};
126+
127+
// Only set devMode if explicitly provided (otherwise use tinybird.json config)
128+
if (options?.devMode !== undefined) {
129+
clientOptions.devMode = options.devMode;
130+
}
131+
132+
return createTinybirdClient(clientOptions);
125133
}
126134

127135
/**

0 commit comments

Comments
 (0)