feat: support CIDR admin allowlists (#377)

This commit is contained in:
Cita
2026-04-01 21:06:13 +08:00
committed by GitHub
parent 76c85d0a13
commit 5fffcb6d66
6 changed files with 135 additions and 6 deletions
@@ -758,6 +758,56 @@ describe('settings and auth events', () => {
expect(saved?.value).toBe(JSON.stringify(['198.51.100.10', '198.51.100.11']));
});
it('allows allowlist update when current request IP is covered by a CIDR range', async () => {
const response = await app.inject({
method: 'PUT',
url: '/api/settings/runtime',
remoteAddress: '198.51.100.10',
payload: {
adminIpAllowlist: ['198.51.100.0/24'],
},
});
expect(response.statusCode).toBe(200);
const body = response.json() as { adminIpAllowlist?: string[] };
expect(body.adminIpAllowlist).toEqual(['198.51.100.0/24']);
const saved = await db.select().from(schema.settings).where(eq(schema.settings.key, 'admin_ip_allowlist')).get();
expect(saved?.value).toBe(JSON.stringify(['198.51.100.0/24']));
});
it('rejects allowlist update when current request IP is outside the CIDR range', async () => {
const response = await app.inject({
method: 'PUT',
url: '/api/settings/runtime',
remoteAddress: '198.51.100.10',
payload: {
adminIpAllowlist: ['198.51.101.0/24'],
},
});
expect(response.statusCode).toBe(400);
const body = response.json() as { message?: string };
expect(body.message).toContain('白名单');
expect(body.message).toContain('198.51.100.10');
});
it('rejects allowlist update when it contains malformed CIDR entries', async () => {
const response = await app.inject({
method: 'PUT',
url: '/api/settings/runtime',
remoteAddress: '198.51.100.10',
payload: {
adminIpAllowlist: ['198.51.100.0/99'],
},
});
expect(response.statusCode).toBe(400);
const body = response.json() as { message?: string };
expect(body.message).toContain('IP 白名单');
expect(body.message).toContain('198.51.100.0/99');
});
it('appends event when admin auth token changes', async () => {
const response = await app.inject({
method: 'POST',
+8 -1
View File
@@ -35,7 +35,7 @@ import {
parseBackupWebdavExportPayload,
} from '../../contracts/settingsRoutePayloads.js';
import { formatUtcSqlDateTime, getResolvedTimeZone } from '../../services/localTimeService.js';
import { extractClientIp, isIpAllowed } from '../../middleware/auth.js';
import { extractClientIp, findInvalidIpAllowlistEntries, isIpAllowed } from '../../middleware/auth.js';
import { invalidateSiteProxyCache, normalizeSiteProxyUrl, withExplicitProxyRequestInit } from '../../services/siteProxy.js';
import { performFactoryReset } from '../../services/factoryResetService.js';
import { normalizeLogCleanupRetentionDays } from '../../services/logCleanupService.js';
@@ -1552,6 +1552,13 @@ export async function settingsRoutes(app: FastifyInstance) {
if (body.adminIpAllowlist !== undefined) {
const nextAllowlist = toStringList(body.adminIpAllowlist);
const invalidAllowlistEntries = findInvalidIpAllowlistEntries(nextAllowlist);
if (invalidAllowlistEntries.length > 0) {
return reply.code(400).send({
success: false,
message: `保存失败:IP 白名单包含无效条目:${invalidAllowlistEntries.join(', ')}。请使用单个 IP 或 IPv4 CIDR 网段(例如 192.168.1.10 或 192.168.1.0/24)。`,
});
}
if (nextAllowlist.length > 0 && !isIpAllowed(currentRequestIp, nextAllowlist)) {
return reply.code(400).send({
success: false,