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
11 changes: 9 additions & 2 deletions dashboard/app/api/auth/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NextResponse } from 'next/server'
import { NextRequest, NextResponse } from 'next/server'
import { cookies } from 'next/headers'
import { createHash } from 'crypto'

Expand Down Expand Up @@ -31,12 +31,19 @@ function validateSession(sessionToken: string): boolean {
}

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

// Check for token auth from headers (passed from URL params by client)
const headerToken = request.headers.get('X-Tinybird-Token')
const headerHost = request.headers.get('X-Tinybird-Host')
if (headerToken && headerHost) {
return NextResponse.json({ authenticated: true })
}

const cookieStore = await cookies()
const sessionCookie = cookieStore.get(SESSION_COOKIE_NAME)

Expand Down
52 changes: 45 additions & 7 deletions dashboard/app/api/config/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NextResponse } from 'next/server'
import { getTinybirdConfig, getWorkspace } from '@/lib/server'
import { NextRequest, NextResponse } from 'next/server'
import { getTinybirdConfig, getWorkspace, getWorkspaceWithCredentials } from '@/lib/server'

interface TinybirdRegionInfo {
provider: string
Expand All @@ -13,7 +13,23 @@ interface TinybirdRegionResponse {
api_host: string
}

// Cache for regions to avoid repeated API calls
// Try to extract workspace name from JWT token payload
function extractWorkspaceNameFromToken(token: string): string | null {
try {
const payload = token.split('.')[1]
if (!payload) return null
const base64 =
payload.replace(/-/g, '+').replace(/_/g, '/') +
'==='.slice((payload.length + 3) % 4)
const json = Buffer.from(base64, 'base64').toString()
const data = JSON.parse(json)
// Skip 'frontend_jwt' as it's just a token label, not workspace name
const name = data.workspace_name || (data.name !== 'frontend_jwt' ? data.name : null)
return name || null
} catch {
return null
}
}

async function fetchTinybirdRegions(): Promise<TinybirdRegionResponse[]> {
try {
Expand Down Expand Up @@ -64,8 +80,15 @@ async function getRegionInfoFromHost(
return { provider: 'Unknown', region: 'unknown' }
}

export async function GET() {
const { token, host } = getTinybirdConfig()
export async function GET(request: NextRequest) {
// Check for token from headers first (public mode), then fall back to env vars
const headerToken = request.headers.get('X-Tinybird-Token')
const headerHost = request.headers.get('X-Tinybird-Host')
const headerWorkspace = request.headers.get('X-Tinybird-Workspace')
const { token: envToken, host: envHost } = getTinybirdConfig()

const token = headerToken || envToken
const host = headerHost || envHost

const missing: string[] = []
if (!token) missing.push('TINYBIRD_TOKEN')
Expand All @@ -75,11 +98,26 @@ export async function GET() {

// Include workspace info if configured
const regionInfo = host ? await getRegionInfoFromHost(host) : null
const tinybirdWorkspace = configured ? await getWorkspace() : null

// Use workspace name from header if provided (avoids permission issues with scoped JWT)
// Otherwise fetch using credentials
let workspaceName = headerWorkspace
if (!workspaceName && configured) {
const tinybirdWorkspace = headerToken && headerHost
? await getWorkspaceWithCredentials(headerToken, headerHost)
: await getWorkspace()
workspaceName = tinybirdWorkspace?.name || null
}

// If still no workspace name and we have a token, try to decode it
if (!workspaceName && headerToken) {
workspaceName = extractWorkspaceNameFromToken(headerToken)
}

const workspace =
configured && host && regionInfo
? {
name: tinybirdWorkspace?.name || 'Unknown',
name: workspaceName || 'Unknown',
provider: regionInfo.provider,
region: regionInfo.region,
}
Expand Down
15 changes: 12 additions & 3 deletions dashboard/app/api/endpoints/[name]/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
import { getServerClient, getTinybirdConfig } from '@/lib/server'
import { getServerClient, getTinybirdConfig, createClientWithCredentials } from '@/lib/server'

const VALID_ENDPOINTS = [
'currentVisitors',
Expand Down Expand Up @@ -44,7 +44,13 @@ export async function GET(
)
}

const { token, host } = getTinybirdConfig()
// Check for token from headers first (public mode), then fall back to env vars
const headerToken = request.headers.get('X-Tinybird-Token')
const headerHost = request.headers.get('X-Tinybird-Host')
const { token: envToken, host: envHost } = getTinybirdConfig()

const token = headerToken || envToken
const host = headerHost || envHost

if (!token || !host) {
const missing = []
Expand All @@ -60,7 +66,10 @@ export async function GET(
)
}

const client = getServerClient()
// Use header credentials if provided, otherwise use server client
const client = headerToken && headerHost
? createClientWithCredentials(headerToken, headerHost)
: getServerClient()
const searchParams = request.nextUrl.searchParams
const queryParams: Record<string, string> = {}

Expand Down
8 changes: 7 additions & 1 deletion dashboard/app/api/sql/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'SQL query required' }, { status: 400 })
}

const { token, host } = getTinybirdConfig()
// Check for token from headers first (public mode), then fall back to env vars
const headerToken = request.headers.get('X-Tinybird-Token')
const headerHost = request.headers.get('X-Tinybird-Host')
const { token: envToken, host: envHost } = getTinybirdConfig()

const token = headerToken || envToken
const host = headerHost || envHost

if (!token || !host) {
const missing = []
Expand Down
4 changes: 2 additions & 2 deletions dashboard/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { useInsightsData } from '@/lib/hooks/use-insights-data'
import useCurrentVisitors from '@/lib/hooks/use-current-visitors'
import React from 'react'
import { Header } from '@/components/Header'
import LoginDialog from '@/components/LoginDialog'
import AuthDialog from '@/components/AuthDialog'
import { useLogin } from '@/lib/hooks/use-login'

export default function DashboardPage() {
Expand All @@ -39,7 +39,7 @@ export default function DashboardPage() {
return (
<AIChatProvider>
{/* Show login dialog overlay when not logged in */}
{showLoginDialog && <LoginDialog isLoading={isLoginLoading} />}
{showLoginDialog && <AuthDialog isLoading={isLoginLoading} />}
<Suspense>
<>
{process.env.NODE_ENV === 'production' && (
Expand Down
Loading