@plutocms/pluto 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## [0.5.0](https://github.com/plutocms/pluto/compare/v0.4.0...v0.5.0) (2026-09-12)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* **permissions:** add capability registry and usePlutoPermissions composable ([#129](https://github.com/plutocms/pluto/issues/129)) ([c98419f](https://github.com/plutocms/pluto/commit/c98419fc967f8e20678389169e937db759c308bc))
|
|
10
|
+
|
|
4
11
|
## [0.4.0](https://github.com/plutocms/pluto/compare/v0.3.2...v0.4.0) (2026-09-12)
|
|
5
12
|
|
|
6
13
|
|
package/FEATURES.md
CHANGED
|
@@ -9,3 +9,6 @@ policies shared by every project here.
|
|
|
9
9
|
- Extension registry: a typed, ordered, reactive registry that lets a layer plug its admin-UI
|
|
10
10
|
pieces (navbar, sidebar nav, pages, dashboard widgets, settings panels and driver) into core
|
|
11
11
|
with one function call. @.claude/skills/extension-registry/SKILL.md
|
|
12
|
+
- Permissions: a typed capability registry and a `usePlutoPermissions()` composable that let a
|
|
13
|
+
layer declare capabilities and check what the current user holds, backed by a registered
|
|
14
|
+
permissions driver. @.claude/skills/permissions/SKILL.md
|
|
@@ -21,4 +21,9 @@ export function definePlutoExtension(definition: PlutoExtension): void {
|
|
|
21
21
|
definition.settingsDriver ? [definition.settingsDriver] : []
|
|
22
22
|
)
|
|
23
23
|
registry.dashboardWidgets.set(definition.id, definition.dashboardWidgets ?? [])
|
|
24
|
+
registry.capabilities.set(definition.id, definition.capabilities ?? [])
|
|
25
|
+
registry.permissionsDrivers.set(
|
|
26
|
+
definition.id,
|
|
27
|
+
definition.permissionsDriver ? [definition.permissionsDriver] : []
|
|
28
|
+
)
|
|
24
29
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** Capabilities declared by every layer, in registry order. */
|
|
2
|
+
export function usePlutoCapabilities() {
|
|
3
|
+
const registry = usePlutoRegistry()
|
|
4
|
+
return { items: registry.capabilities.list }
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The current user's capabilities, backed by whichever permissions driver
|
|
9
|
+
* is registered. `useState` is correct for the loaded list itself (plain
|
|
10
|
+
* strings, and /admin/** is client-only per nuxt.config.ts) — this
|
|
11
|
+
* mirrors usePlutoSettings()'s values/dirty state exactly. The driver
|
|
12
|
+
* registration itself goes through the registry, same as
|
|
13
|
+
* PlutoSettingsDriver.
|
|
14
|
+
*/
|
|
15
|
+
export function usePlutoPermissions() {
|
|
16
|
+
const registry = usePlutoRegistry()
|
|
17
|
+
const driver = computed(() => registry.permissionsDrivers.list.value.at(-1) ?? null)
|
|
18
|
+
|
|
19
|
+
const granted = useState<string[]>('pluto-permissions-granted', () => [])
|
|
20
|
+
const loaded = useState<boolean>('pluto-permissions-loaded', () => false)
|
|
21
|
+
const status = ref<'idle' | 'loading'>('idle')
|
|
22
|
+
|
|
23
|
+
async function load() {
|
|
24
|
+
if (!driver.value) {
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
status.value = 'loading'
|
|
29
|
+
try {
|
|
30
|
+
granted.value = await driver.value.load()
|
|
31
|
+
loaded.value = true
|
|
32
|
+
} finally {
|
|
33
|
+
status.value = 'idle'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function clear() {
|
|
38
|
+
granted.value = []
|
|
39
|
+
loaded.value = false
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* True when the user holds this capability.
|
|
44
|
+
*
|
|
45
|
+
* Fails OPEN when no permissions driver is registered — a site with no
|
|
46
|
+
* permissions backend also has no server-side guard, so hiding UI in
|
|
47
|
+
* that case would only be theatre, and would break a core-only
|
|
48
|
+
* install with nothing registered yet. Read `hasDriver` to tell the two
|
|
49
|
+
* cases apart if a caller needs to.
|
|
50
|
+
*
|
|
51
|
+
* This is never a security boundary by itself. The server-side guard
|
|
52
|
+
* (a backend layer's own requireCapability-equivalent, backed by RLS)
|
|
53
|
+
* is what actually enforces anything. This only decides what the UI
|
|
54
|
+
* shows.
|
|
55
|
+
*/
|
|
56
|
+
function can(capability: string): boolean {
|
|
57
|
+
if (!driver.value) {
|
|
58
|
+
return true
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return granted.value.includes('*') || granted.value.includes(capability)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
granted,
|
|
66
|
+
loaded,
|
|
67
|
+
status,
|
|
68
|
+
hasDriver: computed(() => Boolean(driver.value)),
|
|
69
|
+
isAdmin: computed(() => granted.value.includes('*')),
|
|
70
|
+
load,
|
|
71
|
+
clear,
|
|
72
|
+
can,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -21,6 +21,8 @@ export function usePlutoRegistry(): PlutoRegistry {
|
|
|
21
21
|
settingsPanels: createOwnedRegistry<PlutoSettingsPanel>(),
|
|
22
22
|
settingsDrivers: createOwnedRegistry<PlutoSettingsDriver>(),
|
|
23
23
|
dashboardWidgets: createOwnedRegistry<PlutoDashboardWidget>(),
|
|
24
|
+
capabilities: createOwnedRegistry<PlutoCapability>(),
|
|
25
|
+
permissionsDrivers: createOwnedRegistry<PlutoPermissionsDriver>(),
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
|
package/package.json
CHANGED
package/shared/types/registry.ts
CHANGED
|
@@ -59,6 +59,32 @@ export interface PlutoSettingsDriver extends PlutoEntry {
|
|
|
59
59
|
save: (patch: Record<string, string>) => Promise<void>
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* A capability a layer defines. `key` is the real capability string, for
|
|
64
|
+
* example 'posts:publish'. It is kept separate from `id` on purpose: the
|
|
65
|
+
* registry rewrites `id` to '<extensionId>:<id>' (see registry-factory.ts),
|
|
66
|
+
* which would corrupt a capability string if `id` were reused as the
|
|
67
|
+
* capability itself. `id` identifies this registry entry; `key` identifies
|
|
68
|
+
* the permission.
|
|
69
|
+
*/
|
|
70
|
+
export interface PlutoCapability extends PlutoEntry {
|
|
71
|
+
/** Format: '<namespace>:<action>', lowercase, for example 'posts:publish'. */
|
|
72
|
+
key: string
|
|
73
|
+
label: string
|
|
74
|
+
description?: string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Loads the current user's capability list. One backend layer registers
|
|
79
|
+
* this, the same way one layer registers a PlutoSettingsDriver. `load`
|
|
80
|
+
* returns a flat list of capability key strings the user holds. The
|
|
81
|
+
* single entry '*' means "every capability" (used by the built-in admin
|
|
82
|
+
* role).
|
|
83
|
+
*/
|
|
84
|
+
export interface PlutoPermissionsDriver extends PlutoEntry {
|
|
85
|
+
load: () => Promise<string[]>
|
|
86
|
+
}
|
|
87
|
+
|
|
62
88
|
export interface PlutoExtension {
|
|
63
89
|
/** Layer name. Use the package's $meta.name, e.g. 'supabase-blog'. */
|
|
64
90
|
id: string
|
|
@@ -69,6 +95,8 @@ export interface PlutoExtension {
|
|
|
69
95
|
dashboardWidgets?: PlutoDashboardWidget[]
|
|
70
96
|
settingsPanels?: PlutoSettingsPanel[]
|
|
71
97
|
settingsDriver?: PlutoSettingsDriver
|
|
98
|
+
capabilities?: PlutoCapability[]
|
|
99
|
+
permissionsDriver?: PlutoPermissionsDriver
|
|
72
100
|
}
|
|
73
101
|
|
|
74
102
|
/**
|
|
@@ -91,4 +119,6 @@ export interface PlutoRegistry {
|
|
|
91
119
|
settingsPanels: PlutoRegistryBucket<PlutoSettingsPanel>
|
|
92
120
|
settingsDrivers: PlutoRegistryBucket<PlutoSettingsDriver>
|
|
93
121
|
dashboardWidgets: PlutoRegistryBucket<PlutoDashboardWidget>
|
|
122
|
+
capabilities: PlutoRegistryBucket<PlutoCapability>
|
|
123
|
+
permissionsDrivers: PlutoRegistryBucket<PlutoPermissionsDriver>
|
|
94
124
|
}
|