- Replace admin role with granular roles: view, create, delete, edit, run - Two binding types: resource bindings (role+resource+optional name) and operation bindings (role:run + action like backup, logs, impersonate) - Name-scoped resource bindings for per-instance access control - Remove role from project members (all permissions via RBAC) - Add users, groups, RBAC CRUD endpoints and CLI commands - describe user/group shows all RBAC access (direct + inherited) - create rbac supports --subject, --binding, --operation flags - Backup/restore handles users, groups, RBAC definitions - mcplocal project-based MCP endpoint discovery - Full test coverage for all new functionality Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
61 lines
2.0 KiB
TypeScript
61 lines
2.0 KiB
TypeScript
import type { FastifyInstance } from 'fastify';
|
|
import type { BackupService } from '../services/backup/backup-service.js';
|
|
import type { RestoreService } from '../services/backup/restore-service.js';
|
|
import type { BackupBundle, BackupOptions } from '../services/backup/backup-service.js';
|
|
import type { ConflictStrategy, RestoreOptions } from '../services/backup/restore-service.js';
|
|
|
|
export interface BackupDeps {
|
|
backupService: BackupService;
|
|
restoreService: RestoreService;
|
|
}
|
|
|
|
export function registerBackupRoutes(app: FastifyInstance, deps: BackupDeps): void {
|
|
app.post<{
|
|
Body: {
|
|
password?: string;
|
|
resources?: Array<'servers' | 'secrets' | 'projects' | 'users' | 'groups' | 'rbac'>;
|
|
};
|
|
}>('/api/v1/backup', async (request) => {
|
|
const opts: BackupOptions = {};
|
|
if (request.body?.password) {
|
|
opts.password = request.body.password;
|
|
}
|
|
if (request.body?.resources) {
|
|
opts.resources = request.body.resources;
|
|
}
|
|
const bundle = await deps.backupService.createBackup(opts);
|
|
return bundle;
|
|
});
|
|
|
|
app.post<{
|
|
Body: {
|
|
bundle: BackupBundle;
|
|
password?: string;
|
|
conflictStrategy?: ConflictStrategy;
|
|
};
|
|
}>('/api/v1/restore', async (request, reply) => {
|
|
const { bundle, password, conflictStrategy } = request.body;
|
|
|
|
if (!deps.restoreService.validateBundle(bundle)) {
|
|
reply.code(400);
|
|
return { error: 'Invalid backup bundle format', statusCode: 400 };
|
|
}
|
|
|
|
const restoreOpts: RestoreOptions = {};
|
|
if (password) {
|
|
restoreOpts.password = password;
|
|
}
|
|
if (conflictStrategy) {
|
|
restoreOpts.conflictStrategy = conflictStrategy;
|
|
}
|
|
|
|
const result = await deps.restoreService.restore(bundle, restoreOpts);
|
|
|
|
if (result.errors.length > 0 && result.serversCreated === 0 && result.secretsCreated === 0 && result.projectsCreated === 0 && result.usersCreated === 0 && result.groupsCreated === 0 && result.rbacCreated === 0) {
|
|
reply.code(422);
|
|
}
|
|
|
|
return result;
|
|
});
|
|
}
|