UNPKG

@rxap/authorization

Version:

Provides an Angular module and directives to manage authorization and permissions in your application. It allows you to control the visibility and enabled state of UI elements based on user permissions. The package includes an `AuthorizationService` to ch

341 lines (289 loc) 18.1 kB
<!doctype html> <html class="no-js" lang=""> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <title>angular-authorization</title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="./images/favicon.ico"> <link rel="stylesheet" href="./styles/style.css"> <link rel="stylesheet" href="./styles/dark.css"> </head> <body> <script> // Blocking script to avoid flickering dark mode // Dark mode toggle button var useDark = window.matchMedia('(prefers-color-scheme: dark)'); var darkModeState = useDark.matches; var $darkModeToggleSwitchers = document.querySelectorAll('.dark-mode-switch input'); var $darkModeToggles = document.querySelectorAll('.dark-mode-switch'); var darkModeStateLocal = localStorage.getItem('compodoc_darkmode-state'); function checkToggle(check) { for (var i = 0; i < $darkModeToggleSwitchers.length; i++) { $darkModeToggleSwitchers[i].checked = check; } } function toggleDarkMode(state) { if (window.localStorage) { localStorage.setItem('compodoc_darkmode-state', state); } checkToggle(state); const hasClass = document.body.classList.contains('dark'); if (state) { for (var i = 0; i < $darkModeToggles.length; i++) { $darkModeToggles[i].classList.add('dark'); } if (!hasClass) { document.body.classList.add('dark'); } } else { for (var i = 0; i < $darkModeToggles.length; i++) { $darkModeToggles[i].classList.remove('dark'); } if (hasClass) { document.body.classList.remove('dark'); } } } useDark.addEventListener('change', function (evt) { toggleDarkMode(evt.matches); }); if (darkModeStateLocal) { darkModeState = darkModeStateLocal === 'true'; } toggleDarkMode(darkModeState); </script> <div class="navbar navbar-default navbar-fixed-top d-md-none p-0"> <div class="d-flex"> <a href="./" class="navbar-brand">angular-authorization</a> <button type="button" class="btn btn-default btn-menu ion-ios-menu" id="btn-menu"></button> </div> </div> <div class="xs-menu menu" id="mobile-menu"> <div id="book-search-input" role="search"><input type="text" placeholder="Type to search"></div> <compodoc-menu></compodoc-menu> </div> <div class="container-fluid main"> <div class="row main"> <div class="d-none d-md-block menu"> <compodoc-menu mode="normal"></compodoc-menu> </div> <!-- START CONTENT --> <div class="content getting-started"> <div class="content-data"> <p>Provides an Angular module and directives to manage authorization and permissions in your application. It allows you to control the visibility and enabled state of UI elements based on user permissions. The package includes an &#x60;AuthorizationService&#x60; to check permissions and directives to easily integrate permission checks into your templates and components.</p> <p><a href="https://www.npmjs.com/package/@rxap/authorization"><img src="https://img.shields.io/npm/v/@rxap/authorization?style=flat-square" alt="npm version" class="img-responsive"></a> <a href="https://commitizen.github.io/cz-cli/"><img src="https://img.shields.io/badge/commitizen-friendly-brightgreen.svg?style=flat-square" alt="commitizen friendly" class="img-responsive"></a> <a href="https://github.com/prettier/prettier"><img src="https://img.shields.io/badge/styled_with-prettier-ff69b4.svg?style=flat-square" alt="styled with prettier" class="img-responsive"></a> <img src="https://img.shields.io/librariesio/release/npm/@rxap/authorization" alt="Libraries.io dependency status for latest release, scoped npm package" class="img-responsive"> <img src="https://img.shields.io/npm/dm/@rxap/authorization" alt="npm" class="img-responsive"> <img src="https://img.shields.io/npm/l/@rxap/authorization" alt="NPM" class="img-responsive"></p> <ul> <li><a href="#installation">Installation</a></li> <li><a href="#guides">Guides</a></li> <li><a href="#generators">Generators</a><ul> <li><a href="#init">init</a></li> </ul> </li> </ul> <h1>Installation</h1> <p><strong>Add the package to your workspace:</strong></p> <b>Example :</b><div><pre class="line-numbers"><code class="language-bash">yarn add &#64;rxap/authorization</code></pre></div><p><strong>Install peer dependencies:</strong></p> <b>Example :</b><div><pre class="line-numbers"><code class="language-bash">yarn add &#64;angular/core &#64;angular/forms &#64;angular/material &#64;rxap/utilities rxjs </code></pre></div><p><strong>Execute the init generator:</strong></p> <b>Example :</b><div><pre class="line-numbers"><code class="language-bash">yarn nx g &#64;rxap/authorization:init</code></pre></div><h1>Guides</h1> <h1>Authorization Developer Guide</h1> <p>The <code>@rxap/authorization</code> package provides a robust and flexible way to manage user permissions in Angular applications. It supports permission-based view rendering, component enabling/disabling, and hierarchical scoping.</p> <h2>Installation</h2> <ol> <li><p><strong>Directives</strong>: Import the <code>HasPermissionModule</code> in your application or feature module to use the directives in your templates:</p> <b>Example :</b><div><pre class="line-numbers"><code class="language-typescript">import { HasPermissionModule } from &#39;&#64;rxap/authorization&#39;; &#64;NgModule({ imports: [ HasPermissionModule, // ... ], }) export class AppModule {}</code></pre></div><p>Alternatively, you can import individual standalone directives as needed (e.g., <code>IfHasPermissionDirective</code>, <code>MatButtonHasEnablePermissionDirective</code>).</p> </li> <li><p><strong>Providers</strong>: Use the <code>provideAuthorization()</code> utility to configure the service and its dependencies (like disabling authorization via config).</p> <b>Example :</b><div><pre class="line-numbers"><code class="language-typescript">import { provideAuthorization } from &#39;&#64;rxap/authorization&#39;; import { ApplicationConfig } from &#39;&#64;angular/core&#39;; export const appConfig: ApplicationConfig = { providers: [ provideAuthorization(), // ... other providers, ensure ConfigService is also provided/available if needed ] };</code></pre></div></li> </ol> <h2>Authorization Service</h2> <p>The core of the package is the <code>AuthorizationService</code>. It holds the current user&#39;s permissions and provides methods to check access.</p> <h3>Setting Permissions</h3> <p>Permissions are stored as a list of strings. You typically set these after user authentication.</p> <b>Example :</b><div><pre class="line-numbers"><code class="language-typescript">import { AuthorizationService } from &#39;&#64;rxap/authorization&#39;; &#64;Injectable({ providedIn: &#39;root&#39; }) export class AuthService { constructor(private authorizationService: AuthorizationService) {} login() { // ... authenticate user ... const permissions = [&#39;user.read&#39;, &#39;user.write&#39;, &#39;admin.*&#39;]; this.authorizationService.setPermissions(permissions); } }</code></pre></div><h3>Checking Permissions</h3> <p>You can check permissions programmatically using <code>hasPermission</code> (sync) or <code>hasPermission$</code> (observable).</p> <b>Example :</b><div><pre class="line-numbers"><code class="language-typescript">// Synchronous check if (this.authorizationService.hasPermission(&#39;user.create&#39;)) { // ... } // Observable check this.authorizationService.hasPermission$(&#39;user.create&#39;).subscribe(canCreate =&gt; { // ... });</code></pre></div><h3>Permission Logic &amp; Wildcards</h3> <p>The service supports <strong>dot notation</strong> and <strong>wildcards</strong>:</p> <ul> <li><strong>Exact Match</strong>: <code>&#39;user.read&#39;</code> matches <code>&#39;user.read&#39;</code>.</li> <li><strong>Wildcards (<code>*</code>)</strong>: The <code>*</code> character matches any sequence of characters.<ul> <li><code>&#39;admin.*&#39;</code> matches <code>&#39;admin.settings&#39;</code>, <code>&#39;admin.users&#39;</code>, etc.</li> <li><code>&#39;*.read&#39;</code> matches <code>&#39;user.read&#39;</code>, <code>&#39;product.read&#39;</code>, etc.</li> <li><code>&#39;*&#39;</code> matches everything (superuser).</li> </ul> </li> </ul> <h2>Directives</h2> <p>The package provides several directives to деклараtively control the UI based on permissions.</p> <h3>Structural Directive: <code>*rxapIfHasPermission</code></h3> <p>Conditionally renders an element if the user has the specified permission.</p> <b>Example :</b><div><pre class="line-numbers"><code class="language-html">&lt;div *rxapIfHasPermission=&quot;&#39;feature.view&#39;&quot;&gt; You can see this feature. &lt;/div&gt; &lt;div *rxapIfHasPermission=&quot;&#39;feature.admin&#39;; else accessDenied&quot;&gt; Admin Panel &lt;/div&gt; &lt;ng-template #accessDenied&gt; &lt;p&gt;Access Denied&lt;/p&gt; &lt;/ng-template&gt;</code></pre></div><h3>Enable/Disable Directive: <code>rxapHasEnablePermission</code></h3> <p>Disables the host component if the user lacks the permission. This is often better than hiding controls entirely, as it shows what is available.</p> <p><strong>Supported Components:</strong></p> <ul> <li>Native Buttons (<code>&lt;button&gt;</code>)</li> <li>Angular Material Buttons (<code>mat-button</code>, <code>mat-raised-button</code>, <code>mat-icon-button</code>, <code>mat-fab</code>, etc.)</li> <li>Angular Material Input (<code>matInput</code>)</li> <li>Angular Material Select (<code>mat-select</code>)</li> <li>Angular Material Checkbox (<code>mat-checkbox</code>)</li> <li>Angular Material Slide Toggle (<code>mat-slide-toggle</code>)</li> <li>Reactive Forms Controls (<code>[formControl]</code>, <code>[formControlName]</code>)</li> </ul> <p><strong>Usage:</strong></p> <b>Example :</b><div><pre class="line-numbers"><code class="language-html">&lt;!-- Button is disabled without &#39;user.delete&#39; permission --&gt; &lt;button mat-button [rxapHasEnablePermission]=&quot;&#39;user.delete&#39;&quot; (click)=&quot;deleteUser()&quot;&gt; Delete User &lt;/button&gt; &lt;!-- Form control is disabled without &#39;user.edit&#39; permission --&gt; &lt;input matInput [formControl]=&quot;emailCtrl&quot; [rxapHasEnablePermission]=&quot;&#39;user.edit&#39;&quot;&gt;</code></pre></div><h3>Write Permission Directive: <code>rxapHasWritePermission</code></h3> <p>Sets the <code>readonly</code> attribute of an element based on permission. Useful for inputs where you want to show the value but prevent editing.</p> <b>Example :</b><div><pre class="line-numbers"><code class="language-html">&lt;input [rxapHasWritePermission]=&quot;&#39;user.edit&#39;&quot; value=&quot;Read-only unless you have permission&quot;&gt;</code></pre></div><h2>Scopes and Hierarchical Permissions</h2> <p>The package uses a specific concept for scoping permissions, allowing you to reuse components with generic permission checks in different contexts.</p> <h3>How Scoping Works</h3> <p>Scopes use <strong>slash notation</strong> (<code>scope/permission</code>) in the permission list.</p> <ul> <li><strong>Identifiers</strong>: The code checks for a simple ID (e.g., <code>&#39;edit&#39;</code>).</li> <li><strong>Permissions</strong>: Can be global (e.g., <code>&#39;admin&#39;</code>) or scoped (e.g., <code>&#39;products/edit&#39;</code>).</li> <li><strong>Context</strong>: A component defines its scope (e.g., <code>&#39;products&#39;</code>).</li> </ul> <p>When checking for <code>&#39;edit&#39;</code> inside the <code>&#39;products&#39;</code> scope:</p> <ol> <li>The service looks for permissions starting with <code>&#39;products/&#39;</code>.</li> <li>It strips the prefix. <code>&#39;products/edit&#39;</code> becomes <code>&#39;edit&#39;</code>.</li> <li>It checks if the user has <code>&#39;edit&#39;</code>.</li> </ol> <p>Global permissions (without slashes) are always included in the check.</p> <h3>Using <code>setAuthorizationScope</code></h3> <p>You can define a scope for a component subtree using the <code>setAuthorizationScope</code> helper function.</p> <b>Example :</b><div><pre class="line-numbers"><code class="language-typescript">import { setAuthorizationScope } from &#39;&#64;rxap/authorization&#39;; &#64;Component({ selector: &#39;app-product-list&#39;, template: ` &lt;!-- This checks for &#39;products/create&#39; (mapped to &#39;create&#39;) --&gt; &lt;button *rxapIfHasPermission=&quot;&#39;create&#39;&quot;&gt;Create Product&lt;/button&gt; `, providers: [ setAuthorizationScope(&#39;products&#39;), ] }) export class ProductListComponent {}</code></pre></div><p>If the user has the permission <code>&#39;products/create&#39;</code>, they will see the button. If they have <code>&#39;users/create&#39;</code>, they will not (unless they are also in the <code>&#39;users&#39;</code> scope).</p> <h3>Nested Scopes</h3> <p>You can technically nest scopes by providing dot-separated scopes (e.g., <code>&#39;admin.users&#39;</code>), which would look for <code>&#39;admin.users/permission&#39;</code>.</p> <h2>Disabling Authorization for Development</h2> <p>The <code>provideAuthorization()</code> function automatically configures the service to check the configuration for <code>authorization.disabled</code>.</p> <p>If you are using <code>@rxap/config</code>, you can disable authorization by setting the <code>authorization.disabled</code> property to <code>true</code> in your configuration file/environment.</p> <b>Example :</b><div><pre class="line-numbers"><code class="language-json">{ &quot;authorization&quot;: { &quot;disabled&quot;: true } }</code></pre></div><p>This is useful for local development or testing environments where you want to bypass permission checks.</p> <h1>Generators</h1> <h2>init</h2> <blockquote> <p>Initialize the package in the workspace</p> </blockquote> <b>Example :</b><div><pre class="line-numbers"><code class="language-bash">nx g &#64;rxap/authorization:init</code></pre></div> </div><div class="search-results"> <div class="has-results"> <h1 class="search-results-title"><span class='search-results-count'></span> results matching "<span class='search-query'></span>"</h1> <ul class="search-results-list"></ul> </div> <div class="no-results"> <h1 class="search-results-title">No results matching "<span class='search-query'></span>"</h1> </div> </div> </div> <!-- END CONTENT --> </div> </div> <label class="dark-mode-switch"> <input type="checkbox"> <span class="slider"> <svg class="slider-icon" viewBox="0 0 24 24" fill="none" height="20" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" width="20" xmlns="http://www.w3.org/2000/svg"> <path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"></path> </svg> </span> </label> <script> var COMPODOC_CURRENT_PAGE_DEPTH = 0; var COMPODOC_CURRENT_PAGE_CONTEXT = 'getting-started'; var COMPODOC_CURRENT_PAGE_URL = 'index.html'; var MAX_SEARCH_RESULTS = 15; </script> <script> $darkModeToggleSwitchers = document.querySelectorAll('.dark-mode-switch input'); checkToggle(darkModeState); if ($darkModeToggleSwitchers.length > 0) { for (var i = 0; i < $darkModeToggleSwitchers.length; i++) { $darkModeToggleSwitchers[i].addEventListener('change', function (event) { darkModeState = !darkModeState; toggleDarkMode(darkModeState); }); } } </script> <script src="./js/libs/custom-elements.min.js"></script> <script src="./js/libs/lit-html.js"></script> <script src="./js/menu-wc.js" defer></script> <script nomodule src="./js/menu-wc_es5.js" defer></script> <script src="./js/libs/bootstrap-native.js"></script> <script src="./js/libs/es6-shim.min.js"></script> <script src="./js/libs/EventDispatcher.js"></script> <script src="./js/libs/promise.min.js"></script> <script src="./js/libs/zepto.min.js"></script> <script src="./js/compodoc.js"></script> <script src="./js/tabs.js"></script> <script src="./js/menu.js"></script> <script src="./js/libs/clipboard.min.js"></script> <script src="./js/libs/prism.js"></script> <script src="./js/sourceCode.js"></script> <script src="./js/search/search.js"></script> <script src="./js/search/lunr.min.js"></script> <script src="./js/search/search-lunr.js"></script> <script src="./js/search/search_index.js"></script> <script src="./js/lazy-load-graphs.js"></script> </body> </html>