@dudousxd/nestjs-durable-dashboard 0.55.1 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,5 @@
1
+ import { HttpException, ExecutionContext, Type, ExceptionFilter, ArgumentsHost, CanActivate, Logger, DynamicModule, StreamableFile } from '@nestjs/common';
2
+ import { HttpAdapterHost, ModuleRef } from '@nestjs/core';
1
3
  import * as _dudousxd_nestjs_durable_core from '@dudousxd/nestjs-durable-core';
2
4
  import { RunGateway, StateStore, WorkflowEngine, ScheduleListQuery, ScheduleDescription, RunResult, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, DurableTopology, GroupHealth, RunDetailOptions, RunDetail, FanPageQuery, FanPage, RepublishLifecycleResult, WorkflowLifecycleLinks, UpdateResult, EngineEvent, AttributeFilter } from '@dudousxd/nestjs-durable-core';
3
5
  export { RunDetail } from '@dudousxd/nestjs-durable-core';
@@ -5,7 +7,102 @@ import * as rxjs from 'rxjs';
5
7
  import { Observable } from 'rxjs';
6
8
  import * as _dudousxd_nestjs_filter from '@dudousxd/nestjs-filter';
7
9
  import { FilterRunner } from '@dudousxd/nestjs-filter';
8
- import { DynamicModule, StreamableFile } from '@nestjs/common';
10
+
11
+ type ConsoleAuthorizeResult = boolean | {
12
+ allowed: true;
13
+ } | {
14
+ allowed: false;
15
+ reason?: 'unauthenticated' | 'forbidden';
16
+ } | {
17
+ redirectTo: string;
18
+ };
19
+ /** Class form: an `@Injectable()` provider (constructor injection works). */
20
+ interface ConsoleAuthorizer {
21
+ authorize(context: ExecutionContext): ConsoleAuthorizeResult | Promise<ConsoleAuthorizeResult>;
22
+ }
23
+ type ConsoleAuthorizerClass = Type<ConsoleAuthorizer>;
24
+ /** An `@nestjs/authorization` policy check: `policy[ability](user)`. */
25
+ interface ConsolePolicyCheck {
26
+ policy: Type<unknown>;
27
+ ability: string;
28
+ }
29
+ type ConsoleAuthorize = ((context: ExecutionContext) => ConsoleAuthorizeResult | Promise<ConsoleAuthorizeResult>) | ConsoleAuthorizerClass | (() => Promise<{
30
+ default: ConsoleAuthorizerClass;
31
+ }>) | ConsolePolicyCheck | string;
32
+ type ConsoleDecision = {
33
+ allowed: true;
34
+ } | {
35
+ allowed: false;
36
+ reason: 'unauthenticated' | 'forbidden';
37
+ redirectTo?: string;
38
+ };
39
+ /** What a resolved `authorize` is: called once per request, never throws. */
40
+ type ConsoleDecide = (context: ExecutionContext) => Promise<ConsoleDecision>;
41
+ /**
42
+ * The provider `@dudousxd/nestjs-authz` registers to resolve gate names (`authorize: 'console.x'`).
43
+ * A registry symbol, so it is found without depending on that package.
44
+ */
45
+ declare const CONSOLE_GATE: unique symbol;
46
+ interface ConsoleGate {
47
+ allows(name: string, context: ExecutionContext): Promise<ConsoleAuthorizeResult>;
48
+ }
49
+ /** The one boot warning for a console with no `authorize`. Same wording in every Aviary console. */
50
+ declare const CONSOLE_AUTHORIZE_MISSING_WARNING = "[@dudousxd/nestjs-durable-dashboard] console has no `authorize` configured: it is open outside production and denies every request in production. Set `authorize` in DurableDashboardModule.forRoot().";
51
+ /** Turns any {@link ConsoleAuthorizeResult} into a {@link ConsoleDecision}. */
52
+ declare function normalizeConsoleResult(result: unknown): ConsoleDecision;
53
+ /**
54
+ * Resolves `authorize` into one function the guard calls per request. Resolution errors that are
55
+ * configuration mistakes (a gate name without nestjs-authz, a policy without @nestjs/authorization)
56
+ * throw here, so call it at boot. The returned function never throws: an error is a `forbidden`.
57
+ *
58
+ * `authorize` omitted: open outside production, `forbidden` for every request in production (read
59
+ * when this runs), and one warning logged (unless `warnIfMissing: false`).
60
+ */
61
+ declare function resolveConsoleAuthorize(authorize: ConsoleAuthorize | undefined, moduleRef: ModuleRef, options?: {
62
+ logger?: Logger;
63
+ warnIfMissing?: boolean;
64
+ }): Promise<ConsoleDecide>;
65
+ /**
66
+ * Allows a request carrying `Authorization: Bearer <token>` (compared timing-safe); anything else
67
+ * is `unauthenticated`. Header only: no query string, no custom header. Throws when `token` is
68
+ * empty, so a missing env var fails at config time instead of opening the console.
69
+ */
70
+ declare function authorizeByToken(options: {
71
+ token: string | undefined;
72
+ }): ConsoleAuthorize;
73
+ /** DI token of the resolved {@link ConsoleDecide} the guard runs. */
74
+ declare const CONSOLE_AUTHORIZE_DECIDE: unique symbol;
75
+ /** Marks a controller as serving pages: a denied `GET` that accepts HTML gets a page, not JSON. */
76
+ declare const CONSOLE_PAGE_METADATA = "@dudousxd/nestjs-durable-dashboard:console-page";
77
+ declare const ConsolePage: () => ClassDecorator;
78
+ /**
79
+ * The denial the guard throws. An `HttpException` with the JSON body, so it still answers correctly
80
+ * where {@link ConsoleAuthorizeFilter} is not applied; the filter adds the page answers.
81
+ */
82
+ declare class ConsoleAccessDenied extends HttpException {
83
+ readonly decision: Extract<ConsoleDecision, {
84
+ allowed: false;
85
+ }>;
86
+ readonly page: boolean;
87
+ constructor(decision: Extract<ConsoleDecision, {
88
+ allowed: false;
89
+ }>, page: boolean);
90
+ }
91
+ declare class ConsoleAuthorizeGuard implements CanActivate {
92
+ private readonly decide;
93
+ constructor(decide: ConsoleDecide);
94
+ canActivate(context: ExecutionContext): Promise<boolean>;
95
+ }
96
+ declare class ConsoleAuthorizeFilter implements ExceptionFilter {
97
+ private readonly adapterHost;
98
+ constructor(adapterHost: HttpAdapterHost);
99
+ catch(exception: ConsoleAccessDenied, host: ArgumentsHost): void;
100
+ }
101
+ /**
102
+ * The guard and its filter as one class decorator, for a console you mount yourself
103
+ * (`routes: false`). List it ABOVE your authentication guards, so it runs after them.
104
+ */
105
+ declare const UseConsoleAuthorize: () => ClassDecorator & MethodDecorator;
9
106
 
10
107
  /**
11
108
  * Read-model and actions backing the control-plane UI. Run-facing ops (list/detail/retry/cancel/
@@ -345,6 +442,23 @@ declare class DurableApiController extends DurableApiControllerBase {
345
442
  }
346
443
 
347
444
  interface DurableDashboardOptions {
445
+ /**
446
+ * Who may use the console (the SPA, its JSON API, the run stream and the webhook route). One of:
447
+ *
448
+ * - a function `(context) => boolean | { allowed } | { redirectTo }` (may be async);
449
+ * - an `@Injectable()` class implementing {@link ConsoleAuthorizer} (constructor injection works;
450
+ * its dependencies resolve from {@link imports} or global modules);
451
+ * - a lazy import `() => import('./console-access.js')` whose default export is such a class;
452
+ * - `{ policy, ability }`, checked with `@nestjs/authorization`'s `AuthorizationService`;
453
+ * - a gate name, resolved by `@dudousxd/nestjs-authz`'s `AuthzModule`.
454
+ *
455
+ * `authorizeByToken({ token })` builds one for a bearer token. It runs after the guards in
456
+ * {@link decorators}, so `request.user` is already set.
457
+ *
458
+ * Omitted: the console is open outside production and answers `403` to every request when
459
+ * `NODE_ENV=production`, and one warning is logged at boot.
460
+ */
461
+ authorize?: ConsoleAuthorize;
348
462
  /**
349
463
  * Where the SPA (UI) is served. Default `/durable`. This is a page route — keep it out of an
350
464
  * `/api` prefix so it reads as a UI, not an endpoint.
@@ -372,26 +486,26 @@ interface DurableDashboardOptions {
372
486
  * another registration or onto the exported classes. A guard named in `UseGuards(...)` resolves
373
487
  * its dependencies from {@link imports} (or a global module).
374
488
  *
375
- * A non-empty `decorators` is how a mounted console states its protection. With
376
- * `NODE_ENV=production`, a mounted console with no (or an empty) `decorators` fails the boot; see
377
- * {@link DurableDashboardModule}. The module cannot tell a `Can(...)` from a decorator that
378
- * protects nothing, so passing one is your statement that the console is protected.
489
+ * `decorators` is not access control on its own: {@link authorize} decides who gets in, and runs
490
+ * after these. Use `decorators` for what must run first, typically authentication.
379
491
  *
380
- * Default: none. Outside production the console is then served open, and the controllers are
381
- * marked `Can.Anyone()` for `@nestjs/authorization`, whose global guard otherwise refuses every
382
- * route that declares no `@Can()`. A `@Can()` passed here is still enforced.
492
+ * The controllers are marked `Can.Anyone()` for `@nestjs/authorization`, whose global guard
493
+ * otherwise refuses every route that declares no `@Can()`. A `@Can()` passed here is still
494
+ * enforced.
383
495
  */
384
496
  decorators?: ClassDecorator[];
385
497
  /**
386
498
  * `false` mounts NO controllers: the SPA and its JSON API are not registered,
387
499
  * while every provider still is (the run read/action service, the run filter, the SPA's base
388
- * paths). Serve the console from controllers of your own instead, protected with ordinary Nest
389
- * decorators, by extending {@link DurableUiControllerBase} and {@link DurableApiControllerBase}:
500
+ * paths). Serve the console from controllers of your own instead, by extending
501
+ * {@link DurableUiControllerBase} and {@link DurableApiControllerBase}. Apply `authorize` to them
502
+ * with `@UseConsoleAuthorize()` (listed above your authentication guards, so it runs after them),
503
+ * or protect them with ordinary Nest decorators:
390
504
  *
391
505
  * ```ts
392
- * @Controller('ops/durable') @UseGuards(AuthenticationGuard) @Can(ConsolePolicy, 'view')
506
+ * @Controller('ops/durable') @UseConsoleAuthorize() @UseGuards(AuthenticationGuard)
393
507
  * class ConsoleUi extends DurableUiControllerBase {}
394
- * @Controller('ops/durable/api') @UseGuards(AuthenticationGuard) @Can(ConsolePolicy, 'view')
508
+ * @Controller('ops/durable/api') @UseConsoleAuthorize() @UseGuards(AuthenticationGuard)
395
509
  * class ConsoleApi extends DurableApiControllerBase {}
396
510
  * // in the module that declares them:
397
511
  * imports: [DurableDashboardModule.forRoot({ routes: false, basePath: '/ops/durable', apiBasePath: '/ops/durable/api' })]
@@ -400,8 +514,9 @@ interface DurableDashboardOptions {
400
514
  * `basePath`/`apiBasePath` still matter here: they are where you mounted the two controllers, and
401
515
  * the SPA is told to load its assets from `basePath` and fetch from `apiBasePath`.
402
516
  *
403
- * The production rule does not apply: this module mounts nothing, and the routes are yours,
404
- * protected by what you put on them like any other route you write. The `*Base` classes carry no
517
+ * `authorize` omitted works as for mounted routes: `@UseConsoleAuthorize()` lets every request
518
+ * through outside production and answers `403` in production, and the boot warning is logged.
519
+ * The `*Base` classes carry no
405
520
  * guards and no `Can.Anyone()` mark, so under `@nestjs/authorization`'s global guard a subclass
406
521
  * without a `@Can()` is denied. `decorators` throws when combined with `routes: false` rather than
407
522
  * being silently ignored.
@@ -411,10 +526,6 @@ interface DurableDashboardOptions {
411
526
  */
412
527
  routes?: boolean;
413
528
  }
414
- /** DI token of the boot-time check that refuses an unprotected console in production. */
415
- declare const DASHBOARD_PROTECTION_CHECK: unique symbol;
416
- /** The boot error for a mounted console with no `decorators` under `NODE_ENV=production`. */
417
- declare const UNPROTECTED_CONSOLE_IN_PRODUCTION: string;
418
529
  /**
419
530
  * Holds the JSON API controller + its read service, mounted on its own path by `forRoot`. Its own
420
531
  * dynamic module because enhancers (a guard from `decorators`) DI-instantiate from their
@@ -427,34 +538,19 @@ declare class DurableApiModule {
427
538
  routes?: boolean;
428
539
  /** See {@link DurableDashboardOptions.decorators}. */
429
540
  decorators?: ClassDecorator[];
541
+ /** See {@link DurableDashboardOptions.authorize}. */
542
+ authorize?: ConsoleAuthorize;
430
543
  }): DynamicModule;
431
544
  }
432
- /**
433
- * Mounts the control plane: the bundled React SPA at `basePath` and its JSON API at `apiBasePath`
434
- * (default `<basePath>/api`). Import via `DurableDashboardModule.forRoot(...)` (or `forRootAsync`
435
- * when `dashboardAuth`'s `login` hook needs injected services) alongside `DurableModule` (global),
436
- * so it resolves the engine and store. Front the routes with the first-class `guards` option
437
- * (plus `imports` for the guards' own dependencies) — see {@link DurableDashboardOptions.guards} —
438
- * or with the built-in `dashboardAuth` cookie login, or both.
439
- * With none of those (and no `decorators` / `token` / `allowUnauthenticated`), the console fails
440
- * closed when `NODE_ENV=production` — see {@link DurableDashboardOptions.allowUnauthenticated}.
441
- * `routes: false` mounts nothing and leaves serving the console to the host's own controllers — see
442
- * {@link DurableDashboardOptions.routes}.
443
- */
444
545
  /**
445
546
  * Mounts the control plane: the bundled React SPA at `basePath` and its JSON API at `apiBasePath`
446
547
  * (default `<basePath>/api`). Import via `DurableDashboardModule.forRoot(...)` alongside
447
548
  * `DurableModule` (global), so it resolves the engine and store.
448
549
  *
449
- * Protect the console with the first-party NestJS tools (`@nestjs/authentication`,
450
- * `@nestjs/authorization`), one of two ways:
451
- *
452
- * 1. `decorators` on the mounted controllers (see {@link DurableDashboardOptions.decorators});
453
- * 2. `routes: false`, and mount it yourself by extending `DurableUiControllerBase` /
454
- * `DurableApiControllerBase` (see {@link DurableDashboardOptions.routes}).
455
- *
456
- * With `NODE_ENV=production`, a mounted console with no `decorators` fails the boot with
457
- * {@link UNPROTECTED_CONSOLE_IN_PRODUCTION}. Outside production it is served open, with a warning.
550
+ * Access is decided by {@link DurableDashboardOptions.authorize}. Without it the console is open
551
+ * outside production and denies every request in production, with one warning at boot.
552
+ * `routes: false` mounts nothing and leaves serving the console to the host's own controllers (see
553
+ * {@link DurableDashboardOptions.routes}).
458
554
  */
459
555
  declare class DurableDashboardModule {
460
556
  static forRoot(options?: DurableDashboardOptions): DynamicModule;
@@ -492,4 +588,4 @@ declare class DurableUiControllerBase {
492
588
  declare class DurableUiController extends DurableUiControllerBase {
493
589
  }
494
590
 
495
- export { DASHBOARD_API_PATH, DASHBOARD_BASE_PATH, DASHBOARD_PROTECTION_CHECK, DashboardService, DurableApiController, DurableApiControllerBase, DurableApiModule, DurableDashboardModule, type DurableDashboardOptions, DurableUiController, DurableUiControllerBase, UNPROTECTED_CONSOLE_IN_PRODUCTION, type WorkflowLifecyclePage };
591
+ export { CONSOLE_AUTHORIZE_DECIDE, CONSOLE_AUTHORIZE_MISSING_WARNING, CONSOLE_GATE, CONSOLE_PAGE_METADATA, ConsoleAccessDenied, type ConsoleAuthorize, ConsoleAuthorizeFilter, ConsoleAuthorizeGuard, type ConsoleAuthorizeResult, type ConsoleAuthorizer, type ConsoleAuthorizerClass, type ConsoleDecide, type ConsoleDecision, type ConsoleGate, ConsolePage, type ConsolePolicyCheck, DASHBOARD_API_PATH, DASHBOARD_BASE_PATH, DashboardService, DurableApiController, DurableApiControllerBase, DurableApiModule, DurableDashboardModule, type DurableDashboardOptions, DurableUiController, DurableUiControllerBase, UseConsoleAuthorize, type WorkflowLifecyclePage, authorizeByToken, normalizeConsoleResult, resolveConsoleAuthorize };
@@ -1,3 +1,5 @@
1
+ import { HttpException, ExecutionContext, Type, ExceptionFilter, ArgumentsHost, CanActivate, Logger, DynamicModule, StreamableFile } from '@nestjs/common';
2
+ import { HttpAdapterHost, ModuleRef } from '@nestjs/core';
1
3
  import * as _dudousxd_nestjs_durable_core from '@dudousxd/nestjs-durable-core';
2
4
  import { RunGateway, StateStore, WorkflowEngine, ScheduleListQuery, ScheduleDescription, RunResult, RunQuery, RunListItem, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, DurableTopology, GroupHealth, RunDetailOptions, RunDetail, FanPageQuery, FanPage, RepublishLifecycleResult, WorkflowLifecycleLinks, UpdateResult, EngineEvent, AttributeFilter } from '@dudousxd/nestjs-durable-core';
3
5
  export { RunDetail } from '@dudousxd/nestjs-durable-core';
@@ -5,7 +7,102 @@ import * as rxjs from 'rxjs';
5
7
  import { Observable } from 'rxjs';
6
8
  import * as _dudousxd_nestjs_filter from '@dudousxd/nestjs-filter';
7
9
  import { FilterRunner } from '@dudousxd/nestjs-filter';
8
- import { DynamicModule, StreamableFile } from '@nestjs/common';
10
+
11
+ type ConsoleAuthorizeResult = boolean | {
12
+ allowed: true;
13
+ } | {
14
+ allowed: false;
15
+ reason?: 'unauthenticated' | 'forbidden';
16
+ } | {
17
+ redirectTo: string;
18
+ };
19
+ /** Class form: an `@Injectable()` provider (constructor injection works). */
20
+ interface ConsoleAuthorizer {
21
+ authorize(context: ExecutionContext): ConsoleAuthorizeResult | Promise<ConsoleAuthorizeResult>;
22
+ }
23
+ type ConsoleAuthorizerClass = Type<ConsoleAuthorizer>;
24
+ /** An `@nestjs/authorization` policy check: `policy[ability](user)`. */
25
+ interface ConsolePolicyCheck {
26
+ policy: Type<unknown>;
27
+ ability: string;
28
+ }
29
+ type ConsoleAuthorize = ((context: ExecutionContext) => ConsoleAuthorizeResult | Promise<ConsoleAuthorizeResult>) | ConsoleAuthorizerClass | (() => Promise<{
30
+ default: ConsoleAuthorizerClass;
31
+ }>) | ConsolePolicyCheck | string;
32
+ type ConsoleDecision = {
33
+ allowed: true;
34
+ } | {
35
+ allowed: false;
36
+ reason: 'unauthenticated' | 'forbidden';
37
+ redirectTo?: string;
38
+ };
39
+ /** What a resolved `authorize` is: called once per request, never throws. */
40
+ type ConsoleDecide = (context: ExecutionContext) => Promise<ConsoleDecision>;
41
+ /**
42
+ * The provider `@dudousxd/nestjs-authz` registers to resolve gate names (`authorize: 'console.x'`).
43
+ * A registry symbol, so it is found without depending on that package.
44
+ */
45
+ declare const CONSOLE_GATE: unique symbol;
46
+ interface ConsoleGate {
47
+ allows(name: string, context: ExecutionContext): Promise<ConsoleAuthorizeResult>;
48
+ }
49
+ /** The one boot warning for a console with no `authorize`. Same wording in every Aviary console. */
50
+ declare const CONSOLE_AUTHORIZE_MISSING_WARNING = "[@dudousxd/nestjs-durable-dashboard] console has no `authorize` configured: it is open outside production and denies every request in production. Set `authorize` in DurableDashboardModule.forRoot().";
51
+ /** Turns any {@link ConsoleAuthorizeResult} into a {@link ConsoleDecision}. */
52
+ declare function normalizeConsoleResult(result: unknown): ConsoleDecision;
53
+ /**
54
+ * Resolves `authorize` into one function the guard calls per request. Resolution errors that are
55
+ * configuration mistakes (a gate name without nestjs-authz, a policy without @nestjs/authorization)
56
+ * throw here, so call it at boot. The returned function never throws: an error is a `forbidden`.
57
+ *
58
+ * `authorize` omitted: open outside production, `forbidden` for every request in production (read
59
+ * when this runs), and one warning logged (unless `warnIfMissing: false`).
60
+ */
61
+ declare function resolveConsoleAuthorize(authorize: ConsoleAuthorize | undefined, moduleRef: ModuleRef, options?: {
62
+ logger?: Logger;
63
+ warnIfMissing?: boolean;
64
+ }): Promise<ConsoleDecide>;
65
+ /**
66
+ * Allows a request carrying `Authorization: Bearer <token>` (compared timing-safe); anything else
67
+ * is `unauthenticated`. Header only: no query string, no custom header. Throws when `token` is
68
+ * empty, so a missing env var fails at config time instead of opening the console.
69
+ */
70
+ declare function authorizeByToken(options: {
71
+ token: string | undefined;
72
+ }): ConsoleAuthorize;
73
+ /** DI token of the resolved {@link ConsoleDecide} the guard runs. */
74
+ declare const CONSOLE_AUTHORIZE_DECIDE: unique symbol;
75
+ /** Marks a controller as serving pages: a denied `GET` that accepts HTML gets a page, not JSON. */
76
+ declare const CONSOLE_PAGE_METADATA = "@dudousxd/nestjs-durable-dashboard:console-page";
77
+ declare const ConsolePage: () => ClassDecorator;
78
+ /**
79
+ * The denial the guard throws. An `HttpException` with the JSON body, so it still answers correctly
80
+ * where {@link ConsoleAuthorizeFilter} is not applied; the filter adds the page answers.
81
+ */
82
+ declare class ConsoleAccessDenied extends HttpException {
83
+ readonly decision: Extract<ConsoleDecision, {
84
+ allowed: false;
85
+ }>;
86
+ readonly page: boolean;
87
+ constructor(decision: Extract<ConsoleDecision, {
88
+ allowed: false;
89
+ }>, page: boolean);
90
+ }
91
+ declare class ConsoleAuthorizeGuard implements CanActivate {
92
+ private readonly decide;
93
+ constructor(decide: ConsoleDecide);
94
+ canActivate(context: ExecutionContext): Promise<boolean>;
95
+ }
96
+ declare class ConsoleAuthorizeFilter implements ExceptionFilter {
97
+ private readonly adapterHost;
98
+ constructor(adapterHost: HttpAdapterHost);
99
+ catch(exception: ConsoleAccessDenied, host: ArgumentsHost): void;
100
+ }
101
+ /**
102
+ * The guard and its filter as one class decorator, for a console you mount yourself
103
+ * (`routes: false`). List it ABOVE your authentication guards, so it runs after them.
104
+ */
105
+ declare const UseConsoleAuthorize: () => ClassDecorator & MethodDecorator;
9
106
 
10
107
  /**
11
108
  * Read-model and actions backing the control-plane UI. Run-facing ops (list/detail/retry/cancel/
@@ -345,6 +442,23 @@ declare class DurableApiController extends DurableApiControllerBase {
345
442
  }
346
443
 
347
444
  interface DurableDashboardOptions {
445
+ /**
446
+ * Who may use the console (the SPA, its JSON API, the run stream and the webhook route). One of:
447
+ *
448
+ * - a function `(context) => boolean | { allowed } | { redirectTo }` (may be async);
449
+ * - an `@Injectable()` class implementing {@link ConsoleAuthorizer} (constructor injection works;
450
+ * its dependencies resolve from {@link imports} or global modules);
451
+ * - a lazy import `() => import('./console-access.js')` whose default export is such a class;
452
+ * - `{ policy, ability }`, checked with `@nestjs/authorization`'s `AuthorizationService`;
453
+ * - a gate name, resolved by `@dudousxd/nestjs-authz`'s `AuthzModule`.
454
+ *
455
+ * `authorizeByToken({ token })` builds one for a bearer token. It runs after the guards in
456
+ * {@link decorators}, so `request.user` is already set.
457
+ *
458
+ * Omitted: the console is open outside production and answers `403` to every request when
459
+ * `NODE_ENV=production`, and one warning is logged at boot.
460
+ */
461
+ authorize?: ConsoleAuthorize;
348
462
  /**
349
463
  * Where the SPA (UI) is served. Default `/durable`. This is a page route — keep it out of an
350
464
  * `/api` prefix so it reads as a UI, not an endpoint.
@@ -372,26 +486,26 @@ interface DurableDashboardOptions {
372
486
  * another registration or onto the exported classes. A guard named in `UseGuards(...)` resolves
373
487
  * its dependencies from {@link imports} (or a global module).
374
488
  *
375
- * A non-empty `decorators` is how a mounted console states its protection. With
376
- * `NODE_ENV=production`, a mounted console with no (or an empty) `decorators` fails the boot; see
377
- * {@link DurableDashboardModule}. The module cannot tell a `Can(...)` from a decorator that
378
- * protects nothing, so passing one is your statement that the console is protected.
489
+ * `decorators` is not access control on its own: {@link authorize} decides who gets in, and runs
490
+ * after these. Use `decorators` for what must run first, typically authentication.
379
491
  *
380
- * Default: none. Outside production the console is then served open, and the controllers are
381
- * marked `Can.Anyone()` for `@nestjs/authorization`, whose global guard otherwise refuses every
382
- * route that declares no `@Can()`. A `@Can()` passed here is still enforced.
492
+ * The controllers are marked `Can.Anyone()` for `@nestjs/authorization`, whose global guard
493
+ * otherwise refuses every route that declares no `@Can()`. A `@Can()` passed here is still
494
+ * enforced.
383
495
  */
384
496
  decorators?: ClassDecorator[];
385
497
  /**
386
498
  * `false` mounts NO controllers: the SPA and its JSON API are not registered,
387
499
  * while every provider still is (the run read/action service, the run filter, the SPA's base
388
- * paths). Serve the console from controllers of your own instead, protected with ordinary Nest
389
- * decorators, by extending {@link DurableUiControllerBase} and {@link DurableApiControllerBase}:
500
+ * paths). Serve the console from controllers of your own instead, by extending
501
+ * {@link DurableUiControllerBase} and {@link DurableApiControllerBase}. Apply `authorize` to them
502
+ * with `@UseConsoleAuthorize()` (listed above your authentication guards, so it runs after them),
503
+ * or protect them with ordinary Nest decorators:
390
504
  *
391
505
  * ```ts
392
- * @Controller('ops/durable') @UseGuards(AuthenticationGuard) @Can(ConsolePolicy, 'view')
506
+ * @Controller('ops/durable') @UseConsoleAuthorize() @UseGuards(AuthenticationGuard)
393
507
  * class ConsoleUi extends DurableUiControllerBase {}
394
- * @Controller('ops/durable/api') @UseGuards(AuthenticationGuard) @Can(ConsolePolicy, 'view')
508
+ * @Controller('ops/durable/api') @UseConsoleAuthorize() @UseGuards(AuthenticationGuard)
395
509
  * class ConsoleApi extends DurableApiControllerBase {}
396
510
  * // in the module that declares them:
397
511
  * imports: [DurableDashboardModule.forRoot({ routes: false, basePath: '/ops/durable', apiBasePath: '/ops/durable/api' })]
@@ -400,8 +514,9 @@ interface DurableDashboardOptions {
400
514
  * `basePath`/`apiBasePath` still matter here: they are where you mounted the two controllers, and
401
515
  * the SPA is told to load its assets from `basePath` and fetch from `apiBasePath`.
402
516
  *
403
- * The production rule does not apply: this module mounts nothing, and the routes are yours,
404
- * protected by what you put on them like any other route you write. The `*Base` classes carry no
517
+ * `authorize` omitted works as for mounted routes: `@UseConsoleAuthorize()` lets every request
518
+ * through outside production and answers `403` in production, and the boot warning is logged.
519
+ * The `*Base` classes carry no
405
520
  * guards and no `Can.Anyone()` mark, so under `@nestjs/authorization`'s global guard a subclass
406
521
  * without a `@Can()` is denied. `decorators` throws when combined with `routes: false` rather than
407
522
  * being silently ignored.
@@ -411,10 +526,6 @@ interface DurableDashboardOptions {
411
526
  */
412
527
  routes?: boolean;
413
528
  }
414
- /** DI token of the boot-time check that refuses an unprotected console in production. */
415
- declare const DASHBOARD_PROTECTION_CHECK: unique symbol;
416
- /** The boot error for a mounted console with no `decorators` under `NODE_ENV=production`. */
417
- declare const UNPROTECTED_CONSOLE_IN_PRODUCTION: string;
418
529
  /**
419
530
  * Holds the JSON API controller + its read service, mounted on its own path by `forRoot`. Its own
420
531
  * dynamic module because enhancers (a guard from `decorators`) DI-instantiate from their
@@ -427,34 +538,19 @@ declare class DurableApiModule {
427
538
  routes?: boolean;
428
539
  /** See {@link DurableDashboardOptions.decorators}. */
429
540
  decorators?: ClassDecorator[];
541
+ /** See {@link DurableDashboardOptions.authorize}. */
542
+ authorize?: ConsoleAuthorize;
430
543
  }): DynamicModule;
431
544
  }
432
- /**
433
- * Mounts the control plane: the bundled React SPA at `basePath` and its JSON API at `apiBasePath`
434
- * (default `<basePath>/api`). Import via `DurableDashboardModule.forRoot(...)` (or `forRootAsync`
435
- * when `dashboardAuth`'s `login` hook needs injected services) alongside `DurableModule` (global),
436
- * so it resolves the engine and store. Front the routes with the first-class `guards` option
437
- * (plus `imports` for the guards' own dependencies) — see {@link DurableDashboardOptions.guards} —
438
- * or with the built-in `dashboardAuth` cookie login, or both.
439
- * With none of those (and no `decorators` / `token` / `allowUnauthenticated`), the console fails
440
- * closed when `NODE_ENV=production` — see {@link DurableDashboardOptions.allowUnauthenticated}.
441
- * `routes: false` mounts nothing and leaves serving the console to the host's own controllers — see
442
- * {@link DurableDashboardOptions.routes}.
443
- */
444
545
  /**
445
546
  * Mounts the control plane: the bundled React SPA at `basePath` and its JSON API at `apiBasePath`
446
547
  * (default `<basePath>/api`). Import via `DurableDashboardModule.forRoot(...)` alongside
447
548
  * `DurableModule` (global), so it resolves the engine and store.
448
549
  *
449
- * Protect the console with the first-party NestJS tools (`@nestjs/authentication`,
450
- * `@nestjs/authorization`), one of two ways:
451
- *
452
- * 1. `decorators` on the mounted controllers (see {@link DurableDashboardOptions.decorators});
453
- * 2. `routes: false`, and mount it yourself by extending `DurableUiControllerBase` /
454
- * `DurableApiControllerBase` (see {@link DurableDashboardOptions.routes}).
455
- *
456
- * With `NODE_ENV=production`, a mounted console with no `decorators` fails the boot with
457
- * {@link UNPROTECTED_CONSOLE_IN_PRODUCTION}. Outside production it is served open, with a warning.
550
+ * Access is decided by {@link DurableDashboardOptions.authorize}. Without it the console is open
551
+ * outside production and denies every request in production, with one warning at boot.
552
+ * `routes: false` mounts nothing and leaves serving the console to the host's own controllers (see
553
+ * {@link DurableDashboardOptions.routes}).
458
554
  */
459
555
  declare class DurableDashboardModule {
460
556
  static forRoot(options?: DurableDashboardOptions): DynamicModule;
@@ -492,4 +588,4 @@ declare class DurableUiControllerBase {
492
588
  declare class DurableUiController extends DurableUiControllerBase {
493
589
  }
494
590
 
495
- export { DASHBOARD_API_PATH, DASHBOARD_BASE_PATH, DASHBOARD_PROTECTION_CHECK, DashboardService, DurableApiController, DurableApiControllerBase, DurableApiModule, DurableDashboardModule, type DurableDashboardOptions, DurableUiController, DurableUiControllerBase, UNPROTECTED_CONSOLE_IN_PRODUCTION, type WorkflowLifecyclePage };
591
+ export { CONSOLE_AUTHORIZE_DECIDE, CONSOLE_AUTHORIZE_MISSING_WARNING, CONSOLE_GATE, CONSOLE_PAGE_METADATA, ConsoleAccessDenied, type ConsoleAuthorize, ConsoleAuthorizeFilter, ConsoleAuthorizeGuard, type ConsoleAuthorizeResult, type ConsoleAuthorizer, type ConsoleAuthorizerClass, type ConsoleDecide, type ConsoleDecision, type ConsoleGate, ConsolePage, type ConsolePolicyCheck, DASHBOARD_API_PATH, DASHBOARD_BASE_PATH, DashboardService, DurableApiController, DurableApiControllerBase, DurableApiModule, DurableDashboardModule, type DurableDashboardOptions, DurableUiController, DurableUiControllerBase, UseConsoleAuthorize, type WorkflowLifecyclePage, authorizeByToken, normalizeConsoleResult, resolveConsoleAuthorize };