From 1cc58977fdaf266f96595a57a620c8316817d91c Mon Sep 17 00:00:00 2001 From: Denozordec Date: Sat, 18 Jul 2026 13:49:37 +0700 Subject: [PATCH] =?UTF-8?q?feat(auth):=20allowlist=20private=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20LAN=20return=5Fto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .env.example | 2 +- docker-compose.yml | 2 +- packages/shared/src/contracts/auth.ts | 19 ++++++++++++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 6862e8a..7e49b67 100644 --- a/.env.example +++ b/.env.example @@ -13,7 +13,7 @@ ADMIN_PASSWORD=admin ADMIN_NAME=Admin # Allowed return_to hosts (comma-separated), e.g. .shnt.top or full origins -RETURN_TO_ALLOWLIST=.shnt.top,localhost,http://localhost:5173,http://localhost:5174 +RETURN_TO_ALLOWLIST=.shnt.top,localhost,private,http://localhost:5173,http://localhost:5174 # ReUI PRO (apps/web/components.json → @reui Authorization) # Ключ: https://reui.io/docs/license-setup — класть в apps/web/.env.local (gitignored) diff --git a/docker-compose.yml b/docker-compose.yml index 9ac1aad..f1f24d8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ services: ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@shnt.top} ADMIN_PASSWORD: ${ADMIN_PASSWORD:-} ADMIN_NAME: ${ADMIN_NAME:-Admin} - RETURN_TO_ALLOWLIST: ${RETURN_TO_ALLOWLIST:-.shnt.top,localhost} + RETURN_TO_ALLOWLIST: ${RETURN_TO_ALLOWLIST:-.shnt.top,localhost,private} LOG_LEVEL: info STATIC_DIR: /app/static volumes: diff --git a/packages/shared/src/contracts/auth.ts b/packages/shared/src/contracts/auth.ts index 2b59b66..0dbef9a 100644 --- a/packages/shared/src/contracts/auth.ts +++ b/packages/shared/src/contracts/auth.ts @@ -127,7 +127,8 @@ export function hasPermission( } /** - * Allowlist entries: `.shnt.top`, `localhost`, or full origins `http://localhost:5173`. + * Allowlist entries: `.shnt.top`, `localhost`, full origins `http://localhost:5173`, + * or token `private` for RFC1918 LAN hosts (192.168/10/172.16-31). */ export function isReturnToAllowed( returnTo: string, @@ -146,7 +147,11 @@ export function isReturnToAllowed( .map((s) => s.trim()) .filter(Boolean) + const allowPrivate = entries.some((e) => e.toLowerCase() === 'private') + if (allowPrivate && isPrivateHostname(url.hostname)) return true + for (const entry of entries) { + if (entry.toLowerCase() === 'private') continue if (entry.startsWith('.')) { const suffix = entry.slice(1) if (url.hostname === suffix || url.hostname.endsWith(entry)) return true @@ -167,6 +172,18 @@ export function isReturnToAllowed( return false } +function isPrivateHostname(hostname: string): boolean { + if (hostname === 'localhost' || hostname.endsWith('.local')) return true + const m = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/.exec(hostname) + if (!m) return false + const a = Number(m[1]) + const b = Number(m[2]) + if (a === 10) return true + if (a === 192 && b === 168) return true + if (a === 172 && b >= 16 && b <= 31) return true + return false +} + export function buildSsoRedirectUrl( returnTo: string, accessToken: string,