Merge pull request #45 from cloud-neutral-toolkit/codex/fix/guest-sandbox-demo

fix(demo): fetch sandbox UUID and binding for guest
This commit is contained in:
Haitao Pan 2026-02-07 02:36:48 +08:00 committed by GitHub
commit 135958a5df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 78 additions and 8 deletions

View File

@ -2,9 +2,11 @@ import { cookies } from 'next/headers'
import { NextRequest, NextResponse } from 'next/server'
import { SESSION_COOKIE_NAME, clearSessionCookie } from '@lib/authGateway'
import { getAccountServiceApiBaseUrl } from '@server/serviceConfig'
import { getAccountServiceApiBaseUrl, getAccountServiceBaseUrl } from '@server/serviceConfig'
import { buildInternalServiceHeaders, isServiceTokenConfigured } from '@server/internalServiceAuth'
const ACCOUNT_API_BASE = getAccountServiceApiBaseUrl()
const ACCOUNT_BASE = getAccountServiceBaseUrl()
type AccountUser = {
id?: string
@ -40,6 +42,13 @@ type SessionResponse = {
error?: string
}
type SandboxGuestResponse = {
email?: string
proxyUuid?: string
proxyUuidExpiresAt?: string
error?: string
}
function normalizeRole(role: unknown): string {
if (typeof role !== 'string') {
return 'user'
@ -74,11 +83,65 @@ async function fetchSession(token: string) {
}
}
async function fetchSandboxGuest(): Promise<AccountUser | null> {
if (!isServiceTokenConfigured()) {
return null
}
try {
const response = await fetch(`${ACCOUNT_BASE}/api/internal/sandbox/guest`, {
method: 'GET',
headers: buildInternalServiceHeaders({
Accept: 'application/json',
}),
cache: 'no-store',
})
if (!response.ok) {
return null
}
const payload = (await response.json().catch(() => null)) as SandboxGuestResponse | null
const proxyUuid = typeof payload?.proxyUuid === 'string' ? payload.proxyUuid.trim() : ''
if (!proxyUuid) {
return null
}
const proxyUuidExpiresAt =
typeof payload?.proxyUuidExpiresAt === 'string' && payload.proxyUuidExpiresAt.trim().length > 0
? payload.proxyUuidExpiresAt.trim()
: undefined
// Shape this as a pseudo-session user for the Guest/Demo experience.
return {
id: proxyUuid,
uuid: proxyUuid,
proxyUuid,
proxyUuidExpiresAt,
name: 'Guest user',
username: 'guest',
email: 'sandbox@svc.plus',
role: 'guest',
groups: ['guest', 'sandbox'],
permissions: ['read'],
readOnly: true,
tenantId: 'guest-sandbox',
tenants: [{ id: 'guest-sandbox', name: 'Guest Sandbox', role: 'guest' }],
mfaEnabled: false,
mfaPending: false,
}
} catch (error) {
console.error('Sandbox guest session proxy failed', error)
return null
}
}
export async function GET(request: NextRequest) {
void request
const token = (await cookies()).get(SESSION_COOKIE_NAME)?.value
if (!token) {
return NextResponse.json({ user: null })
const sandboxGuest = await fetchSandboxGuest()
return NextResponse.json({ user: sandboxGuest })
}
const { response, data } = await fetchSession(token)

View File

@ -4,6 +4,7 @@ import { NextRequest, NextResponse } from 'next/server'
import { getAccountServiceApiBaseUrl } from '@server/serviceConfig'
import { getAccountSession } from '@server/account/session'
import { buildInternalServiceHeaders, isServiceTokenConfigured } from '@server/internalServiceAuth'
const ACCOUNT_API_BASE = getAccountServiceApiBaseUrl()
@ -13,17 +14,24 @@ type ErrorPayload = {
export async function GET(request: NextRequest) {
const session = await getAccountSession(request)
if (!session.token) {
const canUseInternalToken = isServiceTokenConfigured()
if (!session.token && !canUseInternalToken) {
return NextResponse.json<ErrorPayload>({ error: 'unauthenticated' }, { status: 401 })
}
try {
const headers = session.token
? new Headers({
Authorization: `Bearer ${session.token}`,
Accept: 'application/json',
})
: buildInternalServiceHeaders({
Accept: 'application/json',
})
const response = await fetch(`${ACCOUNT_API_BASE}/sandbox/binding`, {
method: 'GET',
headers: {
Authorization: `Bearer ${session.token}`,
Accept: 'application/json',
},
headers,
cache: 'no-store',
})
@ -46,4 +54,3 @@ export async function GET(request: NextRequest) {
return NextResponse.json<ErrorPayload>({ error: 'upstream_unreachable' }, { status: 502 })
}
}