eiam-resource-scanner
v4.1.3
Published
Reusable Angular resource scanner: discovers the permission codes a page declares through checkRole, enriches them from a resource manifest, and synchronises the resource definition with a backend Resource Scan API. Discovery only - never an authorization
Downloads
78
Maintainers
Readme
eiam-resource-scanner
Your pages already declare their permission codes:
<button checkRole="CANADD">Add User</button>
<button checkRole="CANEDIT" matTooltip="Edit user">Edit</button>This package reads them and tells IAM they exist — so nobody has to type them in by hand.
// one request per page, sent automatically
{
"Slug": "users/list",
"Component": "ListUsersComponent",
"ExcludedTypes": [],
"Permissions": [
{ "Code": "CANADD", "Name": "CANADD", "IsEnabled": true, "Type": 1 },
{ "Code": "CANEDIT", "Name": "Edit user", "IsEnabled": true, "Type": 1 }
]
}No template changes. No page-by-page setup.
Shared infrastructure: EIAM, GO, EXIM, DCMS and any future project use the same package and differ only in configuration.
Setup — three steps
1. Install
npm install eiam-resource-scanner2. Configure once, in the root
import { ResourceScannerModule } from 'eiam-resource-scanner';
import { environment } from '../environments/environment';
@NgModule({
imports: [
HttpClientModule,
ResourceScannerModule.forRoot({
application: 'EIAM', // your application key
apiUrl: '/PagePermissionApi/SyncPermissions', // your backend endpoint
autoDiscover: true, // scan every page
enabled: !environment.production, // the ON/OFF switch
}),
],
})
export class AppModule {}Standalone application? Use provideResourceScanner({ ... }) with the same options.
3. Import the module wherever checkRole is used
@NgModule({
imports: [CommonModule, ResourceScannerModule], // <- one line, no forRoot
})
export class UserManagementModule {}That is the whole integration.
The ON/OFF switch
enabled decides whether the scanner does anything at all. Off means nothing is registered
and no request is ever made — as if the package were not installed.
Give it a boolean, or a function that returns one. Where that answer comes from is entirely up to you.
enabled: true // always
enabled: !environment.production // recommended
enabled: () => this.flags.isOn('scanner') // from a service
enabled: () => currentUser.isAdministrator // from the userA boolean is read once at startup. A function is asked every time, so a flag that changes later takes effect with no rebuild and no reload.
You can also flip it from anywhere in the application:
const scanner = inject(ResourceScannerService);
scanner.setEnabled(false); // stop now
scanner.setEnabled(true); // resume
scanner.setEnabled(undefined); // back to the configured value
scanner.isEnabled(); // what is in force right nowThree things worth knowing:
- Turning it off never deletes anything. It is a pause, not a rollback.
- Turning it back on sends anything discovered but not yet accepted. If everything was already accepted there is nothing to send — that is correct, not a fault.
- If your function throws, the scanner treats that as off, says so once, and carries on. Your page keeps working.
Control types and excludedTypes
Every permission is sent with the Type of the control that declared it. The scanner works
it out from the element itself — nothing to add to your templates:
| Type | Value | Detected from |
| --- | --- | --- |
| BTN | 1 | <button>, <input type="button/submit/reset">, mat-*-button, role="button" |
| DropDown | 2 | <select>, mat-select, ng-select, p-dropdown, role="combobox" |
| TextBox | 3 | <input> (text, email, number, …), <textarea>, matInput |
| CheckBox | 4 | <input type="checkbox">, mat-checkbox, mat-slide-toggle |
| RadioButton | 5 | <input type="radio">, mat-radio-button, mat-radio-group |
| Link | 6 | <a>, role="link" |
| Tab | 7 | mat-tab, mat-tab-link, role="tab" |
| Grid | 8 | <table>, mat-table, ag-grid-angular, p-table, kendo-grid |
| GridColumn | 9 | <th>, <td>, matColumnDef, mat-header-cell, mat-cell |
| Menu | 10 | <nav>, <menu>, mat-menu-item, <li> inside a menu, role="menu" |
| Section | 11 | <div>, <section>, <fieldset>, <form>, mat-card, mat-expansion-panel |
| Field | 12 | <label>, mat-form-field, and any control not listed above |
To stop a type from being scanned, list it in excludedTypes:
import { ResourceElementType } from 'eiam-resource-scanner';
ResourceScannerModule.forRoot({
// ...
excludedTypes: [ResourceElementType.GridColumn, ResourceElementType.Field],
})The list is sent with every request as ExcludedTypes; the backend skips every permission
whose Type is in it. Only values of the enum above are accepted — anything else is dropped.
What it will never do
- It is not an authorization authority. It never grants, revokes or checks access.
- It never hides, disables, removes or styles anything on your page.
- It never deletes a permission.
- It never changes your existing
checkRolebehaviour. - It never throws an error into your application.
Good to know
Names are a suggestion, not an identity. The code is the identity. The name comes from
scanName, then a tooltip / title / aria-label, then the control's own label, then the code
itself. IAM writes the name only when it creates a permission — so rename it there once
and no scan will ever overwrite it.
One request, not a hundred. Fifty buttons sharing CANEDIT produce one entry, and a
page with 150 controls produces one request.
The component name is real. Component is the actual routed class,
ListUsersComponent, worked out from the application itself — in development and in
production alike. Nothing to configure, no map to generate.
Optional: scan at build time too
The directive only sees a control once Angular creates it, so a page nobody opened is never discovered. The CLI reads your source instead:
npx eiam-resource-scan --dir ./src --dry-run --verboseUse both: the CLI makes the catalogue complete, the directive catches what only exists at runtime.
Keep scanning and writing apart. Writing to IAM is a deployment step. Never put it in
npm run build, or every developer machine and every CI job would change the permissions database without anyone intending it.
"scripts": {
"build": "ng build",
"scan:permissions": "eiam-resource-scan --dir ./src --dry-run", // safe anywhere
"sync:permissions": "eiam-resource-scan --dir ./src --api-url $SCAN_URL" // deployment only
}Options
| Option | Default | What it does |
| --- | --- | --- |
| application | required | Your application key, e.g. EIAM. |
| apiUrl | required | Your endpoint. A relative URL goes through your own HttpClient, so your interceptors and auth apply. |
| enabled | true | ON/OFF. A boolean, or a function returning one. |
| autoDiscover | false | Scan every page. Turn this on. |
| resources | — | Optional: nicer names up front, for the pages you care about. |
| batchDelay | 250 | How long to wait before sending, in ms. |
| validation | 'warn' | What to say about a code that is not in your manifest. |
| nameAttributes | matTooltip, tooltip, ngbTooltip, title, aria-label, data-name | Where to look for a readable name. |
| componentMap | — | Rarely needed: override Component for one page. |
| excludedTypes | [] | Control types the backend should not scan. See Control types. |
| debug | false | Log what is happening, while you integrate. |
Requirements
Angular 16+, RxJS 7+, HttpClient and Router provided, and an existing directive with
the selector [checkRole].
Backend
POST to your apiUrl with
{ Slug, Component, ExcludedTypes[], Permissions[{ Code, Name, IsEnabled, Type }] }.
The operation must be additive — it never deletes. ResourceScannerTransport is
replaceable if your contract differs.
Full walkthrough, checklist and troubleshooting: USER-MANUAL.md Changes between versions: CHANGELOG.md
