feat(auth): 能源管理模块需要 BI-LEADER-ENERGY 角色
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- 新增 ENERGY_ACCESS_ROLES 与 canAccessEnergy(roles) 守卫(全量权限角色亦可访问) - 后端 /api/energy/* 加模块级守卫:无角色返回 403 - 前端 App.tsx 按角色动态注入 EnergyModule,无权限时主导航不显示 - dev mock 用户(前端 + 后端)追加 BI-LEADER-ENERGY 便于本地调试 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
15
src/App.tsx
15
src/App.tsx
@@ -10,14 +10,17 @@ import FeedbackAdminPage from './modules/admin/FeedbackAdminPage';
|
|||||||
import AuthProvider from './auth/AuthProvider';
|
import AuthProvider from './auth/AuthProvider';
|
||||||
import { useAuth } from './auth/useAuth';
|
import { useAuth } from './auth/useAuth';
|
||||||
import UnauthorizedPage from './auth/UnauthorizedPage';
|
import UnauthorizedPage from './auth/UnauthorizedPage';
|
||||||
import { canAccessScheduling } from './shared/auth/roles';
|
import { canAccessScheduling, canAccessEnergy } from './shared/auth/roles';
|
||||||
|
|
||||||
const BASE_MODULES: ModuleConfig[] = [
|
const BASE_MODULES: ModuleConfig[] = [
|
||||||
{ id: 'assets', label: '资产管理', icon: Truck, component: AssetsModule },
|
{ id: 'assets', label: '资产管理', icon: Truck, component: AssetsModule },
|
||||||
{ id: 'mileage', label: '里程管理', icon: Route, component: MileageModule },
|
{ id: 'mileage', label: '里程管理', icon: Route, component: MileageModule },
|
||||||
{ id: 'energy', label: '能源管理', icon: Zap, component: EnergyModule },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const ENERGY_MODULE: ModuleConfig = {
|
||||||
|
id: 'energy', label: '能源管理', icon: Zap, component: EnergyModule,
|
||||||
|
};
|
||||||
|
|
||||||
const SCHEDULING_MODULE: ModuleConfig = {
|
const SCHEDULING_MODULE: ModuleConfig = {
|
||||||
id: 'scheduling', label: '智能调度', icon: Activity, component: SchedulingModule,
|
id: 'scheduling', label: '智能调度', icon: Activity, component: SchedulingModule,
|
||||||
};
|
};
|
||||||
@@ -47,10 +50,10 @@ function AuthGate() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const modules = useMemo(() => {
|
const modules = useMemo(() => {
|
||||||
if (canAccessScheduling(user?.roles)) {
|
const result = [...BASE_MODULES];
|
||||||
return [...BASE_MODULES, SCHEDULING_MODULE];
|
if (canAccessEnergy(user?.roles)) result.push(ENERGY_MODULE);
|
||||||
}
|
if (canAccessScheduling(user?.roles)) result.push(SCHEDULING_MODULE);
|
||||||
return BASE_MODULES;
|
return result;
|
||||||
}, [user?.roles]);
|
}, [user?.roles]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
userName: '本地开发',
|
userName: '本地开发',
|
||||||
permissionLevel: 'full',
|
permissionLevel: 'full',
|
||||||
depName: '',
|
depName: '',
|
||||||
roles: ['所有权限', 'BI-SCHEDULE-OPT', 'BI-ADMIN-FEEDBACK'],
|
roles: ['所有权限', 'BI-SCHEDULE-OPT', 'BI-ADMIN-FEEDBACK', 'BI-LEADER-ENERGY'],
|
||||||
},
|
},
|
||||||
error: null,
|
error: null,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export async function authMiddleware(c: Context, next: Next) {
|
|||||||
depCode: '',
|
depCode: '',
|
||||||
depName: '',
|
depName: '',
|
||||||
permissionLevel: 'full',
|
permissionLevel: 'full',
|
||||||
roles: ['所有权限', 'BI-SCHEDULE-OPT', 'BI-ADMIN-FEEDBACK'],
|
roles: ['所有权限', 'BI-SCHEDULE-OPT', 'BI-ADMIN-FEEDBACK', 'BI-LEADER-ENERGY'],
|
||||||
};
|
};
|
||||||
c.set('user', devUser);
|
c.set('user', devUser);
|
||||||
return next();
|
return next();
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ export {
|
|||||||
DEPT_ACCESS_ROLES,
|
DEPT_ACCESS_ROLES,
|
||||||
SCHEDULING_ACCESS_ROLES,
|
SCHEDULING_ACCESS_ROLES,
|
||||||
FEEDBACK_ADMIN_ROLES,
|
FEEDBACK_ADMIN_ROLES,
|
||||||
|
ENERGY_ACCESS_ROLES,
|
||||||
canAccessScheduling,
|
canAccessScheduling,
|
||||||
canManageFeedback,
|
canManageFeedback,
|
||||||
|
canAccessEnergy,
|
||||||
} from '../../shared/auth/roles.js';
|
} from '../../shared/auth/roles.js';
|
||||||
|
|||||||
@@ -2,9 +2,21 @@ import { Hono } from 'hono';
|
|||||||
import type { RowDataPacket } from 'mysql2';
|
import type { RowDataPacket } from 'mysql2';
|
||||||
import pool from '../../db.js';
|
import pool from '../../db.js';
|
||||||
import { cached } from './cache.js';
|
import { cached } from './cache.js';
|
||||||
|
import type { AuthUser } from '../../auth/types.js';
|
||||||
|
import { canAccessEnergy } from '../../auth/types.js';
|
||||||
|
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
|
|
||||||
|
// 模块级访问守卫:dev 旁路 auth 时 user 为 undefined,直接放行;
|
||||||
|
// 生产环境必须具备 BI-LEADER-ENERGY 或全量权限角色
|
||||||
|
app.use('*', async (c, next) => {
|
||||||
|
const user = (c as { get: (k: string) => unknown }).get('user') as AuthUser | undefined;
|
||||||
|
if (user && !canAccessEnergy(user.roles)) {
|
||||||
|
return c.json({ error: 'Forbidden: 能源管理访问需要 BI-LEADER-ENERGY 角色' }, 403);
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
|
||||||
const HYDROGEN_MIN_DATE = '2024-01-01';
|
const HYDROGEN_MIN_DATE = '2024-01-01';
|
||||||
|
|
||||||
// hydrogen_time 已是 CST 字面值,直接使用即可(不再 +8 小时)
|
// hydrogen_time 已是 CST 字面值,直接使用即可(不再 +8 小时)
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ export const SCHEDULING_ACCESS_ROLES = ['BI-SCHEDULE-OPT'];
|
|||||||
/** 反馈管理(管理员)访问角色 */
|
/** 反馈管理(管理员)访问角色 */
|
||||||
export const FEEDBACK_ADMIN_ROLES = ['BI-ADMIN-FEEDBACK'];
|
export const FEEDBACK_ADMIN_ROLES = ['BI-ADMIN-FEEDBACK'];
|
||||||
|
|
||||||
|
/** 能源管理模块访问角色 */
|
||||||
|
export const ENERGY_ACCESS_ROLES = ['BI-LEADER-ENERGY'];
|
||||||
|
|
||||||
/** 用户是否可访问智能调度模块。仅 BI-SCHEDULE-OPT 角色允许访问。 */
|
/** 用户是否可访问智能调度模块。仅 BI-SCHEDULE-OPT 角色允许访问。 */
|
||||||
export function canAccessScheduling(roles: readonly string[] | null | undefined): boolean {
|
export function canAccessScheduling(roles: readonly string[] | null | undefined): boolean {
|
||||||
if (!roles || roles.length === 0) return false;
|
if (!roles || roles.length === 0) return false;
|
||||||
@@ -24,3 +27,9 @@ export function canManageFeedback(roles: readonly string[] | null | undefined):
|
|||||||
if (!roles || roles.length === 0) return false;
|
if (!roles || roles.length === 0) return false;
|
||||||
return roles.some(r => FEEDBACK_ADMIN_ROLES.includes(r) || FULL_ACCESS_ROLES.includes(r));
|
return roles.some(r => FEEDBACK_ADMIN_ROLES.includes(r) || FULL_ACCESS_ROLES.includes(r));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 用户是否可访问能源管理模块。BI-LEADER-ENERGY 或全量权限角色可访问。 */
|
||||||
|
export function canAccessEnergy(roles: readonly string[] | null | undefined): boolean {
|
||||||
|
if (!roles || roles.length === 0) return false;
|
||||||
|
return roles.some(r => ENERGY_ACCESS_ROLES.includes(r) || FULL_ACCESS_ROLES.includes(r));
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user