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 { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
import { SESSION_COOKIE_NAME, clearSessionCookie } from "@lib/authGateway";
|
import { SESSION_COOKIE_NAME, clearSessionCookie } from "@lib/authGateway";
|
||||||
import {
|
import { getAccountServiceApiBaseUrl } from "@server/serviceConfig";
|
||||||
getAccountServiceApiBaseUrl,
|
|
||||||
getAccountServiceBaseUrl,
|
|
||||||
} from "@server/serviceConfig";
|
|
||||||
import {
|
|
||||||
buildInternalServiceHeaders,
|
|
||||||
isServiceTokenConfigured,
|
|
||||||
} from "@server/internalServiceAuth";
|
|
||||||
|
|
||||||
const ACCOUNT_API_BASE = getAccountServiceApiBaseUrl();
|
const ACCOUNT_API_BASE = getAccountServiceApiBaseUrl();
|
||||||
const ACCOUNT_BASE = getAccountServiceBaseUrl();
|
|
||||||
|
|
||||||
type AccountUser = {
|
type AccountUser = {
|
||||||
id?: string;
|
id?: string;
|
||||||
@ -48,13 +40,6 @@ type SessionResponse = {
|
|||||||
error?: string;
|
error?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SandboxGuestResponse = {
|
|
||||||
email?: string;
|
|
||||||
proxyUuid?: string;
|
|
||||||
proxyUuidExpiresAt?: string;
|
|
||||||
error?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function normalizeRole(role: unknown): string {
|
function normalizeRole(role: unknown): string {
|
||||||
if (typeof role !== "string") {
|
if (typeof role !== "string") {
|
||||||
return "user";
|
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) {
|
export async function GET(request: NextRequest) {
|
||||||
void request;
|
|
||||||
const token = (await cookies()).get(SESSION_COOKIE_NAME)?.value;
|
const token = (await cookies()).get(SESSION_COOKIE_NAME)?.value;
|
||||||
if (!token) {
|
if (!token) {
|
||||||
const sandboxGuest = await fetchSandboxGuest();
|
return NextResponse.json({ user: null });
|
||||||
return NextResponse.json({ user: sandboxGuest });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestHost = request.headers.get("host");
|
const requestHost = request.headers.get("host");
|
||||||
|
|||||||
@ -65,16 +65,6 @@ const KNOWN_ROLE_MAP: Record<string, UserRole> = {
|
|||||||
member: 'user',
|
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 {
|
function normalizeRole(input?: string | null): UserRole {
|
||||||
if (!input || typeof input !== 'string') {
|
if (!input || typeof input !== 'string') {
|
||||||
return 'guest'
|
return 'guest'
|
||||||
@ -88,92 +78,6 @@ function normalizeRole(input?: string | null): UserRole {
|
|||||||
return KNOWN_ROLE_MAP[normalized] ?? 'guest'
|
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> {
|
async function fetchSessionUser(): Promise<User | null> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/auth/session', {
|
const response = await fetch('/api/auth/session', {
|
||||||
@ -185,7 +89,7 @@ async function fetchSessionUser(): Promise<User | null> {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return buildGuestUser()
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload = (await response.json()) as {
|
const payload = (await response.json()) as {
|
||||||
@ -217,7 +121,7 @@ async function fetchSessionUser(): Promise<User | null> {
|
|||||||
|
|
||||||
const sessionUser = payload?.user
|
const sessionUser = payload?.user
|
||||||
if (!sessionUser) {
|
if (!sessionUser) {
|
||||||
return buildGuestUser()
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id, uuid, email, name, username, mfaEnabled, mfa, mfaPending, role, groups, permissions } = sessionUser
|
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) {
|
if (!identifier) {
|
||||||
return buildGuestUser()
|
return null
|
||||||
}
|
}
|
||||||
const normalizedName = typeof name === 'string' && name.trim().length > 0 ? name.trim() : undefined
|
const normalizedName = typeof name === 'string' && name.trim().length > 0 ? name.trim() : undefined
|
||||||
const normalizedUsername =
|
const normalizedUsername =
|
||||||
@ -309,18 +213,6 @@ async function fetchSessionUser(): Promise<User | null> {
|
|||||||
.filter((tenant): tenant is TenantMembership => Boolean(tenant))
|
.filter((tenant): tenant is TenantMembership => Boolean(tenant))
|
||||||
: undefined
|
: 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 {
|
return {
|
||||||
id: identifier,
|
id: identifier,
|
||||||
uuid: identifier,
|
uuid: identifier,
|
||||||
@ -339,13 +231,13 @@ async function fetchSessionUser(): Promise<User | null> {
|
|||||||
isUser: normalizedRole === 'user',
|
isUser: normalizedRole === 'user',
|
||||||
isOperator: normalizedRole === 'operator',
|
isOperator: normalizedRole === 'operator',
|
||||||
isAdmin: normalizedRole === 'admin',
|
isAdmin: normalizedRole === 'admin',
|
||||||
isReadOnly: normalizedRole === 'guest' ? true : normalizedReadOnly,
|
isReadOnly: normalizedReadOnly,
|
||||||
tenantId: effectiveTenantId,
|
tenantId: normalizedTenantId,
|
||||||
tenants: effectiveTenants,
|
tenants: normalizedTenants,
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to resolve user session', error)
|
console.warn('Failed to resolve user session', error)
|
||||||
return buildGuestUser()
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,14 +2,17 @@
|
|||||||
|
|
||||||
import type { VlessNode } from './vless'
|
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'
|
const FALLBACK_ENDPOINT = '/api/agent/nodes'
|
||||||
|
|
||||||
type AgentNodePayload = {
|
type AgentNodePayload =
|
||||||
nodes?: unknown
|
| {
|
||||||
message?: unknown
|
nodes?: unknown
|
||||||
error?: unknown
|
profiles?: unknown
|
||||||
}
|
message?: unknown
|
||||||
|
error?: unknown
|
||||||
|
}
|
||||||
|
| VlessNode[]
|
||||||
|
|
||||||
type AgentNodesError = Error & {
|
type AgentNodesError = Error & {
|
||||||
status?: number
|
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) {
|
if (!response.ok) {
|
||||||
const error = new Error(extractMessage(Array.isArray(payload) ? null : payload, response.status)) as AgentNodesError
|
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)) {
|
if (Array.isArray(payload)) {
|
||||||
return payload as VlessNode[]
|
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
|
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')
|
throw new Error('unexpected_agent_nodes_payload')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user