mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
Require auth on /plugin-api, gate plugin enable/grant/revoke routes behind admin.plugins.manage, and thread the calling user through worker ctx.hosts/ctx.ssh calls instead of always using the install owner. Also adds the plugin permissions grant/revoke UI.
125 lines
3.1 KiB
TypeScript
125 lines
3.1 KiB
TypeScript
// Single source of truth for role permission strings. The admin role editor
|
|
// renders this catalog and PUT /rbac/roles/:id validates against it;
|
|
// PermissionManager.hasPermission resolves wildcards ("*", "<group>.*").
|
|
export interface PermissionCatalogEntry {
|
|
group: string;
|
|
permissions: string[];
|
|
}
|
|
|
|
export const PERMISSION_CATALOG: PermissionCatalogEntry[] = [
|
|
{
|
|
group: "hosts",
|
|
permissions: [
|
|
"hosts.view",
|
|
"hosts.create",
|
|
"hosts.edit",
|
|
"hosts.delete",
|
|
"hosts.share",
|
|
],
|
|
},
|
|
{
|
|
group: "snippets",
|
|
permissions: [
|
|
"snippets.view",
|
|
"snippets.create",
|
|
"snippets.edit",
|
|
"snippets.delete",
|
|
"snippets.share",
|
|
],
|
|
},
|
|
{
|
|
group: "automations",
|
|
permissions: [
|
|
"automations.view",
|
|
"automations.create",
|
|
"automations.edit",
|
|
"automations.delete",
|
|
"automations.run",
|
|
],
|
|
},
|
|
{
|
|
group: "credentials",
|
|
permissions: [
|
|
"credentials.view",
|
|
"credentials.create",
|
|
"credentials.edit",
|
|
"credentials.delete",
|
|
"credentials.share",
|
|
],
|
|
},
|
|
{
|
|
group: "ai",
|
|
permissions: ["ai.use", "ai.manage_providers", "ai.apply_proposals"],
|
|
},
|
|
{
|
|
group: "admin",
|
|
permissions: [
|
|
"admin.users.view",
|
|
"admin.users.manage",
|
|
"admin.roles.manage",
|
|
"admin.settings.manage",
|
|
"admin.sessions.manage",
|
|
"admin.plugins.manage",
|
|
],
|
|
},
|
|
];
|
|
|
|
// Groups registered at runtime, e.g. by plugins. Kept separate from the
|
|
// static catalog so plugin state never mutates the built-in array.
|
|
const runtimePermissionGroups = new Map<string, PermissionCatalogEntry>();
|
|
|
|
let validPermissionsCache: Set<string> | null = null;
|
|
|
|
function buildValidPermissions(): Set<string> {
|
|
return new Set<string>(
|
|
[...PERMISSION_CATALOG, ...runtimePermissionGroups.values()]
|
|
.flatMap((entry) => [...entry.permissions, `${entry.group}.*`])
|
|
.concat("*"),
|
|
);
|
|
}
|
|
|
|
function getValidPermissions(): Set<string> {
|
|
if (!validPermissionsCache) {
|
|
validPermissionsCache = buildValidPermissions();
|
|
}
|
|
return validPermissionsCache;
|
|
}
|
|
|
|
export function registerPermissionGroup(entry: PermissionCatalogEntry): void {
|
|
runtimePermissionGroups.set(entry.group, entry);
|
|
validPermissionsCache = null;
|
|
}
|
|
|
|
export function unregisterPermissionGroup(group: string): void {
|
|
runtimePermissionGroups.delete(group);
|
|
validPermissionsCache = null;
|
|
}
|
|
|
|
// Static entries plus any groups registered at runtime.
|
|
export function getPermissionCatalog(): PermissionCatalogEntry[] {
|
|
return [...PERMISSION_CATALOG, ...runtimePermissionGroups.values()];
|
|
}
|
|
|
|
export function isValidPermission(permission: string): boolean {
|
|
return getValidPermissions().has(permission);
|
|
}
|
|
|
|
// What the seeded system roles grant. Applied only to a role row that has no
|
|
// permissions yet, so an admin's edits to these roles survive restarts.
|
|
export const SYSTEM_ROLE_DEFAULTS = {
|
|
admin: {
|
|
description: "Administrator with full access",
|
|
permissions: ["*"],
|
|
},
|
|
user: {
|
|
description: "Regular user",
|
|
permissions: [
|
|
"hosts.*",
|
|
"snippets.*",
|
|
"automations.*",
|
|
"credentials.*",
|
|
"ai.*",
|
|
],
|
|
},
|
|
} as const;
|