feat(auth): 后台登录、角色权限与会话访问策略
管理员与客服分角色登录,权限由后端判定;前端补齐登录、人员与组织管理页面。
This commit is contained in:
1 parent
9eb8613c05
commit
556b650c7a
123 files changed
+25293
No files matched your search
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
Copyright 2024-2026 ReplyPier
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Groups from the leaf module, not from permissions.ts: this map is built at
|
||||
// module scope, and permissions.ts sits inside the router import cycle.
|
||||
import {
|
||||
AGENT_PERMISSIONS,
|
||||
AI_CONFIG_PERMISSIONS,
|
||||
CHAT_VIEW_PERMISSIONS,
|
||||
KNOWLEDGE_PERMISSIONS,
|
||||
ORGANIZATION_PERMISSIONS,
|
||||
PEOPLE_PERMISSIONS,
|
||||
TICKET_PERMISSIONS,
|
||||
} from '@/utils/permissionGroups'
|
||||
// Only ever called at runtime, so a partially-initialised module is harmless.
|
||||
import { hasAnyPermission } from '@/utils/permissions'
|
||||
|
||||
/**
|
||||
* The one place a route's required permissions are written down.
|
||||
*
|
||||
* Both the router guard and the sidebar (navItems `show`) read this. They used to name permissions independently and had drifted: People
|
||||
* was listed in the nav for anyone with a chat grant but the route required
|
||||
* view_people, so an assigned-chats-only agent saw a link that bounced them.
|
||||
*
|
||||
* An empty array means "any authenticated user".
|
||||
*/
|
||||
export const ROUTE_PERMISSIONS: Record<string, readonly string[]> = {
|
||||
'/ai-agents': AGENT_PERMISSIONS,
|
||||
'/analytics': ['view_analytics'],
|
||||
'/knowledge': KNOWLEDGE_PERMISSIONS,
|
||||
// manage_knowledge, not the read pair: every /help-center endpoint the page
|
||||
// calls requires manage_knowledge, so a view_knowledge role would get the
|
||||
// link and a page that 403s on its first request.
|
||||
'/faq': ['manage_knowledge'],
|
||||
'/tickets': TICKET_PERMISSIONS,
|
||||
'/tickets/:id': TICKET_PERMISSIONS,
|
||||
'/settings/ticketing': ['manage_organization'],
|
||||
'/conversations': CHAT_VIEW_PERMISSIONS,
|
||||
'/people': PEOPLE_PERMISSIONS,
|
||||
'/human-agents': ['manage_users'],
|
||||
'/settings/organization': ORGANIZATION_PERMISSIONS,
|
||||
'/settings/ai-config': AI_CONFIG_PERMISSIONS,
|
||||
'/settings/integrations': ['manage_organization'],
|
||||
'/settings/app-integrations': ['manage_organization'],
|
||||
'/settings/widget-apps': ['manage_organization'],
|
||||
// Everyone can reach their own profile, and 403 must never deny itself or
|
||||
// the guard has nowhere to send anyone.
|
||||
'/settings/user': [],
|
||||
'/403': [],
|
||||
}
|
||||
|
||||
// Deliberately absent: /settings/subscription and its billing-setup child.
|
||||
// The enterprise subscription guard redirects an expired org THERE, so gating
|
||||
// it here would ping-pong (guard sends you to billing, this sends you to /403,
|
||||
// guard sends you back) until Vue Router aborts. The nav hides it via
|
||||
// canViewSubscription instead; gating the route needs the guard fixed first,
|
||||
// which is an enterprise-submodule change.
|
||||
|
||||
/** Shopify embedded routes authenticate by session token, not by user role. */
|
||||
const isShopifyPath = (path: string) => path.startsWith('/shopify')
|
||||
|
||||
/**
|
||||
* Whether the current user may open `path`.
|
||||
*
|
||||
* Unknown paths are allowed: the map lists what is restricted, so a public or
|
||||
* not-yet-mapped route must not become unreachable by omission.
|
||||
*/
|
||||
export function canAccessPath(path: string): boolean {
|
||||
if (isShopifyPath(path)) return true
|
||||
const required = ROUTE_PERMISSIONS[path]
|
||||
if (!required || required.length === 0) return true
|
||||
return hasAnyPermission([...required])
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the user may open a resolved route.
|
||||
*
|
||||
* Takes the matched record paths rather than the URL so a parameterised route
|
||||
* is looked up by its pattern ('/tickets/:id'), not by '/tickets/42'.
|
||||
*/
|
||||
export function canAccessMatchedPaths(matchedPaths: string[]): boolean {
|
||||
return matchedPaths.every(canAccessPath)
|
||||
}
|
||||
Reference in new issue
Block a user