fix: 审计修复 — ErrorBoundary 接入 + data_scope 全端点接线 + inventory.wasm
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

1. H1: App.tsx 接入 ErrorBoundary 包裹 Suspense,防止页面渲染错误导致白屏
2. H2: data_scope 行级权限扩展到 count/aggregate/timeseries 端点,
   所有数据查询操作现在都受 data_scope 过滤
3. M3: 进销存插件 WASM 编译部署到 apps/web/public/inventory.wasm
This commit is contained in:
iven
2026-04-18 08:12:40 +08:00
parent e8739e80c7
commit 40bac74f5c
4 changed files with 49 additions and 4 deletions

Binary file not shown.

View File

@@ -4,6 +4,7 @@ import { ConfigProvider, theme as antdTheme, Spin } from 'antd';
import zhCN from 'antd/locale/zh_CN'; import zhCN from 'antd/locale/zh_CN';
import MainLayout from './layouts/MainLayout'; import MainLayout from './layouts/MainLayout';
import Login from './pages/Login'; import Login from './pages/Login';
import { ErrorBoundary } from './components/ErrorBoundary';
import { useAuthStore } from './stores/auth'; import { useAuthStore } from './stores/auth';
import { useAppStore } from './stores/app'; import { useAppStore } from './stores/app';
@@ -133,6 +134,7 @@ export default function App() {
element={ element={
<PrivateRoute> <PrivateRoute>
<MainLayout> <MainLayout>
<ErrorBoundary>
<Suspense fallback={<div style={{ display: 'flex', justifyContent: 'center', padding: 100 }}><Spin size="large" /></div>}> <Suspense fallback={<div style={{ display: 'flex', justifyContent: 'center', padding: 100 }}><Spin size="large" /></div>}>
<Routes> <Routes>
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
@@ -151,6 +153,7 @@ export default function App() {
<Route path="/plugins/:pluginId/:entityName" element={<PluginCRUDPage />} /> <Route path="/plugins/:pluginId/:entityName" element={<PluginCRUDPage />} />
</Routes> </Routes>
</Suspense> </Suspense>
</ErrorBoundary>
</MainLayout> </MainLayout>
</PrivateRoute> </PrivateRoute>
} }

View File

@@ -527,6 +527,7 @@ impl PluginDataService {
db: &sea_orm::DatabaseConnection, db: &sea_orm::DatabaseConnection,
filter: Option<serde_json::Value>, filter: Option<serde_json::Value>,
search: Option<String>, search: Option<String>,
scope: Option<DataScopeParams>,
) -> AppResult<u64> { ) -> AppResult<u64> {
let info = resolve_entity_info(plugin_id, entity_name, tenant_id, db).await?; let info = resolve_entity_info(plugin_id, entity_name, tenant_id, db).await?;
@@ -543,7 +544,7 @@ impl PluginDataService {
} }
}; };
let (sql, values) = DynamicTableManager::build_filtered_count_sql( let (mut sql, mut values) = DynamicTableManager::build_filtered_count_sql(
&info.table_name, &info.table_name,
tenant_id, tenant_id,
filter, filter,
@@ -551,6 +552,13 @@ impl PluginDataService {
) )
.map_err(|e| AppError::Validation(e))?; .map_err(|e| AppError::Validation(e))?;
// 合并数据权限条件
let scope_condition = build_scope_sql(&scope, &info.generated_fields);
if !scope_condition.0.is_empty() {
sql = merge_scope_condition(sql, &scope_condition);
values.extend(scope_condition.1);
}
#[derive(FromQueryResult)] #[derive(FromQueryResult)]
struct CountResult { struct CountResult {
count: i64, count: i64,
@@ -578,10 +586,11 @@ impl PluginDataService {
db: &sea_orm::DatabaseConnection, db: &sea_orm::DatabaseConnection,
group_by_field: &str, group_by_field: &str,
filter: Option<serde_json::Value>, filter: Option<serde_json::Value>,
scope: Option<DataScopeParams>,
) -> AppResult<Vec<(String, i64)>> { ) -> AppResult<Vec<(String, i64)>> {
let info = resolve_entity_info(plugin_id, entity_name, tenant_id, db).await?; let info = resolve_entity_info(plugin_id, entity_name, tenant_id, db).await?;
let (sql, values) = DynamicTableManager::build_aggregate_sql( let (mut sql, mut values) = DynamicTableManager::build_aggregate_sql(
&info.table_name, &info.table_name,
tenant_id, tenant_id,
group_by_field, group_by_field,
@@ -589,6 +598,13 @@ impl PluginDataService {
) )
.map_err(|e| AppError::Validation(e))?; .map_err(|e| AppError::Validation(e))?;
// 合并数据权限条件
let scope_condition = build_scope_sql(&scope, &info.generated_fields);
if !scope_condition.0.is_empty() {
sql = merge_scope_condition(sql, &scope_condition);
values.extend(scope_condition.1);
}
#[derive(FromQueryResult)] #[derive(FromQueryResult)]
struct AggRow { struct AggRow {
key: Option<String>, key: Option<String>,
@@ -621,7 +637,7 @@ impl PluginDataService {
filter: Option<serde_json::Value>, filter: Option<serde_json::Value>,
) -> AppResult<Vec<(String, i64)>> { ) -> AppResult<Vec<(String, i64)>> {
// TODO: 未来版本添加 Redis 缓存层 // TODO: 未来版本添加 Redis 缓存层
Self::aggregate(plugin_id, entity_name, tenant_id, db, group_by_field, filter).await Self::aggregate(plugin_id, entity_name, tenant_id, db, group_by_field, filter, None).await
} }
/// 时间序列聚合 — 按时间字段截断为 day/week/month 统计计数 /// 时间序列聚合 — 按时间字段截断为 day/week/month 统计计数
@@ -634,10 +650,11 @@ impl PluginDataService {
time_grain: &str, time_grain: &str,
start: Option<String>, start: Option<String>,
end: Option<String>, end: Option<String>,
scope: Option<DataScopeParams>,
) -> AppResult<Vec<crate::data_dto::TimeseriesItem>> { ) -> AppResult<Vec<crate::data_dto::TimeseriesItem>> {
let info = resolve_entity_info(plugin_id, entity_name, tenant_id, db).await?; let info = resolve_entity_info(plugin_id, entity_name, tenant_id, db).await?;
let (sql, values) = DynamicTableManager::build_timeseries_sql( let (mut sql, mut values) = DynamicTableManager::build_timeseries_sql(
&info.table_name, &info.table_name,
tenant_id, tenant_id,
time_field, time_field,
@@ -647,6 +664,13 @@ impl PluginDataService {
) )
.map_err(|e| AppError::Validation(e))?; .map_err(|e| AppError::Validation(e))?;
// 合并数据权限条件
let scope_condition = build_scope_sql(&scope, &info.generated_fields);
if !scope_condition.0.is_empty() {
sql = merge_scope_condition(sql, &scope_condition);
values.extend(scope_condition.1);
}
#[derive(FromQueryResult)] #[derive(FromQueryResult)]
struct TsRow { struct TsRow {
period: Option<String>, period: Option<String>,

View File

@@ -390,6 +390,11 @@ where
let fine_perm = compute_permission_code(&manifest_id, &entity, "list"); let fine_perm = compute_permission_code(&manifest_id, &entity, "list");
require_permission(&ctx, &fine_perm)?; require_permission(&ctx, &fine_perm)?;
// 解析数据权限范围
let scope = resolve_data_scope(
&ctx, &manifest_id, &entity, &fine_perm, &state.db,
).await?;
// 解析 filter JSON // 解析 filter JSON
let filter: Option<serde_json::Value> = params let filter: Option<serde_json::Value> = params
.filter .filter
@@ -403,6 +408,7 @@ where
&state.db, &state.db,
filter, filter,
params.search, params.search,
scope,
) )
.await?; .await?;
@@ -434,6 +440,11 @@ where
let fine_perm = compute_permission_code(&manifest_id, &entity, "list"); let fine_perm = compute_permission_code(&manifest_id, &entity, "list");
require_permission(&ctx, &fine_perm)?; require_permission(&ctx, &fine_perm)?;
// 解析数据权限范围
let scope = resolve_data_scope(
&ctx, &manifest_id, &entity, &fine_perm, &state.db,
).await?;
// 解析 filter JSON // 解析 filter JSON
let filter: Option<serde_json::Value> = params let filter: Option<serde_json::Value> = params
.filter .filter
@@ -447,6 +458,7 @@ where
&state.db, &state.db,
&params.group_by, &params.group_by,
filter, filter,
scope,
) )
.await?; .await?;
@@ -483,6 +495,11 @@ where
let fine_perm = compute_permission_code(&manifest_id, &entity, "list"); let fine_perm = compute_permission_code(&manifest_id, &entity, "list");
require_permission(&ctx, &fine_perm)?; require_permission(&ctx, &fine_perm)?;
// 解析数据权限范围
let scope = resolve_data_scope(
&ctx, &manifest_id, &entity, &fine_perm, &state.db,
).await?;
let result = PluginDataService::timeseries( let result = PluginDataService::timeseries(
plugin_id, plugin_id,
&entity, &entity,
@@ -492,6 +509,7 @@ where
&params.time_grain, &params.time_grain,
params.start, params.start,
params.end, params.end,
scope,
) )
.await?; .await?;