@apipass/permissions 1.0.192

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 ADDED
@@ -0,0 +1,113 @@
1
+ # @apipass/permissions
2
+
3
+ Authorization UI primitives for Angular apps, the counterpart of the React
4
+ `PermissionGate` / `useResourceActions` used in `account-manager`.
5
+
6
+ It provides:
7
+
8
+ - **`PermissionsService`** — a hybrid orchestrator that decides between **Cerbos**
9
+ and the **legacy role model** based on a per-app feature toggle.
10
+ - **`<apipass-permission-gate>`** — a render-prop component that, when blocked,
11
+ shows the action **disabled with an explanatory tooltip** instead of hiding it.
12
+ - **`[apipassPermissionGate]`** — a lightweight attribute directive for native
13
+ elements.
14
+
15
+ ## Design principles
16
+
17
+ These mirror the React `PermissionGate`:
18
+
19
+ 1. **Never hide, always explain.** A blocked action stays in the DOM, disabled,
20
+ with a tooltip telling the user why — it is not removed with `*ngIf`.
21
+ 2. **Presentational.** The gate does not decide permissions; it renders a
22
+ decision passed in via `isAllowed`. The decision comes from `PermissionsService`.
23
+ 3. **Fail-closed.** With Cerbos enabled, any PDP failure denies every action.
24
+
25
+ ## Setup
26
+
27
+ ```ts
28
+ import { PermissionsModule, providePermissionsConfig } from '@apipass/permissions'
29
+ import { provideCerbos } from '@apipass/frontend-utils'
30
+
31
+ @NgModule({
32
+ imports: [PermissionsModule, /* ... */],
33
+ providers: [
34
+ provideCerbos('https://cerbos.example.com'),
35
+ providePermissionsConfig({
36
+ cerbosEnabled: () => featureToggle.getFeatures().then(f => f.isCerbosEnabled()),
37
+ userInfo: () => iamService.getLoggedUserInfo().then(u => u.cerbosUserInfo),
38
+ legacyResolver: async ({ kind, action }, ctx) => {
39
+ const { role } = await iamService.getLoggedUserInfo()
40
+ // map { kind, action } → role.canX()
41
+ if (kind === 'flow' && action === 'read') return role.canAccessFlow((ctx as any)?.projectId)
42
+ return false
43
+ }
44
+ })
45
+ ]
46
+ })
47
+ export class AppModule {}
48
+ ```
49
+
50
+ `HttpClient` must be available (e.g. via `provideHttpClient()`), as required by `provideCerbos`.
51
+
52
+ ## Reading permissions
53
+
54
+ ```ts
55
+ constructor(private readonly permissions: PermissionsService) {}
56
+
57
+ async ngOnInit() {
58
+ const flow = await this.permissions.checkResource({
59
+ kind: 'flow',
60
+ actions: ['read', 'update', 'stop'],
61
+ legacyContext: { projectId: this.projectId }
62
+ })
63
+ this.canEdit = flow.can('update')
64
+ this.canStop = flow.can('stop')
65
+ }
66
+ ```
67
+
68
+ Evaluate several kinds in one round-trip:
69
+
70
+ ```ts
71
+ const result = await this.permissions.checkResources([
72
+ { kind: 'stage', actions: ['approve', 'manage-roles'] },
73
+ { kind: 'flow', actions: ['read', 'stop'], legacyContext: { projectId } }
74
+ ])
75
+ result.can('stage', 'approve') // boolean
76
+ result.for('flow').can('stop') // boolean
77
+ ```
78
+
79
+ ## Gating UI
80
+
81
+ ### Component (recommended for Angular Material)
82
+
83
+ ```html
84
+ <apipass-permission-gate [isAllowed]="canCreate" [noAccessLabel]="'PERMISSIONS.NO_CREATE' | translate">
85
+ <ng-template let-allowed>
86
+ <button mat-raised-button [disabled]="!allowed" (click)="allowed && create()">New</button>
87
+ </ng-template>
88
+ </apipass-permission-gate>
89
+ ```
90
+
91
+ The template receives `allowed` as its implicit context — the equivalent of the
92
+ React render-prop `children(allowed)`.
93
+
94
+ ### Directive (native elements)
95
+
96
+ ```html
97
+ <button [apipassPermissionGate]="canCreate" noAccessLabel="No permission" (click)="create()">New</button>
98
+ ```
99
+
100
+ For Material controls prefer the component: Material reads its own `disabled`
101
+ @Input, not the DOM attribute the directive sets.
102
+
103
+ ## Public API
104
+
105
+ | Symbol | Kind | Description |
106
+ |---|---|---|
107
+ | `PermissionsService` | service | `checkResource` / `checkResources`, hybrid Cerbos + legacy |
108
+ | `PERMISSIONS_CONFIG` / `providePermissionsConfig` | token / fn | Per-app wiring |
109
+ | `PermissionsConfig` / `LegacyCheck` | interface | Config contract |
110
+ | `PermissionGateComponent` | component | `<apipass-permission-gate>` |
111
+ | `PermissionGateDirective` | directive | `[apipassPermissionGate]` |
112
+ | `PermissionCheck` | interface | `{ kind, actions, id?, legacyContext? }` |
113
+ | `PermissionResult` / `PermissionResultMap` | interface | `can(...)` / `for(kind)` |
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generated bundle index. Do not edit.
3
+ */
4
+ export * from './public-api';
5
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpcGFzcy1wZXJtaXNzaW9ucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3Byb2plY3RzL3Blcm1pc3Npb25zL3NyYy9hcGlwYXNzLXBlcm1pc3Npb25zLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBRUgsY0FBYyxjQUFjLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEdlbmVyYXRlZCBidW5kbGUgaW5kZXguIERvIG5vdCBlZGl0LlxuICovXG5cbmV4cG9ydCAqIGZyb20gJy4vcHVibGljLWFwaSc7XG4iXX0=
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGVybWlzc2lvbi1jaGVjay5pbnRlcmZhY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9wcm9qZWN0cy9wZXJtaXNzaW9ucy9zcmMvbW9kZWwvcGVybWlzc2lvbi1jaGVjay5pbnRlcmZhY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQSBwZXJtaXNzaW9uIGNoZWNrIGZvciBhIHNpbmdsZSByZXNvdXJjZSBraW5kIGFuZCBvbmUgb3IgbW9yZSBhY3Rpb25zLlxuICogTWlycm9ycyB0aGUgUmVhY3QgYHVzZVJlc291cmNlQWN0aW9ucyhraW5kLCBhY3Rpb25zKWAgY29udHJhY3Q6IG9uZSBraW5kLFxuICogc2V2ZXJhbCBhY3Rpb25zLCBldmFsdWF0ZWQgdG9nZXRoZXIgaW4gYSBzaW5nbGUgcm91bmQtdHJpcC5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBQZXJtaXNzaW9uQ2hlY2sge1xuICAvKiogQ2VyYm9zIHJlc291cmNlIGtpbmQgKGUuZy4gYGZsb3dgLCBgc3RhZ2VgLCBgcHJvamVjdGApLiAqL1xuICBraW5kOiBzdHJpbmdcbiAgLyoqIEFjdGlvbnMgdG8gZXZhbHVhdGUgZm9yIHRoaXMga2luZCAoZS5nLiBgWydyZWFkJywgJ3VwZGF0ZScsICdzdG9wJ11gKS4gKi9cbiAgYWN0aW9uczogc3RyaW5nW11cbiAgLyoqIE9wdGlvbmFsIHJlc291cmNlIGlkOyBkZWZhdWx0cyB0byB0aGUga2luZCB3aGVuIG9taXR0ZWQgKG1hdGNoZXMgdGhlIENlcmJvcyBtYXBwZXIpLiAqL1xuICBpZD86IHN0cmluZ1xuICAvKipcbiAgICogT3BhcXVlIGNvbnRleHQgZm9yd2FyZGVkIHRvIHRoZSBhcHAncyBgbGVnYWN5UmVzb2x2ZXJgIHdoZW4gQ2VyYm9zIGlzIGRpc2FibGVkLlxuICAgKiBVc2VkIHRvIGNhcnJ5IGRhdGEgdGhlIGxlZ2FjeSByb2xlIGNoZWNrcyBuZWVkIChlLmcuIGB7IHByb2plY3RJZCB9YCkuXG4gICAqL1xuICBsZWdhY3lDb250ZXh0PzogdW5rbm93blxufVxuIl19
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGVybWlzc2lvbi1yZXN1bHQuaW50ZXJmYWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vcHJvamVjdHMvcGVybWlzc2lvbnMvc3JjL21vZGVsL3Blcm1pc3Npb24tcmVzdWx0LmludGVyZmFjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIiwic291cmNlc0NvbnRlbnQiOlsiLyoqIFJlc3VsdCBvZiBhIHNpbmdsZS1raW5kIGNoZWNrLiBgY2FuKGFjdGlvbilgIHJldHVybnMgd2hldGhlciB0aGUgYWN0aW9uIGlzIGFsbG93ZWQuICovXG5leHBvcnQgaW50ZXJmYWNlIFBlcm1pc3Npb25SZXN1bHQge1xuICBjYW4oYWN0aW9uOiBzdHJpbmcpOiBib29sZWFuXG59XG5cbi8qKiBSZXN1bHQgb2YgYSBtdWx0aS1raW5kIGNoZWNrLCBrZXllZCBieSByZXNvdXJjZSBraW5kLiAqL1xuZXhwb3J0IGludGVyZmFjZSBQZXJtaXNzaW9uUmVzdWx0TWFwIHtcbiAgLyoqIFdoZXRoZXIgYGFjdGlvbmAgaXMgYWxsb3dlZCBmb3IgYGtpbmRgLiAqL1xuICBjYW4oa2luZDogc3RyaW5nLCBhY3Rpb246IHN0cmluZyk6IGJvb2xlYW5cbiAgLyoqIE5hcnJvd3MgdGhlIG1hcCB0byBhIHNpbmdsZSBraW5kLCByZXR1cm5pbmcgYSB7QGxpbmsgUGVybWlzc2lvblJlc3VsdH0uICovXG4gIGZvcihraW5kOiBzdHJpbmcpOiBQZXJtaXNzaW9uUmVzdWx0XG59XG4iXX0=
@@ -0,0 +1,42 @@
1
+ import { Component, ContentChild, Input, TemplateRef } from '@angular/core';
2
+ import * as i0 from "@angular/core";
3
+ import * as i1 from "@angular/common";
4
+ import * as i2 from "@angular/material/tooltip";
5
+ /**
6
+ * Angular equivalent of the React `PermissionGate`.
7
+ *
8
+ * It **never hides** the protected action: when blocked it still renders the
9
+ * content, disabled, wrapped in a tooltip that explains why. The decision
10
+ * (`isAllowed`) is supplied by the caller — this component only presents it.
11
+ *
12
+ * @example
13
+ * ```html
14
+ * <apipass-permission-gate [isAllowed]="canCreate" [noAccessLabel]="'PERMISSIONS.NO_CREATE' | translate">
15
+ * <ng-template let-allowed>
16
+ * <button mat-raised-button [disabled]="!allowed" (click)="allowed && create()">New</button>
17
+ * </ng-template>
18
+ * </apipass-permission-gate>
19
+ * ```
20
+ */
21
+ class PermissionGateComponent {
22
+ /** Whether the gated action is allowed. */
23
+ isAllowed = false;
24
+ /** Tooltip text shown when the action is blocked. */
25
+ noAccessLabel = '';
26
+ template;
27
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionGateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
28
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.0.2", type: PermissionGateComponent, selector: "apipass-permission-gate", inputs: { isAllowed: "isAllowed", noAccessLabel: "noAccessLabel" }, queries: [{ propertyName: "template", first: true, predicate: TemplateRef, descendants: true }], ngImport: i0, template: "<ng-container *ngIf=\"isAllowed; else blocked\">\n <ng-container *ngTemplateOutlet=\"template ?? null; context: { $implicit: true }\"></ng-container>\n</ng-container>\n\n<ng-template #blocked>\n <span\n class=\"permission-gate-blocked\"\n [matTooltip]=\"noAccessLabel\"\n [matTooltipDisabled]=\"!noAccessLabel\">\n <ng-container *ngTemplateOutlet=\"template ?? null; context: { $implicit: false }\"></ng-container>\n </span>\n</ng-template>\n", styles: [".permission-gate-blocked{display:inline-flex;cursor:not-allowed}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.MatTooltip, selector: "[matTooltip]", exportAs: ["matTooltip"] }] });
29
+ }
30
+ export { PermissionGateComponent };
31
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionGateComponent, decorators: [{
32
+ type: Component,
33
+ args: [{ selector: 'apipass-permission-gate', template: "<ng-container *ngIf=\"isAllowed; else blocked\">\n <ng-container *ngTemplateOutlet=\"template ?? null; context: { $implicit: true }\"></ng-container>\n</ng-container>\n\n<ng-template #blocked>\n <span\n class=\"permission-gate-blocked\"\n [matTooltip]=\"noAccessLabel\"\n [matTooltipDisabled]=\"!noAccessLabel\">\n <ng-container *ngTemplateOutlet=\"template ?? null; context: { $implicit: false }\"></ng-container>\n </span>\n</ng-template>\n", styles: [".permission-gate-blocked{display:inline-flex;cursor:not-allowed}\n"] }]
34
+ }], propDecorators: { isAllowed: [{
35
+ type: Input
36
+ }], noAccessLabel: [{
37
+ type: Input
38
+ }], template: [{
39
+ type: ContentChild,
40
+ args: [TemplateRef]
41
+ }] } });
42
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGVybWlzc2lvbi1nYXRlLmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3Byb2plY3RzL3Blcm1pc3Npb25zL3NyYy9wZXJtaXNzaW9uLWdhdGUvcGVybWlzc2lvbi1nYXRlLmNvbXBvbmVudC50cyIsIi4uLy4uLy4uLy4uL3Byb2plY3RzL3Blcm1pc3Npb25zL3NyYy9wZXJtaXNzaW9uLWdhdGUvcGVybWlzc2lvbi1nYXRlLmNvbXBvbmVudC5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsWUFBWSxFQUFFLEtBQUssRUFBRSxXQUFXLEVBQUUsTUFBTSxlQUFlLENBQUE7Ozs7QUFPM0U7Ozs7Ozs7Ozs7Ozs7OztHQWVHO0FBQ0gsTUFLYSx1QkFBdUI7SUFFbEMsMkNBQTJDO0lBQ2xDLFNBQVMsR0FBRyxLQUFLLENBQUE7SUFFMUIscURBQXFEO0lBQzVDLGFBQWEsR0FBRyxFQUFFLENBQUE7SUFFQSxRQUFRLENBQXFDO3VHQVI3RCx1QkFBdUI7MkZBQXZCLHVCQUF1Qix5S0FRcEIsV0FBVyxnRENwQzNCLDJjQVlBOztTRGdCYSx1QkFBdUI7MkZBQXZCLHVCQUF1QjtrQkFMbkMsU0FBUzsrQkFDRSx5QkFBeUI7OEJBTzFCLFNBQVM7c0JBQWpCLEtBQUs7Z0JBR0csYUFBYTtzQkFBckIsS0FBSztnQkFFcUIsUUFBUTtzQkFBbEMsWUFBWTt1QkFBQyxXQUFXIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29tcG9uZW50LCBDb250ZW50Q2hpbGQsIElucHV0LCBUZW1wbGF0ZVJlZiB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnXG5cbi8qKiBDb250ZXh0IG9iamVjdCBwYXNzZWQgdG8gdGhlIHByb2plY3RlZCB0ZW1wbGF0ZSDigJQgYCRpbXBsaWNpdGAgaXMgdGhlIGBhbGxvd2VkYCBmbGFnLiAqL1xuZXhwb3J0IGludGVyZmFjZSBQZXJtaXNzaW9uR2F0ZUNvbnRleHQge1xuICAkaW1wbGljaXQ6IGJvb2xlYW5cbn1cblxuLyoqXG4gKiBBbmd1bGFyIGVxdWl2YWxlbnQgb2YgdGhlIFJlYWN0IGBQZXJtaXNzaW9uR2F0ZWAuXG4gKlxuICogSXQgKipuZXZlciBoaWRlcyoqIHRoZSBwcm90ZWN0ZWQgYWN0aW9uOiB3aGVuIGJsb2NrZWQgaXQgc3RpbGwgcmVuZGVycyB0aGVcbiAqIGNvbnRlbnQsIGRpc2FibGVkLCB3cmFwcGVkIGluIGEgdG9vbHRpcCB0aGF0IGV4cGxhaW5zIHdoeS4gVGhlIGRlY2lzaW9uXG4gKiAoYGlzQWxsb3dlZGApIGlzIHN1cHBsaWVkIGJ5IHRoZSBjYWxsZXIg4oCUIHRoaXMgY29tcG9uZW50IG9ubHkgcHJlc2VudHMgaXQuXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYGh0bWxcbiAqIDxhcGlwYXNzLXBlcm1pc3Npb24tZ2F0ZSBbaXNBbGxvd2VkXT1cImNhbkNyZWF0ZVwiIFtub0FjY2Vzc0xhYmVsXT1cIidQRVJNSVNTSU9OUy5OT19DUkVBVEUnIHwgdHJhbnNsYXRlXCI+XG4gKiAgIDxuZy10ZW1wbGF0ZSBsZXQtYWxsb3dlZD5cbiAqICAgICA8YnV0dG9uIG1hdC1yYWlzZWQtYnV0dG9uIFtkaXNhYmxlZF09XCIhYWxsb3dlZFwiIChjbGljayk9XCJhbGxvd2VkICYmIGNyZWF0ZSgpXCI+TmV3PC9idXR0b24+XG4gKiAgIDwvbmctdGVtcGxhdGU+XG4gKiA8L2FwaXBhc3MtcGVybWlzc2lvbi1nYXRlPlxuICogYGBgXG4gKi9cbkBDb21wb25lbnQoe1xuICBzZWxlY3RvcjogJ2FwaXBhc3MtcGVybWlzc2lvbi1nYXRlJyxcbiAgdGVtcGxhdGVVcmw6ICcuL3Blcm1pc3Npb24tZ2F0ZS5jb21wb25lbnQuaHRtbCcsXG4gIHN0eWxlVXJsczogWycuL3Blcm1pc3Npb24tZ2F0ZS5jb21wb25lbnQuc2NzcyddXG59KVxuZXhwb3J0IGNsYXNzIFBlcm1pc3Npb25HYXRlQ29tcG9uZW50IHtcblxuICAvKiogV2hldGhlciB0aGUgZ2F0ZWQgYWN0aW9uIGlzIGFsbG93ZWQuICovXG4gIEBJbnB1dCgpIGlzQWxsb3dlZCA9IGZhbHNlXG5cbiAgLyoqIFRvb2x0aXAgdGV4dCBzaG93biB3aGVuIHRoZSBhY3Rpb24gaXMgYmxvY2tlZC4gKi9cbiAgQElucHV0KCkgbm9BY2Nlc3NMYWJlbCA9ICcnXG5cbiAgQENvbnRlbnRDaGlsZChUZW1wbGF0ZVJlZikgdGVtcGxhdGU/OiBUZW1wbGF0ZVJlZjxQZXJtaXNzaW9uR2F0ZUNvbnRleHQ+XG5cbn1cbiIsIjxuZy1jb250YWluZXIgKm5nSWY9XCJpc0FsbG93ZWQ7IGVsc2UgYmxvY2tlZFwiPlxuICA8bmctY29udGFpbmVyICpuZ1RlbXBsYXRlT3V0bGV0PVwidGVtcGxhdGUgPz8gbnVsbDsgY29udGV4dDogeyAkaW1wbGljaXQ6IHRydWUgfVwiPjwvbmctY29udGFpbmVyPlxuPC9uZy1jb250YWluZXI+XG5cbjxuZy10ZW1wbGF0ZSAjYmxvY2tlZD5cbiAgPHNwYW5cbiAgICBjbGFzcz1cInBlcm1pc3Npb24tZ2F0ZS1ibG9ja2VkXCJcbiAgICBbbWF0VG9vbHRpcF09XCJub0FjY2Vzc0xhYmVsXCJcbiAgICBbbWF0VG9vbHRpcERpc2FibGVkXT1cIiFub0FjY2Vzc0xhYmVsXCI+XG4gICAgPG5nLWNvbnRhaW5lciAqbmdUZW1wbGF0ZU91dGxldD1cInRlbXBsYXRlID8/IG51bGw7IGNvbnRleHQ6IHsgJGltcGxpY2l0OiBmYWxzZSB9XCI+PC9uZy1jb250YWluZXI+XG4gIDwvc3Bhbj5cbjwvbmctdGVtcGxhdGU+XG4iXX0=
@@ -0,0 +1,60 @@
1
+ import { Directive, ElementRef, Input, Renderer2 } from '@angular/core';
2
+ import * as i0 from "@angular/core";
3
+ /**
4
+ * Lightweight, dependency-free variant of {@link PermissionGateComponent} for
5
+ * **native** elements (e.g. `<button>`, `<a>`). When blocked it sets the
6
+ * `disabled` attribute, a `.permission-blocked` class and a native `title`
7
+ * tooltip — the element stays in the DOM, never hidden.
8
+ *
9
+ * For Angular Material controls (`mat-button`, etc.) prefer the component, since
10
+ * Material reads its own `disabled` @Input rather than the DOM attribute.
11
+ *
12
+ * @example
13
+ * ```html
14
+ * <button [apipassPermissionGate]="canCreate" noAccessLabel="No permission" (click)="create()">New</button>
15
+ * ```
16
+ */
17
+ class PermissionGateDirective {
18
+ el;
19
+ renderer;
20
+ /** Whether the gated action is allowed. */
21
+ isAllowed = false;
22
+ /** Native tooltip text shown when the action is blocked. */
23
+ noAccessLabel = '';
24
+ constructor(el, renderer) {
25
+ this.el = el;
26
+ this.renderer = renderer;
27
+ }
28
+ ngOnChanges() {
29
+ const node = this.el.nativeElement;
30
+ if (this.isAllowed) {
31
+ this.renderer.removeAttribute(node, 'disabled');
32
+ this.renderer.removeAttribute(node, 'title');
33
+ this.renderer.removeClass(node, 'permission-blocked');
34
+ return;
35
+ }
36
+ this.renderer.setAttribute(node, 'disabled', 'true');
37
+ this.renderer.addClass(node, 'permission-blocked');
38
+ if (this.noAccessLabel) {
39
+ this.renderer.setAttribute(node, 'title', this.noAccessLabel);
40
+ }
41
+ else {
42
+ this.renderer.removeAttribute(node, 'title');
43
+ }
44
+ }
45
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionGateDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive });
46
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.2", type: PermissionGateDirective, selector: "[apipassPermissionGate]", inputs: { isAllowed: ["apipassPermissionGate", "isAllowed"], noAccessLabel: "noAccessLabel" }, usesOnChanges: true, ngImport: i0 });
47
+ }
48
+ export { PermissionGateDirective };
49
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionGateDirective, decorators: [{
50
+ type: Directive,
51
+ args: [{
52
+ selector: '[apipassPermissionGate]'
53
+ }]
54
+ }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.Renderer2 }]; }, propDecorators: { isAllowed: [{
55
+ type: Input,
56
+ args: ['apipassPermissionGate']
57
+ }], noAccessLabel: [{
58
+ type: Input
59
+ }] } });
60
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGVybWlzc2lvbi1nYXRlLmRpcmVjdGl2ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3Byb2plY3RzL3Blcm1pc3Npb25zL3NyYy9wZXJtaXNzaW9uLWdhdGUvcGVybWlzc2lvbi1nYXRlLmRpcmVjdGl2ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsU0FBUyxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQWEsU0FBUyxFQUFFLE1BQU0sZUFBZSxDQUFBOztBQUVsRjs7Ozs7Ozs7Ozs7OztHQWFHO0FBQ0gsTUFHYSx1QkFBdUI7SUFTZjtJQUNBO0lBUm5CLDJDQUEyQztJQUNYLFNBQVMsR0FBRyxLQUFLLENBQUE7SUFFakQsNERBQTREO0lBQ25ELGFBQWEsR0FBRyxFQUFFLENBQUE7SUFFM0IsWUFDbUIsRUFBMkIsRUFDM0IsUUFBbUI7UUFEbkIsT0FBRSxHQUFGLEVBQUUsQ0FBeUI7UUFDM0IsYUFBUSxHQUFSLFFBQVEsQ0FBVztJQUNuQyxDQUFDO0lBRUosV0FBVztRQUNULE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxFQUFFLENBQUMsYUFBYSxDQUFBO1FBQ2xDLElBQUksSUFBSSxDQUFDLFNBQVMsRUFBRTtZQUNsQixJQUFJLENBQUMsUUFBUSxDQUFDLGVBQWUsQ0FBQyxJQUFJLEVBQUUsVUFBVSxDQUFDLENBQUE7WUFDL0MsSUFBSSxDQUFDLFFBQVEsQ0FBQyxlQUFlLENBQUMsSUFBSSxFQUFFLE9BQU8sQ0FBQyxDQUFBO1lBQzVDLElBQUksQ0FBQyxRQUFRLENBQUMsV0FBVyxDQUFDLElBQUksRUFBRSxvQkFBb0IsQ0FBQyxDQUFBO1lBQ3JELE9BQU07U0FDUDtRQUNELElBQUksQ0FBQyxRQUFRLENBQUMsWUFBWSxDQUFDLElBQUksRUFBRSxVQUFVLEVBQUUsTUFBTSxDQUFDLENBQUE7UUFDcEQsSUFBSSxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsSUFBSSxFQUFFLG9CQUFvQixDQUFDLENBQUE7UUFDbEQsSUFBSSxJQUFJLENBQUMsYUFBYSxFQUFFO1lBQ3RCLElBQUksQ0FBQyxRQUFRLENBQUMsWUFBWSxDQUFDLElBQUksRUFBRSxPQUFPLEVBQUUsSUFBSSxDQUFDLGFBQWEsQ0FBQyxDQUFBO1NBQzlEO2FBQU07WUFDTCxJQUFJLENBQUMsUUFBUSxDQUFDLGVBQWUsQ0FBQyxJQUFJLEVBQUUsT0FBTyxDQUFDLENBQUE7U0FDN0M7SUFDSCxDQUFDO3VHQTVCVSx1QkFBdUI7MkZBQXZCLHVCQUF1Qjs7U0FBdkIsdUJBQXVCOzJGQUF2Qix1QkFBdUI7a0JBSG5DLFNBQVM7bUJBQUM7b0JBQ1QsUUFBUSxFQUFFLHlCQUF5QjtpQkFDcEM7eUhBSWlDLFNBQVM7c0JBQXhDLEtBQUs7dUJBQUMsdUJBQXVCO2dCQUdyQixhQUFhO3NCQUFyQixLQUFLIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgRGlyZWN0aXZlLCBFbGVtZW50UmVmLCBJbnB1dCwgT25DaGFuZ2VzLCBSZW5kZXJlcjIgfSBmcm9tICdAYW5ndWxhci9jb3JlJ1xuXG4vKipcbiAqIExpZ2h0d2VpZ2h0LCBkZXBlbmRlbmN5LWZyZWUgdmFyaWFudCBvZiB7QGxpbmsgUGVybWlzc2lvbkdhdGVDb21wb25lbnR9IGZvclxuICogKipuYXRpdmUqKiBlbGVtZW50cyAoZS5nLiBgPGJ1dHRvbj5gLCBgPGE+YCkuIFdoZW4gYmxvY2tlZCBpdCBzZXRzIHRoZVxuICogYGRpc2FibGVkYCBhdHRyaWJ1dGUsIGEgYC5wZXJtaXNzaW9uLWJsb2NrZWRgIGNsYXNzIGFuZCBhIG5hdGl2ZSBgdGl0bGVgXG4gKiB0b29sdGlwIOKAlCB0aGUgZWxlbWVudCBzdGF5cyBpbiB0aGUgRE9NLCBuZXZlciBoaWRkZW4uXG4gKlxuICogRm9yIEFuZ3VsYXIgTWF0ZXJpYWwgY29udHJvbHMgKGBtYXQtYnV0dG9uYCwgZXRjLikgcHJlZmVyIHRoZSBjb21wb25lbnQsIHNpbmNlXG4gKiBNYXRlcmlhbCByZWFkcyBpdHMgb3duIGBkaXNhYmxlZGAgQElucHV0IHJhdGhlciB0aGFuIHRoZSBET00gYXR0cmlidXRlLlxuICpcbiAqIEBleGFtcGxlXG4gKiBgYGBodG1sXG4gKiA8YnV0dG9uIFthcGlwYXNzUGVybWlzc2lvbkdhdGVdPVwiY2FuQ3JlYXRlXCIgbm9BY2Nlc3NMYWJlbD1cIk5vIHBlcm1pc3Npb25cIiAoY2xpY2spPVwiY3JlYXRlKClcIj5OZXc8L2J1dHRvbj5cbiAqIGBgYFxuICovXG5ARGlyZWN0aXZlKHtcbiAgc2VsZWN0b3I6ICdbYXBpcGFzc1Blcm1pc3Npb25HYXRlXSdcbn0pXG5leHBvcnQgY2xhc3MgUGVybWlzc2lvbkdhdGVEaXJlY3RpdmUgaW1wbGVtZW50cyBPbkNoYW5nZXMge1xuXG4gIC8qKiBXaGV0aGVyIHRoZSBnYXRlZCBhY3Rpb24gaXMgYWxsb3dlZC4gKi9cbiAgQElucHV0KCdhcGlwYXNzUGVybWlzc2lvbkdhdGUnKSBpc0FsbG93ZWQgPSBmYWxzZVxuXG4gIC8qKiBOYXRpdmUgdG9vbHRpcCB0ZXh0IHNob3duIHdoZW4gdGhlIGFjdGlvbiBpcyBibG9ja2VkLiAqL1xuICBASW5wdXQoKSBub0FjY2Vzc0xhYmVsID0gJydcblxuICBjb25zdHJ1Y3RvcihcbiAgICBwcml2YXRlIHJlYWRvbmx5IGVsOiBFbGVtZW50UmVmPEhUTUxFbGVtZW50PixcbiAgICBwcml2YXRlIHJlYWRvbmx5IHJlbmRlcmVyOiBSZW5kZXJlcjJcbiAgKSB7fVxuXG4gIG5nT25DaGFuZ2VzKCk6IHZvaWQge1xuICAgIGNvbnN0IG5vZGUgPSB0aGlzLmVsLm5hdGl2ZUVsZW1lbnRcbiAgICBpZiAodGhpcy5pc0FsbG93ZWQpIHtcbiAgICAgIHRoaXMucmVuZGVyZXIucmVtb3ZlQXR0cmlidXRlKG5vZGUsICdkaXNhYmxlZCcpXG4gICAgICB0aGlzLnJlbmRlcmVyLnJlbW92ZUF0dHJpYnV0ZShub2RlLCAndGl0bGUnKVxuICAgICAgdGhpcy5yZW5kZXJlci5yZW1vdmVDbGFzcyhub2RlLCAncGVybWlzc2lvbi1ibG9ja2VkJylcbiAgICAgIHJldHVyblxuICAgIH1cbiAgICB0aGlzLnJlbmRlcmVyLnNldEF0dHJpYnV0ZShub2RlLCAnZGlzYWJsZWQnLCAndHJ1ZScpXG4gICAgdGhpcy5yZW5kZXJlci5hZGRDbGFzcyhub2RlLCAncGVybWlzc2lvbi1ibG9ja2VkJylcbiAgICBpZiAodGhpcy5ub0FjY2Vzc0xhYmVsKSB7XG4gICAgICB0aGlzLnJlbmRlcmVyLnNldEF0dHJpYnV0ZShub2RlLCAndGl0bGUnLCB0aGlzLm5vQWNjZXNzTGFiZWwpXG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMucmVuZGVyZXIucmVtb3ZlQXR0cmlidXRlKG5vZGUsICd0aXRsZScpXG4gICAgfVxuICB9XG5cbn1cbiJdfQ==
@@ -0,0 +1,40 @@
1
+ import { NgModule } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { MatTooltipModule } from '@angular/material/tooltip';
4
+ import { PermissionGateComponent } from './permission-gate/permission-gate.component';
5
+ import { PermissionGateDirective } from './permission-gate/permission-gate.directive';
6
+ import { PermissionsService } from './service/permissions.service';
7
+ import * as i0 from "@angular/core";
8
+ class PermissionsModule {
9
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
10
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.0.2", ngImport: i0, type: PermissionsModule, declarations: [PermissionGateComponent,
11
+ PermissionGateDirective], imports: [CommonModule,
12
+ MatTooltipModule], exports: [PermissionGateComponent,
13
+ PermissionGateDirective] });
14
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionsModule, providers: [
15
+ PermissionsService
16
+ ], imports: [CommonModule,
17
+ MatTooltipModule] });
18
+ }
19
+ export { PermissionsModule };
20
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionsModule, decorators: [{
21
+ type: NgModule,
22
+ args: [{
23
+ imports: [
24
+ CommonModule,
25
+ MatTooltipModule
26
+ ],
27
+ declarations: [
28
+ PermissionGateComponent,
29
+ PermissionGateDirective
30
+ ],
31
+ exports: [
32
+ PermissionGateComponent,
33
+ PermissionGateDirective
34
+ ],
35
+ providers: [
36
+ PermissionsService
37
+ ]
38
+ }]
39
+ }] });
40
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGVybWlzc2lvbnMubW9kdWxlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vcHJvamVjdHMvcGVybWlzc2lvbnMvc3JjL3Blcm1pc3Npb25zLm1vZHVsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sZUFBZSxDQUFBO0FBQ3hDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQTtBQUM5QyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQTtBQUM1RCxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSw2Q0FBNkMsQ0FBQTtBQUNyRixPQUFPLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSw2Q0FBNkMsQ0FBQTtBQUNyRixPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQTs7QUFFbEUsTUFpQmEsaUJBQWlCO3VHQUFqQixpQkFBaUI7d0dBQWpCLGlCQUFpQixpQkFYMUIsdUJBQXVCO1lBQ3ZCLHVCQUF1QixhQUx2QixZQUFZO1lBQ1osZ0JBQWdCLGFBT2hCLHVCQUF1QjtZQUN2Qix1QkFBdUI7d0dBTWQsaUJBQWlCLGFBSmpCO1lBQ1Qsa0JBQWtCO1NBQ25CLFlBYkMsWUFBWTtZQUNaLGdCQUFnQjs7U0FjUCxpQkFBaUI7MkZBQWpCLGlCQUFpQjtrQkFqQjdCLFFBQVE7bUJBQUM7b0JBQ1IsT0FBTyxFQUFFO3dCQUNQLFlBQVk7d0JBQ1osZ0JBQWdCO3FCQUNqQjtvQkFDRCxZQUFZLEVBQUU7d0JBQ1osdUJBQXVCO3dCQUN2Qix1QkFBdUI7cUJBQ3hCO29CQUNELE9BQU8sRUFBRTt3QkFDUCx1QkFBdUI7d0JBQ3ZCLHVCQUF1QjtxQkFDeEI7b0JBQ0QsU0FBUyxFQUFFO3dCQUNULGtCQUFrQjtxQkFDbkI7aUJBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBOZ01vZHVsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnXG5pbXBvcnQgeyBDb21tb25Nb2R1bGUgfSBmcm9tICdAYW5ndWxhci9jb21tb24nXG5pbXBvcnQgeyBNYXRUb29sdGlwTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvbWF0ZXJpYWwvdG9vbHRpcCdcbmltcG9ydCB7IFBlcm1pc3Npb25HYXRlQ29tcG9uZW50IH0gZnJvbSAnLi9wZXJtaXNzaW9uLWdhdGUvcGVybWlzc2lvbi1nYXRlLmNvbXBvbmVudCdcbmltcG9ydCB7IFBlcm1pc3Npb25HYXRlRGlyZWN0aXZlIH0gZnJvbSAnLi9wZXJtaXNzaW9uLWdhdGUvcGVybWlzc2lvbi1nYXRlLmRpcmVjdGl2ZSdcbmltcG9ydCB7IFBlcm1pc3Npb25zU2VydmljZSB9IGZyb20gJy4vc2VydmljZS9wZXJtaXNzaW9ucy5zZXJ2aWNlJ1xuXG5ATmdNb2R1bGUoe1xuICBpbXBvcnRzOiBbXG4gICAgQ29tbW9uTW9kdWxlLFxuICAgIE1hdFRvb2x0aXBNb2R1bGVcbiAgXSxcbiAgZGVjbGFyYXRpb25zOiBbXG4gICAgUGVybWlzc2lvbkdhdGVDb21wb25lbnQsXG4gICAgUGVybWlzc2lvbkdhdGVEaXJlY3RpdmVcbiAgXSxcbiAgZXhwb3J0czogW1xuICAgIFBlcm1pc3Npb25HYXRlQ29tcG9uZW50LFxuICAgIFBlcm1pc3Npb25HYXRlRGlyZWN0aXZlXG4gIF0sXG4gIHByb3ZpZGVyczogW1xuICAgIFBlcm1pc3Npb25zU2VydmljZVxuICBdXG59KVxuZXhwb3J0IGNsYXNzIFBlcm1pc3Npb25zTW9kdWxlIHt9XG4iXX0=
@@ -0,0 +1,8 @@
1
+ export * from './permissions.module';
2
+ export * from './permission-gate/permission-gate.component';
3
+ export * from './permission-gate/permission-gate.directive';
4
+ export * from './service/permissions.service';
5
+ export * from './service/permissions.config';
6
+ export * from './model/permission-check.interface';
7
+ export * from './model/permission-result.interface';
8
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3Byb2plY3RzL3Blcm1pc3Npb25zL3NyYy9wdWJsaWMtYXBpLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsc0JBQXNCLENBQUE7QUFDcEMsY0FBYyw2Q0FBNkMsQ0FBQTtBQUMzRCxjQUFjLDZDQUE2QyxDQUFBO0FBQzNELGNBQWMsK0JBQStCLENBQUE7QUFDN0MsY0FBYyw4QkFBOEIsQ0FBQTtBQUM1QyxjQUFjLG9DQUFvQyxDQUFBO0FBQ2xELGNBQWMscUNBQXFDLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgKiBmcm9tICcuL3Blcm1pc3Npb25zLm1vZHVsZSdcbmV4cG9ydCAqIGZyb20gJy4vcGVybWlzc2lvbi1nYXRlL3Blcm1pc3Npb24tZ2F0ZS5jb21wb25lbnQnXG5leHBvcnQgKiBmcm9tICcuL3Blcm1pc3Npb24tZ2F0ZS9wZXJtaXNzaW9uLWdhdGUuZGlyZWN0aXZlJ1xuZXhwb3J0ICogZnJvbSAnLi9zZXJ2aWNlL3Blcm1pc3Npb25zLnNlcnZpY2UnXG5leHBvcnQgKiBmcm9tICcuL3NlcnZpY2UvcGVybWlzc2lvbnMuY29uZmlnJ1xuZXhwb3J0ICogZnJvbSAnLi9tb2RlbC9wZXJtaXNzaW9uLWNoZWNrLmludGVyZmFjZSdcbmV4cG9ydCAqIGZyb20gJy4vbW9kZWwvcGVybWlzc2lvbi1yZXN1bHQuaW50ZXJmYWNlJ1xuIl19
@@ -0,0 +1,7 @@
1
+ import { InjectionToken } from '@angular/core';
2
+ export const PERMISSIONS_CONFIG = new InjectionToken('PERMISSIONS_CONFIG');
3
+ /** Registers the {@link PermissionsConfig} used by {@link PermissionsService}. */
4
+ export function providePermissionsConfig(config) {
5
+ return { provide: PERMISSIONS_CONFIG, useValue: config };
6
+ }
7
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGVybWlzc2lvbnMuY29uZmlnLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vcHJvamVjdHMvcGVybWlzc2lvbnMvc3JjL3NlcnZpY2UvcGVybWlzc2lvbnMuY29uZmlnLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxjQUFjLEVBQVksTUFBTSxlQUFlLENBQUE7QUE4QnhELE1BQU0sQ0FBQyxNQUFNLGtCQUFrQixHQUFHLElBQUksY0FBYyxDQUFvQixvQkFBb0IsQ0FBQyxDQUFBO0FBRTdGLGtGQUFrRjtBQUNsRixNQUFNLFVBQVUsd0JBQXdCLENBQUMsTUFBeUI7SUFDaEUsT0FBTyxFQUFFLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxRQUFRLEVBQUUsTUFBTSxFQUFFLENBQUE7QUFDMUQsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdGlvblRva2VuLCBQcm92aWRlciB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnXG5pbXBvcnQgeyBVc2VySW5mbyB9IGZyb20gJ0BhcGlwYXNzL2Zyb250ZW5kLXV0aWxzJ1xuXG5leHBvcnQgdHlwZSBNYXliZUFzeW5jPFQ+ID0gVCB8IFByb21pc2U8VD5cblxuLyoqIEEgc2luZ2xlIGB7IGtpbmQsIGFjdGlvbiB9YCBwYWlyIGhhbmRlZCB0byB0aGUgbGVnYWN5IHJlc29sdmVyLiAqL1xuZXhwb3J0IGludGVyZmFjZSBMZWdhY3lDaGVjayB7XG4gIGtpbmQ6IHN0cmluZ1xuICBhY3Rpb246IHN0cmluZ1xufVxuXG4vKipcbiAqIFBlci1hcHBsaWNhdGlvbiBjb25maWd1cmF0aW9uIGZvciB7QGxpbmsgUGVybWlzc2lvbnNTZXJ2aWNlfS5cbiAqXG4gKiBUaGUgbGlicmFyeSBpcyBkZWNvdXBsZWQgZnJvbSBhbnkgY29uY3JldGUgYXBwOiBlYWNoIGNvbnN1bWVyIHdpcmVzIGluIGhvdyB0b1xuICogcmVhZCB0aGUgQ2VyYm9zIHRvZ2dsZSwgaG93IHRvIGJ1aWxkIHRoZSBDZXJib3MgcHJpbmNpcGFsLCBhbmQgaG93IHRvIHJlc29sdmVcbiAqIHRoZSBsZWdhY3kgKHByZS1DZXJib3MpIGRlY2lzaW9uLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFBlcm1pc3Npb25zQ29uZmlnIHtcbiAgLyoqIFdoZXRoZXIgQ2VyYm9zIGF1dGhvcml6YXRpb24gaXMgYWN0aXZlLiBXaGVuIGBmYWxzZWAsIG9ubHkgYGxlZ2FjeVJlc29sdmVyYCBydW5zLiAqL1xuICBjZXJib3NFbmFibGVkKCk6IE1heWJlQXN5bmM8Ym9vbGVhbj5cbiAgLyoqIEJ1aWxkcyB0aGUgQ2VyYm9zIHByaW5jaXBhbCAoYHsgYWNjb3VudElkLCBkb21haW4sIHJvbGVzIH1gKS4gT25seSBjYWxsZWQgd2hlbiBDZXJib3MgaXMgZW5hYmxlZC4gKi9cbiAgdXNlckluZm8oKTogTWF5YmVBc3luYzxVc2VySW5mbz5cbiAgLyoqXG4gICAqIFJlc29sdmVzIGEgc2luZ2xlIGNoZWNrIHVzaW5nIHRoZSBsZWdhY3kgcm9sZSBtb2RlbC4gT25seSBjYWxsZWQgd2hlbiBDZXJib3MgaXMgZGlzYWJsZWQuXG4gICAqIGBjb250ZXh0YCBpcyB0aGUgYGxlZ2FjeUNvbnRleHRgIGNhcnJpZWQgYnkgdGhlIG9yaWdpbmF0aW5nIHtAbGluayBQZXJtaXNzaW9uQ2hlY2t9LlxuICAgKi9cbiAgbGVnYWN5UmVzb2x2ZXIoY2hlY2s6IExlZ2FjeUNoZWNrLCBjb250ZXh0PzogdW5rbm93bik6IE1heWJlQXN5bmM8Ym9vbGVhbj5cbn1cblxuZXhwb3J0IGNvbnN0IFBFUk1JU1NJT05TX0NPTkZJRyA9IG5ldyBJbmplY3Rpb25Ub2tlbjxQZXJtaXNzaW9uc0NvbmZpZz4oJ1BFUk1JU1NJT05TX0NPTkZJRycpXG5cbi8qKiBSZWdpc3RlcnMgdGhlIHtAbGluayBQZXJtaXNzaW9uc0NvbmZpZ30gdXNlZCBieSB7QGxpbmsgUGVybWlzc2lvbnNTZXJ2aWNlfS4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwcm92aWRlUGVybWlzc2lvbnNDb25maWcoY29uZmlnOiBQZXJtaXNzaW9uc0NvbmZpZyk6IFByb3ZpZGVyIHtcbiAgcmV0dXJuIHsgcHJvdmlkZTogUEVSTUlTU0lPTlNfQ09ORklHLCB1c2VWYWx1ZTogY29uZmlnIH1cbn1cbiJdfQ==
@@ -0,0 +1,90 @@
1
+ import { Inject, Injectable, Optional } from '@angular/core';
2
+ import { CERBOS_SERVICE } from '@apipass/frontend-utils';
3
+ import { PERMISSIONS_CONFIG } from './permissions.config';
4
+ import * as i0 from "@angular/core";
5
+ /**
6
+ * Hybrid authorization orchestrator: decides between Cerbos and the legacy role
7
+ * model based on {@link PermissionsConfig.cerbosEnabled}.
8
+ *
9
+ * - **Cerbos disabled** → behavior is identical to the pre-Cerbos system (legacy resolver).
10
+ * - **Cerbos enabled** → asks the PDP; on any failure it is **fail-closed** (every action denied),
11
+ * so a PDP outage never silently grants access.
12
+ */
13
+ class PermissionsService {
14
+ config;
15
+ cerbos;
16
+ constructor(config, cerbos) {
17
+ this.config = config;
18
+ this.cerbos = cerbos;
19
+ }
20
+ /** Evaluates several resource kinds at once. With Cerbos enabled this is a single round-trip. */
21
+ async checkResources(checks) {
22
+ const cerbosEnabled = await this.config.cerbosEnabled();
23
+ const decisions = cerbosEnabled
24
+ ? await this.resolveWithCerbos(checks)
25
+ : await this.resolveWithLegacy(checks);
26
+ return this.buildResultMap(decisions);
27
+ }
28
+ /** Convenience for the common single-kind case. */
29
+ async checkResource(check) {
30
+ const map = await this.checkResources([check]);
31
+ return map.for(check.kind);
32
+ }
33
+ async resolveWithCerbos(checks) {
34
+ // Fail-closed: no PDP wired in means no grants.
35
+ if (!this.cerbos) {
36
+ console.error(`${PermissionsService.name} - CERBOS_SERVICE not provided; denying all (fail-closed)`);
37
+ return this.denyAll(checks);
38
+ }
39
+ try {
40
+ const userInfo = await this.config.userInfo();
41
+ return await this.cerbos.checkResources(userInfo, checks.map(({ kind, actions, id }) => ({ kind, actions, id })));
42
+ }
43
+ catch (e) {
44
+ console.error(`${PermissionsService.name} - Cerbos check failed; denying all (fail-closed)`, e);
45
+ return this.denyAll(checks);
46
+ }
47
+ }
48
+ async resolveWithLegacy(checks) {
49
+ const decisions = {};
50
+ for (const check of checks) {
51
+ const actions = {};
52
+ for (const action of check.actions) {
53
+ actions[action] = await this.config.legacyResolver({ kind: check.kind, action }, check.legacyContext);
54
+ }
55
+ decisions[check.kind] = actions;
56
+ }
57
+ return decisions;
58
+ }
59
+ denyAll(checks) {
60
+ return checks.reduce((acc, check) => {
61
+ acc[check.kind] = check.actions.reduce((actions, action) => {
62
+ actions[action] = false;
63
+ return actions;
64
+ }, {});
65
+ return acc;
66
+ }, {});
67
+ }
68
+ buildResultMap(decisions) {
69
+ const can = (kind, action) => decisions[kind]?.[action] ?? false;
70
+ return {
71
+ can,
72
+ for: (kind) => ({ can: (action) => can(kind, action) })
73
+ };
74
+ }
75
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionsService, deps: [{ token: PERMISSIONS_CONFIG }, { token: CERBOS_SERVICE, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
76
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionsService });
77
+ }
78
+ export { PermissionsService };
79
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionsService, decorators: [{
80
+ type: Injectable
81
+ }], ctorParameters: function () { return [{ type: undefined, decorators: [{
82
+ type: Inject,
83
+ args: [PERMISSIONS_CONFIG]
84
+ }] }, { type: undefined, decorators: [{
85
+ type: Optional
86
+ }, {
87
+ type: Inject,
88
+ args: [CERBOS_SERVICE]
89
+ }] }]; } });
90
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGVybWlzc2lvbnMuc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3Byb2plY3RzL3Blcm1pc3Npb25zL3NyYy9zZXJ2aWNlL3Blcm1pc3Npb25zLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sRUFBRSxVQUFVLEVBQUUsUUFBUSxFQUFFLE1BQU0sZUFBZSxDQUFBO0FBQzVELE9BQU8sRUFBRSxjQUFjLEVBQWtCLE1BQU0seUJBQXlCLENBQUE7QUFDeEUsT0FBTyxFQUFFLGtCQUFrQixFQUFxQixNQUFNLHNCQUFzQixDQUFBOztBQU01RTs7Ozs7OztHQU9HO0FBQ0gsTUFDYSxrQkFBa0I7SUFHa0I7SUFDUTtJQUZ2RCxZQUMrQyxNQUF5QixFQUNqQixNQUE2QjtRQURyQyxXQUFNLEdBQU4sTUFBTSxDQUFtQjtRQUNqQixXQUFNLEdBQU4sTUFBTSxDQUF1QjtJQUNqRixDQUFDO0lBRUosaUdBQWlHO0lBQ2pHLEtBQUssQ0FBQyxjQUFjLENBQUMsTUFBeUI7UUFDNUMsTUFBTSxhQUFhLEdBQUcsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLGFBQWEsRUFBRSxDQUFBO1FBQ3ZELE1BQU0sU0FBUyxHQUFHLGFBQWE7WUFDN0IsQ0FBQyxDQUFDLE1BQU0sSUFBSSxDQUFDLGlCQUFpQixDQUFDLE1BQU0sQ0FBQztZQUN0QyxDQUFDLENBQUMsTUFBTSxJQUFJLENBQUMsaUJBQWlCLENBQUMsTUFBTSxDQUFDLENBQUE7UUFDeEMsT0FBTyxJQUFJLENBQUMsY0FBYyxDQUFDLFNBQVMsQ0FBQyxDQUFBO0lBQ3ZDLENBQUM7SUFFRCxtREFBbUQ7SUFDbkQsS0FBSyxDQUFDLGFBQWEsQ0FBQyxLQUFzQjtRQUN4QyxNQUFNLEdBQUcsR0FBRyxNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFBO1FBQzlDLE9BQU8sR0FBRyxDQUFDLEdBQUcsQ0FB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LFVBQVU7OzBCQUlOLE1BQU07MkJBQUMsa0JBQWtCOzswQkFDekIsUUFBUTs7MEJBQUksTUFBTTsyQkFBQyxjQUFjIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSW5qZWN0LCBJbmplY3RhYmxlLCBPcHRpb25hbCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnXG5pbXBvcnQgeyBDRVJCT1NfU0VSVklDRSwgSUNlcmJvc1NlcnZpY2UgfSBmcm9tICdAYXBpcGFzcy9mcm9udGVuZC11dGlscydcbmltcG9ydCB7IFBFUk1JU1NJT05TX0NPTkZJRywgUGVybWlzc2lvbnNDb25maWcgfSBmcm9tICcuL3Blcm1pc3Npb25zLmNvbmZpZydcbmltcG9ydCB7IFBlcm1pc3Npb25DaGVjayB9IGZyb20gJy4uL21vZGVsL3Blcm1pc3Npb24tY2hlY2suaW50ZXJmYWNlJ1xuaW1wb3J0IHsgUGVybWlzc2lvblJlc3VsdCwgUGVybWlzc2lvblJlc3VsdE1hcCB9IGZyb20gJy4uL21vZGVsL3Blcm1pc3Npb24tcmVzdWx0LmludGVyZmFjZSdcblxudHlwZSBEZWNpc2lvbk1hcCA9IFJlY29yZDxzdHJpbmcsIFJlY29yZDxzdHJpbmcsIGJvb2xlYW4+PlxuXG4vKipcbiAqIEh5YnJpZCBhdXRob3JpemF0aW9uIG9yY2hlc3RyYXRvcjogZGVjaWRlcyBiZXR3ZWVuIENlcmJvcyBhbmQgdGhlIGxlZ2FjeSByb2xlXG4gKiBtb2RlbCBiYXNlZCBvbiB7QGxpbmsgUGVybWlzc2lvbnNDb25maWcuY2VyYm9zRW5hYmxlZH0uXG4gKlxuICogLSAqKkNlcmJvcyBkaXNhYmxlZCoqIOKGkiBiZWhhdmlvciBpcyBpZGVudGljYWwgdG8gdGhlIHByZS1DZXJib3Mgc3lzdGVtIChsZWdhY3kgcmVzb2x2ZXIpLlxuICogLSAqKkNlcmJvcyBlbmFibGVkKiog4oaSIGFza3MgdGhlIFBEUDsgb24gYW55IGZhaWx1cmUgaXQgaXMgKipmYWlsLWNsb3NlZCoqIChldmVyeSBhY3Rpb24gZGVuaWVkKSxcbiAqICAgc28gYSBQRFAgb3V0YWdlIG5ldmVyIHNpbGVudGx5IGdyYW50cyBhY2Nlc3MuXG4gKi9cbkBJbmplY3RhYmxlKClcbmV4cG9ydCBjbGFzcyBQZXJtaXNzaW9uc1NlcnZpY2Uge1xuXG4gIGNvbnN0cnVjdG9yKFxuICAgIEBJbmplY3QoUEVSTUlTU0lPTlNfQ09ORklHKSBwcml2YXRlIHJlYWRvbmx5IGNvbmZpZzogUGVybWlzc2lvbnNDb25maWcsXG4gICAgQE9wdGlvbmFsKCkgQEluamVjdChDRVJCT1NfU0VSVklDRSkgcHJpdmF0ZSByZWFkb25seSBjZXJib3M6IElDZXJib3NTZXJ2aWNlIHwgbnVsbFxuICApIHt9XG5cbiAgLyoqIEV2YWx1YXRlcyBzZXZlcmFsIHJlc291cmNlIGtpbmRzIGF0IG9uY2UuIFdpdGggQ2VyYm9zIGVuYWJsZWQgdGhpcyBpcyBhIHNpbmdsZSByb3VuZC10cmlwLiAqL1xuICBhc3luYyBjaGVja1Jlc291cmNlcyhjaGVja3M6IFBlcm1pc3Npb25DaGVja1tdKTogUHJvbWlzZTxQZXJtaXNzaW9uUmVzdWx0TWFwPiB7XG4gICAgY29uc3QgY2VyYm9zRW5hYmxlZCA9IGF3YWl0IHRoaXMuY29uZmlnLmNlcmJvc0VuYWJsZWQoKVxuICAgIGNvbnN0IGRlY2lzaW9ucyA9IGNlcmJvc0VuYWJsZWRcbiAgICAgID8gYXdhaXQgdGhpcy5yZXNvbHZlV2l0aENlcmJvcyhjaGVja3MpXG4gICAgICA6IGF3YWl0IHRoaXMucmVzb2x2ZVdpdGhMZWdhY3koY2hlY2tzKVxuICAgIHJldHVybiB0aGlzLmJ1aWxkUmVzdWx0TWFwKGRlY2lzaW9ucylcbiAgfVxuXG4gIC8qKiBDb252ZW5pZW5jZSBmb3IgdGhlIGNvbW1vbiBzaW5nbGUta2luZCBjYXNlLiAqL1xuICBhc3luYyBjaGVja1Jlc291cmNlKGNoZWNrOiBQZXJtaXNzaW9uQ2hlY2spOiBQcm9taXNlPFBlcm1pc3Npb25SZXN1bHQ+IHtcbiAgICBjb25zdCBtYXAgPSBhd2FpdCB0aGlzLmNoZWNrUmVzb3VyY2VzKFtjaGVja10pXG4gICAgcmV0dXJuIG1hcC5mb3IoY2hlY2sua2luZClcbiAgfVxuXG4gIHByaXZhdGUgYXN5bmMgcmVzb2x2ZVdpdGhDZXJib3MoY2hlY2tzOiBQZXJtaXNzaW9uQ2hlY2tbXSk6IFByb21pc2U8RGVjaXNpb25NYXA+IHtcbiAgICAvLyBGYWlsLWNsb3NlZDogbm8gUERQIHdpcmVkIGluIG1lYW5zIG5vIGdyYW50cy5cbiAgICBpZiAoIXRoaXMuY2VyYm9zKSB7XG4gICAgICBjb25zb2xlLmVycm9yKGAke1Blcm1pc3Npb25zU2VydmljZS5uYW1lfSAtIENFUkJPU19TRVJWSUNFIG5vdCBwcm92aWRlZDsgZGVueWluZyBhbGwgKGZhaWwtY2xvc2VkKWApXG4gICAgICByZXR1cm4gdGhpcy5kZW55QWxsKGNoZWNrcylcbiAgICB9XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHVzZXJJbmZvID0gYXdhaXQgdGhpcy5jb25maWcudXNlckluZm8oKVxuICAgICAgcmV0dXJuIGF3YWl0IHRoaXMuY2VyYm9zLmNoZWNrUmVzb3VyY2VzKFxuICAgICAgICB1c2VySW5mbyxcbiAgICAgICAgY2hlY2tzLm1hcCgoeyBraW5kLCBhY3Rpb25zLCBpZCB9KSA9PiAoeyBraW5kLCBhY3Rpb25zLCBpZCB9KSlcbiAgICAgIClcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICBjb25zb2xlLmVycm9yKGAke1Blcm1pc3Npb25zU2VydmljZS5uYW1lfSAtIENlcmJvcyBjaGVjayBmYWlsZWQ7IGRlbnlpbmcgYWxsIChmYWlsLWNsb3NlZClgLCBlKVxuICAgICAgcmV0dXJuIHRoaXMuZGVueUFsbChjaGVja3MpXG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBhc3luYyByZXNvbHZlV2l0aExlZ2FjeShjaGVja3M6IFBlcm1pc3Npb25DaGVja1tdKTogUHJvbWlzZTxEZWNpc2lvbk1hcD4ge1xuICAgIGNvbnN0IGRlY2lzaW9uczogRGVjaXNpb25NYXAgPSB7fVxuICAgIGZvciAoY29uc3QgY2hlY2sgb2YgY2hlY2tzKSB7XG4gICAgICBjb25zdCBhY3Rpb25zOiBSZWNvcmQ8c3RyaW5nLCBib29sZWFuPiA9IHt9XG4gICAgICBmb3IgKGNvbnN0IGFjdGlvbiBvZiBjaGVjay5hY3Rpb25zKSB7XG4gICAgICAgIGFjdGlvbnNbYWN0aW9uXSA9IGF3YWl0IHRoaXMuY29uZmlnLmxlZ2FjeVJlc29sdmVyKHsga2luZDogY2hlY2sua2luZCwgYWN0aW9uIH0sIGNoZWNrLmxlZ2FjeUNvbnRleHQpXG4gICAgICB9XG4gICAgICBkZWNpc2lvbnNbY2hlY2sua2luZF0gPSBhY3Rpb25zXG4gICAgfVxuICAgIHJldHVybiBkZWNpc2lvbnNcbiAgfVxuXG4gIHByaXZhdGUgZGVueUFsbChjaGVja3M6IFBlcm1pc3Npb25DaGVja1tdKTogRGVjaXNpb25NYXAge1xuICAgIHJldHVybiBjaGVja3MucmVkdWNlPERlY2lzaW9uTWFwPigoYWNjLCBjaGVjaykgPT4ge1xuICAgICAgYWNjW2NoZWNrLmtpbmRdID0gY2hlY2suYWN0aW9ucy5yZWR1Y2U8UmVjb3JkPHN0cmluZywgYm9vbGVhbj4+KChhY3Rpb25zLCBhY3Rpb24pID0+IHtcbiAgICAgICAgYWN0aW9uc1thY3Rpb25dID0gZmFsc2VcbiAgICAgICAgcmV0dXJuIGFjdGlvbnNcbiAgICAgIH0sIHt9KVxuICAgICAgcmV0dXJuIGFjY1xuICAgIH0sIHt9KVxuICB9XG5cbiAgcHJpdmF0ZSBidWlsZFJlc3VsdE1hcChkZWNpc2lvbnM6IERlY2lzaW9uTWFwKTogUGVybWlzc2lvblJlc3VsdE1hcCB7XG4gICAgY29uc3QgY2FuID0gKGtpbmQ6IHN0cmluZywgYWN0aW9uOiBzdHJpbmcpOiBib29sZWFuID0+IGRlY2lzaW9uc1traW5kXT8uW2FjdGlvbl0gPz8gZmFsc2VcbiAgICByZXR1cm4ge1xuICAgICAgY2FuLFxuICAgICAgZm9yOiAoa2luZDogc3RyaW5nKTogUGVybWlzc2lvblJlc3VsdCA9PiAoeyBjYW46IChhY3Rpb246IHN0cmluZykgPT4gY2FuKGtpbmQsIGFjdGlvbikgfSlcbiAgICB9XG4gIH1cblxufVxuIl19
@@ -0,0 +1,231 @@
1
+ import * as i0 from '@angular/core';
2
+ import { TemplateRef, Component, Input, ContentChild, Directive, InjectionToken, Injectable, Inject, Optional, NgModule } from '@angular/core';
3
+ import * as i1 from '@angular/common';
4
+ import { CommonModule } from '@angular/common';
5
+ import * as i2 from '@angular/material/tooltip';
6
+ import { MatTooltipModule } from '@angular/material/tooltip';
7
+ import { CERBOS_SERVICE } from '@apipass/frontend-utils';
8
+
9
+ /**
10
+ * Angular equivalent of the React `PermissionGate`.
11
+ *
12
+ * It **never hides** the protected action: when blocked it still renders the
13
+ * content, disabled, wrapped in a tooltip that explains why. The decision
14
+ * (`isAllowed`) is supplied by the caller — this component only presents it.
15
+ *
16
+ * @example
17
+ * ```html
18
+ * <apipass-permission-gate [isAllowed]="canCreate" [noAccessLabel]="'PERMISSIONS.NO_CREATE' | translate">
19
+ * <ng-template let-allowed>
20
+ * <button mat-raised-button [disabled]="!allowed" (click)="allowed && create()">New</button>
21
+ * </ng-template>
22
+ * </apipass-permission-gate>
23
+ * ```
24
+ */
25
+ class PermissionGateComponent {
26
+ /** Whether the gated action is allowed. */
27
+ isAllowed = false;
28
+ /** Tooltip text shown when the action is blocked. */
29
+ noAccessLabel = '';
30
+ template;
31
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionGateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
32
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.0.2", type: PermissionGateComponent, selector: "apipass-permission-gate", inputs: { isAllowed: "isAllowed", noAccessLabel: "noAccessLabel" }, queries: [{ propertyName: "template", first: true, predicate: TemplateRef, descendants: true }], ngImport: i0, template: "<ng-container *ngIf=\"isAllowed; else blocked\">\n <ng-container *ngTemplateOutlet=\"template ?? null; context: { $implicit: true }\"></ng-container>\n</ng-container>\n\n<ng-template #blocked>\n <span\n class=\"permission-gate-blocked\"\n [matTooltip]=\"noAccessLabel\"\n [matTooltipDisabled]=\"!noAccessLabel\">\n <ng-container *ngTemplateOutlet=\"template ?? null; context: { $implicit: false }\"></ng-container>\n </span>\n</ng-template>\n", styles: [".permission-gate-blocked{display:inline-flex;cursor:not-allowed}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.MatTooltip, selector: "[matTooltip]", exportAs: ["matTooltip"] }] });
33
+ }
34
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionGateComponent, decorators: [{
35
+ type: Component,
36
+ args: [{ selector: 'apipass-permission-gate', template: "<ng-container *ngIf=\"isAllowed; else blocked\">\n <ng-container *ngTemplateOutlet=\"template ?? null; context: { $implicit: true }\"></ng-container>\n</ng-container>\n\n<ng-template #blocked>\n <span\n class=\"permission-gate-blocked\"\n [matTooltip]=\"noAccessLabel\"\n [matTooltipDisabled]=\"!noAccessLabel\">\n <ng-container *ngTemplateOutlet=\"template ?? null; context: { $implicit: false }\"></ng-container>\n </span>\n</ng-template>\n", styles: [".permission-gate-blocked{display:inline-flex;cursor:not-allowed}\n"] }]
37
+ }], propDecorators: { isAllowed: [{
38
+ type: Input
39
+ }], noAccessLabel: [{
40
+ type: Input
41
+ }], template: [{
42
+ type: ContentChild,
43
+ args: [TemplateRef]
44
+ }] } });
45
+
46
+ /**
47
+ * Lightweight, dependency-free variant of {@link PermissionGateComponent} for
48
+ * **native** elements (e.g. `<button>`, `<a>`). When blocked it sets the
49
+ * `disabled` attribute, a `.permission-blocked` class and a native `title`
50
+ * tooltip — the element stays in the DOM, never hidden.
51
+ *
52
+ * For Angular Material controls (`mat-button`, etc.) prefer the component, since
53
+ * Material reads its own `disabled` @Input rather than the DOM attribute.
54
+ *
55
+ * @example
56
+ * ```html
57
+ * <button [apipassPermissionGate]="canCreate" noAccessLabel="No permission" (click)="create()">New</button>
58
+ * ```
59
+ */
60
+ class PermissionGateDirective {
61
+ el;
62
+ renderer;
63
+ /** Whether the gated action is allowed. */
64
+ isAllowed = false;
65
+ /** Native tooltip text shown when the action is blocked. */
66
+ noAccessLabel = '';
67
+ constructor(el, renderer) {
68
+ this.el = el;
69
+ this.renderer = renderer;
70
+ }
71
+ ngOnChanges() {
72
+ const node = this.el.nativeElement;
73
+ if (this.isAllowed) {
74
+ this.renderer.removeAttribute(node, 'disabled');
75
+ this.renderer.removeAttribute(node, 'title');
76
+ this.renderer.removeClass(node, 'permission-blocked');
77
+ return;
78
+ }
79
+ this.renderer.setAttribute(node, 'disabled', 'true');
80
+ this.renderer.addClass(node, 'permission-blocked');
81
+ if (this.noAccessLabel) {
82
+ this.renderer.setAttribute(node, 'title', this.noAccessLabel);
83
+ }
84
+ else {
85
+ this.renderer.removeAttribute(node, 'title');
86
+ }
87
+ }
88
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionGateDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive });
89
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.2", type: PermissionGateDirective, selector: "[apipassPermissionGate]", inputs: { isAllowed: ["apipassPermissionGate", "isAllowed"], noAccessLabel: "noAccessLabel" }, usesOnChanges: true, ngImport: i0 });
90
+ }
91
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionGateDirective, decorators: [{
92
+ type: Directive,
93
+ args: [{
94
+ selector: '[apipassPermissionGate]'
95
+ }]
96
+ }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.Renderer2 }]; }, propDecorators: { isAllowed: [{
97
+ type: Input,
98
+ args: ['apipassPermissionGate']
99
+ }], noAccessLabel: [{
100
+ type: Input
101
+ }] } });
102
+
103
+ const PERMISSIONS_CONFIG = new InjectionToken('PERMISSIONS_CONFIG');
104
+ /** Registers the {@link PermissionsConfig} used by {@link PermissionsService}. */
105
+ function providePermissionsConfig(config) {
106
+ return { provide: PERMISSIONS_CONFIG, useValue: config };
107
+ }
108
+
109
+ /**
110
+ * Hybrid authorization orchestrator: decides between Cerbos and the legacy role
111
+ * model based on {@link PermissionsConfig.cerbosEnabled}.
112
+ *
113
+ * - **Cerbos disabled** → behavior is identical to the pre-Cerbos system (legacy resolver).
114
+ * - **Cerbos enabled** → asks the PDP; on any failure it is **fail-closed** (every action denied),
115
+ * so a PDP outage never silently grants access.
116
+ */
117
+ class PermissionsService {
118
+ config;
119
+ cerbos;
120
+ constructor(config, cerbos) {
121
+ this.config = config;
122
+ this.cerbos = cerbos;
123
+ }
124
+ /** Evaluates several resource kinds at once. With Cerbos enabled this is a single round-trip. */
125
+ async checkResources(checks) {
126
+ const cerbosEnabled = await this.config.cerbosEnabled();
127
+ const decisions = cerbosEnabled
128
+ ? await this.resolveWithCerbos(checks)
129
+ : await this.resolveWithLegacy(checks);
130
+ return this.buildResultMap(decisions);
131
+ }
132
+ /** Convenience for the common single-kind case. */
133
+ async checkResource(check) {
134
+ const map = await this.checkResources([check]);
135
+ return map.for(check.kind);
136
+ }
137
+ async resolveWithCerbos(checks) {
138
+ // Fail-closed: no PDP wired in means no grants.
139
+ if (!this.cerbos) {
140
+ console.error(`${PermissionsService.name} - CERBOS_SERVICE not provided; denying all (fail-closed)`);
141
+ return this.denyAll(checks);
142
+ }
143
+ try {
144
+ const userInfo = await this.config.userInfo();
145
+ return await this.cerbos.checkResources(userInfo, checks.map(({ kind, actions, id }) => ({ kind, actions, id })));
146
+ }
147
+ catch (e) {
148
+ console.error(`${PermissionsService.name} - Cerbos check failed; denying all (fail-closed)`, e);
149
+ return this.denyAll(checks);
150
+ }
151
+ }
152
+ async resolveWithLegacy(checks) {
153
+ const decisions = {};
154
+ for (const check of checks) {
155
+ const actions = {};
156
+ for (const action of check.actions) {
157
+ actions[action] = await this.config.legacyResolver({ kind: check.kind, action }, check.legacyContext);
158
+ }
159
+ decisions[check.kind] = actions;
160
+ }
161
+ return decisions;
162
+ }
163
+ denyAll(checks) {
164
+ return checks.reduce((acc, check) => {
165
+ acc[check.kind] = check.actions.reduce((actions, action) => {
166
+ actions[action] = false;
167
+ return actions;
168
+ }, {});
169
+ return acc;
170
+ }, {});
171
+ }
172
+ buildResultMap(decisions) {
173
+ const can = (kind, action) => decisions[kind]?.[action] ?? false;
174
+ return {
175
+ can,
176
+ for: (kind) => ({ can: (action) => can(kind, action) })
177
+ };
178
+ }
179
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionsService, deps: [{ token: PERMISSIONS_CONFIG }, { token: CERBOS_SERVICE, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
180
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionsService });
181
+ }
182
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionsService, decorators: [{
183
+ type: Injectable
184
+ }], ctorParameters: function () { return [{ type: undefined, decorators: [{
185
+ type: Inject,
186
+ args: [PERMISSIONS_CONFIG]
187
+ }] }, { type: undefined, decorators: [{
188
+ type: Optional
189
+ }, {
190
+ type: Inject,
191
+ args: [CERBOS_SERVICE]
192
+ }] }]; } });
193
+
194
+ class PermissionsModule {
195
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
196
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.0.2", ngImport: i0, type: PermissionsModule, declarations: [PermissionGateComponent,
197
+ PermissionGateDirective], imports: [CommonModule,
198
+ MatTooltipModule], exports: [PermissionGateComponent,
199
+ PermissionGateDirective] });
200
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionsModule, providers: [
201
+ PermissionsService
202
+ ], imports: [CommonModule,
203
+ MatTooltipModule] });
204
+ }
205
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.2", ngImport: i0, type: PermissionsModule, decorators: [{
206
+ type: NgModule,
207
+ args: [{
208
+ imports: [
209
+ CommonModule,
210
+ MatTooltipModule
211
+ ],
212
+ declarations: [
213
+ PermissionGateComponent,
214
+ PermissionGateDirective
215
+ ],
216
+ exports: [
217
+ PermissionGateComponent,
218
+ PermissionGateDirective
219
+ ],
220
+ providers: [
221
+ PermissionsService
222
+ ]
223
+ }]
224
+ }] });
225
+
226
+ /**
227
+ * Generated bundle index. Do not edit.
228
+ */
229
+
230
+ export { PERMISSIONS_CONFIG, PermissionGateComponent, PermissionGateDirective, PermissionsModule, PermissionsService, providePermissionsConfig };
231
+ //# sourceMappingURL=apipass-permissions.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apipass-permissions.mjs","sources":["../../../projects/permissions/src/permission-gate/permission-gate.component.ts","../../../projects/permissions/src/permission-gate/permission-gate.component.html","../../../projects/permissions/src/permission-gate/permission-gate.directive.ts","../../../projects/permissions/src/service/permissions.config.ts","../../../projects/permissions/src/service/permissions.service.ts","../../../projects/permissions/src/permissions.module.ts","../../../projects/permissions/src/apipass-permissions.ts"],"sourcesContent":["import { Component, ContentChild, Input, TemplateRef } from '@angular/core'\n\n/** Context object passed to the projected template — `$implicit` is the `allowed` flag. */\nexport interface PermissionGateContext {\n $implicit: boolean\n}\n\n/**\n * Angular equivalent of the React `PermissionGate`.\n *\n * It **never hides** the protected action: when blocked it still renders the\n * content, disabled, wrapped in a tooltip that explains why. The decision\n * (`isAllowed`) is supplied by the caller — this component only presents it.\n *\n * @example\n * ```html\n * <apipass-permission-gate [isAllowed]=\"canCreate\" [noAccessLabel]=\"'PERMISSIONS.NO_CREATE' | translate\">\n * <ng-template let-allowed>\n * <button mat-raised-button [disabled]=\"!allowed\" (click)=\"allowed && create()\">New</button>\n * </ng-template>\n * </apipass-permission-gate>\n * ```\n */\n@Component({\n selector: 'apipass-permission-gate',\n templateUrl: './permission-gate.component.html',\n styleUrls: ['./permission-gate.component.scss']\n})\nexport class PermissionGateComponent {\n\n /** Whether the gated action is allowed. */\n @Input() isAllowed = false\n\n /** Tooltip text shown when the action is blocked. */\n @Input() noAccessLabel = ''\n\n @ContentChild(TemplateRef) template?: TemplateRef<PermissionGateContext>\n\n}\n","<ng-container *ngIf=\"isAllowed; else blocked\">\n <ng-container *ngTemplateOutlet=\"template ?? null; context: { $implicit: true }\"></ng-container>\n</ng-container>\n\n<ng-template #blocked>\n <span\n class=\"permission-gate-blocked\"\n [matTooltip]=\"noAccessLabel\"\n [matTooltipDisabled]=\"!noAccessLabel\">\n <ng-container *ngTemplateOutlet=\"template ?? null; context: { $implicit: false }\"></ng-container>\n </span>\n</ng-template>\n","import { Directive, ElementRef, Input, OnChanges, Renderer2 } from '@angular/core'\n\n/**\n * Lightweight, dependency-free variant of {@link PermissionGateComponent} for\n * **native** elements (e.g. `<button>`, `<a>`). When blocked it sets the\n * `disabled` attribute, a `.permission-blocked` class and a native `title`\n * tooltip — the element stays in the DOM, never hidden.\n *\n * For Angular Material controls (`mat-button`, etc.) prefer the component, since\n * Material reads its own `disabled` @Input rather than the DOM attribute.\n *\n * @example\n * ```html\n * <button [apipassPermissionGate]=\"canCreate\" noAccessLabel=\"No permission\" (click)=\"create()\">New</button>\n * ```\n */\n@Directive({\n selector: '[apipassPermissionGate]'\n})\nexport class PermissionGateDirective implements OnChanges {\n\n /** Whether the gated action is allowed. */\n @Input('apipassPermissionGate') isAllowed = false\n\n /** Native tooltip text shown when the action is blocked. */\n @Input() noAccessLabel = ''\n\n constructor(\n private readonly el: ElementRef<HTMLElement>,\n private readonly renderer: Renderer2\n ) {}\n\n ngOnChanges(): void {\n const node = this.el.nativeElement\n if (this.isAllowed) {\n this.renderer.removeAttribute(node, 'disabled')\n this.renderer.removeAttribute(node, 'title')\n this.renderer.removeClass(node, 'permission-blocked')\n return\n }\n this.renderer.setAttribute(node, 'disabled', 'true')\n this.renderer.addClass(node, 'permission-blocked')\n if (this.noAccessLabel) {\n this.renderer.setAttribute(node, 'title', this.noAccessLabel)\n } else {\n this.renderer.removeAttribute(node, 'title')\n }\n }\n\n}\n","import { InjectionToken, Provider } from '@angular/core'\nimport { UserInfo } from '@apipass/frontend-utils'\n\nexport type MaybeAsync<T> = T | Promise<T>\n\n/** A single `{ kind, action }` pair handed to the legacy resolver. */\nexport interface LegacyCheck {\n kind: string\n action: string\n}\n\n/**\n * Per-application configuration for {@link PermissionsService}.\n *\n * The library is decoupled from any concrete app: each consumer wires in how to\n * read the Cerbos toggle, how to build the Cerbos principal, and how to resolve\n * the legacy (pre-Cerbos) decision.\n */\nexport interface PermissionsConfig {\n /** Whether Cerbos authorization is active. When `false`, only `legacyResolver` runs. */\n cerbosEnabled(): MaybeAsync<boolean>\n /** Builds the Cerbos principal (`{ accountId, domain, roles }`). Only called when Cerbos is enabled. */\n userInfo(): MaybeAsync<UserInfo>\n /**\n * Resolves a single check using the legacy role model. Only called when Cerbos is disabled.\n * `context` is the `legacyContext` carried by the originating {@link PermissionCheck}.\n */\n legacyResolver(check: LegacyCheck, context?: unknown): MaybeAsync<boolean>\n}\n\nexport const PERMISSIONS_CONFIG = new InjectionToken<PermissionsConfig>('PERMISSIONS_CONFIG')\n\n/** Registers the {@link PermissionsConfig} used by {@link PermissionsService}. */\nexport function providePermissionsConfig(config: PermissionsConfig): Provider {\n return { provide: PERMISSIONS_CONFIG, useValue: config }\n}\n","import { Inject, Injectable, Optional } from '@angular/core'\nimport { CERBOS_SERVICE, ICerbosService } from '@apipass/frontend-utils'\nimport { PERMISSIONS_CONFIG, PermissionsConfig } from './permissions.config'\nimport { PermissionCheck } from '../model/permission-check.interface'\nimport { PermissionResult, PermissionResultMap } from '../model/permission-result.interface'\n\ntype DecisionMap = Record<string, Record<string, boolean>>\n\n/**\n * Hybrid authorization orchestrator: decides between Cerbos and the legacy role\n * model based on {@link PermissionsConfig.cerbosEnabled}.\n *\n * - **Cerbos disabled** → behavior is identical to the pre-Cerbos system (legacy resolver).\n * - **Cerbos enabled** → asks the PDP; on any failure it is **fail-closed** (every action denied),\n * so a PDP outage never silently grants access.\n */\n@Injectable()\nexport class PermissionsService {\n\n constructor(\n @Inject(PERMISSIONS_CONFIG) private readonly config: PermissionsConfig,\n @Optional() @Inject(CERBOS_SERVICE) private readonly cerbos: ICerbosService | null\n ) {}\n\n /** Evaluates several resource kinds at once. With Cerbos enabled this is a single round-trip. */\n async checkResources(checks: PermissionCheck[]): Promise<PermissionResultMap> {\n const cerbosEnabled = await this.config.cerbosEnabled()\n const decisions = cerbosEnabled\n ? await this.resolveWithCerbos(checks)\n : await this.resolveWithLegacy(checks)\n return this.buildResultMap(decisions)\n }\n\n /** Convenience for the common single-kind case. */\n async checkResource(check: PermissionCheck): Promise<PermissionResult> {\n const map = await this.checkResources([check])\n return map.for(check.kind)\n }\n\n private async resolveWithCerbos(checks: PermissionCheck[]): Promise<DecisionMap> {\n // Fail-closed: no PDP wired in means no grants.\n if (!this.cerbos) {\n console.error(`${PermissionsService.name} - CERBOS_SERVICE not provided; denying all (fail-closed)`)\n return this.denyAll(checks)\n }\n try {\n const userInfo = await this.config.userInfo()\n return await this.cerbos.checkResources(\n userInfo,\n checks.map(({ kind, actions, id }) => ({ kind, actions, id }))\n )\n } catch (e) {\n console.error(`${PermissionsService.name} - Cerbos check failed; denying all (fail-closed)`, e)\n return this.denyAll(checks)\n }\n }\n\n private async resolveWithLegacy(checks: PermissionCheck[]): Promise<DecisionMap> {\n const decisions: DecisionMap = {}\n for (const check of checks) {\n const actions: Record<string, boolean> = {}\n for (const action of check.actions) {\n actions[action] = await this.config.legacyResolver({ kind: check.kind, action }, check.legacyContext)\n }\n decisions[check.kind] = actions\n }\n return decisions\n }\n\n private denyAll(checks: PermissionCheck[]): DecisionMap {\n return checks.reduce<DecisionMap>((acc, check) => {\n acc[check.kind] = check.actions.reduce<Record<string, boolean>>((actions, action) => {\n actions[action] = false\n return actions\n }, {})\n return acc\n }, {})\n }\n\n private buildResultMap(decisions: DecisionMap): PermissionResultMap {\n const can = (kind: string, action: string): boolean => decisions[kind]?.[action] ?? false\n return {\n can,\n for: (kind: string): PermissionResult => ({ can: (action: string) => can(kind, action) })\n }\n }\n\n}\n","import { NgModule } from '@angular/core'\nimport { CommonModule } from '@angular/common'\nimport { MatTooltipModule } from '@angular/material/tooltip'\nimport { PermissionGateComponent } from './permission-gate/permission-gate.component'\nimport { PermissionGateDirective } from './permission-gate/permission-gate.directive'\nimport { PermissionsService } from './service/permissions.service'\n\n@NgModule({\n imports: [\n CommonModule,\n MatTooltipModule\n ],\n declarations: [\n PermissionGateComponent,\n PermissionGateDirective\n ],\n exports: [\n PermissionGateComponent,\n PermissionGateDirective\n ],\n providers: [\n PermissionsService\n ]\n})\nexport class PermissionsModule {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generated bundle index. Do not edit.
3
+ */
4
+ /// <amd-module name="@apipass/permissions" />
5
+ export * from './public-api';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * A permission check for a single resource kind and one or more actions.
3
+ * Mirrors the React `useResourceActions(kind, actions)` contract: one kind,
4
+ * several actions, evaluated together in a single round-trip.
5
+ */
6
+ export interface PermissionCheck {
7
+ /** Cerbos resource kind (e.g. `flow`, `stage`, `project`). */
8
+ kind: string;
9
+ /** Actions to evaluate for this kind (e.g. `['read', 'update', 'stop']`). */
10
+ actions: string[];
11
+ /** Optional resource id; defaults to the kind when omitted (matches the Cerbos mapper). */
12
+ id?: string;
13
+ /**
14
+ * Opaque context forwarded to the app's `legacyResolver` when Cerbos is disabled.
15
+ * Used to carry data the legacy role checks need (e.g. `{ projectId }`).
16
+ */
17
+ legacyContext?: unknown;
18
+ }
@@ -0,0 +1,11 @@
1
+ /** Result of a single-kind check. `can(action)` returns whether the action is allowed. */
2
+ export interface PermissionResult {
3
+ can(action: string): boolean;
4
+ }
5
+ /** Result of a multi-kind check, keyed by resource kind. */
6
+ export interface PermissionResultMap {
7
+ /** Whether `action` is allowed for `kind`. */
8
+ can(kind: string, action: string): boolean;
9
+ /** Narrows the map to a single kind, returning a {@link PermissionResult}. */
10
+ for(kind: string): PermissionResult;
11
+ }
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@apipass/permissions",
3
+ "version": "1.0.192",
4
+ "peerDependencies": {
5
+ "@angular/common": "16.0.2",
6
+ "@angular/core": "16.0.2",
7
+ "@angular/material": "16.0.1",
8
+ "@apipass/frontend-utils": "*"
9
+ },
10
+ "dependencies": {
11
+ "tslib": "2.5.2"
12
+ },
13
+ "module": "fesm2022/apipass-permissions.mjs",
14
+ "typings": "index.d.ts",
15
+ "exports": {
16
+ "./package.json": {
17
+ "default": "./package.json"
18
+ },
19
+ ".": {
20
+ "types": "./index.d.ts",
21
+ "esm2022": "./esm2022/apipass-permissions.mjs",
22
+ "esm": "./esm2022/apipass-permissions.mjs",
23
+ "default": "./fesm2022/apipass-permissions.mjs"
24
+ }
25
+ },
26
+ "sideEffects": false
27
+ }
@@ -0,0 +1,31 @@
1
+ import { TemplateRef } from '@angular/core';
2
+ import * as i0 from "@angular/core";
3
+ /** Context object passed to the projected template — `$implicit` is the `allowed` flag. */
4
+ export interface PermissionGateContext {
5
+ $implicit: boolean;
6
+ }
7
+ /**
8
+ * Angular equivalent of the React `PermissionGate`.
9
+ *
10
+ * It **never hides** the protected action: when blocked it still renders the
11
+ * content, disabled, wrapped in a tooltip that explains why. The decision
12
+ * (`isAllowed`) is supplied by the caller — this component only presents it.
13
+ *
14
+ * @example
15
+ * ```html
16
+ * <apipass-permission-gate [isAllowed]="canCreate" [noAccessLabel]="'PERMISSIONS.NO_CREATE' | translate">
17
+ * <ng-template let-allowed>
18
+ * <button mat-raised-button [disabled]="!allowed" (click)="allowed && create()">New</button>
19
+ * </ng-template>
20
+ * </apipass-permission-gate>
21
+ * ```
22
+ */
23
+ export declare class PermissionGateComponent {
24
+ /** Whether the gated action is allowed. */
25
+ isAllowed: boolean;
26
+ /** Tooltip text shown when the action is blocked. */
27
+ noAccessLabel: string;
28
+ template?: TemplateRef<PermissionGateContext>;
29
+ static ɵfac: i0.ɵɵFactoryDeclaration<PermissionGateComponent, never>;
30
+ static ɵcmp: i0.ɵɵComponentDeclaration<PermissionGateComponent, "apipass-permission-gate", never, { "isAllowed": { "alias": "isAllowed"; "required": false; }; "noAccessLabel": { "alias": "noAccessLabel"; "required": false; }; }, {}, ["template"], never, false, never>;
31
+ }
@@ -0,0 +1,28 @@
1
+ import { ElementRef, OnChanges, Renderer2 } from '@angular/core';
2
+ import * as i0 from "@angular/core";
3
+ /**
4
+ * Lightweight, dependency-free variant of {@link PermissionGateComponent} for
5
+ * **native** elements (e.g. `<button>`, `<a>`). When blocked it sets the
6
+ * `disabled` attribute, a `.permission-blocked` class and a native `title`
7
+ * tooltip — the element stays in the DOM, never hidden.
8
+ *
9
+ * For Angular Material controls (`mat-button`, etc.) prefer the component, since
10
+ * Material reads its own `disabled` @Input rather than the DOM attribute.
11
+ *
12
+ * @example
13
+ * ```html
14
+ * <button [apipassPermissionGate]="canCreate" noAccessLabel="No permission" (click)="create()">New</button>
15
+ * ```
16
+ */
17
+ export declare class PermissionGateDirective implements OnChanges {
18
+ private readonly el;
19
+ private readonly renderer;
20
+ /** Whether the gated action is allowed. */
21
+ isAllowed: boolean;
22
+ /** Native tooltip text shown when the action is blocked. */
23
+ noAccessLabel: string;
24
+ constructor(el: ElementRef<HTMLElement>, renderer: Renderer2);
25
+ ngOnChanges(): void;
26
+ static ɵfac: i0.ɵɵFactoryDeclaration<PermissionGateDirective, never>;
27
+ static ɵdir: i0.ɵɵDirectiveDeclaration<PermissionGateDirective, "[apipassPermissionGate]", never, { "isAllowed": { "alias": "apipassPermissionGate"; "required": false; }; "noAccessLabel": { "alias": "noAccessLabel"; "required": false; }; }, {}, never, never, false, never>;
28
+ }
@@ -0,0 +1,10 @@
1
+ import * as i0 from "@angular/core";
2
+ import * as i1 from "./permission-gate/permission-gate.component";
3
+ import * as i2 from "./permission-gate/permission-gate.directive";
4
+ import * as i3 from "@angular/common";
5
+ import * as i4 from "@angular/material/tooltip";
6
+ export declare class PermissionsModule {
7
+ static ɵfac: i0.ɵɵFactoryDeclaration<PermissionsModule, never>;
8
+ static ɵmod: i0.ɵɵNgModuleDeclaration<PermissionsModule, [typeof i1.PermissionGateComponent, typeof i2.PermissionGateDirective], [typeof i3.CommonModule, typeof i4.MatTooltipModule], [typeof i1.PermissionGateComponent, typeof i2.PermissionGateDirective]>;
9
+ static ɵinj: i0.ɵɵInjectorDeclaration<PermissionsModule>;
10
+ }
@@ -0,0 +1,7 @@
1
+ export * from './permissions.module';
2
+ export * from './permission-gate/permission-gate.component';
3
+ export * from './permission-gate/permission-gate.directive';
4
+ export * from './service/permissions.service';
5
+ export * from './service/permissions.config';
6
+ export * from './model/permission-check.interface';
7
+ export * from './model/permission-result.interface';
@@ -0,0 +1,29 @@
1
+ import { InjectionToken, Provider } from '@angular/core';
2
+ import { UserInfo } from '@apipass/frontend-utils';
3
+ export type MaybeAsync<T> = T | Promise<T>;
4
+ /** A single `{ kind, action }` pair handed to the legacy resolver. */
5
+ export interface LegacyCheck {
6
+ kind: string;
7
+ action: string;
8
+ }
9
+ /**
10
+ * Per-application configuration for {@link PermissionsService}.
11
+ *
12
+ * The library is decoupled from any concrete app: each consumer wires in how to
13
+ * read the Cerbos toggle, how to build the Cerbos principal, and how to resolve
14
+ * the legacy (pre-Cerbos) decision.
15
+ */
16
+ export interface PermissionsConfig {
17
+ /** Whether Cerbos authorization is active. When `false`, only `legacyResolver` runs. */
18
+ cerbosEnabled(): MaybeAsync<boolean>;
19
+ /** Builds the Cerbos principal (`{ accountId, domain, roles }`). Only called when Cerbos is enabled. */
20
+ userInfo(): MaybeAsync<UserInfo>;
21
+ /**
22
+ * Resolves a single check using the legacy role model. Only called when Cerbos is disabled.
23
+ * `context` is the `legacyContext` carried by the originating {@link PermissionCheck}.
24
+ */
25
+ legacyResolver(check: LegacyCheck, context?: unknown): MaybeAsync<boolean>;
26
+ }
27
+ export declare const PERMISSIONS_CONFIG: InjectionToken<PermissionsConfig>;
28
+ /** Registers the {@link PermissionsConfig} used by {@link PermissionsService}. */
29
+ export declare function providePermissionsConfig(config: PermissionsConfig): Provider;
@@ -0,0 +1,28 @@
1
+ import { ICerbosService } from '@apipass/frontend-utils';
2
+ import { PermissionsConfig } from './permissions.config';
3
+ import { PermissionCheck } from '../model/permission-check.interface';
4
+ import { PermissionResult, PermissionResultMap } from '../model/permission-result.interface';
5
+ import * as i0 from "@angular/core";
6
+ /**
7
+ * Hybrid authorization orchestrator: decides between Cerbos and the legacy role
8
+ * model based on {@link PermissionsConfig.cerbosEnabled}.
9
+ *
10
+ * - **Cerbos disabled** → behavior is identical to the pre-Cerbos system (legacy resolver).
11
+ * - **Cerbos enabled** → asks the PDP; on any failure it is **fail-closed** (every action denied),
12
+ * so a PDP outage never silently grants access.
13
+ */
14
+ export declare class PermissionsService {
15
+ private readonly config;
16
+ private readonly cerbos;
17
+ constructor(config: PermissionsConfig, cerbos: ICerbosService | null);
18
+ /** Evaluates several resource kinds at once. With Cerbos enabled this is a single round-trip. */
19
+ checkResources(checks: PermissionCheck[]): Promise<PermissionResultMap>;
20
+ /** Convenience for the common single-kind case. */
21
+ checkResource(check: PermissionCheck): Promise<PermissionResult>;
22
+ private resolveWithCerbos;
23
+ private resolveWithLegacy;
24
+ private denyAll;
25
+ private buildResultMap;
26
+ static ɵfac: i0.ɵɵFactoryDeclaration<PermissionsService, [null, { optional: true; }]>;
27
+ static ɵprov: i0.ɵɵInjectableDeclaration<PermissionsService>;
28
+ }