@seifer-webapp-factory/authorization 0.1.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/README.md +17 -0
- package/backend/templates/cache/ttl-cache.ts +41 -0
- package/backend/templates/config/config-fragment.ts +41 -0
- package/backend/templates/nestjs/authz.controller.ts +253 -0
- package/backend/templates/nestjs/authz.module.ts +158 -0
- package/backend/templates/nestjs/tokens.ts +41 -0
- package/backend/templates/persistence/migrations/0001_authz.sql +45 -0
- package/backend/templates/persistence/migrations/index.ts +84 -0
- package/backend/templates/persistence/pg-policy-store.ts +193 -0
- package/backend/templates/persistence/seed.ts +60 -0
- package/dist/backend/src/index.d.ts +10 -0
- package/dist/backend/src/index.d.ts.map +1 -0
- package/dist/backend/src/index.js +9 -0
- package/dist/backend/src/index.js.map +1 -0
- package/dist/backend/src/policy.d.ts +13 -0
- package/dist/backend/src/policy.d.ts.map +1 -0
- package/dist/backend/src/policy.js +49 -0
- package/dist/backend/src/policy.js.map +1 -0
- package/dist/backend/src/ports.d.ts +90 -0
- package/dist/backend/src/ports.d.ts.map +1 -0
- package/dist/backend/src/ports.js +2 -0
- package/dist/backend/src/ports.js.map +1 -0
- package/dist/backend/src/services.d.ts +81 -0
- package/dist/backend/src/services.d.ts.map +1 -0
- package/dist/backend/src/services.js +234 -0
- package/dist/backend/src/services.js.map +1 -0
- package/dist/contract/endpoints.d.ts +433 -0
- package/dist/contract/endpoints.d.ts.map +1 -0
- package/dist/contract/endpoints.js +57 -0
- package/dist/contract/endpoints.js.map +1 -0
- package/dist/contract/errors.d.ts +33 -0
- package/dist/contract/errors.d.ts.map +1 -0
- package/dist/contract/errors.js +51 -0
- package/dist/contract/errors.js.map +1 -0
- package/dist/contract/events.d.ts +50 -0
- package/dist/contract/events.d.ts.map +1 -0
- package/dist/contract/events.js +13 -0
- package/dist/contract/events.js.map +1 -0
- package/dist/contract/index.d.ts +10 -0
- package/dist/contract/index.d.ts.map +1 -0
- package/dist/contract/index.js +10 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/contract/permissions.d.ts +35 -0
- package/dist/contract/permissions.d.ts.map +1 -0
- package/dist/contract/permissions.js +37 -0
- package/dist/contract/permissions.js.map +1 -0
- package/dist/contract/schemas.d.ts +288 -0
- package/dist/contract/schemas.d.ts.map +1 -0
- package/dist/contract/schemas.js +91 -0
- package/dist/contract/schemas.js.map +1 -0
- package/dist/frontend/src/client.d.ts +31 -0
- package/dist/frontend/src/client.d.ts.map +1 -0
- package/dist/frontend/src/client.js +83 -0
- package/dist/frontend/src/client.js.map +1 -0
- package/dist/frontend/src/composables.d.ts +62 -0
- package/dist/frontend/src/composables.d.ts.map +1 -0
- package/dist/frontend/src/composables.js +170 -0
- package/dist/frontend/src/composables.js.map +1 -0
- package/dist/frontend/src/guards.d.ts +12 -0
- package/dist/frontend/src/guards.d.ts.map +1 -0
- package/dist/frontend/src/guards.js +10 -0
- package/dist/frontend/src/guards.js.map +1 -0
- package/dist/frontend/src/index.d.ts +12 -0
- package/dist/frontend/src/index.d.ts.map +1 -0
- package/dist/frontend/src/index.js +9 -0
- package/dist/frontend/src/index.js.map +1 -0
- package/dist/manifest.d.ts +56 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +100 -0
- package/dist/manifest.js.map +1 -0
- package/dist/scaffolder/core/config.d.ts +86 -0
- package/dist/scaffolder/core/config.d.ts.map +1 -0
- package/dist/scaffolder/core/config.js +92 -0
- package/dist/scaffolder/core/config.js.map +1 -0
- package/dist/scaffolder/core/errors.d.ts +46 -0
- package/dist/scaffolder/core/errors.d.ts.map +1 -0
- package/dist/scaffolder/core/errors.js +60 -0
- package/dist/scaffolder/core/errors.js.map +1 -0
- package/dist/scaffolder/core/extend.d.ts +86 -0
- package/dist/scaffolder/core/extend.d.ts.map +1 -0
- package/dist/scaffolder/core/extend.js +94 -0
- package/dist/scaffolder/core/extend.js.map +1 -0
- package/dist/scaffolder/core/materialize.d.ts +71 -0
- package/dist/scaffolder/core/materialize.d.ts.map +1 -0
- package/dist/scaffolder/core/materialize.js +47 -0
- package/dist/scaffolder/core/materialize.js.map +1 -0
- package/dist/scaffolder/core/ports.d.ts +39 -0
- package/dist/scaffolder/core/ports.d.ts.map +1 -0
- package/dist/scaffolder/core/ports.js +33 -0
- package/dist/scaffolder/core/ports.js.map +1 -0
- package/dist/scaffolder/core/presence.d.ts +34 -0
- package/dist/scaffolder/core/presence.d.ts.map +1 -0
- package/dist/scaffolder/core/presence.js +29 -0
- package/dist/scaffolder/core/presence.js.map +1 -0
- package/dist/scaffolder/core/three-way-merge.d.ts +113 -0
- package/dist/scaffolder/core/three-way-merge.d.ts.map +1 -0
- package/dist/scaffolder/core/three-way-merge.js +184 -0
- package/dist/scaffolder/core/three-way-merge.js.map +1 -0
- package/dist/scaffolder/index.d.ts +25 -0
- package/dist/scaffolder/index.d.ts.map +1 -0
- package/dist/scaffolder/index.js +24 -0
- package/dist/scaffolder/index.js.map +1 -0
- package/frontend/templates/components/PermissionMatrix.vue +134 -0
- package/frontend/templates/i18n/en.json +61 -0
- package/frontend/templates/i18n/nl.json +61 -0
- package/frontend/templates/middleware/permission.ts +54 -0
- package/frontend/templates/pages/access-assignments.vue +151 -0
- package/frontend/templates/pages/role-editor.vue +169 -0
- package/frontend/templates/pages/roles-list.vue +84 -0
- package/frontend/templates/plugins/authz.client.ts +108 -0
- package/frontend/templates/runtime.ts +60 -0
- package/package.json +76 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scaffolder (authorization) — de version-aware materializer van de authorization-capability-module.
|
|
3
|
+
*
|
|
4
|
+
* Composeert het manifest (US-Z0801/0802) tot een werkende generate + upgrade-merge, en levert de
|
|
5
|
+
* divergentie-ladder niveau 1–3 (configureren, uitbreiden, materialiseren & mergen). Nieuw t.o.v. de
|
|
6
|
+
* authentication-tier: de module→module presence-check (US-Z0803), want dit is de eerste module die
|
|
7
|
+
* van een ándere capability-module afhangt. Alle core-functies zijn puur/deterministisch; het
|
|
8
|
+
* wegschrijven en inlezen gaat via een geïnjecteerde fs-port. Geen module-globale mutable state, geen
|
|
9
|
+
* echt filesystem. Dit is een build/dev-tool, geen runtime-mechanisme.
|
|
10
|
+
*/
|
|
11
|
+
export { createMemoryFsPort } from './core/ports.js';
|
|
12
|
+
// Materialize (US-Z0803): surface stampen + all-or-nothing collision-refusal.
|
|
13
|
+
export { materializeAuthzModule, stampedVersions } from './core/materialize.js';
|
|
14
|
+
// Three-way-merge / upgrade (US-Z0804 / US-Z1004).
|
|
15
|
+
export { mergeSurface, upgradeSurface } from './core/three-way-merge.js';
|
|
16
|
+
// Presence-check (US-Z0803): module→module-afhankelijkheid.
|
|
17
|
+
export { checkRequiredModules, assertAssemblable } from './core/presence.js';
|
|
18
|
+
// Configure (US-Z1001): niveau-1-divergentie.
|
|
19
|
+
export { resolveAuthzConfig, filterSurfaceByConfig, authzConfigSchema } from './core/config.js';
|
|
20
|
+
// Extend (US-Z1002): niveau-2-divergentie (additief).
|
|
21
|
+
export { definePermissionExtension, createPolicyHookRegistry, defineRelationResolver, } from './core/extend.js';
|
|
22
|
+
// Fouten.
|
|
23
|
+
export { AuthzMaterializationConflictError, MissingRequiredPortError, MissingRequiredModuleError, AuthzConfigValidationError, } from './core/errors.js';
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../scaffolder/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,8EAA8E;AAC9E,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAQhF,mDAAmD;AACnD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAazE,4DAA4D;AAC5D,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAG7E,8CAA8C;AAC9C,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAOhG,sDAAsD;AACtD,OAAO,EACL,yBAAyB,EACzB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,kBAAkB,CAAC;AAW1B,UAAU;AACV,OAAO,EACL,iCAAiC,EACjC,wBAAwB,EACxB,0BAA0B,EAC1B,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* US-Z0205/US-Z0903 — PermissionMatrix (surface, design-system-agnostisch, herbruikbaar). Een
|
|
4
|
+
* toegankelijke, gelabelde `<table>` die de code-declared permissie-catalogus (US-Z0203/US-Z0104)
|
|
5
|
+
* als rijen toont met per rij een echte, toetsenbord-bedienbare checkbox-toggle. De component houdt
|
|
6
|
+
* géén eigen bron aan: hij krijgt de catalogus (`permissions`) en de geselecteerde sleutels
|
|
7
|
+
* (`modelValue`) via props en emit `update:modelValue` bij een wijziging (v-model-vorm) — geen
|
|
8
|
+
* transport, geen validatie (dat is de grens van de contract-client + forms-kit).
|
|
9
|
+
*
|
|
10
|
+
* Toegankelijkheid (P-UX, US-Z0205 AC2) is een invariant:
|
|
11
|
+
* - correct gelabelde tabel met `<caption>` en `scope`-gekoppelde rij-/kolom-headers;
|
|
12
|
+
* - elke toggle is een echte, met Tab bereikbare en met Space/Enter schakelbare checkbox met een
|
|
13
|
+
* eigen `<label>` (visueel verborgen) én een `aria-describedby` naar de omschrijvingscel;
|
|
14
|
+
* - elke gewijzigde toggle-status wordt via een `aria-live`-regio aangekondigd (geen kale sleutel).
|
|
15
|
+
*/
|
|
16
|
+
import { computed, ref } from 'vue';
|
|
17
|
+
import type { Permission } from '../../contract/index.js';
|
|
18
|
+
import { useTranslate } from '../runtime.js';
|
|
19
|
+
|
|
20
|
+
const props = withDefaults(
|
|
21
|
+
defineProps<{
|
|
22
|
+
/** De permissie-catalogus (rijen). Sleutels zijn stabiel; omschrijvingen zijn menselijke tekst. */
|
|
23
|
+
permissions: readonly Permission[];
|
|
24
|
+
/** De geselecteerde permissie-sleutels (v-model). */
|
|
25
|
+
modelValue: readonly string[];
|
|
26
|
+
/** Presentatie-vlag: schakelt de toggles uit (bv. tijdens opslaan). */
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
}>(),
|
|
29
|
+
{ disabled: false },
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const emit = defineEmits<{
|
|
33
|
+
(event: 'update:modelValue', value: string[]): void;
|
|
34
|
+
}>();
|
|
35
|
+
|
|
36
|
+
const t = useTranslate();
|
|
37
|
+
|
|
38
|
+
/** Snelle lookup van de geselecteerde sleutels (deny-by-default: onbekend => niet toegekend). */
|
|
39
|
+
const selected = computed(() => new Set(props.modelValue));
|
|
40
|
+
|
|
41
|
+
/** De laatst aangekondigde wijziging (screenreader-feedback via `aria-live`). */
|
|
42
|
+
const announcement = ref('');
|
|
43
|
+
|
|
44
|
+
function isGranted(key: string): boolean {
|
|
45
|
+
return selected.value.has(key);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Zet een permissie-toggle om en emit de nieuwe set; kondigt de wijziging toegankelijk aan. */
|
|
49
|
+
function toggle(key: string): void {
|
|
50
|
+
if (props.disabled) return;
|
|
51
|
+
const next = new Set(props.modelValue);
|
|
52
|
+
if (next.has(key)) {
|
|
53
|
+
next.delete(key);
|
|
54
|
+
announcement.value = t('authz.matrix.announceRevoked', { permission: key });
|
|
55
|
+
} else {
|
|
56
|
+
next.add(key);
|
|
57
|
+
announcement.value = t('authz.matrix.announceGranted', { permission: key });
|
|
58
|
+
}
|
|
59
|
+
emit('update:modelValue', [...next]);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Stabiele, unieke id per checkbox (voor `<label for>`-koppeling). */
|
|
63
|
+
function checkId(key: string): string {
|
|
64
|
+
return `authz-perm-${key}`;
|
|
65
|
+
}
|
|
66
|
+
/** Stabiele id per omschrijvingscel (voor `aria-describedby`). */
|
|
67
|
+
function descId(key: string): string {
|
|
68
|
+
return `authz-perm-desc-${key}`;
|
|
69
|
+
}
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<template>
|
|
73
|
+
<div class="authz-matrix">
|
|
74
|
+
<table class="authz-matrix__table">
|
|
75
|
+
<caption class="authz-matrix__caption">{{ t('authz.matrix.caption') }}</caption>
|
|
76
|
+
<thead>
|
|
77
|
+
<tr>
|
|
78
|
+
<th scope="col" class="authz-matrix__col authz-matrix__col--permission">
|
|
79
|
+
{{ t('authz.matrix.colPermission') }}
|
|
80
|
+
</th>
|
|
81
|
+
<th scope="col" class="authz-matrix__col authz-matrix__col--description">
|
|
82
|
+
{{ t('authz.matrix.colDescription') }}
|
|
83
|
+
</th>
|
|
84
|
+
<th scope="col" class="authz-matrix__col authz-matrix__col--granted">
|
|
85
|
+
{{ t('authz.matrix.colGranted') }}
|
|
86
|
+
</th>
|
|
87
|
+
</tr>
|
|
88
|
+
</thead>
|
|
89
|
+
<tbody>
|
|
90
|
+
<tr v-if="permissions.length === 0" class="authz-matrix__empty-row">
|
|
91
|
+
<td colspan="3" class="authz-matrix__empty">{{ t('authz.matrix.empty') }}</td>
|
|
92
|
+
</tr>
|
|
93
|
+
<tr
|
|
94
|
+
v-for="permission in permissions"
|
|
95
|
+
:key="permission.key"
|
|
96
|
+
class="authz-matrix__row"
|
|
97
|
+
>
|
|
98
|
+
<th scope="row" class="authz-matrix__key">
|
|
99
|
+
<code>{{ permission.key }}</code>
|
|
100
|
+
</th>
|
|
101
|
+
<td :id="descId(permission.key)" class="authz-matrix__description">
|
|
102
|
+
{{ permission.description }}
|
|
103
|
+
</td>
|
|
104
|
+
<td class="authz-matrix__cell">
|
|
105
|
+
<label class="authz-matrix__label" :for="checkId(permission.key)">
|
|
106
|
+
<span class="authz-matrix__label-text">
|
|
107
|
+
{{ t('authz.matrix.toggle', { permission: permission.key }) }}
|
|
108
|
+
</span>
|
|
109
|
+
</label>
|
|
110
|
+
<input
|
|
111
|
+
:id="checkId(permission.key)"
|
|
112
|
+
class="authz-matrix__checkbox"
|
|
113
|
+
type="checkbox"
|
|
114
|
+
:checked="isGranted(permission.key)"
|
|
115
|
+
:disabled="disabled"
|
|
116
|
+
:aria-describedby="descId(permission.key)"
|
|
117
|
+
:data-testid="`authz-perm-${permission.key}`"
|
|
118
|
+
@change="toggle(permission.key)"
|
|
119
|
+
/>
|
|
120
|
+
</td>
|
|
121
|
+
</tr>
|
|
122
|
+
</tbody>
|
|
123
|
+
</table>
|
|
124
|
+
|
|
125
|
+
<p
|
|
126
|
+
class="authz-matrix__announce"
|
|
127
|
+
role="status"
|
|
128
|
+
aria-live="polite"
|
|
129
|
+
data-testid="authz-matrix-announce"
|
|
130
|
+
>
|
|
131
|
+
{{ announcement }}
|
|
132
|
+
</p>
|
|
133
|
+
</div>
|
|
134
|
+
</template>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"authz.roles.title": "Roles",
|
|
3
|
+
"authz.roles.subtitle": "Manage roles, their permissions and the role hierarchy.",
|
|
4
|
+
"authz.roles.new": "New role",
|
|
5
|
+
"authz.roles.colKey": "Key",
|
|
6
|
+
"authz.roles.colName": "Name",
|
|
7
|
+
"authz.roles.colPermissions": "Permissions",
|
|
8
|
+
"authz.roles.colParent": "Parent role",
|
|
9
|
+
"authz.roles.empty": "No roles have been created yet.",
|
|
10
|
+
"authz.roles.loading": "Loading roles…",
|
|
11
|
+
"authz.roles.noPermissions": "No permissions",
|
|
12
|
+
"authz.roles.noParent": "—",
|
|
13
|
+
|
|
14
|
+
"authz.role.title": "New role",
|
|
15
|
+
"authz.role.editTitle": "Edit role",
|
|
16
|
+
"authz.role.key": "Key",
|
|
17
|
+
"authz.role.keyHint": "Lowercase letters, digits and hyphens, for example content-editor.",
|
|
18
|
+
"authz.role.name": "Name",
|
|
19
|
+
"authz.role.permissions": "Permissions",
|
|
20
|
+
"authz.role.parent": "Parent role",
|
|
21
|
+
"authz.role.parentNone": "No parent role",
|
|
22
|
+
"authz.role.save": "Save",
|
|
23
|
+
"authz.role.saving": "Saving…",
|
|
24
|
+
"authz.role.saved": "The role has been saved.",
|
|
25
|
+
"authz.role.back": "Back to roles",
|
|
26
|
+
|
|
27
|
+
"authz.matrix.caption": "Permission matrix: tick which permissions this role receives.",
|
|
28
|
+
"authz.matrix.colPermission": "Permission",
|
|
29
|
+
"authz.matrix.colDescription": "Description",
|
|
30
|
+
"authz.matrix.colGranted": "Granted",
|
|
31
|
+
"authz.matrix.toggle": "Grant permission {permission} to this role",
|
|
32
|
+
"authz.matrix.empty": "The permission catalog has not been loaded yet.",
|
|
33
|
+
"authz.matrix.announceGranted": "Permission {permission} granted.",
|
|
34
|
+
"authz.matrix.announceRevoked": "Permission {permission} revoked.",
|
|
35
|
+
|
|
36
|
+
"authz.assign.title": "Assign access",
|
|
37
|
+
"authz.assign.subtitle": "Assign roles to a subject or revoke them.",
|
|
38
|
+
"authz.assign.subjectId": "Subject id",
|
|
39
|
+
"authz.assign.subjectIdHint": "The unique id of the user or subject.",
|
|
40
|
+
"authz.assign.load": "Load roles",
|
|
41
|
+
"authz.assign.role": "Role",
|
|
42
|
+
"authz.assign.assign": "Assign",
|
|
43
|
+
"authz.assign.revoke": "Revoke",
|
|
44
|
+
"authz.assign.currentRoles": "Current roles",
|
|
45
|
+
"authz.assign.noRoles": "This subject has no roles yet.",
|
|
46
|
+
"authz.assign.assigned": "The role has been assigned.",
|
|
47
|
+
"authz.assign.revoked": "The role has been revoked.",
|
|
48
|
+
"authz.assign.selectRole": "Choose a role…",
|
|
49
|
+
|
|
50
|
+
"authz.error.forbidden": "You are not allowed to perform this action.",
|
|
51
|
+
"authz.error.role_not_found": "This role no longer exists.",
|
|
52
|
+
"authz.error.permission_unknown": "One or more permissions are unknown in the catalog.",
|
|
53
|
+
"authz.error.role_cycle": "This change would create a cycle in the role hierarchy.",
|
|
54
|
+
"authz.error.assignment_exists": "This subject already has this role.",
|
|
55
|
+
"authz.error.self_lockout": "This change would lock you out of your own management access.",
|
|
56
|
+
"authz.error.escalation_denied": "You cannot grant rights that you do not hold yourself.",
|
|
57
|
+
"authz.error.invalid_relation": "The provided relation is invalid.",
|
|
58
|
+
"authz.error.unauthenticated": "You are no longer signed in. Please sign in again.",
|
|
59
|
+
"authz.error.validation_failed": "Please check the details you entered and try again.",
|
|
60
|
+
"authz.error.unknown": "Something went wrong. Please try again later."
|
|
61
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"authz.roles.title": "Rollen",
|
|
3
|
+
"authz.roles.subtitle": "Beheer rollen, hun permissies en de rol-hiërarchie.",
|
|
4
|
+
"authz.roles.new": "Nieuwe rol",
|
|
5
|
+
"authz.roles.colKey": "Sleutel",
|
|
6
|
+
"authz.roles.colName": "Naam",
|
|
7
|
+
"authz.roles.colPermissions": "Permissies",
|
|
8
|
+
"authz.roles.colParent": "Bovenliggende rol",
|
|
9
|
+
"authz.roles.empty": "Er zijn nog geen rollen aangemaakt.",
|
|
10
|
+
"authz.roles.loading": "Rollen laden…",
|
|
11
|
+
"authz.roles.noPermissions": "Geen permissies",
|
|
12
|
+
"authz.roles.noParent": "—",
|
|
13
|
+
|
|
14
|
+
"authz.role.title": "Nieuwe rol",
|
|
15
|
+
"authz.role.editTitle": "Rol bewerken",
|
|
16
|
+
"authz.role.key": "Sleutel",
|
|
17
|
+
"authz.role.keyHint": "Kleine letters, cijfers en koppeltekens, bijvoorbeeld content-editor.",
|
|
18
|
+
"authz.role.name": "Naam",
|
|
19
|
+
"authz.role.permissions": "Permissies",
|
|
20
|
+
"authz.role.parent": "Bovenliggende rol",
|
|
21
|
+
"authz.role.parentNone": "Geen bovenliggende rol",
|
|
22
|
+
"authz.role.save": "Opslaan",
|
|
23
|
+
"authz.role.saving": "Bezig met opslaan…",
|
|
24
|
+
"authz.role.saved": "De rol is opgeslagen.",
|
|
25
|
+
"authz.role.back": "Terug naar rollen",
|
|
26
|
+
|
|
27
|
+
"authz.matrix.caption": "Permissie-matrix: kruis aan welke permissies deze rol krijgt.",
|
|
28
|
+
"authz.matrix.colPermission": "Permissie",
|
|
29
|
+
"authz.matrix.colDescription": "Omschrijving",
|
|
30
|
+
"authz.matrix.colGranted": "Toegekend",
|
|
31
|
+
"authz.matrix.toggle": "Permissie {permission} toekennen aan deze rol",
|
|
32
|
+
"authz.matrix.empty": "De permissie-catalogus is nog niet geladen.",
|
|
33
|
+
"authz.matrix.announceGranted": "Permissie {permission} toegekend.",
|
|
34
|
+
"authz.matrix.announceRevoked": "Permissie {permission} ingetrokken.",
|
|
35
|
+
|
|
36
|
+
"authz.assign.title": "Toegang toewijzen",
|
|
37
|
+
"authz.assign.subtitle": "Ken rollen toe aan een subject of trek ze in.",
|
|
38
|
+
"authz.assign.subjectId": "Subject-id",
|
|
39
|
+
"authz.assign.subjectIdHint": "De unieke id van de gebruiker of het subject.",
|
|
40
|
+
"authz.assign.load": "Rollen laden",
|
|
41
|
+
"authz.assign.role": "Rol",
|
|
42
|
+
"authz.assign.assign": "Toewijzen",
|
|
43
|
+
"authz.assign.revoke": "Intrekken",
|
|
44
|
+
"authz.assign.currentRoles": "Huidige rollen",
|
|
45
|
+
"authz.assign.noRoles": "Dit subject heeft nog geen rollen.",
|
|
46
|
+
"authz.assign.assigned": "De rol is toegewezen.",
|
|
47
|
+
"authz.assign.revoked": "De rol is ingetrokken.",
|
|
48
|
+
"authz.assign.selectRole": "Kies een rol…",
|
|
49
|
+
|
|
50
|
+
"authz.error.forbidden": "Je hebt geen toegang tot deze actie.",
|
|
51
|
+
"authz.error.role_not_found": "Deze rol bestaat niet (meer).",
|
|
52
|
+
"authz.error.permission_unknown": "Een of meer permissies zijn onbekend in de catalogus.",
|
|
53
|
+
"authz.error.role_cycle": "Deze wijziging zou een lus in de rol-hiërarchie veroorzaken.",
|
|
54
|
+
"authz.error.assignment_exists": "Dit subject heeft deze rol al.",
|
|
55
|
+
"authz.error.self_lockout": "Deze wijziging zou je eigen beheertoegang blokkeren.",
|
|
56
|
+
"authz.error.escalation_denied": "Je kunt geen rechten toekennen die je zelf niet hebt.",
|
|
57
|
+
"authz.error.invalid_relation": "De opgegeven relatie is ongeldig.",
|
|
58
|
+
"authz.error.unauthenticated": "Je bent niet (meer) ingelogd. Log opnieuw in.",
|
|
59
|
+
"authz.error.validation_failed": "Controleer de ingevulde gegevens en probeer het opnieuw.",
|
|
60
|
+
"authz.error.unknown": "Er ging iets mis. Probeer het later opnieuw."
|
|
61
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* US-Z0503 — Route-middleware `permission` (surface, gematerialiseerd, Nuxt). Gate't de
|
|
3
|
+
* `/admin/*`-beheerroutes op `authz.manage` door de pinned `permissionGuard` (mechanism, uit de
|
|
4
|
+
* access-control-kit) te composeren tegen de door de plugin geprovide evaluator (`$authzEvaluator`,
|
|
5
|
+
* gehydrateerd uit `/authz/me/permissions`, US-Z0405). Deny-by-default (US-Z0701): zonder evaluator
|
|
6
|
+
* of zonder de permissie volgt de redirect-policy — anoniem => `/login` (met terugkeer-parameter),
|
|
7
|
+
* geauthenticeerd-maar-onvoldoende => `/403`. Geen module-globale state; elke navigatie wordt vers
|
|
8
|
+
* geëvalueerd.
|
|
9
|
+
*
|
|
10
|
+
* Gebruik op een pagina: `definePageMeta({ middleware: 'permission' })`, of globaal voor `/admin/*`.
|
|
11
|
+
*/
|
|
12
|
+
import { createRedirectPolicy, permissionGuard } from '../../src/index.js';
|
|
13
|
+
import type { PermissionEvaluator } from '../../src/index.js';
|
|
14
|
+
|
|
15
|
+
// Nuxt auto-imports (host-context).
|
|
16
|
+
declare const defineNuxtRouteMiddleware: (
|
|
17
|
+
fn: (
|
|
18
|
+
to: { path: string; fullPath: string; meta: Record<string, unknown> },
|
|
19
|
+
from: { path: string; fullPath: string; meta: Record<string, unknown> },
|
|
20
|
+
) => unknown,
|
|
21
|
+
) => unknown;
|
|
22
|
+
declare const useNuxtApp: () => {
|
|
23
|
+
$authzEvaluator?: PermissionEvaluator;
|
|
24
|
+
$authIsAuthenticated?: () => boolean;
|
|
25
|
+
};
|
|
26
|
+
declare const navigateTo: (to: unknown) => unknown;
|
|
27
|
+
|
|
28
|
+
export default defineNuxtRouteMiddleware((to, from) => {
|
|
29
|
+
// Alleen de beheer-routes bewaken; overige routes ongemoeid laten.
|
|
30
|
+
if (!to.path.startsWith('/admin')) return;
|
|
31
|
+
|
|
32
|
+
const nuxt = useNuxtApp();
|
|
33
|
+
const evaluator = nuxt.$authzEvaluator;
|
|
34
|
+
const isAuthenticated = nuxt.$authIsAuthenticated ?? (() => false);
|
|
35
|
+
|
|
36
|
+
// Nog geen evaluator (plugin niet gebootstrapt): deny-by-default naar login.
|
|
37
|
+
if (evaluator === undefined) {
|
|
38
|
+
return navigateTo({ path: '/login', query: { redirect: to.fullPath } });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const guard = permissionGuard('authz.manage', {
|
|
42
|
+
evaluator,
|
|
43
|
+
isAuthenticated,
|
|
44
|
+
redirect: createRedirectPolicy({ forbidden: '/403', login: '/login', returnParam: 'redirect' }),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const decision = guard(
|
|
48
|
+
to as unknown as Parameters<ReturnType<typeof permissionGuard>>[0],
|
|
49
|
+
from as unknown as Parameters<ReturnType<typeof permissionGuard>>[1],
|
|
50
|
+
);
|
|
51
|
+
if (decision !== true && decision !== undefined) {
|
|
52
|
+
return navigateTo(decision);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* US-Z0304/US-Z0305 — Access-assignments-pagina (surface, gematerialiseerd, mag divergeren). Kiest
|
|
4
|
+
* een subject, laadt zijn rollen via `useAssignments().loadSubject` (mechanism) over de getypte
|
|
5
|
+
* contract-client (US-Z0105) tegen `GET /authz/subjects/:id/roles`, en kent rollen toe of trekt ze
|
|
6
|
+
* in (`POST`/`DELETE /authz/assignments`). De rollen-dropdown wordt gevuld via `useRoles`
|
|
7
|
+
* (`GET /authz/roles`). Security-invarianten aan de UI-kant: `escalation_denied` (geen
|
|
8
|
+
* rechten-escalatie, US-Z0302/US-Z0701) en `self_lockout` (eigen beheertoegang niet blokkeren,
|
|
9
|
+
* US-Z0303/US-Z0702) worden — net als elke taxonomie-fout (US-Z0102) — via i18n vertaald en in een
|
|
10
|
+
* `aria-live`-regio aangekondigd, zonder policy-detail te lekken. Geen eigen transport.
|
|
11
|
+
*/
|
|
12
|
+
import { computed, onMounted, ref } from 'vue';
|
|
13
|
+
import { useAssignments, useRoles } from '../../src/composables.js';
|
|
14
|
+
import { useOptionalToasts, useTranslate } from '../runtime.js';
|
|
15
|
+
|
|
16
|
+
const t = useTranslate();
|
|
17
|
+
const notifySuccess = useOptionalToasts();
|
|
18
|
+
const assignments = useAssignments();
|
|
19
|
+
const roles = useRoles();
|
|
20
|
+
|
|
21
|
+
const subjectId = ref('');
|
|
22
|
+
const selectedRoleId = ref('');
|
|
23
|
+
/** Het subject waarvoor de rollen momenteel getoond worden (na een geslaagde load). */
|
|
24
|
+
const loadedSubject = ref('');
|
|
25
|
+
/** Laatste geslaagde mutatie-melding (toegankelijk aangekondigd). */
|
|
26
|
+
const notice = ref('');
|
|
27
|
+
|
|
28
|
+
onMounted(async () => {
|
|
29
|
+
await roles.refresh();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/** Vertaalde, veilige serverfout (of leeg). */
|
|
33
|
+
const submitError = computed(() => (assignments.error.value !== null ? t(assignments.error.value) : ''));
|
|
34
|
+
const hasSubject = computed(() => loadedSubject.value !== '');
|
|
35
|
+
|
|
36
|
+
async function onLoad(): Promise<void> {
|
|
37
|
+
notice.value = '';
|
|
38
|
+
await assignments.loadSubject(subjectId.value);
|
|
39
|
+
if (assignments.error.value === null) loadedSubject.value = subjectId.value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function onAssign(): Promise<void> {
|
|
43
|
+
notice.value = '';
|
|
44
|
+
if (selectedRoleId.value === '' || loadedSubject.value === '') return;
|
|
45
|
+
const ok = await assignments.assign({ subjectId: loadedSubject.value, roleId: selectedRoleId.value });
|
|
46
|
+
if (!ok) return;
|
|
47
|
+
notice.value = t('authz.assign.assigned');
|
|
48
|
+
notifySuccess?.(notice.value);
|
|
49
|
+
await assignments.loadSubject(loadedSubject.value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function onRevoke(): Promise<void> {
|
|
53
|
+
notice.value = '';
|
|
54
|
+
if (selectedRoleId.value === '' || loadedSubject.value === '') return;
|
|
55
|
+
const ok = await assignments.revoke({ subjectId: loadedSubject.value, roleId: selectedRoleId.value });
|
|
56
|
+
if (!ok) return;
|
|
57
|
+
notice.value = t('authz.assign.revoked');
|
|
58
|
+
notifySuccess?.(notice.value);
|
|
59
|
+
await assignments.loadSubject(loadedSubject.value);
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<template>
|
|
64
|
+
<section class="authz-page authz-page--assignments">
|
|
65
|
+
<header class="authz-page__header">
|
|
66
|
+
<h1 class="authz-page__title">{{ t('authz.assign.title') }}</h1>
|
|
67
|
+
<p class="authz-page__subtitle">{{ t('authz.assign.subtitle') }}</p>
|
|
68
|
+
</header>
|
|
69
|
+
|
|
70
|
+
<p
|
|
71
|
+
v-if="submitError"
|
|
72
|
+
class="authz-page__error"
|
|
73
|
+
role="alert"
|
|
74
|
+
aria-live="assertive"
|
|
75
|
+
data-testid="authz-assign-error"
|
|
76
|
+
>
|
|
77
|
+
{{ submitError }}
|
|
78
|
+
</p>
|
|
79
|
+
|
|
80
|
+
<p
|
|
81
|
+
v-if="notice"
|
|
82
|
+
class="authz-page__status"
|
|
83
|
+
role="status"
|
|
84
|
+
aria-live="polite"
|
|
85
|
+
data-testid="authz-assign-notice"
|
|
86
|
+
>
|
|
87
|
+
{{ notice }}
|
|
88
|
+
</p>
|
|
89
|
+
|
|
90
|
+
<form class="authz-form authz-form--subject" novalidate @submit.prevent="onLoad">
|
|
91
|
+
<div class="authz-field">
|
|
92
|
+
<label class="authz-field__label" for="authz-subject-id">{{ t('authz.assign.subjectId') }}</label>
|
|
93
|
+
<input
|
|
94
|
+
id="authz-subject-id"
|
|
95
|
+
v-model="subjectId"
|
|
96
|
+
class="authz-field__input"
|
|
97
|
+
type="text"
|
|
98
|
+
required
|
|
99
|
+
aria-describedby="authz-subject-id-hint"
|
|
100
|
+
/>
|
|
101
|
+
<p id="authz-subject-id-hint" class="authz-field__hint">{{ t('authz.assign.subjectIdHint') }}</p>
|
|
102
|
+
</div>
|
|
103
|
+
<button class="authz-form__submit" type="submit">{{ t('authz.assign.load') }}</button>
|
|
104
|
+
</form>
|
|
105
|
+
|
|
106
|
+
<div v-if="hasSubject" class="authz-assign__panel">
|
|
107
|
+
<form class="authz-form authz-form--assign" novalidate @submit.prevent="onAssign">
|
|
108
|
+
<div class="authz-field">
|
|
109
|
+
<label class="authz-field__label" for="authz-assign-role">{{ t('authz.assign.role') }}</label>
|
|
110
|
+
<select
|
|
111
|
+
id="authz-assign-role"
|
|
112
|
+
v-model="selectedRoleId"
|
|
113
|
+
class="authz-field__input"
|
|
114
|
+
>
|
|
115
|
+
<option value="">{{ t('authz.assign.selectRole') }}</option>
|
|
116
|
+
<option v-for="role in roles.roles.value" :key="role.id" :value="role.id">
|
|
117
|
+
{{ role.name }}
|
|
118
|
+
</option>
|
|
119
|
+
</select>
|
|
120
|
+
</div>
|
|
121
|
+
<div class="authz-form__actions">
|
|
122
|
+
<button class="authz-form__submit" type="submit" :disabled="selectedRoleId === ''">
|
|
123
|
+
{{ t('authz.assign.assign') }}
|
|
124
|
+
</button>
|
|
125
|
+
<button
|
|
126
|
+
class="authz-form__revoke"
|
|
127
|
+
type="button"
|
|
128
|
+
:disabled="selectedRoleId === ''"
|
|
129
|
+
@click="onRevoke"
|
|
130
|
+
>
|
|
131
|
+
{{ t('authz.assign.revoke') }}
|
|
132
|
+
</button>
|
|
133
|
+
</div>
|
|
134
|
+
</form>
|
|
135
|
+
|
|
136
|
+
<section class="authz-assign__current" aria-labelledby="authz-assign-current-title">
|
|
137
|
+
<h2 id="authz-assign-current-title" class="authz-assign__subtitle">
|
|
138
|
+
{{ t('authz.assign.currentRoles') }}
|
|
139
|
+
</h2>
|
|
140
|
+
<p v-if="assignments.subjectRoles.value.length === 0" class="authz-assign__empty">
|
|
141
|
+
{{ t('authz.assign.noRoles') }}
|
|
142
|
+
</p>
|
|
143
|
+
<ul v-else class="authz-assign__roles">
|
|
144
|
+
<li v-for="role in assignments.subjectRoles.value" :key="role.id">
|
|
145
|
+
{{ role.name }} (<code>{{ role.key }}</code>)
|
|
146
|
+
</li>
|
|
147
|
+
</ul>
|
|
148
|
+
</section>
|
|
149
|
+
</div>
|
|
150
|
+
</section>
|
|
151
|
+
</template>
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* US-Z0205 — Role-editor + permissie-matrix (surface, gematerialiseerd, mag divergeren). Bewerkt of
|
|
4
|
+
* maakt een rol via de pinned `useRoles`-composable (mechanism) over de getypte contract-client
|
|
5
|
+
* (US-Z0105) tegen `POST`/`PATCH /authz/roles`. Het hart is de toegankelijke `PermissionMatrix`,
|
|
6
|
+
* gevuld uit de code-declared permissie-catalogus (`GET /authz/permissions`, US-Z0203). Een
|
|
7
|
+
* parent-role-select stelt de rol-hiërarchie in. Serverfoutcodes uit de taxonomie (US-Z0102) —
|
|
8
|
+
* o.a. `permission_unknown`/`role_cycle` — worden via i18n vertaald en in een `aria-live`-regio
|
|
9
|
+
* aangekondigd (nooit rauwe details, no-catalog-leak). Geen eigen transport of validatiemechanisme.
|
|
10
|
+
*
|
|
11
|
+
* Design-system-agnostisch: plain semantische HTML; de host swapt zijn componenten in.
|
|
12
|
+
*/
|
|
13
|
+
import { computed, onMounted, ref } from 'vue';
|
|
14
|
+
import type { Role } from '../../../contract/index.js';
|
|
15
|
+
import { useRoles } from '../../src/composables.js';
|
|
16
|
+
import PermissionMatrix from '../components/PermissionMatrix.vue';
|
|
17
|
+
import { readQueryParam, useNavigate, useOptionalToasts, useTranslate } from '../runtime.js';
|
|
18
|
+
|
|
19
|
+
const t = useTranslate();
|
|
20
|
+
const navigate = useNavigate();
|
|
21
|
+
const notifySuccess = useOptionalToasts();
|
|
22
|
+
const roles = useRoles();
|
|
23
|
+
|
|
24
|
+
/** Bewerk-modus: de rol-id komt uit de `?id=`-query. Leeg => aanmaak-modus. */
|
|
25
|
+
const editingId = ref<string>(readQueryParam('id'));
|
|
26
|
+
const isEditing = computed(() => editingId.value !== '');
|
|
27
|
+
|
|
28
|
+
/** Formulier-velden. `key` is alleen bij aanmaak bewerkbaar (stabiele identiteit). */
|
|
29
|
+
const key = ref('');
|
|
30
|
+
const name = ref('');
|
|
31
|
+
const parentRoleId = ref<string | null>(null);
|
|
32
|
+
const selectedPermissions = ref<string[]>([]);
|
|
33
|
+
|
|
34
|
+
/** Toont de bevestiging na een geslaagde opslag; togglet terug bij een nieuwe wijziging. */
|
|
35
|
+
const saved = ref(false);
|
|
36
|
+
|
|
37
|
+
/** De ouder-opties: alle rollen behalve de rol die we bewerken (geen zelf-referentie). */
|
|
38
|
+
const parentOptions = computed(() => roles.roles.value.filter((role) => role.id !== editingId.value));
|
|
39
|
+
|
|
40
|
+
/** Vult het formulier uit een bestaande rol (bewerk-modus). */
|
|
41
|
+
function hydrateFrom(role: Role): void {
|
|
42
|
+
key.value = role.key;
|
|
43
|
+
name.value = role.name;
|
|
44
|
+
parentRoleId.value = role.parentRoleId;
|
|
45
|
+
selectedPermissions.value = [...role.permissions];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
onMounted(async () => {
|
|
49
|
+
await roles.refresh();
|
|
50
|
+
if (isEditing.value) {
|
|
51
|
+
const existing = roles.roles.value.find((role) => role.id === editingId.value);
|
|
52
|
+
if (existing) hydrateFrom(existing);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/** Vertaalde, veilige serverfout (of leeg). */
|
|
57
|
+
const submitError = computed(() => (roles.error.value !== null ? t(roles.error.value) : ''));
|
|
58
|
+
|
|
59
|
+
async function onSubmit(): Promise<void> {
|
|
60
|
+
saved.value = false;
|
|
61
|
+
if (isEditing.value) {
|
|
62
|
+
const updated = await roles.update(editingId.value, {
|
|
63
|
+
name: name.value,
|
|
64
|
+
permissions: selectedPermissions.value,
|
|
65
|
+
parentRoleId: parentRoleId.value,
|
|
66
|
+
});
|
|
67
|
+
if (updated === undefined) return; // fout staat reactief in `roles.error` (i18n-sleutel)
|
|
68
|
+
hydrateFrom(updated);
|
|
69
|
+
} else {
|
|
70
|
+
const created = await roles.create({
|
|
71
|
+
key: key.value,
|
|
72
|
+
name: name.value,
|
|
73
|
+
permissions: selectedPermissions.value,
|
|
74
|
+
parentRoleId: parentRoleId.value,
|
|
75
|
+
});
|
|
76
|
+
if (created === undefined) return;
|
|
77
|
+
editingId.value = created.id; // wissel naar bewerk-modus op de nieuwe rol
|
|
78
|
+
hydrateFrom(created);
|
|
79
|
+
}
|
|
80
|
+
saved.value = true;
|
|
81
|
+
notifySuccess?.(t('authz.role.saved'));
|
|
82
|
+
}
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<template>
|
|
86
|
+
<section class="authz-page authz-page--role-editor">
|
|
87
|
+
<header class="authz-page__header">
|
|
88
|
+
<h1 class="authz-page__title">
|
|
89
|
+
{{ isEditing ? t('authz.role.editTitle') : t('authz.role.title') }}
|
|
90
|
+
</h1>
|
|
91
|
+
</header>
|
|
92
|
+
|
|
93
|
+
<form class="authz-form" novalidate @submit.prevent="onSubmit">
|
|
94
|
+
<p
|
|
95
|
+
v-if="submitError"
|
|
96
|
+
class="authz-form__error"
|
|
97
|
+
role="alert"
|
|
98
|
+
aria-live="assertive"
|
|
99
|
+
data-testid="authz-role-error"
|
|
100
|
+
>
|
|
101
|
+
{{ submitError }}
|
|
102
|
+
</p>
|
|
103
|
+
|
|
104
|
+
<p
|
|
105
|
+
v-if="saved"
|
|
106
|
+
class="authz-form__success"
|
|
107
|
+
role="status"
|
|
108
|
+
aria-live="polite"
|
|
109
|
+
data-testid="authz-role-saved"
|
|
110
|
+
>
|
|
111
|
+
{{ t('authz.role.saved') }}
|
|
112
|
+
</p>
|
|
113
|
+
|
|
114
|
+
<div v-if="!isEditing" class="authz-field">
|
|
115
|
+
<label class="authz-field__label" for="authz-role-key">{{ t('authz.role.key') }}</label>
|
|
116
|
+
<input
|
|
117
|
+
id="authz-role-key"
|
|
118
|
+
v-model="key"
|
|
119
|
+
class="authz-field__input"
|
|
120
|
+
type="text"
|
|
121
|
+
required
|
|
122
|
+
aria-describedby="authz-role-key-hint"
|
|
123
|
+
/>
|
|
124
|
+
<p id="authz-role-key-hint" class="authz-field__hint">{{ t('authz.role.keyHint') }}</p>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div class="authz-field">
|
|
128
|
+
<label class="authz-field__label" for="authz-role-name">{{ t('authz.role.name') }}</label>
|
|
129
|
+
<input
|
|
130
|
+
id="authz-role-name"
|
|
131
|
+
v-model="name"
|
|
132
|
+
class="authz-field__input"
|
|
133
|
+
type="text"
|
|
134
|
+
required
|
|
135
|
+
/>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<div class="authz-field">
|
|
139
|
+
<label class="authz-field__label" for="authz-role-parent">{{ t('authz.role.parent') }}</label>
|
|
140
|
+
<select
|
|
141
|
+
id="authz-role-parent"
|
|
142
|
+
v-model="parentRoleId"
|
|
143
|
+
class="authz-field__input"
|
|
144
|
+
>
|
|
145
|
+
<option :value="null">{{ t('authz.role.parentNone') }}</option>
|
|
146
|
+
<option v-for="role in parentOptions" :key="role.id" :value="role.id">
|
|
147
|
+
{{ role.name }}
|
|
148
|
+
</option>
|
|
149
|
+
</select>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<fieldset class="authz-fieldset">
|
|
153
|
+
<legend class="authz-fieldset__legend">{{ t('authz.role.permissions') }}</legend>
|
|
154
|
+
<PermissionMatrix
|
|
155
|
+
v-model="selectedPermissions"
|
|
156
|
+
:permissions="roles.permissions.value"
|
|
157
|
+
:disabled="roles.pending.value"
|
|
158
|
+
/>
|
|
159
|
+
</fieldset>
|
|
160
|
+
|
|
161
|
+
<div class="authz-form__actions">
|
|
162
|
+
<button class="authz-form__submit" type="submit" :disabled="roles.pending.value">
|
|
163
|
+
{{ roles.pending.value ? t('authz.role.saving') : t('authz.role.save') }}
|
|
164
|
+
</button>
|
|
165
|
+
<a class="authz-form__link" href="/admin/roles">{{ t('authz.role.back') }}</a>
|
|
166
|
+
</div>
|
|
167
|
+
</form>
|
|
168
|
+
</section>
|
|
169
|
+
</template>
|