Files
gig-admin/src/auth/permissions.ts
kongkx 6ebf21ddd6 feat(db-studio): add gated entry to the database console
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>
2026-08-15 09:39:51 +08:00

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 : '/' })
}
}