SnowLuma v2.4.2 source snapshot
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run

This commit is contained in:
TanTan committed 2026-11-13 09:02:00 +08:00
commit fa9c73363d
1367 files changed
+355735

No files matched your search

@@ -0,0 +1,145 @@
import { describe, expect, it } from 'vitest';
import { makeDefaultOneBotConfig } from '@snowluma/onebot/config';
import {
OneBotAccessTokenPolicyError,
validateOneBotAccessTokenChanges,
} from '../src/webui/onebot-token-policy';
function validate(
previous: ReturnType<typeof makeDefaultOneBotConfig> | null,
next: ReturnType<typeof makeDefaultOneBotConfig>,
clientIp: string,
): void {
validateOneBotAccessTokenChanges(previous, next, { clientIp, uin: '10001' });
}
describe('OneBot access token save policy', () => {
it('rejects clearing an inbound token from a non-loopback client when the bind host is public', () => {
const previous = makeDefaultOneBotConfig();
const next = structuredClone(previous);
next.networks.httpServers[0].host = '0.0.0.0';
next.networks.httpServers[0].accessToken = undefined;
expect(() => validate(previous, next, '192.0.2.10')).toThrow(OneBotAccessTokenPolicyError);
expect(() => validate(previous, next, '192.0.2.10')).toThrow(/令牌/);
});
it('allows a remote client to clear a token when the inbound host is loopback', () => {
const previous = makeDefaultOneBotConfig();
const next = structuredClone(previous);
next.networks.httpServers[0].host = '127.0.0.1';
next.networks.httpServers[0].accessToken = undefined;
expect(() => validate(previous, next, '192.0.2.10')).not.toThrow();
});
it('treats localhost as a loopback bind host for empty inbound tokens', () => {
const previous = makeDefaultOneBotConfig();
const next = structuredClone(previous);
next.networks.wsServers[0].host = 'localhost';
next.networks.wsServers[0].accessToken = undefined;
expect(() => validate(previous, next, '192.0.2.10')).not.toThrow();
});
it('does not treat a missing bind host as loopback', () => {
const previous = makeDefaultOneBotConfig();
const next = structuredClone(previous);
next.networks.httpServers[0].host = undefined;
next.networks.httpServers[0].accessToken = undefined;
expect(() => validate(previous, next, '192.0.2.10')).toThrow(OneBotAccessTokenPolicyError);
});
it('treats every inbound token as new when no readable prior config exists', () => {
const next = makeDefaultOneBotConfig();
next.networks.httpServers[0].host = '0.0.0.0';
next.networks.httpServers[0].accessToken = undefined;
expect(() => validate(null, next, '192.0.2.10')).toThrow(OneBotAccessTokenPolicyError);
});
it('allows a loopback client to deliberately clear an inbound token', () => {
const previous = makeDefaultOneBotConfig();
const next = structuredClone(previous);
next.networks.wsServers[0].accessToken = undefined;
expect(() => validate(previous, next, '127.0.0.2')).not.toThrow();
});
it('rejects a newly changed weak inbound token', () => {
const previous = makeDefaultOneBotConfig();
const next = structuredClone(previous);
next.networks.httpServers[0].accessToken = 'SnowLumaSnowLuma';
expect(() => validate(previous, next, '127.0.0.1')).toThrow(/容易被猜中/);
});
it('does not strand installations whose legacy token is unchanged', () => {
const previous = makeDefaultOneBotConfig();
previous.networks.httpServers[0].accessToken = 'legacy';
const next = structuredClone(previous);
next.networks.httpServers[0].name = 'renamed-http';
next.networks.httpServers[0].port = 3100;
expect(() => validate(previous, next, '192.0.2.10')).not.toThrow();
});
it('allows remote edits that leave an existing empty inbound token unchanged', () => {
const previous = makeDefaultOneBotConfig();
previous.networks.httpServers[0].accessToken = undefined;
const next = structuredClone(previous);
next.networks.httpServers[0].port = 3100;
expect(() => validate(previous, next, '192.0.2.10')).not.toThrow();
});
it('preserves legacy tokens when existing endpoints are reordered by name', () => {
const previous = makeDefaultOneBotConfig();
previous.networks.httpServers[0].accessToken = 'legacy';
previous.networks.httpServers.push({
...previous.networks.httpServers[0],
name: 'second-http',
port: 3100,
});
const next = structuredClone(previous);
next.networks.httpServers.reverse();
expect(() => validate(previous, next, '192.0.2.10')).not.toThrow();
});
it('does not mistake an inserted endpoint for a renamed legacy endpoint', () => {
const previous = makeDefaultOneBotConfig();
previous.networks.httpServers[0].accessToken = 'legacy';
const next = structuredClone(previous);
next.networks.httpServers.unshift({
...next.networks.httpServers[0],
name: 'new-http',
port: 3100,
});
expect(() => validate(previous, next, '192.0.2.10')).toThrow(OneBotAccessTokenPolicyError);
});
it('does not impose local inbound-token policy on outbound adapters', () => {
const previous = makeDefaultOneBotConfig();
const next = structuredClone(previous);
next.networks.httpClients.push({
name: 'remote-service',
url: 'https://example.com/events',
accessToken: 'remote-chosen-token',
messageFormat: 'array',
reportSelfMessage: false,
});
expect(() => validate(previous, next, '192.0.2.10')).not.toThrow();
});
it('accepts a newly changed strong inbound token', () => {
const previous = makeDefaultOneBotConfig();
const next = structuredClone(previous);
next.networks.wsServers[0].accessToken = 'E5xqVb7_9pLt2QwR4M8kZ1nY';
expect(() => validate(previous, next, '192.0.2.10')).not.toThrow();
});
});