@adonis-agora/agent-dashboard 0.2.2 → 0.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @adonis-agora/agent-dashboard
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#10](https://github.com/DavideCarvalho/adonis-agent/pull/10) [`c2ddde3`](https://github.com/DavideCarvalho/adonis-agent/commit/c2ddde3d4fdb5f87f3c49984c5cbbe145fbd1038) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Add an optional `authorize` gate to the dashboard config
8
+
9
+ The console serves the governance read-model, which spans EVERY actor's spend and
10
+ usage — so authenticating the caller (the default gate, shared with
11
+ `/agent/governance/*`) is often not enough; you want to restrict it to admins.
12
+
13
+ `config('agent').dashboard.authorize` is an optional `(actor, ctx) => boolean |
14
+ Promise<boolean>` run after the actor resolves. Return `false` (or throw) to deny
15
+ — the request gets `403`. Omit it to keep the previous behavior (any resolved
16
+ actor allowed). Typical use: `authorize: (actor) => actor.roles?.includes('ADMIN')
17
+ ?? false`. The gate decision lives in a router-free `evaluateDashboardGate` helper
18
+ so it is unit tested directly.
19
+
3
20
  ## 0.2.2
4
21
 
5
22
  ### Patch Changes
@@ -21,8 +21,10 @@ export default class AgentDashboardProvider {
21
21
  private spaDirUrl;
22
22
  boot(): Promise<void>;
23
23
  /**
24
- * Resolve the actor through the agent config's resolver, mirroring the governance routes. Returns
25
- * `true` to proceed; on a missing/failed resolver replies `401` and returns `false`.
24
+ * Resolve the actor through the agent config's resolver, mirroring the governance routes, then run
25
+ * the optional `authorize` gate. Returns `true` to proceed; replies `401` on a missing/failed
26
+ * resolver and `403` when `authorize` denies the resolved actor. The decision itself lives in the
27
+ * router-free {@link evaluateDashboardGate} so it can be unit tested; this only writes the response.
26
28
  */
27
29
  private gate;
28
30
  /** Read `dist/spa/index.html`, inject the API base, and send it. */
@@ -1 +1 @@
1
- {"version":3,"file":"agent_dashboard_provider.d.ts","sourceRoot":"","sources":["../../providers/agent_dashboard_provider.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAU/D;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,OAAO,OAAO,sBAAsB;IAC7B,SAAS,CAAC,GAAG,EAAE,kBAAkB;gBAAvB,GAAG,EAAE,kBAAkB;IAE7C,+FAA+F;IAC/F,OAAO,CAAC,SAAS,CAAuC;IAElD,IAAI;IAkCV;;;OAGG;YACW,IAAI;IAgBlB,oEAAoE;YACtD,SAAS;IAOvB,4FAA4F;YAC9E,SAAS;CAcxB"}
1
+ {"version":3,"file":"agent_dashboard_provider.d.ts","sourceRoot":"","sources":["../../providers/agent_dashboard_provider.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAe/D;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,OAAO,OAAO,sBAAsB;IAC7B,SAAS,CAAC,GAAG,EAAE,kBAAkB;gBAAvB,GAAG,EAAE,kBAAkB;IAE7C,+FAA+F;IAC/F,OAAO,CAAC,SAAS,CAAuC;IAElD,IAAI;IAoCV;;;;;OAKG;YACW,IAAI;IAalB,oEAAoE;YACtD,SAAS;IAOvB,4FAA4F;YAC9E,SAAS;CAcxB"}
@@ -1,6 +1,7 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import router from '@adonisjs/core/services/router';
3
- import { resolveDashboardConfig } from '../src/server/define_config.js';
3
+ import { resolveDashboardConfig, } from '../src/server/define_config.js';
4
+ import { evaluateDashboardGate } from '../src/server/gate.js';
4
5
  import { apiBaseFor, contentTypeFor, injectApiBase, mountPathFor, safeAssetSegments, } from '../src/server/paths.js';
5
6
  /**
6
7
  * Serves the `@adonis-agora/agent-dashboard` governance SPA (a Vite build in `dist/spa`) as static
@@ -36,16 +37,17 @@ export default class AgentDashboardProvider {
36
37
  // Bare mount → canonical trailing slash so the SPA's relative `./assets/*` URLs resolve against
37
38
  // the mount directory rather than its parent.
38
39
  router.get(mount, (ctx) => ctx.response.redirect().toPath(`${mount}/`));
40
+ const authorize = dashboardConfig.authorize;
39
41
  // The SPA shell.
40
42
  router.get(`${mount}/`, async (ctx) => {
41
- if (!(await this.gate(ctx, actorResolver)))
43
+ if (!(await this.gate(ctx, actorResolver, authorize)))
42
44
  return;
43
45
  await this.sendIndex(ctx, apiBase);
44
46
  });
45
47
  // Built assets (JS/CSS/fonts/...), with an index fallback for any unmatched path so the
46
48
  // client-rendered console still boots on a deep link.
47
49
  router.get(`${mount}/*`, async (ctx) => {
48
- if (!(await this.gate(ctx, actorResolver)))
50
+ if (!(await this.gate(ctx, actorResolver, authorize)))
49
51
  return;
50
52
  const segments = safeAssetSegments(ctx.params['*']);
51
53
  if (segments === null) {
@@ -55,24 +57,18 @@ export default class AgentDashboardProvider {
55
57
  });
56
58
  }
57
59
  /**
58
- * Resolve the actor through the agent config's resolver, mirroring the governance routes. Returns
59
- * `true` to proceed; on a missing/failed resolver replies `401` and returns `false`.
60
+ * Resolve the actor through the agent config's resolver, mirroring the governance routes, then run
61
+ * the optional `authorize` gate. Returns `true` to proceed; replies `401` on a missing/failed
62
+ * resolver and `403` when `authorize` denies the resolved actor. The decision itself lives in the
63
+ * router-free {@link evaluateDashboardGate} so it can be unit tested; this only writes the response.
60
64
  */
61
- async gate(ctx, actorResolver) {
62
- if (actorResolver === undefined) {
63
- ctx.response.status(401).json({ error: 'no actor resolver configured' });
64
- return false;
65
- }
66
- try {
67
- await actorResolver.resolve(ctx);
68
- return true;
69
- }
70
- catch (error) {
71
- ctx.response
72
- .status(401)
73
- .json({ error: error instanceof Error ? error.message : 'unauthorized' });
65
+ async gate(ctx, actorResolver, authorize) {
66
+ const verdict = await evaluateDashboardGate(ctx, actorResolver, authorize);
67
+ if (!verdict.ok) {
68
+ ctx.response.status(verdict.status).json({ error: verdict.error });
74
69
  return false;
75
70
  }
71
+ return true;
76
72
  }
77
73
  /** Read `dist/spa/index.html`, inject the API base, and send it. */
78
74
  async sendIndex(ctx, apiBase) {
@@ -1 +1 @@
1
- {"version":3,"file":"agent_dashboard_provider.js","sourceRoot":"","sources":["../../providers/agent_dashboard_provider.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent_dashboard_provider.js","sourceRoot":"","sources":["../../providers/agent_dashboard_provider.ts"],"names":[],"mappings":"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"}
@@ -1,17 +1,32 @@
1
+ import type { Actor } from '@adonis-agora/agent';
2
+ import type { HttpContext } from '@adonisjs/core/http';
3
+ /**
4
+ * An extra authorization gate for the dashboard, run AFTER the agent config's `actorResolver` has
5
+ * resolved the caller. Return `false` (or throw) to deny — the request gets `403`. The governance
6
+ * read-model the SPA serves spans EVERY actor's spend/usage, so most apps restrict the console beyond
7
+ * mere authentication, e.g. `(actor) => actor.roles?.includes('ADMIN') ?? false`.
8
+ */
9
+ export type AgentDashboardAuthorize = (actor: Actor, ctx: HttpContext) => boolean | Promise<boolean>;
1
10
  /**
2
11
  * Optional `config('agent').dashboard` block. The dashboard reuses the agent config's `path` and
3
- * `actorResolver` (so it sits behind the SAME actor gating as the governance routes); this block only
4
- * toggles it on/off and optionally overrides the mount path.
12
+ * `actorResolver` (so it sits behind the SAME actor gating as the governance routes); this block
13
+ * toggles it on/off, optionally overrides the mount path, and optionally adds an `authorize` gate.
5
14
  */
6
15
  export interface AgentDashboardConfig {
7
16
  /** Mount the SPA. Default `true` — set `false` to keep the routes off entirely. */
8
17
  enabled?: boolean;
9
18
  /** Override the mount path. Default `<agentPath>/dashboard`. */
10
19
  path?: string;
20
+ /**
21
+ * Extra authorization run after the actor resolves. Return `false` to deny (`403`). Omit to allow
22
+ * any resolved actor (the default), matching the `/agent/governance/*` routes' gating.
23
+ */
24
+ authorize?: AgentDashboardAuthorize;
11
25
  }
12
26
  export interface ResolvedAgentDashboardConfig {
13
27
  enabled: boolean;
14
28
  path?: string;
29
+ authorize?: AgentDashboardAuthorize;
15
30
  }
16
31
  /** Fill defaults for the optional dashboard config block. */
17
32
  export declare function resolveDashboardConfig(config: AgentDashboardConfig | undefined): ResolvedAgentDashboardConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"define_config.d.ts","sourceRoot":"","sources":["../../../src/server/define_config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,mFAAmF;IACnF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,6DAA6D;AAC7D,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,oBAAoB,GAAG,SAAS,GACvC,4BAA4B,CAG9B"}
1
+ {"version":3,"file":"define_config.d.ts","sourceRoot":"","sources":["../../../src/server/define_config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,MAAM,uBAAuB,GAAG,CACpC,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,WAAW,KACb,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,mFAAmF;IACnF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,SAAS,CAAC,EAAE,uBAAuB,CAAC;CACrC;AAED,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,uBAAuB,CAAC;CACrC;AAED,6DAA6D;AAC7D,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,oBAAoB,GAAG,SAAS,GACvC,4BAA4B,CAK9B"}
@@ -1,6 +1,10 @@
1
1
  /** Fill defaults for the optional dashboard config block. */
2
2
  export function resolveDashboardConfig(config) {
3
- const enabled = config?.enabled ?? true;
4
- return config?.path !== undefined ? { enabled, path: config.path } : { enabled };
3
+ const resolved = { enabled: config?.enabled ?? true };
4
+ if (config?.path !== undefined)
5
+ resolved.path = config.path;
6
+ if (config?.authorize !== undefined)
7
+ resolved.authorize = config.authorize;
8
+ return resolved;
5
9
  }
6
10
  //# sourceMappingURL=define_config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"define_config.js","sourceRoot":"","sources":["../../../src/server/define_config.ts"],"names":[],"mappings":"AAiBA,6DAA6D;AAC7D,MAAM,UAAU,sBAAsB,CACpC,MAAwC;IAExC,MAAM,OAAO,GAAG,MAAM,EAAE,OAAO,IAAI,IAAI,CAAC;IACxC,OAAO,MAAM,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;AACnF,CAAC"}
1
+ {"version":3,"file":"define_config.js","sourceRoot":"","sources":["../../../src/server/define_config.ts"],"names":[],"mappings":"AAqCA,6DAA6D;AAC7D,MAAM,UAAU,sBAAsB,CACpC,MAAwC;IAExC,MAAM,QAAQ,GAAiC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,IAAI,IAAI,EAAE,CAAC;IACpF,IAAI,MAAM,EAAE,IAAI,KAAK,SAAS;QAAE,QAAQ,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IAC5D,IAAI,MAAM,EAAE,SAAS,KAAK,SAAS;QAAE,QAAQ,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;IAC3E,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { ActorResolver } from '@adonis-agora/agent';
2
+ import type { HttpContext } from '@adonisjs/core/http';
3
+ import type { AgentDashboardAuthorize } from './define_config.js';
4
+ /** The outcome of the dashboard access gate: proceed, or deny with an HTTP status + message. */
5
+ export type DashboardGateVerdict = {
6
+ ok: true;
7
+ } | {
8
+ ok: false;
9
+ status: 401 | 403;
10
+ error: string;
11
+ };
12
+ /**
13
+ * Decide whether a request may reach the dashboard, WITHOUT touching the response — so it is unit
14
+ * testable free of the AdonisJS router/app. Mirrors the `/agent/governance/*` gating: resolve the
15
+ * actor through the agent config's resolver (missing/failed → `401`), then run the optional
16
+ * `authorize` gate over the resolved actor (denied/threw → `403`). No resolver and no `authorize`
17
+ * means the SPA is exposed to any request — the caller (the provider) is expected to short-circuit on
18
+ * a missing resolver via the `401` here.
19
+ */
20
+ export declare function evaluateDashboardGate(ctx: HttpContext, actorResolver: ActorResolver | undefined, authorize?: AgentDashboardAuthorize): Promise<DashboardGateVerdict>;
21
+ //# sourceMappingURL=gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../../src/server/gate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAElE,gGAAgG;AAChG,MAAM,MAAM,oBAAoB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAElG;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,WAAW,EAChB,aAAa,EAAE,aAAa,GAAG,SAAS,EACxC,SAAS,CAAC,EAAE,uBAAuB,GAClC,OAAO,CAAC,oBAAoB,CAAC,CA4B/B"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Decide whether a request may reach the dashboard, WITHOUT touching the response — so it is unit
3
+ * testable free of the AdonisJS router/app. Mirrors the `/agent/governance/*` gating: resolve the
4
+ * actor through the agent config's resolver (missing/failed → `401`), then run the optional
5
+ * `authorize` gate over the resolved actor (denied/threw → `403`). No resolver and no `authorize`
6
+ * means the SPA is exposed to any request — the caller (the provider) is expected to short-circuit on
7
+ * a missing resolver via the `401` here.
8
+ */
9
+ export async function evaluateDashboardGate(ctx, actorResolver, authorize) {
10
+ if (actorResolver === undefined) {
11
+ return { ok: false, status: 401, error: 'no actor resolver configured' };
12
+ }
13
+ let actor;
14
+ try {
15
+ actor = await actorResolver.resolve(ctx);
16
+ }
17
+ catch (error) {
18
+ return {
19
+ ok: false,
20
+ status: 401,
21
+ error: error instanceof Error ? error.message : 'unauthorized',
22
+ };
23
+ }
24
+ if (authorize !== undefined) {
25
+ try {
26
+ if (!(await authorize(actor, ctx))) {
27
+ return { ok: false, status: 403, error: 'forbidden' };
28
+ }
29
+ }
30
+ catch (error) {
31
+ return {
32
+ ok: false,
33
+ status: 403,
34
+ error: error instanceof Error ? error.message : 'forbidden',
35
+ };
36
+ }
37
+ }
38
+ return { ok: true };
39
+ }
40
+ //# sourceMappingURL=gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate.js","sourceRoot":"","sources":["../../../src/server/gate.ts"],"names":[],"mappings":"AAOA;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,GAAgB,EAChB,aAAwC,EACxC,SAAmC;IAEnC,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAChC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC;IAC3E,CAAC;IACD,IAAI,KAAoD,CAAC;IACzD,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG;YACX,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc;SAC/D,CAAC;IACJ,CAAC;IACD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,IAAI,CAAC;YACH,IAAI,CAAC,CAAC,MAAM,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;gBACnC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;YACxD,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,GAAG;gBACX,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW;aAC5D,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonis-agora/agent-dashboard",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Read-only governance console (spend by model/actor, usage trend, recent threads & tool calls, quota) for @adonis-agora/agent — a dependency-light React SPA served by a thin AdonisJS provider. Part of the Agora ecosystem.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -57,7 +57,7 @@
57
57
  "typescript": "^5.4.0",
58
58
  "vite": "^7.0.0",
59
59
  "vitest": "^3.0.0",
60
- "@adonis-agora/agent": "0.4.0"
60
+ "@adonis-agora/agent": "0.4.1"
61
61
  },
62
62
  "engines": {
63
63
  "node": ">=22.0.0"