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 <[email protected]>
This commit is contained in:
2026-08-15 09:39:51 +08:00
co-authored by Claude Opus 5
parent ae29e7b2fc
commit 6ebf21ddd6
5 changed files with 403 additions and 274 deletions
+29
View File
@@ -0,0 +1,29 @@
import { api } from '@/api/client'
import type { ApiResponse } from '@/api/types'
/**
* Operator consoles that live outside Laravel (database admin, ...) but are
* gated by our own admin permissions. The backend registry is
* config/internal_surfaces.php; the surface id must match a key there.
*/
export type InternalSurface = 'db-studio'
export interface InternalSurfaceSession {
kind: 'internal_surface_session'
surface: InternalSurface
label: string
/** single-use entry link — redeem it in the browser before it expires */
url: string
expires_in: number
}
/**
* Exchanges the admin bearer token for a single-use entry link. The link sets
* an HttpOnly session cookie on the surface's own hostname, so it must be
* opened in the browser rather than fetched.
*/
export function openInternalSurface(surface: InternalSurface) {
return api.post<ApiResponse<InternalSurfaceSession>>(
`/api/admin/v1/internal-surfaces/${surface}/session`,
)
}