From 63369de7eb071fbb4aac84e94b470e7c680f96bb Mon Sep 17 00:00:00 2001 From: John Leider Date: Mon, 20 Jul 2026 11:25:03 -0500 Subject: [PATCH] chore(permissions): gate key actions with usePermissions Wire the already-registered permissions plugin into real product flows. Expand the role matrix to exact action/subject pairs (adapter has no wildcard expansion), persist the demo role via useStorage, and hide create/rotate/revoke UI that the current role cannot perform. --- src/components/DkTable.vue | 29 +++++--- src/composables/useRole.ts | 18 +++++ src/pages/DashboardPage.vue | 134 +++++++++++++++++++++++++++++++----- src/plugins/devkey.ts | 14 +++- 4 files changed, 165 insertions(+), 30 deletions(-) create mode 100644 src/composables/useRole.ts diff --git a/src/components/DkTable.vue b/src/components/DkTable.vue index c1d78b3..1992b9c 100644 --- a/src/components/DkTable.vue +++ b/src/components/DkTable.vue @@ -10,8 +10,14 @@ defineOptions({ name: 'DkTable' }) - const { items } = defineProps<{ + const { + items, + canRotate = true, + canRevoke = true, + } = defineProps<{ items: ApiKey[] + canRotate?: boolean + canRevoke?: boolean }>() const emit = defineEmits<{ @@ -80,7 +86,7 @@
- + + + diff --git a/src/composables/useRole.ts b/src/composables/useRole.ts new file mode 100644 index 0000000..79d9918 --- /dev/null +++ b/src/composables/useRole.ts @@ -0,0 +1,18 @@ +import { useStorage } from '@vuetify/v0' +import type { Ref } from 'vue' + +export type Role = 'admin' | 'developer' | 'viewer' + +export const ROLES: { id: Role, label: string, value: Role }[] = [ + { id: 'admin', label: 'Admin', value: 'admin' }, + { id: 'developer', label: 'Developer', value: 'developer' }, + { id: 'viewer', label: 'Viewer', value: 'viewer' }, +] + +/** Persisted demo role for usePermissions checks. Default admin so lifecycle actions work out of the box. */ +export function useRole () { + const storage = useStorage() + const role = storage.get('devkey:role', 'admin') as Ref + + return { role } +} diff --git a/src/pages/DashboardPage.vue b/src/pages/DashboardPage.vue index 88bd8f1..6ff106b 100644 --- a/src/pages/DashboardPage.vue +++ b/src/pages/DashboardPage.vue @@ -1,6 +1,6 @@ @@ -315,4 +366,49 @@ .dk-dashboard__tab-desc { color: var(--v0-theme-muted); } + + .dk-dashboard__tab-desc code { + font-size: 0.875em; + color: var(--v0-theme-primary); + } + + .dk-dashboard__settings { + display: flex; + flex-direction: column; + gap: 1rem; + } + + .dk-dashboard__setting { + display: flex; + flex-direction: column; + gap: 0.5rem; + max-width: 20rem; + } + + .dk-dashboard__setting-label { + font-size: 0.875rem; + font-weight: 500; + color: var(--v0-theme-text); + } + + .dk-dashboard__perm-list { + list-style: none; + padding: 0; + margin: 0.25rem 0 0; + display: flex; + flex-direction: column; + gap: 0.25rem; + font-size: 0.8125rem; + color: var(--v0-theme-muted); + } + + .dk-dashboard__perm-list li::before { + content: '✗ '; + color: var(--v0-theme-error); + } + + .dk-dashboard__perm-list li[data-allowed]::before { + content: '✓ '; + color: var(--v0-theme-success); + } diff --git a/src/plugins/devkey.ts b/src/plugins/devkey.ts index 235f125..c1152e4 100644 --- a/src/plugins/devkey.ts +++ b/src/plugins/devkey.ts @@ -7,12 +7,20 @@ import { import type { App } from 'vue' export default function devkey (app: App) { + // Exact action/subject pairs — the default adapter looks up + // `${role}.${action}.${subject}` literally (no wildcard expansion). app.use( createPermissionsPlugin({ permissions: { - admin: [['manage', '*']], - developer: [['read', 'keys'], ['create', 'keys']], - viewer: [['read', 'keys']], + admin: [ + [['read', 'create', 'rotate', 'revoke'], 'keys'], + ], + developer: [ + [['read', 'create', 'rotate'], 'keys'], + ], + viewer: [ + ['read', 'keys'], + ], }, }) )