Repository navigation
fix: use target route workspaceId in permission guard #7865
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,40 @@ | ||
| /** 提供当前路由资源范围和工作空间上下文的读取函数。 */ | ||
|
|
||
| import router from '@/router/admin' | ||
| import type { RouteLocationNormalized, RouteLocationNormalizedLoaded, RouteLocationResolvedGeneric } from 'vue-router' | ||
|
|
||
| type WorkspaceRouteLike = RouteLocationNormalized | RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric | ||
|
|
||
| let hasRouteWorkspace = false | ||
| let routeWorkspaceId: string | undefined | ||
|
|
||
| const readWorkspaceId = (route: WorkspaceRouteLike): string | undefined => { | ||
| const value = route.params.workspaceId | ||
| return Array.isArray(value) ? value[0] : value | ||
| } | ||
|
|
||
| /** | ||
| * 获取当前工作空间 id。 | ||
| * 当前实现取自路由参数;不在工作空间上下文下时返回 undefined。 | ||
| * 取自目标路由参数;不在工作空间上下文下时返回 undefined。 | ||
| */ | ||
| export const getWorkspaceId = (): string | undefined => { | ||
| const value = router.currentRoute.value.params.workspaceId | ||
| return Array.isArray(value) ? value[0] : value | ||
| export const getWorkspaceId = (): string | undefined => (hasRouteWorkspace ? routeWorkspaceId : readWorkspaceId(router.currentRoute.value)) | ||
|
|
||
| /** | ||
| * 在给定路由的工作空间上下文内执行回调,期间 getWorkspaceId 使用该路由的 workspaceId。 | ||
| * 用于路由守卫评估目标路由权限:此时 router.currentRoute 尚未更新为待跳转路由, | ||
| * 需要以被评估路由的参数为准,避免读到上一条(或初始 START)路由导致结果错误。 | ||
| */ | ||
| export const withRouteWorkspace = <T>(route: WorkspaceRouteLike, fn: () => T): T => { | ||
| const prevHas = hasRouteWorkspace | ||
| const prevId = routeWorkspaceId | ||
| hasRouteWorkspace = true | ||
| routeWorkspaceId = readWorkspaceId(route) | ||
| try { | ||
| return fn() | ||
| } finally { | ||
| hasRouteWorkspace = prevHas | ||
| routeWorkspaceId = prevId | ||
| } | ||
| } | ||
|
|
||
| /** 判断当前路由是否属于工作空间资源。 */ | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code is syntactically valid, and the synchronous nested save/restore logic in Main issues
Recommended approachAvoid the global override and make the route/workspace explicit: type WorkspaceRouteLike = Pick<RouteLocationNormalized, 'params'>
const readWorkspaceId = (route: WorkspaceRouteLike): string | undefined => {
const value = route.params.workspaceId
return Array.isArray(value) ? value[0] : value
}
export const getWorkspaceId = (
route: WorkspaceRouteLike = router.currentRoute.value,
): string | undefined => readWorkspaceId(route)Then use it in guards as: router.beforeEach((to) => {
const workspaceId = getWorkspaceId(to)
// check permissions with workspaceId
})This removes global mutable state entirely and works correctly with async guards. If you must keep
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review
Overall the diff is small and the indentation fix is welcome, but the change adds a non-trivial dependency (
withRouteWorkspace) into a security-relevant guard, so a few things are worth verifying before merging.1. Correctness / security (highest priority)
!to.meta?.permission || withRouteWorkspace(to, () => perm(to))relies on the wrapper returning the boolean result of the inner function. IfwithRouteWorkspaceisasync, or is written asfunction withRouteWorkspace(route, fn) { setContext(route); fn(); restore(); }(returningundefined), then:undefined→ falsy → every guarded route is denied, including valid ones.Please double-check (and ideally type it as
withRouteWorkspace<T>(route, fn: () => T): T).finally. If the permission function throws (or if any route permission callback is buggy), a non-finallyrestore will leak the workspace context into subsequent navigations/checks.toactually belongs to the user. Since the wrapper presumably derives context from the route (params/query), a crafted URL pointing at another tenant's workspace could make the permission callback evaluate against the wrong/foreign context. The wrapper should treat an unknown/unauthorized workspace as "no context" rather than silently accepting it.withRouteWorkspace. If it throws or sets a bogus context for those, system routes will break.getChildRouteList('system').some((item) => item.permission ? item.permission() : true)is not wrapped. If permission callbacks now depend on resource context, this check can behave differently from the main check. Either wrap it too (with a sensible route/context) or document why it's exempt.2. Robustness
beforeEachhas notry/catcharound the permission evaluation. A throw from the wrapper (e.g., malformed param) will reject the navigation and surface as an uncaught router error. Consider catching and doingreturn { name: 'error-...' }/return false, or guarantee the wrapper never throws.nextPermission. Make the two call sites behave consistently.3. Types
nextPermissionis typed as(route?: RouteLocationNormalized | RouteLocationResolvedGeneric | RouteLocationNormalizedLoaded) => boolean, but inside the loop it's called withnextRouteof that union type.withRouteWorkspace(nextRoute, ...)must accept the same union, not onlyRouteLocationNormalizedLoaded. Check the wrapper's signature.to.meta.permission as (route: RouteLocationNormalizedLoaded) => booleanis applied toto, which inbeforeEachisRouteLocationNormalized. Pre-existing, but the new wrapper makes the mismatch more visible — worth tightening with a shared type/type guard.4. Duplication / readability
withRouteWorkspace(route, () => perm(route))). Extract a small helper, e.g.:if (hasPermission(to)) return trueandif (hasPermission(nextRoute)) return {...}. This also keeps the wrapper contract in one place.5. Style / minor
@/utils/resource-contextis added after the relative imports. Most codebases group alias imports before local relative ones — align with the existing convention in the file.6. Testing suggestions
true/false, and navigations redirect correctly in both cases.