Adds 开发工具 → 数据库管理台, which exchanges the admin bearer token for a single-use entry link (POST /api/admin/v1/internal-surfaces/db-studio/session) and opens it in a new tab. The link sets an HttpOnly session cookie on the console's own hostname, so it has to be opened by the browser rather than fetched — the nginx gate in front of the console then re-checks the permission on every request. The tab is opened synchronously inside the click handler, before the request resolves, or the popup blocker kills it; `noopener` is unusable there since it makes window.open return null, so the opener is detached manually. Gated on auth:db-studio:access. That key is `auth:`-prefixed rather than `admin:` on purpose — PermissionSeeder syncs the admin role to every `admin:%` key, which would grant database-owner SQL access to every admin account. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
62 lines
2.4 KiB
TypeScript
62 lines
2.4 KiB
TypeScript
import { redirect } from '@tanstack/react-router'
|
|
import { can, useAuthStore } from '@/auth/store'
|
|
import { getFirstAccessibleRoute } from '@/components/layout/nav'
|
|
|
|
/**
|
|
* Admin permission keys — mirrors the backend catalog
|
|
* (Modules/{Module}/app/Permissions/{Module}Permissions.php).
|
|
*/
|
|
export const PERM = {
|
|
CATEGORY_READ: 'admin:category:read',
|
|
CATEGORY_CREATE: 'admin:category:create',
|
|
CATEGORY_UPDATE: 'admin:category:update',
|
|
CATEGORY_DELETE: 'admin:category:delete',
|
|
CATEGORY_APPROVE: 'admin:category:approve',
|
|
COMMENT_READ: 'admin:comment:read',
|
|
COMMENT_MODERATE: 'admin:comment:moderate',
|
|
FILEX_READ: 'admin:filex:read',
|
|
FILEX_MODERATE: 'admin:filex:moderate',
|
|
DIMZOU_READ: 'admin:dimzou:read',
|
|
DIMZOU_RETRANSLATE: 'admin:dimzou:retranslate',
|
|
EXC_READ: 'admin:exc:read',
|
|
STUDIO_READ: 'admin:studio:read',
|
|
STUDIO_MANAGE: 'admin:studio:manage',
|
|
STUDIO_AGENT_TOKEN_ISSUE: 'admin:studio:agent-token:issue',
|
|
LOCALE_READ: 'admin:locale:read',
|
|
LOCALE_CREATE: 'admin:locale:create',
|
|
LOCALE_UPDATE: 'admin:locale:update',
|
|
LOCALE_DELETE: 'admin:locale:delete',
|
|
SID_MANAGE: 'admin:sid:manage',
|
|
SID_OVERRIDE: 'admin:sid:override',
|
|
NOTIFICATION_READ: 'admin:notification:read',
|
|
ROBOT_TOKEN_ISSUE: 'auth:robot-token:issue',
|
|
API_DOCS_READ: 'auth:api-docs:read',
|
|
// internal operator consoles — `auth:` prefixed, so super-admin only
|
|
// (the `admin` role is synced to admin:% keys and would otherwise inherit it)
|
|
DB_STUDIO_ACCESS: 'auth:db-studio:access',
|
|
// role management — only super-admin holds these
|
|
ROLE_READ: 'auth:role:read',
|
|
ROLE_CREATE: 'auth:role:create',
|
|
ROLE_UPDATE: 'auth:role:update',
|
|
ROLE_DELETE: 'auth:role:delete',
|
|
ROLE_ASSIGN: 'auth:role:assign',
|
|
PERMISSION_READ: 'auth:permission:read',
|
|
USER_READ: 'admin:user:read',
|
|
USER_MANAGE: 'admin:user:manage',
|
|
} as const
|
|
|
|
export type Permission = (typeof PERM)[keyof typeof PERM]
|
|
|
|
/**
|
|
* Route `beforeLoad` guard. Redirects home when the session is loaded and
|
|
* lacks the permission; lets the request through while the session is still
|
|
* empty (the API's 403 is the backstop, and /auth/me refreshes the store).
|
|
*/
|
|
export function requirePermission(permission: Permission): void {
|
|
const { user } = useAuthStore.getState()
|
|
if (user && !can(permission)) {
|
|
const target = getFirstAccessibleRoute()
|
|
throw redirect({ to: target && target !== '/' ? target : '/' })
|
|
}
|
|
}
|