@adonis-agora/durable 0.27.0 → 0.29.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/assets/spa/assets/index-Db5aPtlJ.js +16 -0
  3. package/dist/assets/spa/assets/index-Db5aPtlJ.js.map +1 -0
  4. package/dist/assets/spa/index.html +1 -1
  5. package/dist/providers/dashboard_provider.d.ts +17 -15
  6. package/dist/providers/dashboard_provider.d.ts.map +1 -1
  7. package/dist/providers/dashboard_provider.js +100 -33
  8. package/dist/providers/dashboard_provider.js.map +1 -1
  9. package/dist/src/dashboard/access_denied_page.d.ts +102 -0
  10. package/dist/src/dashboard/access_denied_page.d.ts.map +1 -0
  11. package/dist/src/dashboard/access_denied_page.js +266 -0
  12. package/dist/src/dashboard/access_denied_page.js.map +1 -0
  13. package/dist/src/dashboard/compat-view.d.ts +1 -1
  14. package/dist/src/dashboard/compat-view.d.ts.map +1 -1
  15. package/dist/src/dashboard/compat-view.js +1 -1
  16. package/dist/src/dashboard/compat-view.js.map +1 -1
  17. package/dist/src/dashboard/define_config.d.ts +27 -0
  18. package/dist/src/dashboard/define_config.d.ts.map +1 -1
  19. package/dist/src/dashboard/define_config.js +1 -0
  20. package/dist/src/dashboard/define_config.js.map +1 -1
  21. package/dist/src/dashboard/handlers.d.ts +3 -3
  22. package/dist/src/dashboard/handlers.js +2 -2
  23. package/dist/src/dashboard/index.d.ts +4 -3
  24. package/dist/src/dashboard/index.d.ts.map +1 -1
  25. package/dist/src/dashboard/index.js +3 -2
  26. package/dist/src/dashboard/index.js.map +1 -1
  27. package/dist/src/dashboard/login_page.d.ts +23 -10
  28. package/dist/src/dashboard/login_page.d.ts.map +1 -1
  29. package/dist/src/dashboard/login_page.js +29 -41
  30. package/dist/src/dashboard/login_page.js.map +1 -1
  31. package/dist/src/dashboard/spa.d.ts +21 -4
  32. package/dist/src/dashboard/spa.d.ts.map +1 -1
  33. package/dist/src/dashboard/spa.js +19 -5
  34. package/dist/src/dashboard/spa.js.map +1 -1
  35. package/dist/src/index.d.ts +1 -1
  36. package/dist/src/index.js +1 -1
  37. package/package.json +3 -3
  38. package/dist/assets/dashboard.html +0 -559
  39. package/dist/assets/spa/assets/index-CS0tc60i.js +0 -16
  40. package/dist/assets/spa/assets/index-CS0tc60i.js.map +0 -1
  41. package/dist/src/dashboard/html.d.ts +0 -7
  42. package/dist/src/dashboard/html.d.ts.map +0 -1
  43. package/dist/src/dashboard/html.js +0 -24
  44. package/dist/src/dashboard/html.js.map +0 -1
@@ -0,0 +1,266 @@
1
+ /**
2
+ * The built-in "access denied" page a BROWSER sees when the dashboard guard refuses a page
3
+ * navigation — the SPA shell, its assets, or (Mode A only) a session-less visit. Before this page
4
+ * existed the guard answered every denial with the JSON it answers the API with
5
+ * (`{ "error": "forbidden" }`), which is the right thing for the SPA's `fetch` calls and the wrong
6
+ * thing for a human staring at a browser tab.
7
+ *
8
+ * Deliberately a small, dependency-free, hand-authored HTML page (like `login_page.ts`) — NOT part
9
+ * of the bundled SPA, so a denial never needs the SPA to load first. It carries NO `<script>` at all,
10
+ * so a host running a nonce'd `script-src` CSP (the `@adonisjs/shield` default) cannot break it; the
11
+ * one inline `<style>` takes the request's CSP nonce when the provider hands one over.
12
+ *
13
+ * Every string the host can supply ({@link AccessDeniedPageOptions}) is HTML-escaped on the way in.
14
+ * They are developer-controlled config values, not user input — but escaping them costs nothing and
15
+ * removes the one class of bug a "just interpolate it" template would invite.
16
+ *
17
+ * The visual language (Aviary neutrals, the console's own accent, mono eyebrow over a sans body)
18
+ * mirrors the durable SPA so the refusal reads as part of the same product, not a server default.
19
+ *
20
+ * Ported from `@adonis-agora/payments` (also in `@adonis-agora/media`, `@adonis-agora/agent` and
21
+ * `@adonis-agora/telescope` with only {@link CONSOLE} changed, so the Agora consoles share ONE
22
+ * refusal story rather than each inventing its own.
23
+ */
24
+ /** This console's identity — the ONLY thing that differs between the Agora ports of this file. */
25
+ export const CONSOLE = {
26
+ brand: 'Durable',
27
+ accent: '#a3e635',
28
+ packageName: '@adonis-agora/durable',
29
+ };
30
+ const DEFAULT_COPY = {
31
+ unauthenticated: {
32
+ title: 'Sign in required',
33
+ message: 'You need to be signed in to open this console.',
34
+ },
35
+ forbidden: {
36
+ title: 'Access denied',
37
+ message: 'Your account is not allowed to open this console. If that looks wrong, ask an administrator to grant you access.',
38
+ },
39
+ 'session-required': {
40
+ title: 'Open this console from your app',
41
+ message: 'This console only accepts a session minted by your application. Go back to the app and open the console from there.',
42
+ },
43
+ };
44
+ /** Escape the five characters that matter inside HTML text and double-quoted attributes. */
45
+ export function escapeHtml(value) {
46
+ return value
47
+ .replaceAll('&', '&amp;')
48
+ .replaceAll('<', '&lt;')
49
+ .replaceAll('>', '&gt;')
50
+ .replaceAll('"', '&quot;')
51
+ .replaceAll("'", '&#39;');
52
+ }
53
+ /**
54
+ * Accept only a same-origin, root-relative path for a button `href` — the same open-redirect rule
55
+ * `sanitizeReturnTo` applies to `returnTo`. A host-supplied absolute URL is still a developer's
56
+ * choice, so it is allowed; what is refused is anything that could smuggle a `javascript:` scheme.
57
+ */
58
+ function safeHref(candidate) {
59
+ const trimmed = candidate.trim();
60
+ if (trimmed === '')
61
+ return null;
62
+ if (/^(https?:)?\/\//i.test(trimmed))
63
+ return trimmed;
64
+ if (trimmed.startsWith('/'))
65
+ return trimmed;
66
+ return null;
67
+ }
68
+ /** Only a plausible CSS colour token reaches the stylesheet; anything else falls back to the accent. */
69
+ function safeColor(candidate) {
70
+ return /^[#a-zA-Z0-9(),.%\s-]+$/.test(candidate) ? candidate : CONSOLE.accent;
71
+ }
72
+ const LOCK_ICON = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>';
73
+ const KEY_ICON = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="7.5" cy="15.5" r="4.5"/><path d="m21 2-9.6 9.6"/><path d="m15.5 7.5 3 3L22 7l-3-3"/></svg>';
74
+ /**
75
+ * Render the built-in page. Pure: same `info` + `options` in, same HTML out — so it is trivially
76
+ * snapshot/regex testable and safe to call from anywhere (the provider, a test, a host that wants
77
+ * to embed the default page inside its own layout).
78
+ */
79
+ export function renderAccessDeniedPage(info, options = {}) {
80
+ const copy = DEFAULT_COPY[info.reason];
81
+ const brand = escapeHtml(options.brand ?? CONSOLE.brand);
82
+ const title = escapeHtml(options.title ?? copy.title);
83
+ const message = escapeHtml(options.message ?? copy.message);
84
+ const accent = safeColor(options.accent ?? CONSOLE.accent);
85
+ const nonceAttr = info.nonce !== undefined ? ` nonce="${escapeHtml(info.nonce)}"` : '';
86
+ const loginHref = options.loginHref === false
87
+ ? null
88
+ : (safeHref(options.loginHref ?? info.loginHref ?? '') ?? null);
89
+ const homeHref = options.homeHref === false ? null : safeHref(options.homeHref ?? '/');
90
+ const buttons = [];
91
+ if (loginHref !== null && info.reason !== 'forbidden') {
92
+ buttons.push(`<a class="btn primary" href="${escapeHtml(loginHref)}">${KEY_ICON}${escapeHtml(options.loginLabel ?? 'Sign in')}</a>`);
93
+ }
94
+ if (homeHref !== null) {
95
+ buttons.push(`<a class="btn${buttons.length === 0 ? ' primary' : ''}" href="${escapeHtml(homeHref)}">${escapeHtml(options.homeLabel ?? 'Back to app')}</a>`);
96
+ }
97
+ if (loginHref !== null && info.reason === 'forbidden') {
98
+ // A forbidden user might simply be the wrong user: offer a way to switch accounts, second.
99
+ buttons.push(`<a class="btn" href="${escapeHtml(loginHref)}">${escapeHtml(options.loginLabel ?? 'Sign in as someone else')}</a>`);
100
+ }
101
+ const detail = info.detail !== undefined && info.detail !== ''
102
+ ? `\n <p class="detail">${escapeHtml(info.detail)}</p>`
103
+ : '';
104
+ const actions = buttons.length > 0 ? `\n <div class="actions">${buttons.join('')}</div>` : '';
105
+ return `<!doctype html>
106
+ <html lang="en">
107
+ <head>
108
+ <meta charset="utf-8" />
109
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
110
+ <meta name="robots" content="noindex, nofollow" />
111
+ <title>${title} — ${brand}</title>
112
+ <style${nonceAttr}>
113
+ :root {
114
+ color-scheme: dark;
115
+ --bg: #09090b;
116
+ --panel: #0c0c0f;
117
+ --panel-2: #101017;
118
+ --line: #1c1c22;
119
+ --text: #e7e7ea;
120
+ --muted: #76767f;
121
+ --bad: #f87171;
122
+ --accent: ${accent};
123
+ }
124
+ * { box-sizing: border-box; }
125
+ body {
126
+ margin: 0;
127
+ min-height: 100vh;
128
+ display: flex;
129
+ align-items: center;
130
+ justify-content: center;
131
+ padding: 24px;
132
+ background:
133
+ radial-gradient(900px 480px at 50% -10%, color-mix(in srgb, var(--accent) 9%, transparent), transparent 70%),
134
+ var(--bg);
135
+ color: var(--text);
136
+ font: 15px/1.6 "Space Grotesk", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
137
+ -webkit-font-smoothing: antialiased;
138
+ }
139
+ .card {
140
+ position: relative;
141
+ overflow: hidden;
142
+ width: 100%;
143
+ max-width: 440px;
144
+ padding: 36px 32px 28px;
145
+ background: var(--panel);
146
+ border: 1px solid var(--line);
147
+ border-radius: 12px;
148
+ box-shadow: 0 30px 60px -24px rgb(0 0 0 / 0.7);
149
+ }
150
+ .card::before {
151
+ content: "";
152
+ position: absolute;
153
+ inset: 0 0 auto 0;
154
+ height: 2px;
155
+ background: linear-gradient(90deg, var(--accent), transparent 80%);
156
+ }
157
+ .eyebrow {
158
+ display: flex;
159
+ align-items: center;
160
+ gap: 10px;
161
+ margin: 0 0 22px;
162
+ font: 11px/1 "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
163
+ letter-spacing: 0.12em;
164
+ text-transform: uppercase;
165
+ color: var(--muted);
166
+ }
167
+ .eyebrow .brand { color: var(--accent); }
168
+ .eyebrow .status {
169
+ margin-left: auto;
170
+ padding: 4px 7px;
171
+ border: 1px solid var(--line);
172
+ border-radius: 4px;
173
+ color: var(--bad);
174
+ letter-spacing: 0.08em;
175
+ }
176
+ .icon {
177
+ display: grid;
178
+ place-items: center;
179
+ width: 40px;
180
+ height: 40px;
181
+ margin-bottom: 18px;
182
+ border-radius: 10px;
183
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
184
+ color: var(--accent);
185
+ }
186
+ h1 {
187
+ margin: 0 0 8px;
188
+ font-size: 22px;
189
+ font-weight: 600;
190
+ letter-spacing: -0.01em;
191
+ }
192
+ p { margin: 0; color: var(--muted); }
193
+ .detail {
194
+ margin-top: 14px;
195
+ padding: 10px 12px;
196
+ border: 1px solid var(--line);
197
+ border-radius: 6px;
198
+ background: var(--panel-2);
199
+ font: 12px/1.5 "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
200
+ color: var(--text);
201
+ overflow-wrap: anywhere;
202
+ }
203
+ .actions {
204
+ display: flex;
205
+ flex-wrap: wrap;
206
+ gap: 10px;
207
+ margin-top: 26px;
208
+ }
209
+ .btn {
210
+ display: inline-flex;
211
+ align-items: center;
212
+ gap: 8px;
213
+ padding: 9px 14px;
214
+ border: 1px solid var(--line);
215
+ border-radius: 6px;
216
+ background: var(--panel-2);
217
+ color: var(--text);
218
+ font-size: 13px;
219
+ font-weight: 500;
220
+ text-decoration: none;
221
+ }
222
+ .btn:hover { border-color: color-mix(in srgb, var(--accent) 50%, var(--line)); }
223
+ .btn.primary {
224
+ background: color-mix(in srgb, var(--accent) 14%, transparent);
225
+ border-color: color-mix(in srgb, var(--accent) 45%, transparent);
226
+ color: var(--accent);
227
+ }
228
+ .btn.primary:hover { background: color-mix(in srgb, var(--accent) 22%, transparent); }
229
+ .foot {
230
+ margin-top: 26px;
231
+ font: 11px/1 "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
232
+ letter-spacing: 0.04em;
233
+ color: var(--muted);
234
+ opacity: 0.7;
235
+ }
236
+ </style>
237
+ </head>
238
+ <body>
239
+ <main class="card">
240
+ <p class="eyebrow"><span class="brand">${brand}</span><span>console</span><span class="status">${info.status}</span></p>
241
+ <div class="icon" aria-hidden="true">${info.reason === 'forbidden' ? LOCK_ICON : KEY_ICON}</div>
242
+ <h1>${title}</h1>
243
+ <p>${message}</p>${detail}${actions}
244
+ <p class="foot">${escapeHtml(CONSOLE.packageName)}</p>
245
+ </main>
246
+ </body>
247
+ </html>`;
248
+ }
249
+ /**
250
+ * Resolve WHAT to serve for a refusal, honouring the host's `accessDenied` option in either form.
251
+ * Returns the HTML to send, or `null` when a custom renderer already answered the request itself
252
+ * (`answered()` reports whether it did — the provider wires that to "a `location` header or a body
253
+ * is already set"). Framework-light: knows nothing about the HTTP context beyond passing it through.
254
+ */
255
+ export async function resolveAccessDeniedPage(info, option, ctx, answered) {
256
+ if (typeof option === 'function') {
257
+ const rendered = await option(info, ctx);
258
+ if (typeof rendered === 'string')
259
+ return rendered;
260
+ if (answered())
261
+ return null;
262
+ return renderAccessDeniedPage(info);
263
+ }
264
+ return renderAccessDeniedPage(info, option ?? {});
265
+ }
266
+ //# sourceMappingURL=access_denied_page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access_denied_page.js","sourceRoot":"","sources":["../../../src/dashboard/access_denied_page.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,7 @@ import type { NegotiationOutcome } from '../handshake/negotiate.js';
4
4
  * server-side `compat` handler and the dashboard's inline renderer (which mirrors these byte-for-byte).
5
5
  * Kept here — not inline in the HTML — so the mapping is unit-testable and can't silently drift.
6
6
  */
7
- /** CSS class for a negotiation outcome's badge (mirrors the `.c-*` classes in `dashboard.html`). */
7
+ /** CSS class for a negotiation outcome's badge (`c-<outcome>`). */
8
8
  export declare function outcomeClass(outcome: NegotiationOutcome): string;
9
9
  /** Human label for a negotiation outcome (the badge text). */
10
10
  export declare function outcomeLabel(outcome: NegotiationOutcome): string;
@@ -1 +1 @@
1
- {"version":3,"file":"compat-view.d.ts","sourceRoot":"","sources":["../../../src/dashboard/compat-view.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAEpE;;;;GAIG;AAEH,oGAAoG;AACpG,wBAAgB,YAAY,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAShE;AAED,8DAA8D;AAC9D,wBAAgB,YAAY,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAShE;AAED,kGAAkG;AAClG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAEnE"}
1
+ {"version":3,"file":"compat-view.d.ts","sourceRoot":"","sources":["../../../src/dashboard/compat-view.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAEpE;;;;GAIG;AAEH,mEAAmE;AACnE,wBAAgB,YAAY,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAShE;AAED,8DAA8D;AAC9D,wBAAgB,YAAY,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAShE;AAED,kGAAkG;AAClG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAEnE"}
@@ -3,7 +3,7 @@
3
3
  * server-side `compat` handler and the dashboard's inline renderer (which mirrors these byte-for-byte).
4
4
  * Kept here — not inline in the HTML — so the mapping is unit-testable and can't silently drift.
5
5
  */
6
- /** CSS class for a negotiation outcome's badge (mirrors the `.c-*` classes in `dashboard.html`). */
6
+ /** CSS class for a negotiation outcome's badge (`c-<outcome>`). */
7
7
  export function outcomeClass(outcome) {
8
8
  switch (outcome) {
9
9
  case 'compatible':
@@ -1 +1 @@
1
- {"version":3,"file":"compat-view.js","sourceRoot":"","sources":["../../../src/dashboard/compat-view.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,oGAAoG;AACpG,MAAM,UAAU,YAAY,CAAC,OAA2B;IACtD,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,YAAY;YACf,OAAO,MAAM,CAAC;QAChB,KAAK,UAAU;YACb,OAAO,YAAY,CAAC;QACtB,KAAK,cAAc;YACjB,OAAO,OAAO,CAAC;IACnB,CAAC;AACH,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,YAAY,CAAC,OAA2B;IACtD,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,YAAY;YACf,OAAO,YAAY,CAAC;QACtB,KAAK,UAAU;YACb,OAAO,UAAU,CAAC;QACpB,KAAK,cAAc;YACjB,OAAO,cAAc,CAAC;IAC1B,CAAC;AACH,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,mBAAmB,CAAC,KAAuB;IACzD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAC7E,CAAC"}
1
+ {"version":3,"file":"compat-view.js","sourceRoot":"","sources":["../../../src/dashboard/compat-view.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,mEAAmE;AACnE,MAAM,UAAU,YAAY,CAAC,OAA2B;IACtD,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,YAAY;YACf,OAAO,MAAM,CAAC;QAChB,KAAK,UAAU;YACb,OAAO,YAAY,CAAC;QACtB,KAAK,cAAc;YACjB,OAAO,OAAO,CAAC;IACnB,CAAC;AACH,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,YAAY,CAAC,OAA2B;IACtD,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,YAAY;YACf,OAAO,YAAY,CAAC;QACtB,KAAK,UAAU;YACb,OAAO,UAAU,CAAC;QACpB,KAAK,cAAc;YACjB,OAAO,cAAc,CAAC;IAC1B,CAAC;AACH,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,mBAAmB,CAAC,KAAuB;IACzD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAC7E,CAAC"}
@@ -1,5 +1,16 @@
1
1
  import type { HttpContext } from '@adonisjs/core/http';
2
+ import type { AccessDeniedOption as GenericAccessDeniedOption, AccessDeniedRenderer as GenericAccessDeniedRenderer } from './access_denied_page.js';
2
3
  import { type DashboardAuthOptions, type ResolvedDashboardAuth } from './auth.js';
4
+ /**
5
+ * The function form of {@link DurableDashboardConfig.accessDenied}: render (or answer) a refused
6
+ * page navigation yourself. Receives the refusal ({@link AccessDeniedInfo}) and the AdonisJS
7
+ * {@link HttpContext}. Return an HTML string to have it served; answer the request yourself (a
8
+ * redirect, most commonly) and return nothing to make the provider stand down; return nothing
9
+ * WITHOUT answering and the built-in page is served.
10
+ */
11
+ export type AccessDeniedRenderer = GenericAccessDeniedRenderer<HttpContext>;
12
+ /** `accessDenied` in either form — an options object for the built-in page, or a renderer. */
13
+ export type AccessDeniedOption = GenericAccessDeniedOption<HttpContext>;
3
14
  /**
4
15
  * Authorization guard for the dashboard. Runs before every dashboard route
5
16
  * (API + HTML). Return `true` to allow the request, `false` to deny it (the
@@ -42,6 +53,20 @@ export interface DurableDashboardConfig {
42
53
  * closed at boot.
43
54
  */
44
55
  dashboardAuth?: DashboardAuthOptions;
56
+ /**
57
+ * What a BROWSER sees when the guard refuses a page navigation (the SPA shell, its assets, or —
58
+ * Mode A only — a session-less visit). API requests are unaffected:
59
+ * they keep getting the JSON the SPA relies on (`403 { error: 'forbidden' }` /
60
+ * `401 { error: 'unauthorized', auth }`).
61
+ *
62
+ * Omit it for the built-in page — a dark card in the console's own visual language, with the
63
+ * status, a sentence explaining the refusal, a "Back to app" link and, when `dashboardAuth.login`
64
+ * exists, a "Sign in" button. Pass an object to tweak that page (`brand`, `title`, `message`,
65
+ * `homeHref`, `loginHref`, `accent`, …), or a function to render/answer it yourself — see
66
+ * {@link AccessDeniedRenderer}. Either way, an `authorize` hook that already wrote a redirect
67
+ * still wins: the provider never overwrites a `location` header.
68
+ */
69
+ accessDenied?: AccessDeniedOption;
45
70
  }
46
71
  /** A fully-resolved config — every field present (defaults applied). */
47
72
  export interface ResolvedDurableDashboardConfig {
@@ -50,6 +75,8 @@ export interface ResolvedDurableDashboardConfig {
50
75
  authorize: AuthorizeHook;
51
76
  /** Resolved built-in login config, or `null` when `dashboardAuth` is unconfigured. */
52
77
  dashboardAuth: ResolvedDashboardAuth | null;
78
+ /** The host's `accessDenied` option as given, or `null` for the built-in page with defaults. */
79
+ accessDenied: AccessDeniedOption | null;
53
80
  }
54
81
  /**
55
82
  * The default guard: open outside production; in production it requires a
@@ -1 +1 @@
1
- {"version":3,"file":"define_config.d.ts","sourceRoot":"","sources":["../../../src/dashboard/define_config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAE3B,MAAM,WAAW,CAAC;AAEnB;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAE7E,8CAA8C;AAC9C,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,EAAE,oBAAoB,CAAC;CACtC;AAED,wEAAwE;AACxE,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,aAAa,CAAC;IACzB,sFAAsF;IACtF,aAAa,EAAE,qBAAqB,GAAG,IAAI,CAAC;CAC7C;AAyCD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAQ1D;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,MAAM,GAAE,sBAA2B,GAAG,8BAA8B,CAYjG;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,MAAM,GAAE,sBAA2B,GAAG,sBAAsB,CAExF"}
1
+ {"version":3,"file":"define_config.d.ts","sourceRoot":"","sources":["../../../src/dashboard/define_config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EACV,kBAAkB,IAAI,yBAAyB,EAC/C,oBAAoB,IAAI,2BAA2B,EACpD,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAE3B,MAAM,WAAW,CAAC;AAEnB;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAAG,2BAA2B,CAAC,WAAW,CAAC,CAAC;AAE5E,8FAA8F;AAC9F,MAAM,MAAM,kBAAkB,GAAG,yBAAyB,CAAC,WAAW,CAAC,CAAC;AAExE;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAE7E,8CAA8C;AAC9C,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,EAAE,oBAAoB,CAAC;IACrC;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,EAAE,kBAAkB,CAAC;CACnC;AAED,wEAAwE;AACxE,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,aAAa,CAAC;IACzB,sFAAsF;IACtF,aAAa,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAC5C,gGAAgG;IAChG,YAAY,EAAE,kBAAkB,GAAG,IAAI,CAAC;CACzC;AAyCD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAQ1D;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,MAAM,GAAE,sBAA2B,GAAG,8BAA8B,CAajG;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,MAAM,GAAE,sBAA2B,GAAG,sBAAsB,CAExF"}
@@ -70,6 +70,7 @@ export function resolveConfig(config = {}) {
70
70
  // Validate + resolve now so a misconfigured secret/login fails closed at boot,
71
71
  // not on the first login attempt. `null` when `dashboardAuth` is omitted.
72
72
  dashboardAuth: resolveDashboardAuth(config.dashboardAuth),
73
+ accessDenied: config.accessDenied ?? null,
73
74
  };
74
75
  }
75
76
  /** Identity helper giving `config/durable_dashboard.ts` full type-checking. */
@@ -1 +1 @@
1
- {"version":3,"file":"define_config.js","sourceRoot":"","sources":["../../../src/dashboard/define_config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAGL,oBAAoB,GACrB,MAAM,WAAW,CAAC;AAwDnB;;;GAGG;AACH,SAAS,YAAY;IACnB,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,YAAY,CAAC;AACrE,CAAC;AAED;;;;GAIG;AACH,SAAS,SAAS,CAAC,GAAgB;IACjC,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IACnD,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9C,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACzC,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;IACtD,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;IACnC,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC;IAClC,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IACvD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;GAKG;AACH,SAAS,YAAY,CAAC,CAAS,EAAE,CAAS;IACxC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAgB;IAC/C,IAAI,CAAC,YAAY,EAAE;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC;IACrD,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5B,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAChC,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACzC,gFAAgF;IAChF,OAAO,YAAY,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAC1C,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,aAAa,CAAC,MAAM,GAA2B,EAAE;IAC/D,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,IAAI,UAAU,CAAC;IAC1C,mFAAmF;IACnF,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;IACtE,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,IAAI;QAC/B,IAAI,EAAE,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO;QACpC,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,gBAAgB;QAC/C,+EAA+E;QAC/E,0EAA0E;QAC1E,aAAa,EAAE,oBAAoB,CAAC,MAAM,CAAC,aAAa,CAAC;KAC1D,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,YAAY,CAAC,MAAM,GAA2B,EAAE;IAC9D,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"define_config.js","sourceRoot":"","sources":["../../../src/dashboard/define_config.ts"],"names":[],"mappings":"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"}
@@ -13,8 +13,8 @@ import type { EngineEvent, GroupHealth, RunQuery, RunResult, SignalWaiter, StepC
13
13
  * shapes.
14
14
  *
15
15
  * `listRuns`/`getRun`/`retryRun`/`redispatchPendingRun`/`cancelRun`/`health` are the original
16
- * handlers this file always had (kept response-shape compatible with the legacy static
17
- * `assets/dashboard.html`, still served as a compat fallback — see `compat.ts`/`compat-view.ts`).
16
+ * handlers this file always had (response shapes unchanged, so a hand-written client over the
17
+ * JSON API keeps working — see `compat.ts`/`compat-view.ts`).
18
18
  * `workers`, `topology`, `retryWithInputRun`, `continueRun`, and `bulkAction` are additions that give
19
19
  * the new `@adonis-agora/durable-dashboard` React SPA parity with `@dudousxd/nestjs-durable-dashboard`'s
20
20
  * `DurableApiController` (fix-and-replay, bulk retry/cancel, breakpoint continue, full worker
@@ -124,7 +124,7 @@ export declare function continueRun(deps: Deps, req: ApiRequest): Promise<ApiRes
124
124
  */
125
125
  export declare function bulkAction(deps: Deps, req: ApiRequest): Promise<ApiResponse>;
126
126
  /** `GET /health` — per-group worker health (queue backlog + live worker heartbeats), reduced to a
127
- * compact shape. Kept for the legacy static dashboard (`assets/dashboard.html`, compat fallback). */
127
+ * compact shape. Kept as a public endpoint for hand-written clients; the console uses `/workers`. */
128
128
  export declare function health(deps: Deps): Promise<ApiResponse>;
129
129
  /** `GET /workers` — full per-group worker health (every live worker's heartbeat, not just a count),
130
130
  * for the new SPA's Workers panel (`pivot-by-worker.ts`/`group-by-partition.ts` need per-instance
@@ -70,7 +70,7 @@ function parseFlag(raw) {
70
70
  *
71
71
  * Both channels are read because both are in use: the bundled React console sends
72
72
  * `POST .../cancel?compensate=true` (no body at all), while the documented `{ compensate: true }`
73
- * body is what a hand-written client and the legacy page send. Reading only one of them meant a
73
+ * body is what a hand-written client sends. Reading only one of them meant a
74
74
  * "Cancel + Undo" from the console performed a plain cancel and reported success — a silent wrong
75
75
  * answer, worse than an error. The body wins when it carries the key, so every existing body-based
76
76
  * caller keeps its exact behaviour and the query is purely additive.
@@ -267,7 +267,7 @@ export async function bulkAction(deps, req) {
267
267
  return ok({ matched: runs.length, applied });
268
268
  }
269
269
  /** `GET /health` — per-group worker health (queue backlog + live worker heartbeats), reduced to a
270
- * compact shape. Kept for the legacy static dashboard (`assets/dashboard.html`, compat fallback). */
270
+ * compact shape. Kept as a public endpoint for hand-written clients; the console uses `/workers`. */
271
271
  export async function health(deps) {
272
272
  const groups = await deps.engine.workerHealth();
273
273
  return ok({
@@ -1,11 +1,13 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export declare const VERSION = "0.27.0";
2
+ export declare const VERSION = "0.29.0";
3
+ export type { AccessDeniedInfo, AccessDeniedPageOptions, AccessDeniedReason, } from './access_denied_page.js';
4
+ export { CONSOLE as ACCESS_DENIED_CONSOLE, escapeHtml, renderAccessDeniedPage, resolveAccessDeniedPage, } from './access_denied_page.js';
3
5
  export type { AuthMode, DashboardAuthOptions, LoginHook, LoginOutcome, ResolvedDashboardAuth, SessionHook, SessionOutcome, } from './auth.js';
4
6
  export { performLogin, performSession, readSession, resolveDashboardAuth, SESSION_COOKIE_NAME, sanitizeReturnTo, } from './auth.js';
5
7
  export type { CompatSource, FleetGroup, FleetTransport } from './compat.js';
6
8
  export { compat, enumerateLiveFleet, mergeFleets } from './compat.js';
7
9
  export { formatProtocolRange, outcomeClass, outcomeLabel } from './compat-view.js';
8
- export type { AuthorizeHook, DurableDashboardConfig, ResolvedDurableDashboardConfig, } from './define_config.js';
10
+ export type { AccessDeniedOption, AccessDeniedRenderer, AuthorizeHook, DurableDashboardConfig, ResolvedDurableDashboardConfig, } from './define_config.js';
9
11
  export { defaultAuthorize, defineConfig, resolveConfig } from './define_config.js';
10
12
  export type { EngineEventSource, RecordedBlock } from './diagnostics-recorder.js';
11
13
  export { BlockedDiagnosticsRecorder } from './diagnostics-recorder.js';
@@ -13,7 +15,6 @@ export type { DashboardContainer } from './gateway-adapter.js';
13
15
  export { dashboardEngineForRole, gatewayDashboardEngine } from './gateway-adapter.js';
14
16
  export type { ApiRequest, ApiResponse, DashboardEngine, Deps } from './handlers.js';
15
17
  export { bulkAction, cancelRun, continueRun, getRun, health, listRuns, ok, redispatchPendingRun, retryRun, retryWithInputRun, topology, workers, } from './handlers.js';
16
- export { renderDashboard } from './html.js';
17
18
  export { renderLoginPage } from './login_page.js';
18
19
  export type { DashboardSession, DashboardSessionUser, SignOptions, VerifyOptions, } from './session_cookie.js';
19
20
  export { signSessionCookie, verifySessionCookie, } from './session_cookie.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,eAAO,MAAM,OAAO,WAAW,CAAC;AAEhC,YAAY,EACV,QAAQ,EACR,oBAAoB,EACpB,SAAS,EACT,YAAY,EACZ,qBAAqB,EACrB,WAAW,EACX,cAAc,GACf,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,YAAY,EACZ,cAAc,EACd,WAAW,EACX,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE5E,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACnF,YAAY,EACV,aAAa,EACb,sBAAsB,EACtB,8BAA8B,GAC/B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnF,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,YAAY,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG/D,OAAO,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACtF,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACpF,OAAO,EACL,UAAU,EACV,SAAS,EACT,WAAW,EACX,MAAM,EACN,MAAM,EACN,QAAQ,EACR,EAAE,EACF,oBAAoB,EACpB,QAAQ,EACR,iBAAiB,EACjB,QAAQ,EACR,OAAO,GACR,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EACV,gBAAgB,EAChB,oBAAoB,EACpB,WAAW,EACX,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,eAAO,MAAM,OAAO,WAAW,CAAC;AAEhC,YAAY,EACV,gBAAgB,EAChB,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,OAAO,IAAI,qBAAqB,EAChC,UAAU,EACV,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,QAAQ,EACR,oBAAoB,EACpB,SAAS,EACT,YAAY,EACZ,qBAAqB,EACrB,WAAW,EACX,cAAc,GACf,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,YAAY,EACZ,cAAc,EACd,WAAW,EACX,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE5E,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACnF,YAAY,EACV,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,EACb,sBAAsB,EACtB,8BAA8B,GAC/B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnF,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,YAAY,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG/D,OAAO,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACtF,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACpF,OAAO,EACL,UAAU,EACV,SAAS,EACT,WAAW,EACX,MAAM,EACN,MAAM,EACN,QAAQ,EACR,EAAE,EACF,oBAAoB,EACpB,QAAQ,EACR,iBAAiB,EACjB,QAAQ,EACR,OAAO,GACR,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EACV,gBAAgB,EAChB,oBAAoB,EACpB,WAAW,EACX,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC"}
@@ -1,5 +1,7 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export const VERSION = '0.27.0';
2
+ export const VERSION = '0.29.0';
3
+ // The built-in "access denied" page (what a browser sees on a refused page navigation).
4
+ export { CONSOLE as ACCESS_DENIED_CONSOLE, escapeHtml, renderAccessDeniedPage, resolveAccessDeniedPage, } from './access_denied_page.js';
3
5
  // Built-in `dashboardAuth` login screen (optional; opt-in via `config/durable_dashboard.ts`).
4
6
  export { performLogin, performSession, readSession, resolveDashboardAuth, SESSION_COOKIE_NAME, sanitizeReturnTo, } from './auth.js';
5
7
  // Fleet health / protocol-compatibility panel (design §7.6, §10).
@@ -10,7 +12,6 @@ export { BlockedDiagnosticsRecorder } from './diagnostics-recorder.js';
10
12
  // Store-less `tenant` dashboard: adapt the RunGateway to the handlers' read/control port (design §8).
11
13
  export { dashboardEngineForRole, gatewayDashboardEngine } from './gateway-adapter.js';
12
14
  export { bulkAction, cancelRun, continueRun, getRun, health, listRuns, ok, redispatchPendingRun, retryRun, retryWithInputRun, topology, workers, } from './handlers.js';
13
- export { renderDashboard } from './html.js';
14
15
  export { renderLoginPage } from './login_page.js';
15
16
  export { signSessionCookie, verifySessionCookie, } from './session_cookie.js';
16
17
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,MAAM,CAAC,MAAM,OAAO,GAAG,QAAQ,CAAC;AAWhC,8FAA8F;AAC9F,OAAO,EACL,YAAY,EACZ,cAAc,EACd,WAAW,EACX,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,WAAW,CAAC;AAEnB,kEAAkE;AAClE,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAMnF,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnF,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAGvE,sGAAsG;AACtG,OAAO,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEtF,OAAO,EACL,UAAU,EACV,SAAS,EACT,WAAW,EACX,MAAM,EACN,MAAM,EACN,QAAQ,EACR,EAAE,EACF,oBAAoB,EACpB,QAAQ,EACR,iBAAiB,EACjB,QAAQ,EACR,OAAO,GACR,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAOlD,OAAO,EACL,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,MAAM,CAAC,MAAM,OAAO,GAAG,QAAQ,CAAC;AAOhC,wFAAwF;AACxF,OAAO,EACL,OAAO,IAAI,qBAAqB,EAChC,UAAU,EACV,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,yBAAyB,CAAC;AAUjC,8FAA8F;AAC9F,OAAO,EACL,YAAY,EACZ,cAAc,EACd,WAAW,EACX,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,WAAW,CAAC;AAEnB,kEAAkE;AAClE,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAQnF,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnF,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAGvE,sGAAsG;AACtG,OAAO,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEtF,OAAO,EACL,UAAU,EACV,SAAS,EACT,WAAW,EACX,MAAM,EACN,MAAM,EACN,QAAQ,EACR,EAAE,EACF,oBAAoB,EACpB,QAAQ,EACR,iBAAiB,EACjB,QAAQ,EACR,OAAO,GACR,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAOlD,OAAO,EACL,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC"}
@@ -1,23 +1,36 @@
1
1
  /**
2
2
  * The built-in `dashboardAuth` login page (`GET <basePath>/login`). Deliberately a small,
3
3
  * dependency-free, hand-authored HTML page — NOT part of the bundled dashboard SPA — so gating the
4
- * dashboard shell doesn't require rebuilding or extending the bundled `assets/dashboard.html`.
4
+ * dashboard shell doesn't require rebuilding or extending the bundled SPA.
5
5
  *
6
- * `returnTo` and any error state are read CLIENT-SIDE from `location.search` (never server-echoed
7
- * into the HTML), so this function's only per-request-ish input is `basePath` — a developer-
8
- * controlled config value, not user input — and the page body is otherwise a static template.
9
- * This sidesteps HTML-escaping entirely: there is no reflected query-param interpolation surface
10
- * to get wrong.
6
+ * It works WITHOUT JavaScript: the markup is a classic `<form method="post">` carrying `returnTo`
7
+ * in a hidden field, and the provider answers a form-encoded `POST` with a redirect (to `returnTo`
8
+ * on success, back here with `?error` on failure). The inline `<script>` is progressive
9
+ * enhancement only — it swaps the full-page round trip for a JSON `fetch` and shows the error in
10
+ * place — and it carries the request's CSP nonce when the provider hands one over, so a host
11
+ * running `@adonisjs/shield`'s `script-src 'self' @nonce` keeps the enhancement instead of a
12
+ * dead form. The `<style>` takes the same nonce.
13
+ *
14
+ * The only per-request values interpolated are `returnTo` (already passed through
15
+ * {@link sanitizeReturnTo}, so it is a root-relative path) and the `error` flag; both go through
16
+ * {@link escapeHtml} (and `JSON.stringify` inside the script) so nothing in the query string can
17
+ * break out of an attribute or the script. `basePath` is developer-controlled config.
11
18
  *
12
19
  * The visual language (dark zinc card, mono type, emerald accent) mirrors the Agora consoles so
13
- * they feel like one family. The submit flow POSTs JSON via `fetch` and follows the JSON
14
- * `redirectTo` it gets back (rather than a classic form POST + server redirect), which is why the
15
- * markup keeps its own `<script>`.
20
+ * they feel like one family.
16
21
  *
17
22
  * The password input has NO `required` attribute and the value is forwarded AS-IS (empty string
18
23
  * when blank): the host's `login` hook — not this page — decides whether a password matters, so an
19
24
  * email-only host (gate on username, ignore password) works with the same page as a host with real
20
25
  * passwords.
21
26
  */
22
- export declare function renderLoginPage(basePath: string): string;
27
+ export interface LoginPageOptions {
28
+ /** The request's CSP nonce, applied to the inline `<style>` and `<script>`. */
29
+ nonce?: string;
30
+ /** Render the "Invalid username or password." notice (the no-JS failure round trip). */
31
+ error?: boolean;
32
+ /** Where to send the operator after a successful sign-in. Sanitized here; default `basePath`. */
33
+ returnTo?: unknown;
34
+ }
35
+ export declare function renderLoginPage(basePath: string, options?: LoginPageOptions): string;
23
36
  //# sourceMappingURL=login_page.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"login_page.d.ts","sourceRoot":"","sources":["../../../src/dashboard/login_page.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAmJxD"}
1
+ {"version":3,"file":"login_page.d.ts","sourceRoot":"","sources":["../../../src/dashboard/login_page.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+EAA+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wFAAwF;IACxF,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iGAAiG;IACjG,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAkBD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CA2IxF"}