fix(console): correct panel node loading
This commit is contained in:
parent
2eb72e4aea
commit
6dfc9454fa
@ -2,17 +2,9 @@ import { cookies } from "next/headers";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
import { SESSION_COOKIE_NAME, clearSessionCookie } from "@lib/authGateway";
|
||||
import {
|
||||
getAccountServiceApiBaseUrl,
|
||||
getAccountServiceBaseUrl,
|
||||
} from "@server/serviceConfig";
|
||||
import {
|
||||
buildInternalServiceHeaders,
|
||||
isServiceTokenConfigured,
|
||||
} from "@server/internalServiceAuth";
|
||||
import { getAccountServiceApiBaseUrl } from "@server/serviceConfig";
|
||||
|
||||
const ACCOUNT_API_BASE = getAccountServiceApiBaseUrl();
|
||||
const ACCOUNT_BASE = getAccountServiceBaseUrl();
|
||||
|
||||
type AccountUser = {
|
||||
id?: string;
|
||||
@ -48,13 +40,6 @@ 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";
|
||||
@ -94,69 +79,10 @@ async function fetchSession(token: string, requestHost?: string | null) {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
const sandboxGuest = await fetchSandboxGuest();
|
||||
return NextResponse.json({ user: sandboxGuest });
|
||||
return NextResponse.json({ user: null });
|
||||
}
|
||||
|
||||
const requestHost = request.headers.get("host");
|
||||
|
||||
@ -65,16 +65,6 @@ const KNOWN_ROLE_MAP: Record<string, UserRole> = {
|
||||
member: 'user',
|
||||
}
|
||||
|
||||
const GUEST_SESSION_STORAGE_KEY = 'xcontrol.guest.session'
|
||||
const GUEST_SESSION_TTL_MS = 60 * 60 * 1000
|
||||
const GUEST_SANDBOX_TENANT_ID = 'guest-sandbox'
|
||||
const GUEST_SANDBOX_TENANT_NAME = 'Guest Sandbox'
|
||||
|
||||
type GuestSession = {
|
||||
uuid: string
|
||||
issuedAt: number
|
||||
}
|
||||
|
||||
function normalizeRole(input?: string | null): UserRole {
|
||||
if (!input || typeof input !== 'string') {
|
||||
return 'guest'
|
||||
@ -88,92 +78,6 @@ function normalizeRole(input?: string | null): UserRole {
|
||||
return KNOWN_ROLE_MAP[normalized] ?? 'guest'
|
||||
}
|
||||
|
||||
function createUUID(): string {
|
||||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||
return crypto.randomUUID()
|
||||
}
|
||||
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`
|
||||
}
|
||||
|
||||
function readGuestSession(): GuestSession | null {
|
||||
if (typeof window === 'undefined') {
|
||||
return null
|
||||
}
|
||||
const raw = window.sessionStorage.getItem(GUEST_SESSION_STORAGE_KEY)
|
||||
if (!raw) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(raw) as GuestSession
|
||||
if (
|
||||
typeof parsed?.uuid === 'string' &&
|
||||
parsed.uuid.trim().length > 0 &&
|
||||
typeof parsed?.issuedAt === 'number' &&
|
||||
Number.isFinite(parsed.issuedAt)
|
||||
) {
|
||||
return {
|
||||
uuid: parsed.uuid.trim(),
|
||||
issuedAt: parsed.issuedAt,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to parse guest session payload', error)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function writeGuestSession(session: GuestSession) {
|
||||
if (typeof window === 'undefined') {
|
||||
return
|
||||
}
|
||||
window.sessionStorage.setItem(GUEST_SESSION_STORAGE_KEY, JSON.stringify(session))
|
||||
}
|
||||
|
||||
function resolveGuestUUID(now = Date.now()): string {
|
||||
const existing = readGuestSession()
|
||||
if (!existing || now - existing.issuedAt >= GUEST_SESSION_TTL_MS) {
|
||||
const next: GuestSession = { uuid: createUUID(), issuedAt: now }
|
||||
writeGuestSession(next)
|
||||
return next.uuid
|
||||
}
|
||||
return existing.uuid
|
||||
}
|
||||
|
||||
function buildGuestUser(): User {
|
||||
const identifier = resolveGuestUUID()
|
||||
return {
|
||||
id: identifier,
|
||||
uuid: identifier,
|
||||
email: 'sandbox@svc.plus',
|
||||
name: 'Guest user',
|
||||
username: 'guest',
|
||||
mfaEnabled: false,
|
||||
mfaPending: false,
|
||||
mfa: {
|
||||
totpEnabled: false,
|
||||
totpPending: false,
|
||||
},
|
||||
role: 'guest',
|
||||
groups: ['guest', 'sandbox'],
|
||||
permissions: ['read'],
|
||||
isGuest: true,
|
||||
isUser: false,
|
||||
isOperator: false,
|
||||
isAdmin: false,
|
||||
isReadOnly: true,
|
||||
tenantId: GUEST_SANDBOX_TENANT_ID,
|
||||
tenants: [
|
||||
{
|
||||
id: GUEST_SANDBOX_TENANT_ID,
|
||||
name: GUEST_SANDBOX_TENANT_NAME,
|
||||
role: 'guest',
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchSessionUser(): Promise<User | null> {
|
||||
try {
|
||||
const response = await fetch('/api/auth/session', {
|
||||
@ -185,7 +89,7 @@ async function fetchSessionUser(): Promise<User | null> {
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
return buildGuestUser()
|
||||
return null
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as {
|
||||
@ -217,7 +121,7 @@ async function fetchSessionUser(): Promise<User | null> {
|
||||
|
||||
const sessionUser = payload?.user
|
||||
if (!sessionUser) {
|
||||
return buildGuestUser()
|
||||
return null
|
||||
}
|
||||
|
||||
const { id, uuid, email, name, username, mfaEnabled, mfa, mfaPending, role, groups, permissions } = sessionUser
|
||||
@ -229,7 +133,7 @@ async function fetchSessionUser(): Promise<User | null> {
|
||||
: ''
|
||||
|
||||
if (!identifier) {
|
||||
return buildGuestUser()
|
||||
return null
|
||||
}
|
||||
const normalizedName = typeof name === 'string' && name.trim().length > 0 ? name.trim() : undefined
|
||||
const normalizedUsername =
|
||||
@ -309,18 +213,6 @@ async function fetchSessionUser(): Promise<User | null> {
|
||||
.filter((tenant): tenant is TenantMembership => Boolean(tenant))
|
||||
: undefined
|
||||
|
||||
const effectiveTenantId = normalizedRole === 'guest' ? GUEST_SANDBOX_TENANT_ID : normalizedTenantId
|
||||
const effectiveTenants =
|
||||
normalizedRole === 'guest'
|
||||
? [
|
||||
{
|
||||
id: GUEST_SANDBOX_TENANT_ID,
|
||||
name: GUEST_SANDBOX_TENANT_NAME,
|
||||
role: 'guest' as UserRole,
|
||||
},
|
||||
]
|
||||
: normalizedTenants
|
||||
|
||||
return {
|
||||
id: identifier,
|
||||
uuid: identifier,
|
||||
@ -339,13 +231,13 @@ async function fetchSessionUser(): Promise<User | null> {
|
||||
isUser: normalizedRole === 'user',
|
||||
isOperator: normalizedRole === 'operator',
|
||||
isAdmin: normalizedRole === 'admin',
|
||||
isReadOnly: normalizedRole === 'guest' ? true : normalizedReadOnly,
|
||||
tenantId: effectiveTenantId,
|
||||
tenants: effectiveTenants,
|
||||
isReadOnly: normalizedReadOnly,
|
||||
tenantId: normalizedTenantId,
|
||||
tenants: normalizedTenants,
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to resolve user session', error)
|
||||
return buildGuestUser()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,14 +2,17 @@
|
||||
|
||||
import type { VlessNode } from './vless'
|
||||
|
||||
const PRIMARY_ENDPOINT = '/api/agent-server/v1/nodes'
|
||||
const PRIMARY_ENDPOINT = '/api/auth/sync/config?since_version=0'
|
||||
const FALLBACK_ENDPOINT = '/api/agent/nodes'
|
||||
|
||||
type AgentNodePayload = {
|
||||
nodes?: unknown
|
||||
message?: unknown
|
||||
error?: unknown
|
||||
}
|
||||
type AgentNodePayload =
|
||||
| {
|
||||
nodes?: unknown
|
||||
profiles?: unknown
|
||||
message?: unknown
|
||||
error?: unknown
|
||||
}
|
||||
| VlessNode[]
|
||||
|
||||
type AgentNodesError = Error & {
|
||||
status?: number
|
||||
@ -34,7 +37,7 @@ async function requestAgentNodes(url: string): Promise<VlessNode[]> {
|
||||
},
|
||||
})
|
||||
|
||||
const payload = (await response.json().catch(() => null)) as AgentNodePayload | VlessNode[] | null
|
||||
const payload = (await response.json().catch(() => null)) as AgentNodePayload | null
|
||||
|
||||
if (!response.ok) {
|
||||
const error = new Error(extractMessage(Array.isArray(payload) ? null : payload, response.status)) as AgentNodesError
|
||||
@ -45,10 +48,15 @@ async function requestAgentNodes(url: string): Promise<VlessNode[]> {
|
||||
if (Array.isArray(payload)) {
|
||||
return payload as VlessNode[]
|
||||
}
|
||||
if (payload && Array.isArray((payload as AgentNodePayload).nodes)) {
|
||||
|
||||
if (payload && Array.isArray((payload as { nodes?: unknown }).nodes)) {
|
||||
return (payload as { nodes: VlessNode[] }).nodes
|
||||
}
|
||||
|
||||
if (payload && Array.isArray((payload as { profiles?: unknown }).profiles)) {
|
||||
return (payload as { profiles: VlessNode[] }).profiles
|
||||
}
|
||||
|
||||
throw new Error('unexpected_agent_nodes_payload')
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user