XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEServerManager

【Java】我的世界XFE服务器管理器

公开
关注 0 Fork 0 Star 0
UTF-8
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { App } from '../app';
import { ServerProvider } from '../context/server-context';
import { ApiClient } from '../lib/api';
import { I18nProvider } from '../lib/i18n';
import type { ServerSettings, SessionInfo } from '../types';

const flags = {
  consoleEnabled: true,
  moderationEnabled: false,
  claimsEnabled: false,
  worldTrackingEnabled: false,
  rollbackEnabled: false,
};
const settingsValue: ServerSettings = {
  bindAddress: '127.0.0.1', port: 8765, publicBaseUrl: 'http://127.0.0.1:8765',
  trustedProxyCidrs: [], idleSessionSeconds: 1800, absoluteSessionSeconds: 28800,
  recentAuthenticationSeconds: 300, httpThreads: 8, maximumSseClients: 20,
  webConsoleEnabled: true, rawItemEditingEnabled: false, metricsEnabled: false,
  moderationEnabled: false, claimsEnabled: false, worldTrackingEnabled: false,
  rollbackEnabled: false, joinExperienceEnabled: false,
  joinWelcomeEnabled: true, joinWelcomeMessage: 'Welcome {player}',
  firstJoinMessageEnabled: true, firstJoinMessage: 'First join {player}',
  dailyAnnouncementsEnabled: true, dailyAnnouncements: ['Daily {date}'],
  announcementTimeZone: 'UTC', rulesReminderEnabled: true, rulesMessage: 'Rules',
  maintenanceJoinReminderEnabled: true, defaultMessageSender: 'XFEServerManager', features: flags,
  crashProtection: { enabled: true, scanIntervalTicks: 100, itemEntitiesPerDimension: 2000,
    itemEntitiesPerChunk: 256, livingEntitiesPerDimension: 2000, livingEntitiesPerChunk: 128,
    totalEntitiesPerDimension: 5000, totalEntitiesPerChunk: 512,
    entitiesPerNamespacePerDimension: 512, loadedChunksPerDimension: 8000,
    spawnBurstLimit: 400, spawnBurstWindowTicks: 100, commandBlockCommandsPerSecond: 100,
    slowTickMillis: 200, consecutiveSlowTicks: 3, heapUsagePercent: 90,
    blockExcessSpawns: true, removeExcessItems: true, stopRunawayCommandBlocks: true },
};

describe('account security settings', () => {
  afterEach(() => {
    location.hash = '';
  });

  it('enrolls TOTP, confirms it, and exposes the one-time recovery codes', async () => {
    localStorage.setItem('xfesm.locale', 'en-US');
    location.hash = '/settings';
    const client = new ApiClient();
    const initial: SessionInfo = {
      authenticated: true,
      csrfToken: 'csrf',
      actor: { id: 'a1', displayName: 'owner', roles: ['owner'], permissions: [], totpVerified: false, totpEnabled: false },
    };
    const secured: SessionInfo = {
      ...initial,
      actor: { ...initial.actor!, totpVerified: true, totpEnabled: true, reauthenticationRequired: false },
    };
    vi.spyOn(client, 'session').mockResolvedValueOnce(initial).mockResolvedValue(secured);
    vi.spyOn(client, 'status').mockResolvedValue({ sampledAt: '2026-01-01T00:00:00Z', uptimeSeconds: 1, playersOnline: 0 });
    vi.spyOn(client, 'features').mockResolvedValue(flags);
    vi.spyOn(client, 'settings').mockResolvedValue(settingsValue);
    vi.spyOn(client, 'subscribe').mockReturnValue(() => undefined);
    const begin = vi.spyOn(client, 'beginTotpEnrollment').mockResolvedValue({ secret: 'JBSWY3DPEHPK3PXP', provisioningUri: 'otpauth://totp/XFE', expiresAt: '2026-01-01T00:10:00Z' });
    const confirm = vi.spyOn(client, 'confirmTotpEnrollment').mockResolvedValue({ recoveryCodes: ['recover-one', 'recover-two'] });
    const user = userEvent.setup();

    render(<I18nProvider><ServerProvider client={client}><App /></ServerProvider></I18nProvider>);
    await user.type(await screen.findByLabelText('Password'), 'a-strong-password');
    await user.click(screen.getByRole('button', { name: 'Generate TOTP secret' }));
    await waitFor(() => expect(begin).toHaveBeenCalledWith('a-strong-password'));
    expect(await screen.findByDisplayValue('JBSWY3DPEHPK3PXP')).toBeInTheDocument();
    await user.type(screen.getByLabelText('TOTP (when enabled)'), '123456');
    await user.click(screen.getByRole('button', { name: 'Confirm and enable' }));

    await waitFor(() => expect(confirm).toHaveBeenCalledWith('123456'));
    expect(await screen.findByText('recover-one')).toBeInTheDocument();
    expect(screen.getByText(/Recovery codes are shown once/)).toBeInTheDocument();
  });

  it('reauthenticates an owner before exposing the secure console', async () => {
    localStorage.setItem('xfesm.locale', 'en-US');
    location.hash = '/settings';
    const client = new ApiClient();
    const expired: SessionInfo = {
      authenticated: true,
      csrfToken: 'csrf',
      actor: { id: 'a1', displayName: 'owner', roles: ['owner'], permissions: [], totpVerified: true, totpEnabled: true, reauthenticationRequired: true },
    };
    const fresh: SessionInfo = { ...expired, actor: { ...expired.actor!, reauthenticationRequired: false } };
    vi.spyOn(client, 'session').mockResolvedValueOnce(expired).mockResolvedValue(fresh);
    vi.spyOn(client, 'status').mockResolvedValue({ sampledAt: '2026-01-01T00:00:00Z', uptimeSeconds: 1, playersOnline: 0 });
    vi.spyOn(client, 'features').mockResolvedValue(flags);
    vi.spyOn(client, 'settings').mockResolvedValue(settingsValue);
    vi.spyOn(client, 'subscribe').mockReturnValue(() => undefined);
    const reauthenticate = vi.spyOn(client, 'reauthenticateWithTotp').mockResolvedValue(undefined);
    const user = userEvent.setup();

    render(<I18nProvider><ServerProvider client={client}><App /></ServerProvider></I18nProvider>);
    const totpInput = await screen.findByLabelText('TOTP (when enabled)');
    await user.type(totpInput, '654321');
    expect(totpInput).toHaveValue('654321');
    const verifyButton = screen.getByRole('button', { name: 'Verify again' });
    expect(verifyButton).toBeEnabled();
    await user.click(verifyButton);
    await waitFor(() => expect(reauthenticate).toHaveBeenCalledWith('654321'));
    await waitFor(() => expect(screen.getByText('This session satisfies the TOTP and recent-authentication requirements.')).toBeInTheDocument());

    await user.click(screen.getByRole('button', { name: 'Secure console' }));
    await waitFor(() => expect(screen.getAllByRole('heading', { name: 'Secure console' }).length).toBeGreaterThan(0));
    expect(screen.queryByText('The console is disabled by server configuration or this session does not have owner access.')).not.toBeInTheDocument();
    expect(screen.getByPlaceholderText('save-all flush')).toBeInTheDocument();
  });
});