@dudousxd/nestjs-durable-dashboard 0.55.1 → 0.57.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,108 @@ 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
+ /**
92
+ * Marks a console route that is not part of the console: `authorize` does not apply to it. Used on
93
+ * the `ctx.webhook()` callback, which third parties call with the token as the secret.
94
+ */
95
+ declare const CONSOLE_PUBLIC_METADATA = "@dudousxd/nestjs-durable-dashboard:console-public";
96
+ declare const ConsolePublic: () => MethodDecorator;
97
+ declare class ConsoleAuthorizeGuard implements CanActivate {
98
+ private readonly decide;
99
+ constructor(decide: ConsoleDecide);
100
+ canActivate(context: ExecutionContext): Promise<boolean>;
101
+ }
102
+ declare class ConsoleAuthorizeFilter implements ExceptionFilter {
103
+ private readonly adapterHost;
104
+ constructor(adapterHost: HttpAdapterHost);
105
+ catch(exception: ConsoleAccessDenied, host: ArgumentsHost): void;
106
+ }
107
+ /**
108
+ * The guard and its filter as one class decorator, for a console you mount yourself
109
+ * (`routes: false`). List it ABOVE your authentication guards, so it runs after them.
110
+ */
111
+ declare const UseConsoleAuthorize: () => ClassDecorator & MethodDecorator;
9
112
 
10
113
  /**
11
114
  * Read-model and actions backing the control-plane UI. Run-facing ops (list/detail/retry/cancel/
@@ -325,9 +428,9 @@ declare class DurableApiControllerBase {
325
428
  }>;
326
429
  /**
327
430
  * Public callback endpoint for `ctx.webhook()`: a third party POSTs here (the url handed to it),
328
- * and the body resumes the waiting run. NOTE: this is reachable by external systems — protect it
329
- * by treating the token as a secret (it embeds runId:seq) and/or fronting it with signature
330
- * verification in your own middleware.
431
+ * and the body resumes the waiting run. Not a console route: the console's `authorize` does not
432
+ * apply, the token is the secret. The app's `decorators` still do. Treat the token as a secret
433
+ * and/or front it with signature verification in your own middleware.
331
434
  */
332
435
  webhook(token: string, body: unknown): Promise<_dudousxd_nestjs_durable_core.RunResult>;
333
436
  /** Live query: the latest value a run published for `key` via `ctx.setEvent` (no side effect). */
@@ -345,6 +448,25 @@ declare class DurableApiController extends DurableApiControllerBase {
345
448
  }
346
449
 
347
450
  interface DurableDashboardOptions {
451
+ /**
452
+ * Who may use the console (the SPA, its JSON API and the run stream). Not the `ctx.webhook()`
453
+ * callback (`POST <apiBasePath>/webhooks/:token`), which third parties call with the token as the
454
+ * secret; {@link decorators} still apply to it. One of:
455
+ *
456
+ * - a function `(context) => boolean | { allowed } | { redirectTo }` (may be async);
457
+ * - an `@Injectable()` class implementing {@link ConsoleAuthorizer} (constructor injection works;
458
+ * its dependencies resolve from {@link imports} or global modules);
459
+ * - a lazy import `() => import('./console-access.js')` whose default export is such a class;
460
+ * - `{ policy, ability }`, checked with `@nestjs/authorization`'s `AuthorizationService`;
461
+ * - a gate name, resolved by `@dudousxd/nestjs-authz`'s `AuthzModule`.
462
+ *
463
+ * `authorizeByToken({ token })` builds one for a bearer token. It runs after the guards in
464
+ * {@link decorators}, so `request.user` is already set.
465
+ *
466
+ * Omitted: the console is open outside production and answers `403` to every request when
467
+ * `NODE_ENV=production`, and one warning is logged at boot.
468
+ */
469
+ authorize?: ConsoleAuthorize;
348
470
  /**
349
471
  * Where the SPA (UI) is served. Default `/durable`. This is a page route — keep it out of an
350
472
  * `/api` prefix so it reads as a UI, not an endpoint.
@@ -372,26 +494,26 @@ interface DurableDashboardOptions {
372
494
  * another registration or onto the exported classes. A guard named in `UseGuards(...)` resolves
373
495
  * its dependencies from {@link imports} (or a global module).
374
496
  *
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.
497
+ * `decorators` is not access control on its own: {@link authorize} decides who gets in, and runs
498
+ * after these. Use `decorators` for what must run first, typically authentication.
379
499
  *
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.
500
+ * The controllers are marked `Can.Anyone()` for `@nestjs/authorization`, whose global guard
501
+ * otherwise refuses every route that declares no `@Can()`. A `@Can()` passed here is still
502
+ * enforced.
383
503
  */
384
504
  decorators?: ClassDecorator[];
385
505
  /**
386
506
  * `false` mounts NO controllers: the SPA and its JSON API are not registered,
387
507
  * 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}:
508
+ * paths). Serve the console from controllers of your own instead, by extending
509
+ * {@link DurableUiControllerBase} and {@link DurableApiControllerBase}. Apply `authorize` to them
510
+ * with `@UseConsoleAuthorize()` (listed above your authentication guards, so it runs after them),
511
+ * or protect them with ordinary Nest decorators:
390
512
  *
391
513
  * ```ts
392
- * @Controller('ops/durable') @UseGuards(AuthenticationGuard) @Can(ConsolePolicy, 'view')
514
+ * @Controller('ops/durable') @UseConsoleAuthorize() @UseGuards(AuthenticationGuard)
393
515
  * class ConsoleUi extends DurableUiControllerBase {}
394
- * @Controller('ops/durable/api') @UseGuards(AuthenticationGuard) @Can(ConsolePolicy, 'view')
516
+ * @Controller('ops/durable/api') @UseConsoleAuthorize() @UseGuards(AuthenticationGuard)
395
517
  * class ConsoleApi extends DurableApiControllerBase {}
396
518
  * // in the module that declares them:
397
519
  * imports: [DurableDashboardModule.forRoot({ routes: false, basePath: '/ops/durable', apiBasePath: '/ops/durable/api' })]
@@ -400,8 +522,9 @@ interface DurableDashboardOptions {
400
522
  * `basePath`/`apiBasePath` still matter here: they are where you mounted the two controllers, and
401
523
  * the SPA is told to load its assets from `basePath` and fetch from `apiBasePath`.
402
524
  *
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
525
+ * `authorize` omitted works as for mounted routes: `@UseConsoleAuthorize()` lets every request
526
+ * through outside production and answers `403` in production, and the boot warning is logged.
527
+ * The `*Base` classes carry no
405
528
  * guards and no `Can.Anyone()` mark, so under `@nestjs/authorization`'s global guard a subclass
406
529
  * without a `@Can()` is denied. `decorators` throws when combined with `routes: false` rather than
407
530
  * being silently ignored.
@@ -411,10 +534,6 @@ interface DurableDashboardOptions {
411
534
  */
412
535
  routes?: boolean;
413
536
  }
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
537
  /**
419
538
  * Holds the JSON API controller + its read service, mounted on its own path by `forRoot`. Its own
420
539
  * dynamic module because enhancers (a guard from `decorators`) DI-instantiate from their
@@ -427,34 +546,19 @@ declare class DurableApiModule {
427
546
  routes?: boolean;
428
547
  /** See {@link DurableDashboardOptions.decorators}. */
429
548
  decorators?: ClassDecorator[];
549
+ /** See {@link DurableDashboardOptions.authorize}. */
550
+ authorize?: ConsoleAuthorize;
430
551
  }): DynamicModule;
431
552
  }
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
553
  /**
445
554
  * Mounts the control plane: the bundled React SPA at `basePath` and its JSON API at `apiBasePath`
446
555
  * (default `<basePath>/api`). Import via `DurableDashboardModule.forRoot(...)` alongside
447
556
  * `DurableModule` (global), so it resolves the engine and store.
448
557
  *
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.
558
+ * Access is decided by {@link DurableDashboardOptions.authorize}. Without it the console is open
559
+ * outside production and denies every request in production, with one warning at boot.
560
+ * `routes: false` mounts nothing and leaves serving the console to the host's own controllers (see
561
+ * {@link DurableDashboardOptions.routes}).
458
562
  */
459
563
  declare class DurableDashboardModule {
460
564
  static forRoot(options?: DurableDashboardOptions): DynamicModule;
@@ -492,4 +596,4 @@ declare class DurableUiControllerBase {
492
596
  declare class DurableUiController extends DurableUiControllerBase {
493
597
  }
494
598
 
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 };
599
+ export { CONSOLE_AUTHORIZE_DECIDE, CONSOLE_AUTHORIZE_MISSING_WARNING, CONSOLE_GATE, CONSOLE_PAGE_METADATA, CONSOLE_PUBLIC_METADATA, ConsoleAccessDenied, type ConsoleAuthorize, ConsoleAuthorizeFilter, ConsoleAuthorizeGuard, type ConsoleAuthorizeResult, type ConsoleAuthorizer, type ConsoleAuthorizerClass, type ConsoleDecide, type ConsoleDecision, type ConsoleGate, ConsolePage, type ConsolePolicyCheck, ConsolePublic, 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,108 @@ 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
+ /**
92
+ * Marks a console route that is not part of the console: `authorize` does not apply to it. Used on
93
+ * the `ctx.webhook()` callback, which third parties call with the token as the secret.
94
+ */
95
+ declare const CONSOLE_PUBLIC_METADATA = "@dudousxd/nestjs-durable-dashboard:console-public";
96
+ declare const ConsolePublic: () => MethodDecorator;
97
+ declare class ConsoleAuthorizeGuard implements CanActivate {
98
+ private readonly decide;
99
+ constructor(decide: ConsoleDecide);
100
+ canActivate(context: ExecutionContext): Promise<boolean>;
101
+ }
102
+ declare class ConsoleAuthorizeFilter implements ExceptionFilter {
103
+ private readonly adapterHost;
104
+ constructor(adapterHost: HttpAdapterHost);
105
+ catch(exception: ConsoleAccessDenied, host: ArgumentsHost): void;
106
+ }
107
+ /**
108
+ * The guard and its filter as one class decorator, for a console you mount yourself
109
+ * (`routes: false`). List it ABOVE your authentication guards, so it runs after them.
110
+ */
111
+ declare const UseConsoleAuthorize: () => ClassDecorator & MethodDecorator;
9
112
 
10
113
  /**
11
114
  * Read-model and actions backing the control-plane UI. Run-facing ops (list/detail/retry/cancel/
@@ -325,9 +428,9 @@ declare class DurableApiControllerBase {
325
428
  }>;
326
429
  /**
327
430
  * Public callback endpoint for `ctx.webhook()`: a third party POSTs here (the url handed to it),
328
- * and the body resumes the waiting run. NOTE: this is reachable by external systems — protect it
329
- * by treating the token as a secret (it embeds runId:seq) and/or fronting it with signature
330
- * verification in your own middleware.
431
+ * and the body resumes the waiting run. Not a console route: the console's `authorize` does not
432
+ * apply, the token is the secret. The app's `decorators` still do. Treat the token as a secret
433
+ * and/or front it with signature verification in your own middleware.
331
434
  */
332
435
  webhook(token: string, body: unknown): Promise<_dudousxd_nestjs_durable_core.RunResult>;
333
436
  /** Live query: the latest value a run published for `key` via `ctx.setEvent` (no side effect). */
@@ -345,6 +448,25 @@ declare class DurableApiController extends DurableApiControllerBase {
345
448
  }
346
449
 
347
450
  interface DurableDashboardOptions {
451
+ /**
452
+ * Who may use the console (the SPA, its JSON API and the run stream). Not the `ctx.webhook()`
453
+ * callback (`POST <apiBasePath>/webhooks/:token`), which third parties call with the token as the
454
+ * secret; {@link decorators} still apply to it. One of:
455
+ *
456
+ * - a function `(context) => boolean | { allowed } | { redirectTo }` (may be async);
457
+ * - an `@Injectable()` class implementing {@link ConsoleAuthorizer} (constructor injection works;
458
+ * its dependencies resolve from {@link imports} or global modules);
459
+ * - a lazy import `() => import('./console-access.js')` whose default export is such a class;
460
+ * - `{ policy, ability }`, checked with `@nestjs/authorization`'s `AuthorizationService`;
461
+ * - a gate name, resolved by `@dudousxd/nestjs-authz`'s `AuthzModule`.
462
+ *
463
+ * `authorizeByToken({ token })` builds one for a bearer token. It runs after the guards in
464
+ * {@link decorators}, so `request.user` is already set.
465
+ *
466
+ * Omitted: the console is open outside production and answers `403` to every request when
467
+ * `NODE_ENV=production`, and one warning is logged at boot.
468
+ */
469
+ authorize?: ConsoleAuthorize;
348
470
  /**
349
471
  * Where the SPA (UI) is served. Default `/durable`. This is a page route — keep it out of an
350
472
  * `/api` prefix so it reads as a UI, not an endpoint.
@@ -372,26 +494,26 @@ interface DurableDashboardOptions {
372
494
  * another registration or onto the exported classes. A guard named in `UseGuards(...)` resolves
373
495
  * its dependencies from {@link imports} (or a global module).
374
496
  *
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.
497
+ * `decorators` is not access control on its own: {@link authorize} decides who gets in, and runs
498
+ * after these. Use `decorators` for what must run first, typically authentication.
379
499
  *
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.
500
+ * The controllers are marked `Can.Anyone()` for `@nestjs/authorization`, whose global guard
501
+ * otherwise refuses every route that declares no `@Can()`. A `@Can()` passed here is still
502
+ * enforced.
383
503
  */
384
504
  decorators?: ClassDecorator[];
385
505
  /**
386
506
  * `false` mounts NO controllers: the SPA and its JSON API are not registered,
387
507
  * 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}:
508
+ * paths). Serve the console from controllers of your own instead, by extending
509
+ * {@link DurableUiControllerBase} and {@link DurableApiControllerBase}. Apply `authorize` to them
510
+ * with `@UseConsoleAuthorize()` (listed above your authentication guards, so it runs after them),
511
+ * or protect them with ordinary Nest decorators:
390
512
  *
391
513
  * ```ts
392
- * @Controller('ops/durable') @UseGuards(AuthenticationGuard) @Can(ConsolePolicy, 'view')
514
+ * @Controller('ops/durable') @UseConsoleAuthorize() @UseGuards(AuthenticationGuard)
393
515
  * class ConsoleUi extends DurableUiControllerBase {}
394
- * @Controller('ops/durable/api') @UseGuards(AuthenticationGuard) @Can(ConsolePolicy, 'view')
516
+ * @Controller('ops/durable/api') @UseConsoleAuthorize() @UseGuards(AuthenticationGuard)
395
517
  * class ConsoleApi extends DurableApiControllerBase {}
396
518
  * // in the module that declares them:
397
519
  * imports: [DurableDashboardModule.forRoot({ routes: false, basePath: '/ops/durable', apiBasePath: '/ops/durable/api' })]
@@ -400,8 +522,9 @@ interface DurableDashboardOptions {
400
522
  * `basePath`/`apiBasePath` still matter here: they are where you mounted the two controllers, and
401
523
  * the SPA is told to load its assets from `basePath` and fetch from `apiBasePath`.
402
524
  *
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
525
+ * `authorize` omitted works as for mounted routes: `@UseConsoleAuthorize()` lets every request
526
+ * through outside production and answers `403` in production, and the boot warning is logged.
527
+ * The `*Base` classes carry no
405
528
  * guards and no `Can.Anyone()` mark, so under `@nestjs/authorization`'s global guard a subclass
406
529
  * without a `@Can()` is denied. `decorators` throws when combined with `routes: false` rather than
407
530
  * being silently ignored.
@@ -411,10 +534,6 @@ interface DurableDashboardOptions {
411
534
  */
412
535
  routes?: boolean;
413
536
  }
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
537
  /**
419
538
  * Holds the JSON API controller + its read service, mounted on its own path by `forRoot`. Its own
420
539
  * dynamic module because enhancers (a guard from `decorators`) DI-instantiate from their
@@ -427,34 +546,19 @@ declare class DurableApiModule {
427
546
  routes?: boolean;
428
547
  /** See {@link DurableDashboardOptions.decorators}. */
429
548
  decorators?: ClassDecorator[];
549
+ /** See {@link DurableDashboardOptions.authorize}. */
550
+ authorize?: ConsoleAuthorize;
430
551
  }): DynamicModule;
431
552
  }
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
553
  /**
445
554
  * Mounts the control plane: the bundled React SPA at `basePath` and its JSON API at `apiBasePath`
446
555
  * (default `<basePath>/api`). Import via `DurableDashboardModule.forRoot(...)` alongside
447
556
  * `DurableModule` (global), so it resolves the engine and store.
448
557
  *
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.
558
+ * Access is decided by {@link DurableDashboardOptions.authorize}. Without it the console is open
559
+ * outside production and denies every request in production, with one warning at boot.
560
+ * `routes: false` mounts nothing and leaves serving the console to the host's own controllers (see
561
+ * {@link DurableDashboardOptions.routes}).
458
562
  */
459
563
  declare class DurableDashboardModule {
460
564
  static forRoot(options?: DurableDashboardOptions): DynamicModule;
@@ -492,4 +596,4 @@ declare class DurableUiControllerBase {
492
596
  declare class DurableUiController extends DurableUiControllerBase {
493
597
  }
494
598
 
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 };
599
+ export { CONSOLE_AUTHORIZE_DECIDE, CONSOLE_AUTHORIZE_MISSING_WARNING, CONSOLE_GATE, CONSOLE_PAGE_METADATA, CONSOLE_PUBLIC_METADATA, ConsoleAccessDenied, type ConsoleAuthorize, ConsoleAuthorizeFilter, ConsoleAuthorizeGuard, type ConsoleAuthorizeResult, type ConsoleAuthorizer, type ConsoleAuthorizerClass, type ConsoleDecide, type ConsoleDecision, type ConsoleGate, ConsolePage, type ConsolePolicyCheck, ConsolePublic, DASHBOARD_API_PATH, DASHBOARD_BASE_PATH, DashboardService, DurableApiController, DurableApiControllerBase, DurableApiModule, DurableDashboardModule, type DurableDashboardOptions, DurableUiController, DurableUiControllerBase, UseConsoleAuthorize, type WorkflowLifecyclePage, authorizeByToken, normalizeConsoleResult, resolveConsoleAuthorize };