diff --git a/README.md b/README.md index ba3efea..229e33f 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ POST /v1/auth/tickets/redeem "login": { "enabled": true, "callbackUrl": "https://asset.jiansun.vip/api/dingtalk/broker-login", - "allowedRedirectPathPrefixes": ["/workflows/disposals", "/dashboard"] + "allowedRedirectPathPrefixes": ["/workflows/disposals", "/dashboard", "/inventory"] } } ``` diff --git a/scripts/deploy-material-host.sh b/scripts/deploy-material-host.sh index fa0730f..da24664 100644 --- a/scripts/deploy-material-host.sh +++ b/scripts/deploy-material-host.sh @@ -143,7 +143,7 @@ def secret_for(code): return old if len(old) >= 24 else secrets.token_hex(32) -def project_with_preserved_login(code, name, allowed_hosts): +def project_with_preserved_login(code, name, allowed_hosts, required_redirect_prefixes=()): project = { 'code': code, 'name': name, @@ -152,7 +152,15 @@ def project_with_preserved_login(code, name, allowed_hosts): } old_login = by_code.get(code, {}).get('login') if isinstance(old_login, dict): - project['login'] = old_login + login = dict(old_login) + existing_prefixes = login.get('allowedRedirectPathPrefixes') + if isinstance(existing_prefixes, list): + prefixes = [str(prefix).strip() for prefix in existing_prefixes if str(prefix).strip()] + for prefix in required_redirect_prefixes: + if prefix not in prefixes: + prefixes.append(prefix) + login['allowedRedirectPathPrefixes'] = prefixes + project['login'] = login return project @@ -166,6 +174,16 @@ projects = [ 'fixed_asset', '固定资产', ['asset.jiansun.vip', 'www.jsgdzc.cn', 'jsgdzc.cn'], + [ + '/workflows/disposals', + '/maintenance', + '/my-assets', + '/equipment', + '/assets', + '/notifications', + '/dashboard', + '/inventory', + ], ), ] if any(item.get('login', {}).get('enabled') is True for item in projects) and not corp_id: diff --git a/scripts/git-build-deploy.ps1 b/scripts/git-build-deploy.ps1 index dff7779..9382b90 100644 --- a/scripts/git-build-deploy.ps1 +++ b/scripts/git-build-deploy.ps1 @@ -108,6 +108,64 @@ stamp="$(date +%Y%m%d%H%M%S)" cp -a .env "$backup_root/env-before-git-release-$stamp" chmod 600 "$backup_root/env-before-git-release-$stamp" +# 盘点任务通过统一 H5 免登入口打开,必须显式保留 /inventory 白名单;不放宽为全站路径。 +python3 - <<'PY' +from pathlib import Path +import json + + +def parse_env_value(raw: str) -> str: + value = raw.strip() + if value.startswith('"') and value.endswith('"'): + return json.loads(value) + if value.startswith("'") and value.endswith("'"): + return value[1:-1] + return value + + +env_path = Path('.env') +lines = env_path.read_text(encoding='utf-8').splitlines() +projects_index = next( + ( + index + for index, line in enumerate(lines) + if not line.lstrip().startswith('#') and line.startswith('YYD_NOTIFICATION_PROJECTS_JSON=') + ), + None, +) +if projects_index is None: + raise SystemExit('通知中心缺少 YYD_NOTIFICATION_PROJECTS_JSON,拒绝放宽或猜测免登配置') + +key, raw_value = lines[projects_index].split('=', 1) +projects = json.loads(parse_env_value(raw_value)) +if not isinstance(projects, list): + raise SystemExit('通知中心项目配置不是数组,拒绝修改') + +fixed_asset = next( + (item for item in projects if isinstance(item, dict) and item.get('code') == 'fixed_asset'), + None, +) +if not isinstance(fixed_asset, dict): + raise SystemExit('通知中心缺少 fixed_asset 项目配置,拒绝修改') + +login = fixed_asset.get('login') +if isinstance(login, dict) and login.get('enabled') is True: + prefixes = login.get('allowedRedirectPathPrefixes') + if not isinstance(prefixes, list): + raise SystemExit('fixed_asset 免登白名单格式无效,拒绝修改') + normalized = [str(prefix).strip() for prefix in prefixes if str(prefix).strip()] + if '/inventory' not in normalized: + normalized.append('/inventory') + login['allowedRedirectPathPrefixes'] = normalized + lines[projects_index] = key + '=' + json.dumps( + json.dumps(projects, ensure_ascii=False, separators=(',', ':')), + ensure_ascii=False, + ) + env_path.write_text('\n'.join(lines) + '\n', encoding='utf-8') + env_path.chmod(0o600) +print('fixed_asset 免登回跳白名单已核对 /inventory') +PY + if docker container inspect youyidian-notification-center >/dev/null 2>&1; then was_running="$(docker inspect -f '{{.State.Running}}' youyidian-notification-center)" if [ "$was_running" = true ]; then diff --git a/src/__tests__/auth.test.ts b/src/__tests__/auth.test.ts index bdd88d4..fa5a699 100644 --- a/src/__tests__/auth.test.ts +++ b/src/__tests__/auth.test.ts @@ -19,7 +19,7 @@ const projects = loadProjectConfigs(JSON.stringify([ login: { enabled: true, callbackUrl: 'https://asset.example.com/api/dingtalk/broker-login', - allowedRedirectPathPrefixes: ['/workflows/disposals', '/dashboard'], + allowedRedirectPathPrefixes: ['/workflows/disposals', '/dashboard', '/inventory'], }, }, { @@ -36,6 +36,10 @@ test('内部回跳路径拒绝外链、编码斜杠和相似前缀绕过', () => validateRedirectPath('/workflows/disposals/53?tab=detail#items', project), '/workflows/disposals/53?tab=detail#items', ) + assert.equal( + validateRedirectPath('/inventory/task/public-token-123', project), + '/inventory/task/public-token-123', + ) for (const value of [ '//evil.example.com/path', '/%2f%2fevil.example.com/path',