@adonis-agora/durable 0.6.1 → 0.8.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 (159) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +1 -1
  3. package/dist/commands/main.d.ts +3 -2
  4. package/dist/commands/main.d.ts.map +1 -1
  5. package/dist/commands/main.js +3 -2
  6. package/dist/commands/main.js.map +1 -1
  7. package/dist/commands/make_workflow.d.ts +4 -3
  8. package/dist/commands/make_workflow.d.ts.map +1 -1
  9. package/dist/commands/make_workflow.js +4 -3
  10. package/dist/commands/make_workflow.js.map +1 -1
  11. package/dist/configure.d.ts +6 -5
  12. package/dist/configure.d.ts.map +1 -1
  13. package/dist/configure.js +9 -6
  14. package/dist/configure.js.map +1 -1
  15. package/dist/providers/dashboard_provider.d.ts +34 -3
  16. package/dist/providers/dashboard_provider.d.ts.map +1 -1
  17. package/dist/providers/dashboard_provider.js +110 -6
  18. package/dist/providers/dashboard_provider.js.map +1 -1
  19. package/dist/providers/durable_provider.d.ts.map +1 -1
  20. package/dist/providers/durable_provider.js +41 -4
  21. package/dist/providers/durable_provider.js.map +1 -1
  22. package/dist/src/base-workflow.d.ts +78 -0
  23. package/dist/src/base-workflow.d.ts.map +1 -0
  24. package/dist/src/base-workflow.js +106 -0
  25. package/dist/src/base-workflow.js.map +1 -0
  26. package/dist/src/codec-state-store.d.ts +13 -0
  27. package/dist/src/codec-state-store.d.ts.map +1 -1
  28. package/dist/src/codec-state-store.js +13 -0
  29. package/dist/src/codec-state-store.js.map +1 -1
  30. package/dist/src/control-flow-signal.d.ts +50 -0
  31. package/dist/src/control-flow-signal.d.ts.map +1 -0
  32. package/dist/src/control-flow-signal.js +54 -0
  33. package/dist/src/control-flow-signal.js.map +1 -0
  34. package/dist/src/control-plane-redis/redis-control-plane.d.ts +57 -5
  35. package/dist/src/control-plane-redis/redis-control-plane.d.ts.map +1 -1
  36. package/dist/src/control-plane-redis/redis-control-plane.js +100 -2
  37. package/dist/src/control-plane-redis/redis-control-plane.js.map +1 -1
  38. package/dist/src/dashboard/auth.d.ts +89 -0
  39. package/dist/src/dashboard/auth.d.ts.map +1 -0
  40. package/dist/src/dashboard/auth.js +113 -0
  41. package/dist/src/dashboard/auth.js.map +1 -0
  42. package/dist/src/dashboard/define_config.d.ts +17 -0
  43. package/dist/src/dashboard/define_config.d.ts.map +1 -1
  44. package/dist/src/dashboard/define_config.js +23 -1
  45. package/dist/src/dashboard/define_config.js.map +1 -1
  46. package/dist/src/dashboard/index.d.ts +6 -1
  47. package/dist/src/dashboard/index.d.ts.map +1 -1
  48. package/dist/src/dashboard/index.js +5 -1
  49. package/dist/src/dashboard/index.js.map +1 -1
  50. package/dist/src/dashboard/login_page.d.ts +23 -0
  51. package/dist/src/dashboard/login_page.d.ts.map +1 -0
  52. package/dist/src/dashboard/login_page.js +170 -0
  53. package/dist/src/dashboard/login_page.js.map +1 -0
  54. package/dist/src/dashboard/session_cookie.d.ts +42 -0
  55. package/dist/src/dashboard/session_cookie.d.ts.map +1 -0
  56. package/dist/src/dashboard/session_cookie.js +97 -0
  57. package/dist/src/dashboard/session_cookie.js.map +1 -0
  58. package/dist/src/define_config.d.ts +11 -2
  59. package/dist/src/define_config.d.ts.map +1 -1
  60. package/dist/src/define_config.js.map +1 -1
  61. package/dist/src/engine.d.ts +91 -3
  62. package/dist/src/engine.d.ts.map +1 -1
  63. package/dist/src/engine.js +331 -44
  64. package/dist/src/engine.js.map +1 -1
  65. package/dist/src/entities.js +2 -2
  66. package/dist/src/entities.js.map +1 -1
  67. package/dist/src/errors.d.ts +6 -1
  68. package/dist/src/errors.d.ts.map +1 -1
  69. package/dist/src/errors.js +7 -2
  70. package/dist/src/errors.js.map +1 -1
  71. package/dist/src/event-accumulators.d.ts +1 -1
  72. package/dist/src/event-accumulators.d.ts.map +1 -1
  73. package/dist/src/events.d.ts +16 -0
  74. package/dist/src/events.d.ts.map +1 -1
  75. package/dist/src/events.js +26 -1
  76. package/dist/src/events.js.map +1 -1
  77. package/dist/src/hooks/steps.d.ts +43 -0
  78. package/dist/src/hooks/steps.d.ts.map +1 -0
  79. package/dist/src/hooks/steps.js +46 -0
  80. package/dist/src/hooks/steps.js.map +1 -0
  81. package/dist/src/hooks/workflows.d.ts +3 -2
  82. package/dist/src/hooks/workflows.d.ts.map +1 -1
  83. package/dist/src/hooks/workflows.js +3 -2
  84. package/dist/src/hooks/workflows.js.map +1 -1
  85. package/dist/src/index.d.ts +8 -2
  86. package/dist/src/index.d.ts.map +1 -1
  87. package/dist/src/index.js +8 -2
  88. package/dist/src/index.js.map +1 -1
  89. package/dist/src/interfaces.d.ts +160 -43
  90. package/dist/src/interfaces.d.ts.map +1 -1
  91. package/dist/src/services/main.d.ts +13 -0
  92. package/dist/src/services/main.d.ts.map +1 -0
  93. package/dist/src/services/main.js +17 -0
  94. package/dist/src/services/main.js.map +1 -0
  95. package/dist/src/step-discovery.d.ts +42 -0
  96. package/dist/src/step-discovery.d.ts.map +1 -0
  97. package/dist/src/step-discovery.js +127 -0
  98. package/dist/src/step-discovery.js.map +1 -0
  99. package/dist/src/step-name-symbol.d.ts +63 -0
  100. package/dist/src/step-name-symbol.d.ts.map +1 -0
  101. package/dist/src/step-name-symbol.js +42 -0
  102. package/dist/src/step-name-symbol.js.map +1 -0
  103. package/dist/src/step-ref.d.ts +70 -0
  104. package/dist/src/step-ref.d.ts.map +1 -0
  105. package/dist/src/step-ref.js +93 -0
  106. package/dist/src/step-ref.js.map +1 -0
  107. package/dist/src/stores/lucid-schema.d.ts +1 -0
  108. package/dist/src/stores/lucid-schema.d.ts.map +1 -1
  109. package/dist/src/stores/lucid-schema.js +15 -0
  110. package/dist/src/stores/lucid-schema.js.map +1 -1
  111. package/dist/src/stores/lucid.d.ts +13 -0
  112. package/dist/src/stores/lucid.d.ts.map +1 -1
  113. package/dist/src/stores/lucid.js +43 -0
  114. package/dist/src/stores/lucid.js.map +1 -1
  115. package/dist/src/tenant-group.d.ts +18 -0
  116. package/dist/src/tenant-group.d.ts.map +1 -0
  117. package/dist/src/tenant-group.js +24 -0
  118. package/dist/src/tenant-group.js.map +1 -0
  119. package/dist/src/testing/in-memory-state-store.d.ts +16 -0
  120. package/dist/src/testing/in-memory-state-store.d.ts.map +1 -1
  121. package/dist/src/testing/in-memory-state-store.js +29 -0
  122. package/dist/src/testing/in-memory-state-store.js.map +1 -1
  123. package/dist/src/testing-kit/state-store-conformance.d.ts.map +1 -1
  124. package/dist/src/testing-kit/state-store-conformance.js +33 -2
  125. package/dist/src/testing-kit/state-store-conformance.js.map +1 -1
  126. package/dist/src/testing-kit/transport-conformance.d.ts.map +1 -1
  127. package/dist/src/testing-kit/transport-conformance.js +7 -12
  128. package/dist/src/testing-kit/transport-conformance.js.map +1 -1
  129. package/dist/src/transports/db.d.ts +14 -5
  130. package/dist/src/transports/db.d.ts.map +1 -1
  131. package/dist/src/transports/db.js +20 -15
  132. package/dist/src/transports/db.js.map +1 -1
  133. package/dist/src/transports/queue.d.ts +15 -5
  134. package/dist/src/transports/queue.d.ts.map +1 -1
  135. package/dist/src/transports/queue.js +19 -17
  136. package/dist/src/transports/queue.js.map +1 -1
  137. package/dist/src/workflow-als.d.ts +21 -0
  138. package/dist/src/workflow-als.d.ts.map +1 -0
  139. package/dist/src/workflow-als.js +22 -0
  140. package/dist/src/workflow-als.js.map +1 -0
  141. package/dist/src/workflow-ctx.d.ts +27 -8
  142. package/dist/src/workflow-ctx.d.ts.map +1 -1
  143. package/dist/src/workflow-ctx.js +183 -19
  144. package/dist/src/workflow-ctx.js.map +1 -1
  145. package/dist/src/workflow-discovery.d.ts +21 -15
  146. package/dist/src/workflow-discovery.d.ts.map +1 -1
  147. package/dist/src/workflow-discovery.js +17 -13
  148. package/dist/src/workflow-discovery.js.map +1 -1
  149. package/dist/src/workflow-ref.d.ts +9 -27
  150. package/dist/src/workflow-ref.d.ts.map +1 -1
  151. package/dist/src/workflow-ref.js +12 -34
  152. package/dist/src/workflow-ref.js.map +1 -1
  153. package/dist/stubs/database/migrations/create_durable_tables.stub +13 -1
  154. package/dist/stubs/make/workflow/main.stub +5 -4
  155. package/package.json +11 -1
  156. package/dist/src/remote-step-factory.d.ts +0 -15
  157. package/dist/src/remote-step-factory.d.ts.map +0 -1
  158. package/dist/src/remote-step-factory.js +0 -12
  159. package/dist/src/remote-step-factory.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"redis-control-plane.js","sourceRoot":"","sources":["../../../src/control-plane-redis/redis-control-plane.ts"],"names":[],"mappings":"AA2CA;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,iBAAiB;IACX,UAAU,CAAc;IACxB,OAAO,CAAS;IACzB,UAAU,CAA0B;IACpC,UAAU,GAAG,KAAK,CAAC;IAE3B,YAAY,OAAiC;QAC3C,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,OAAO,GAAG,GAAG,OAAO,CAAC,MAAM,IAAI,SAAS,UAAU,CAAC;IAC1D,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,GAAmB;QACtC,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IACnE,CAAC;IAED,SAAS,CAAC,OAAsC;QAC9C,IAAI,IAAI,CAAC,UAAU;YAAE,OAAO,CAAC,qCAAqC;QAClE,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QAEvB,MAAM,OAAO,GAAG,CAAC,OAAe,EAAE,EAAE;YAClC,IAAI,GAAmB,CAAC;YACxB,IAAI,CAAC;gBACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAmB,CAAC;YAC9C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,6EAA6E;YACvF,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,CAAC;QACf,CAAC,CAAC;QAEF,IAAI,OAAO,IAAI,CAAC,UAAU,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;YACpD,wFAAwF;YACxF,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC;YACxC,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC;YACtB,KAAK,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACjC,GAAG,CAAC,EAAE,EAAE,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;QAC/D,CAAC;aAAM,CAAC;YACN,iGAAiG;YACjG,KAAK,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;IAED,2FAA2F;IAC3F,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,EAAE,CAAC;QAChC,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;IAC9B,CAAC;CACF"}
1
+ {"version":3,"file":"redis-control-plane.js","sourceRoot":"","sources":["../../../src/control-plane-redis/redis-control-plane.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ import { type DashboardSession, type DashboardSessionUser } from './session_cookie.js';
2
+ /**
3
+ * Framework-light `dashboardAuth` mechanism for the durable dashboard — config resolution, the
4
+ * open-redirect guard, the login decision, and session verification. NO AdonisJS types leak in
5
+ * (mirroring {@link file://./handlers.ts handlers.ts}), so every branch is unit-testable against a
6
+ * plain object with no HTTP server. The provider (`providers/dashboard_provider.ts`) adapts an
7
+ * `HttpContext` to these functions and owns the cookie read/write via AdonisJS's native cookie API.
8
+ *
9
+ * When configured it gates BOTH the dashboard HTML shell (a full-page navigation redirected `302`
10
+ * to a server-rendered login page) and the JSON API (a plain `401`) behind a signed session cookie.
11
+ * When left unconfigured the dashboard behaves exactly as before (no login/logout routes, no
12
+ * session guard).
13
+ */
14
+ /** Host hook validating submitted credentials from the built-in login page. */
15
+ export type LoginHook = (username: string, password: string) => Promise<DashboardSessionUser | null> | DashboardSessionUser | null;
16
+ /**
17
+ * Author-facing `dashboardAuth` option on `config/durable_dashboard.ts`. Gates the dashboard behind
18
+ * a signed session cookie via the built-in server-rendered login page (`GET <path>/login`) — the
19
+ * bundled dashboard SPA stays untouched.
20
+ *
21
+ * Nest needed a `forRootAsync` to reach app services from the `login` hook; AdonisJS does not — the
22
+ * config module is a plain file and `login` is just a function that may be `async` and close over
23
+ * `app.container`/services (import them at the top of `config/durable_dashboard.ts`), so there is
24
+ * nothing extra to build for the async case.
25
+ */
26
+ export interface DashboardAuthOptions {
27
+ /** REQUIRED HMAC-SHA256 signing key. Missing/empty => boot error (fail closed). */
28
+ secret: string;
29
+ /** Cookie TTL as a duration string (`'8h'`, `'30m'`, `'7d'`). Default `'8h'`. */
30
+ ttl?: string;
31
+ /** Validates submitted username/password; return the session user, or `null` to deny. Thrown
32
+ * errors are treated as a denial (logged once, never surfaced to the client). May be async. */
33
+ login: LoginHook;
34
+ }
35
+ /** Resolved, validated `dashboardAuth` config shared by the guard, the login handler, and the page. */
36
+ export interface ResolvedDashboardAuth {
37
+ secret: string;
38
+ ttlMs: number;
39
+ login: LoginHook;
40
+ }
41
+ /** Cookie name carrying the signed dashboard session. */
42
+ export declare const SESSION_COOKIE_NAME = "durable_dashboard_session";
43
+ /**
44
+ * Validate + resolve the `dashboardAuth` option. Returns `null` when unconfigured (today's
45
+ * unauthenticated behavior, unchanged). Throws at boot (fail closed) when configured but missing a
46
+ * secret or a `login` hook — the host learns immediately rather than shipping an un-mintable gate.
47
+ */
48
+ export declare function resolveDashboardAuth(options: DashboardAuthOptions | undefined): ResolvedDashboardAuth | null;
49
+ /**
50
+ * Guard a client-supplied redirect target against an open redirect: it must be a same-origin,
51
+ * root-relative path (`/durable/foo`), never a protocol-relative (`//evil.com`) or absolute
52
+ * (`https://evil.com`) URL. Falls back to `fallback` (the dashboard's own `basePath`) for anything
53
+ * else, including a missing/non-string value.
54
+ */
55
+ export declare function sanitizeReturnTo(candidate: unknown, fallback: string): string;
56
+ /**
57
+ * The framework-light outcome of a login attempt, mapped to HTTP by the provider:
58
+ * - `ok` → set `cookieValue` as the session cookie, reply `200 { redirectTo }`.
59
+ * - `bad-request` → `400` (body missing string `username`/`password`).
60
+ * - `unauthorized` → uniform `401 { error }` for EVERY credential failure (unknown user, wrong
61
+ * password, or a throwing hook) — no user-enumeration. `hookError` is set only when the hook
62
+ * threw, so the provider can warn-log it once without changing the client-visible response.
63
+ */
64
+ export type LoginOutcome = {
65
+ kind: 'ok';
66
+ cookieValue: string;
67
+ redirectTo: string;
68
+ } | {
69
+ kind: 'bad-request';
70
+ message: string;
71
+ } | {
72
+ kind: 'unauthorized';
73
+ message: string;
74
+ hookError?: unknown;
75
+ };
76
+ /**
77
+ * Decide a login attempt end-to-end (validate body → run hook → mint cookie) with no HTTP types.
78
+ * On success returns the signed cookie value to set plus the sanitized `redirectTo`; the password
79
+ * is forwarded to the hook AS-IS (empty string when blank), so the hook alone decides whether a
80
+ * password is required.
81
+ */
82
+ export declare function performLogin(auth: ResolvedDashboardAuth, body: unknown, basePath: string, now?: number): Promise<LoginOutcome>;
83
+ /**
84
+ * Read + verify a session cookie value, returning the session or `null` when absent, tampered, or
85
+ * expired. Thin wrapper over {@link verifySessionCookie} so the provider never touches crypto.
86
+ */
87
+ export declare function readSession(auth: ResolvedDashboardAuth, cookieValue: string | undefined, now?: number): DashboardSession | null;
88
+ export type { DashboardSession, DashboardSessionUser } from './session_cookie.js';
89
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/dashboard/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EAG1B,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;GAWG;AAEH,+EAA+E;AAC/E,MAAM,MAAM,SAAS,GAAG,CACtB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,GAAG,oBAAoB,GAAG,IAAI,CAAC;AAExE;;;;;;;;;GASG;AACH,MAAM,WAAW,oBAAoB;IACnC,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;oGACgG;IAChG,KAAK,EAAE,SAAS,CAAC;CAClB;AAED,uGAAuG;AACvG,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,CAAC;CAClB;AAED,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,8BAA8B,CAAC;AAoB/D;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,oBAAoB,GAAG,SAAS,GACxC,qBAAqB,GAAG,IAAI,CAgB9B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAM7E;AAaD;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAqBnE;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,qBAAqB,EAC3B,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,YAAY,CAAC,CAiBvB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,qBAAqB,EAC3B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,GAAG,CAAC,EAAE,MAAM,GACX,gBAAgB,GAAG,IAAI,CAMzB;AAED,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,113 @@
1
+ import { signSessionCookie, verifySessionCookie, } from './session_cookie.js';
2
+ /** Cookie name carrying the signed dashboard session. */
3
+ export const SESSION_COOKIE_NAME = 'durable_dashboard_session';
4
+ const DEFAULT_TTL = '8h';
5
+ const DEFAULT_TTL_MS = 8 * 60 * 60 * 1000;
6
+ const DURATION_UNITS = {
7
+ s: 1000,
8
+ m: 60 * 1000,
9
+ h: 60 * 60 * 1000,
10
+ d: 24 * 60 * 60 * 1000,
11
+ };
12
+ /** Parse a `'<number><s|m|h|d>'` duration to ms; falls back to the 8h default on a bad value. */
13
+ function durationToMs(ttl) {
14
+ const match = /^(\d+)([smhd])$/.exec(ttl.trim());
15
+ if (!match)
16
+ return DEFAULT_TTL_MS;
17
+ const unit = DURATION_UNITS[match[2] ?? ''];
18
+ if (unit === undefined)
19
+ return DEFAULT_TTL_MS;
20
+ return Number(match[1]) * unit;
21
+ }
22
+ /**
23
+ * Validate + resolve the `dashboardAuth` option. Returns `null` when unconfigured (today's
24
+ * unauthenticated behavior, unchanged). Throws at boot (fail closed) when configured but missing a
25
+ * secret or a `login` hook — the host learns immediately rather than shipping an un-mintable gate.
26
+ */
27
+ export function resolveDashboardAuth(options) {
28
+ if (options === undefined)
29
+ return null;
30
+ if (typeof options.secret !== 'string' || options.secret === '') {
31
+ throw new Error('durable_dashboard: dashboardAuth.secret is required and must be a non-empty string ' +
32
+ '(HMAC-SHA256 signing key, 32+ bytes recommended). Failing closed.');
33
+ }
34
+ if (typeof options.login !== 'function') {
35
+ throw new Error('durable_dashboard: dashboardAuth.login is required (a login hook).');
36
+ }
37
+ return {
38
+ secret: options.secret,
39
+ ttlMs: durationToMs(options.ttl ?? DEFAULT_TTL),
40
+ login: options.login,
41
+ };
42
+ }
43
+ /**
44
+ * Guard a client-supplied redirect target against an open redirect: it must be a same-origin,
45
+ * root-relative path (`/durable/foo`), never a protocol-relative (`//evil.com`) or absolute
46
+ * (`https://evil.com`) URL. Falls back to `fallback` (the dashboard's own `basePath`) for anything
47
+ * else, including a missing/non-string value.
48
+ */
49
+ export function sanitizeReturnTo(candidate, fallback) {
50
+ if (typeof candidate !== 'string')
51
+ return fallback;
52
+ if (!candidate.startsWith('/'))
53
+ return fallback;
54
+ if (candidate.startsWith('//'))
55
+ return fallback;
56
+ if (candidate.includes('://'))
57
+ return fallback;
58
+ return candidate;
59
+ }
60
+ function isString(value) {
61
+ return typeof value === 'string';
62
+ }
63
+ const UNAUTHORIZED_MESSAGE = 'Invalid username or password.';
64
+ /**
65
+ * Run the host's `login` hook defensively: a throw is treated as a denial (uniform failure) so a
66
+ * buggy hook never 500s the endpoint into a stack-trace leak. The thrown error is surfaced on the
67
+ * outcome (`hookError`) so the provider can warn-log it once, but it never reaches the client.
68
+ */
69
+ async function runLoginHook(auth, username, password) {
70
+ try {
71
+ return { user: (await auth.login(username, password)) ?? null };
72
+ }
73
+ catch (error) {
74
+ return { user: null, error };
75
+ }
76
+ }
77
+ /**
78
+ * Decide a login attempt end-to-end (validate body → run hook → mint cookie) with no HTTP types.
79
+ * On success returns the signed cookie value to set plus the sanitized `redirectTo`; the password
80
+ * is forwarded to the hook AS-IS (empty string when blank), so the hook alone decides whether a
81
+ * password is required.
82
+ */
83
+ export async function performLogin(auth, body, basePath, now) {
84
+ const parsed = (body ?? {});
85
+ if (!isString(parsed.username) || !isString(parsed.password)) {
86
+ return { kind: 'bad-request', message: 'Body must include string `username` and `password`.' };
87
+ }
88
+ const { user, error } = await runLoginHook(auth, parsed.username, parsed.password);
89
+ if (!user) {
90
+ return error !== undefined
91
+ ? { kind: 'unauthorized', message: UNAUTHORIZED_MESSAGE, hookError: error }
92
+ : { kind: 'unauthorized', message: UNAUTHORIZED_MESSAGE };
93
+ }
94
+ const cookieValue = signSessionCookie(user, {
95
+ secret: auth.secret,
96
+ ttlMs: auth.ttlMs,
97
+ ...(now !== undefined ? { now } : {}),
98
+ });
99
+ return { kind: 'ok', cookieValue, redirectTo: sanitizeReturnTo(parsed.returnTo, basePath) };
100
+ }
101
+ /**
102
+ * Read + verify a session cookie value, returning the session or `null` when absent, tampered, or
103
+ * expired. Thin wrapper over {@link verifySessionCookie} so the provider never touches crypto.
104
+ */
105
+ export function readSession(auth, cookieValue, now) {
106
+ if (cookieValue === undefined || cookieValue === '')
107
+ return null;
108
+ return verifySessionCookie(cookieValue, {
109
+ secret: auth.secret,
110
+ ...(now !== undefined ? { now } : {}),
111
+ });
112
+ }
113
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/dashboard/auth.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import type { HttpContext } from '@adonisjs/core/http';
2
+ import { type DashboardAuthOptions, type ResolvedDashboardAuth } from './auth.js';
2
3
  /**
3
4
  * Authorization guard for the dashboard. Runs before every dashboard route
4
5
  * (API + HTML). Return `true` to allow the request, `false` to deny it (the
@@ -27,12 +28,28 @@ export interface DurableDashboardConfig {
27
28
  * token matching the `DURABLE_DASHBOARD_TOKEN` env var (deny if it is unset).
28
29
  */
29
30
  authorize?: AuthorizeHook;
31
+ /**
32
+ * Optional built-in login screen. When set, the provider mounts a
33
+ * server-rendered `GET <path>/login` page plus `POST <path>/login` /
34
+ * `GET <path>/logout`, and stamps a session guard on the dashboard: an
35
+ * unauthenticated page navigation is redirected (`302`) to the login page and
36
+ * an unauthenticated API request gets `401`. The signed session cookie is
37
+ * minted only by the host's {@link DashboardAuthOptions.login} hook.
38
+ *
39
+ * This is ADDITIVE and composes WITH {@link authorize} (both must pass) — it
40
+ * does not replace it. Omit it entirely to keep today's behavior byte-for-byte
41
+ * (no login/logout routes, no session guard). Missing `secret`/`login` fails
42
+ * closed at boot.
43
+ */
44
+ dashboardAuth?: DashboardAuthOptions;
30
45
  }
31
46
  /** A fully-resolved config — every field present (defaults applied). */
32
47
  export interface ResolvedDurableDashboardConfig {
33
48
  enabled: boolean;
34
49
  path: string;
35
50
  authorize: AuthorizeHook;
51
+ /** Resolved built-in login config, or `null` when `dashboardAuth` is unconfigured. */
52
+ dashboardAuth: ResolvedDashboardAuth | null;
36
53
  }
37
54
  /**
38
55
  * 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":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;;;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;CAC3B;AAED,wEAAwE;AACxE,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,aAAa,CAAC;CAC1B;AA4BD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAK1D;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,MAAM,GAAE,sBAA2B,GAAG,8BAA8B,CASjG;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,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,3 +1,5 @@
1
+ import { timingSafeEqual } from 'node:crypto';
2
+ import { resolveDashboardAuth, } from './auth.js';
1
3
  /**
2
4
  * Whether the process is running in production. Mirrors how AdonisJS reads the
3
5
  * environment without taking a hard dependency on its env service.
@@ -25,6 +27,19 @@ function readToken(ctx) {
25
27
  return qs;
26
28
  return undefined;
27
29
  }
30
+ /**
31
+ * Compare two secrets in constant time (guarding for equal byte-length first,
32
+ * since {@link timingSafeEqual} throws on a length mismatch). Returns `false`
33
+ * for any length difference, otherwise the timing-safe equality — so the token
34
+ * check leaks neither a match nor the token's length via response time.
35
+ */
36
+ function secretsMatch(a, b) {
37
+ const bufA = Buffer.from(a);
38
+ const bufB = Buffer.from(b);
39
+ if (bufA.length !== bufB.length)
40
+ return false;
41
+ return timingSafeEqual(bufA, bufB);
42
+ }
28
43
  /**
29
44
  * The default guard: open outside production; in production it requires a
30
45
  * bearer token equal to `DURABLE_DASHBOARD_TOKEN`. If that env var is unset in
@@ -37,7 +52,11 @@ export function defaultAuthorize(ctx) {
37
52
  const expected = process.env.DURABLE_DASHBOARD_TOKEN;
38
53
  if (!expected)
39
54
  return false;
40
- return readToken(ctx) === expected;
55
+ const provided = readToken(ctx);
56
+ if (provided === undefined)
57
+ return false;
58
+ // Constant-time compare to remove the timing side-channel from the token check.
59
+ return secretsMatch(provided, expected);
41
60
  }
42
61
  /** Apply defaults to a partial config, producing a fully-resolved one. */
43
62
  export function resolveConfig(config = {}) {
@@ -48,6 +67,9 @@ export function resolveConfig(config = {}) {
48
67
  enabled: config.enabled ?? true,
49
68
  path: trimmed === '/' ? '' : trimmed,
50
69
  authorize: config.authorize ?? defaultAuthorize,
70
+ // Validate + resolve now so a misconfigured secret/login fails closed at boot,
71
+ // not on the first login attempt. `null` when `dashboardAuth` is omitted.
72
+ dashboardAuth: resolveDashboardAuth(config.dashboardAuth),
51
73
  };
52
74
  }
53
75
  /** 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":"AAwCA;;;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,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,OAAO,SAAS,CAAC,GAAG,CAAC,KAAK,QAAQ,CAAC;AACrC,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,aAAa,CAAC,SAAiC,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;KAChD,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,YAAY,CAAC,SAAiC,EAAE;IAC9D,OAAO,MAAM,CAAC;AAChB,CAAC"}
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,SAAiC,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,SAAiC,EAAE;IAC9D,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -1,8 +1,13 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export declare const VERSION = "0.6.0";
2
+ export declare const VERSION = "0.7.0";
3
3
  export { defineConfig, defaultAuthorize, resolveConfig } from './define_config.js';
4
4
  export type { AuthorizeHook, DurableDashboardConfig, ResolvedDurableDashboardConfig, } from './define_config.js';
5
5
  export { listRuns, getRun, retryRun, cancelRun, health, } from './handlers.js';
6
6
  export type { ApiRequest, ApiResponse, Deps } from './handlers.js';
7
7
  export { renderDashboard } from './html.js';
8
+ export { resolveDashboardAuth, performLogin, readSession, sanitizeReturnTo, SESSION_COOKIE_NAME, } from './auth.js';
9
+ export type { DashboardAuthOptions, ResolvedDashboardAuth, LoginHook, LoginOutcome, } from './auth.js';
10
+ export { signSessionCookie, verifySessionCookie, } from './session_cookie.js';
11
+ export type { DashboardSession, DashboardSessionUser, SignOptions, VerifyOptions, } from './session_cookie.js';
12
+ export { renderLoginPage } from './login_page.js';
8
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnF,YAAY,EACV,aAAa,EACb,sBAAsB,EACtB,8BAA8B,GAC/B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,SAAS,EACT,MAAM,GACP,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnF,YAAY,EACV,aAAa,EACb,sBAAsB,EACtB,8BAA8B,GAC/B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,SAAS,EACT,MAAM,GACP,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAG5C,OAAO,EACL,oBAAoB,EACpB,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,oBAAoB,EACpB,qBAAqB,EACrB,SAAS,EACT,YAAY,GACb,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,gBAAgB,EAChB,oBAAoB,EACpB,WAAW,EACX,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
@@ -1,6 +1,10 @@
1
1
  /** Keep in sync with this package's `version` in package.json. */
2
- export const VERSION = '0.6.0';
2
+ export const VERSION = '0.7.0';
3
3
  export { defineConfig, defaultAuthorize, resolveConfig } from './define_config.js';
4
4
  export { listRuns, getRun, retryRun, cancelRun, health, } from './handlers.js';
5
5
  export { renderDashboard } from './html.js';
6
+ // Built-in `dashboardAuth` login screen (optional; opt-in via `config/durable_dashboard.ts`).
7
+ export { resolveDashboardAuth, performLogin, readSession, sanitizeReturnTo, SESSION_COOKIE_NAME, } from './auth.js';
8
+ export { signSessionCookie, verifySessionCookie, } from './session_cookie.js';
9
+ export { renderLoginPage } from './login_page.js';
6
10
  //# 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,OAAO,CAAC;AAE/B,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAMnF,OAAO,EACL,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,SAAS,EACT,MAAM,GACP,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAMnF,OAAO,EACL,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,SAAS,EACT,MAAM,GACP,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAE5C,8FAA8F;AAC9F,OAAO,EACL,oBAAoB,EACpB,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,WAAW,CAAC;AAOnB,OAAO,EACL,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAO7B,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The built-in `dashboardAuth` login page (`GET <basePath>/login`). Deliberately a small,
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`.
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.
11
+ *
12
+ * 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>`.
16
+ *
17
+ * The password input has NO `required` attribute and the value is forwarded AS-IS (empty string
18
+ * when blank): the host's `login` hook — not this page — decides whether a password matters, so an
19
+ * email-only host (gate on username, ignore password) works with the same page as a host with real
20
+ * passwords.
21
+ */
22
+ export declare function renderLoginPage(basePath: string): string;
23
+ //# sourceMappingURL=login_page.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,170 @@
1
+ /**
2
+ * The built-in `dashboardAuth` login page (`GET <basePath>/login`). Deliberately a small,
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`.
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.
11
+ *
12
+ * 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>`.
16
+ *
17
+ * The password input has NO `required` attribute and the value is forwarded AS-IS (empty string
18
+ * when blank): the host's `login` hook — not this page — decides whether a password matters, so an
19
+ * email-only host (gate on username, ignore password) works with the same page as a host with real
20
+ * passwords.
21
+ */
22
+ export function renderLoginPage(basePath) {
23
+ const loginAction = `${basePath}/login`;
24
+ const defaultReturnTo = basePath === '' ? '/' : basePath;
25
+ return `<!doctype html>
26
+ <html lang="en">
27
+ <head>
28
+ <meta charset="utf-8" />
29
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
30
+ <meta name="robots" content="noindex, nofollow" />
31
+ <title>Sign in — Durable</title>
32
+ <style>
33
+ :root { color-scheme: dark; }
34
+ * { box-sizing: border-box; }
35
+ body {
36
+ margin: 0;
37
+ min-height: 100vh;
38
+ display: flex;
39
+ align-items: center;
40
+ justify-content: center;
41
+ background: #09090b;
42
+ color: #e4e4e7;
43
+ font: 14px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
44
+ padding: 16px;
45
+ }
46
+ .card {
47
+ width: 100%;
48
+ max-width: 384px;
49
+ border: 1px solid #27272a;
50
+ background: #18181b;
51
+ border-radius: 8px;
52
+ padding: 32px;
53
+ box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.5);
54
+ }
55
+ .brand {
56
+ margin: 0 0 24px;
57
+ text-align: center;
58
+ font-size: 18px;
59
+ font-weight: 600;
60
+ color: #34d399;
61
+ }
62
+ form { display: flex; flex-direction: column; gap: 16px; }
63
+ label { display: flex; flex-direction: column; gap: 6px; }
64
+ .field-label {
65
+ font-size: 11px;
66
+ text-transform: uppercase;
67
+ letter-spacing: 0.05em;
68
+ color: #71717a;
69
+ }
70
+ input {
71
+ border-radius: 4px;
72
+ border: 1px solid #3f3f46;
73
+ background: #09090b;
74
+ color: #f4f4f5;
75
+ padding: 8px 12px;
76
+ font: inherit;
77
+ outline: none;
78
+ }
79
+ input:focus { border-color: rgb(52 211 153 / 0.6); }
80
+ #error {
81
+ display: none;
82
+ margin: 0;
83
+ font-size: 12px;
84
+ color: #fb7185;
85
+ }
86
+ button {
87
+ margin-top: 8px;
88
+ border-radius: 4px;
89
+ border: 1px solid rgb(52 211 153 / 0.4);
90
+ background: rgb(52 211 153 / 0.1);
91
+ color: #6ee7b7;
92
+ padding: 8px 12px;
93
+ font: inherit;
94
+ font-size: 12px;
95
+ font-weight: 500;
96
+ text-transform: uppercase;
97
+ letter-spacing: 0.05em;
98
+ cursor: pointer;
99
+ }
100
+ button:hover:not(:disabled) { background: rgb(52 211 153 / 0.2); }
101
+ button:disabled { opacity: 0.6; cursor: default; }
102
+ </style>
103
+ </head>
104
+ <body>
105
+ <div class="card">
106
+ <p class="brand">Durable</p>
107
+ <form id="login-form" autocomplete="on">
108
+ <label>
109
+ <span class="field-label">Username</span>
110
+ <input id="username" name="username" type="text" autocomplete="username" required autofocus />
111
+ </label>
112
+ <label>
113
+ <span class="field-label">Password</span>
114
+ <input id="password" name="password" type="password" autocomplete="current-password" />
115
+ </label>
116
+ <p id="error" role="alert">Invalid username or password.</p>
117
+ <button id="submit" type="submit">Sign in</button>
118
+ </form>
119
+ </div>
120
+ <script>
121
+ (function () {
122
+ var LOGIN_ACTION = ${JSON.stringify(loginAction)};
123
+ var DEFAULT_RETURN_TO = ${JSON.stringify(defaultReturnTo)};
124
+ var params = new URLSearchParams(window.location.search);
125
+ var errorBox = document.getElementById('error');
126
+ if (params.get('error')) errorBox.style.display = 'block';
127
+
128
+ function sameOriginReturnTo(value) {
129
+ if (typeof value !== 'string' || value === '') return DEFAULT_RETURN_TO;
130
+ if (value.charAt(0) !== '/' || value.charAt(1) === '/' || value.indexOf('://') !== -1) {
131
+ return DEFAULT_RETURN_TO;
132
+ }
133
+ return value;
134
+ }
135
+
136
+ var form = document.getElementById('login-form');
137
+ var submitButton = document.getElementById('submit');
138
+ form.addEventListener('submit', function (event) {
139
+ event.preventDefault();
140
+ submitButton.disabled = true;
141
+ errorBox.style.display = 'none';
142
+ var returnTo = sameOriginReturnTo(params.get('returnTo'));
143
+ fetch(LOGIN_ACTION, {
144
+ method: 'POST',
145
+ headers: { 'content-type': 'application/json' },
146
+ credentials: 'same-origin',
147
+ body: JSON.stringify({
148
+ username: document.getElementById('username').value,
149
+ password: document.getElementById('password').value,
150
+ returnTo: returnTo,
151
+ }),
152
+ })
153
+ .then(function (response) {
154
+ if (!response.ok) throw new Error('unauthorized');
155
+ return response.json();
156
+ })
157
+ .then(function (data) {
158
+ window.location.href = data.redirectTo || DEFAULT_RETURN_TO;
159
+ })
160
+ .catch(function () {
161
+ errorBox.style.display = 'block';
162
+ submitButton.disabled = false;
163
+ });
164
+ });
165
+ })();
166
+ </script>
167
+ </body>
168
+ </html>`;
169
+ }
170
+ //# sourceMappingURL=login_page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login_page.js","sourceRoot":"","sources":["../../../src/dashboard/login_page.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,MAAM,WAAW,GAAG,GAAG,QAAQ,QAAQ,CAAC;IACxC,MAAM,eAAe,GAAG,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;IACzD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAiGc,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;4BACtB,IAAI,CAAC,SAAS,CAAC,eAAe,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA6CnD,CAAC;AACT,CAAC"}
@@ -0,0 +1,42 @@
1
+ /** The validated dashboard session attached to a request once its cookie verifies. */
2
+ export interface DashboardSession {
3
+ /** Stable user id (the session user's `id`). */
4
+ sub: string;
5
+ /** Optional display name. */
6
+ name?: string;
7
+ /** Free-form role strings; the dashboard does not interpret them. */
8
+ roles: string[];
9
+ /** Issued-at, epoch milliseconds. */
10
+ iat: number;
11
+ /** Expiry, epoch milliseconds. */
12
+ exp: number;
13
+ }
14
+ /** The session user the host `login` hook returns to mint a cookie. */
15
+ export interface DashboardSessionUser {
16
+ id: string;
17
+ name?: string;
18
+ roles?: string[];
19
+ }
20
+ export interface SignOptions {
21
+ secret: string;
22
+ ttlMs: number;
23
+ /** Injectable clock (epoch ms) for deterministic tests. Defaults to `Date.now()`. */
24
+ now?: number;
25
+ }
26
+ export interface VerifyOptions {
27
+ secret: string;
28
+ /** Injectable clock (epoch ms) for deterministic tests. Defaults to `Date.now()`. */
29
+ now?: number;
30
+ }
31
+ /**
32
+ * Sign a session into the cookie value `base64url(payload).base64url(hmac)`. Stateless
33
+ * HMAC-SHA256, no store, `node:crypto` only — no JWT dependency.
34
+ */
35
+ export declare function signSessionCookie(user: DashboardSessionUser, options: SignOptions): string;
36
+ /**
37
+ * Verify a cookie value and return the session, or `null` for anything that's tampered,
38
+ * malformed, or expired (past `exp` + a 30s grace). Constant-time signature comparison. NEVER
39
+ * throws — any parse failure yields `null`.
40
+ */
41
+ export declare function verifySessionCookie(value: string, options: VerifyOptions): DashboardSession | null;
42
+ //# sourceMappingURL=session_cookie.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session_cookie.d.ts","sourceRoot":"","sources":["../../../src/dashboard/session_cookie.ts"],"names":[],"mappings":"AAcA,sFAAsF;AACtF,MAAM,WAAW,gBAAgB;IAC/B,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,6BAA6B;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,qCAAqC;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,uEAAuE;AACvE,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,qFAAqF;IACrF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAcD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,WAAW,GAAG,MAAM,CAY1F;AAeD;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,aAAa,GACrB,gBAAgB,GAAG,IAAI,CA4BzB"}