fix(api): пускать к бэкапу и миграции только глобального админа
Скачивание и восстановление файла БД, JSON-бэкап и legacy-импорт затрагивают все spaces и содержат учётные данные API, поэтому прав vps:settings:admin в одном space для них недостаточно. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
273883f657
commit
0b9b1cad54
3 files changed
+21
No files matched your search
@@ -186,6 +186,20 @@ export function requireSpaceRole(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whole-DB operations (backup download/restore, legacy import) cross every space and carry API credentials,
|
||||||
|
* so per-space settings admin is not enough.
|
||||||
|
*/
|
||||||
|
export function requireGlobalAdmin(request: FastifyRequest, reply: FastifyReply): boolean {
|
||||||
|
const user = request.authUser
|
||||||
|
if (!user) return true
|
||||||
|
if (user.isAdmin || hasPermission(user.permissions, 'vps:spaces:admin')) return true
|
||||||
|
void reply.code(403).send({
|
||||||
|
error: { code: 'FORBIDDEN', message: 'Операция доступна только глобальному администратору' },
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
export function canWriteInSpace(request: FastifyRequest): boolean {
|
export function canWriteInSpace(request: FastifyRequest): boolean {
|
||||||
const role = request.spaceRole ?? 'viewer'
|
const role = request.spaceRole ?? 'viewer'
|
||||||
return roleAtLeast(role, 'member')
|
return roleAtLeast(role, 'member')
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
type FullBackupPayload,
|
type FullBackupPayload,
|
||||||
} from '../services/backup-full.js'
|
} from '../services/backup-full.js'
|
||||||
import { restartScheduler } from '../services/scheduler.js'
|
import { restartScheduler } from '../services/scheduler.js'
|
||||||
|
import { requireGlobalAdmin } from '../plugins/space.js'
|
||||||
|
|
||||||
/** Лимит тела для импорта бэкапа (Fastify default = 1 MiB → 413). */
|
/** Лимит тела для импорта бэкапа (Fastify default = 1 MiB → 413). */
|
||||||
function backupBodyLimitBytes(): number {
|
function backupBodyLimitBytes(): number {
|
||||||
@@ -29,6 +30,10 @@ function backupBodyLimitBytes(): number {
|
|||||||
export const backupRoutes: FastifyPluginAsync = async (app) => {
|
export const backupRoutes: FastifyPluginAsync = async (app) => {
|
||||||
const bodyLimit = backupBodyLimitBytes()
|
const bodyLimit = backupBodyLimitBytes()
|
||||||
|
|
||||||
|
app.addHook('preHandler', async (req, reply) => {
|
||||||
|
if (!requireGlobalAdmin(req, reply)) return reply
|
||||||
|
})
|
||||||
|
|
||||||
app.addContentTypeParser(
|
app.addContentTypeParser(
|
||||||
'application/octet-stream',
|
'application/octet-stream',
|
||||||
{ parseAs: 'buffer', bodyLimit },
|
{ parseAs: 'buffer', bodyLimit },
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import type { FastifyPluginAsync } from 'fastify'
|
import type { FastifyPluginAsync } from 'fastify'
|
||||||
import { consolidateAllProviderApiSources, getSqlite } from '@cfdm/db'
|
import { consolidateAllProviderApiSources, getSqlite } from '@cfdm/db'
|
||||||
|
import { requireGlobalAdmin } from '../plugins/space.js'
|
||||||
|
|
||||||
export const migrateRoutes: FastifyPluginAsync = async (app) => {
|
export const migrateRoutes: FastifyPluginAsync = async (app) => {
|
||||||
app.post('/api/migrate', async (req, reply) => {
|
app.post('/api/migrate', async (req, reply) => {
|
||||||
|
if (!requireGlobalAdmin(req, reply)) return reply
|
||||||
const data = req.body
|
const data = req.body
|
||||||
if (!data || typeof data !== 'object') {
|
if (!data || typeof data !== 'object') {
|
||||||
return reply.code(400).send({ error: { code: 'VALIDATION', message: 'Invalid payload' } })
|
return reply.code(400).send({ error: { code: 'VALIDATION', message: 'Invalid payload' } })
|
||||||
|
|||||||
Reference in new issue
Block a user