Permissions are a fixed list from plugin code; grant them on Users and Groups
This commit is contained in:
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import {
|
||||
checkApiVersion,
|
||||
declaredPermissions,
|
||||
definePlugin,
|
||||
findConflicts,
|
||||
HOST_API_VERSION,
|
||||
@@ -58,6 +59,18 @@ test("isValidPermissionName requires <resource>:<action> — a bare word names a
|
||||
}
|
||||
});
|
||||
|
||||
test("declaredPermissions is the catalog: every plugin's declarations, deduped by name and sorted", () => {
|
||||
const a: Plugin = { apiVersion: "1.0.0", id: "a", permissions: [{ description: "Write things", name: "things:write" }, { description: "Read things", name: "things:read" }] };
|
||||
const b: Plugin = { apiVersion: "1.0.0", id: "b", permissions: [{ description: "b's wording", name: "things:read" }, { name: "orders:read" }] };
|
||||
const c: Plugin = { apiVersion: "1.0.0", id: "c" }; // declaring none is fine
|
||||
|
||||
const catalog = declaredPermissions([a, b, c]);
|
||||
assert.deepEqual(catalog.map((p) => p.name), ["orders:read", "things:read", "things:write"]);
|
||||
// A shared name is legitimate (findConflicts only warns); the first declaration wins its wording.
|
||||
assert.equal(catalog.find((p) => p.name === "things:read")?.description, "Read things");
|
||||
assert.deepEqual(declaredPermissions([]), []);
|
||||
});
|
||||
|
||||
test("parseSemver follows the semver core, rejecting ranges, prefixes, leading zeros and missing parts", () => {
|
||||
assert.deepEqual(parseSemver("1.2.3"), { major: 1, minor: 2, patch: 3 });
|
||||
assert.deepEqual(parseSemver("1.2.3-rc.1+build.5"), { major: 1, minor: 2, patch: 3 }); // prerelease/build tolerated, ignored
|
||||
|
||||
@@ -54,6 +54,18 @@ export function isValidPermissionName(name: string): boolean {
|
||||
return name.length <= 64 && PERMISSION_NAME.test(name);
|
||||
}
|
||||
|
||||
// Every permission the installed plugins declare, deduped by name and sorted — the fixed list the
|
||||
// admin screens offer when granting. Permissions are authored in code, never invented in the GUI, so
|
||||
// this *is* the catalog; a name in Keto that no plugin declares gates nothing and is not offered.
|
||||
// First declaration of a name wins its description (shared names are legitimate, findConflicts warns).
|
||||
export function declaredPermissions(plugins: Plugin[]): PermissionDecl[] {
|
||||
const byName = new Map<string, PermissionDecl>();
|
||||
for (const plugin of plugins) {
|
||||
for (const decl of plugin.permissions ?? []) if (!byName.has(decl.name)) byName.set(decl.name, decl);
|
||||
}
|
||||
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
// Optional hooks on system actions. Crash-isolation is a non-goal — a throwing hook fails loud.
|
||||
export interface PluginHooks {
|
||||
onBoot?: () => Promise<void> | void; // after discovery, before the server listens
|
||||
|
||||
Reference in New Issue
Block a user