@dudousxd/nestjs-durable-dashboard 0.36.0 → 0.37.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.
@@ -552,7 +552,7 @@ DurableApiController = _ts_decorate2([
552
552
  ], DurableApiController);
553
553
 
554
554
  // src/server/durable-auth.controller.ts
555
- var import_common5 = require("@nestjs/common");
555
+ var import_common6 = require("@nestjs/common");
556
556
 
557
557
  // src/server/auth/dashboard-auth-config.ts
558
558
  var DASHBOARD_AUTH = Symbol("DASHBOARD_AUTH");
@@ -577,28 +577,51 @@ function resolveDashboardAuth(options) {
577
577
  if (typeof options.secret !== "string" || options.secret === "") {
578
578
  throw new Error("DurableDashboardModule: dashboardAuth.secret is required and must be a non-empty string (HMAC-SHA256 signing key, 32+ bytes recommended). Failing closed.");
579
579
  }
580
- if (typeof options.login !== "function") {
581
- throw new Error("DurableDashboardModule: dashboardAuth.login is required (a login hook).");
580
+ const modes = [];
581
+ if (options.session !== void 0) {
582
+ if (typeof options.session !== "function") {
583
+ throw new Error("DurableDashboardModule: dashboardAuth.session must be a function.");
584
+ }
585
+ modes.push("session");
586
+ }
587
+ if (options.login !== void 0) {
588
+ if (typeof options.login !== "function") {
589
+ throw new Error("DurableDashboardModule: dashboardAuth.login must be a function.");
590
+ }
591
+ modes.push("login");
592
+ }
593
+ if (options.revalidate !== void 0 && typeof options.revalidate !== "function") {
594
+ throw new Error("DurableDashboardModule: dashboardAuth.revalidate must be a function.");
595
+ }
596
+ if (modes.length === 0) {
597
+ throw new Error("DurableDashboardModule: dashboardAuth needs at least one of `session` or `login` (otherwise the cookie can never be minted). Failing closed.");
582
598
  }
583
599
  return {
584
600
  secret: options.secret,
585
601
  ttlMs: durationToMs(options.ttl ?? DEFAULT_TTL),
586
- login: options.login
602
+ modes,
603
+ ...options.session !== void 0 ? {
604
+ session: options.session
605
+ } : {},
606
+ ...options.login !== void 0 ? {
607
+ login: options.login
608
+ } : {},
609
+ ...options.revalidate !== void 0 ? {
610
+ revalidate: options.revalidate
611
+ } : {}
587
612
  };
588
613
  }
589
614
  __name(resolveDashboardAuth, "resolveDashboardAuth");
590
615
 
591
616
  // src/server/auth/login-page.ts
592
- function renderLoginPage(basePath) {
593
- const loginAction = `${basePath}/login`;
594
- const defaultReturnTo = basePath;
617
+ function page(content, title = "Durable") {
595
618
  return `<!doctype html>
596
619
  <html lang="en">
597
620
  <head>
598
621
  <meta charset="utf-8" />
599
622
  <meta name="viewport" content="width=device-width, initial-scale=1" />
600
623
  <meta name="robots" content="noindex, nofollow" />
601
- <title>Sign in \u2014 Durable</title>
624
+ <title>${title}</title>
602
625
  <style>
603
626
  :root { color-scheme: dark; }
604
627
  * { box-sizing: border-box; }
@@ -629,6 +652,13 @@ function renderLoginPage(basePath) {
629
652
  font-weight: 600;
630
653
  color: #34d399;
631
654
  }
655
+ h1 {
656
+ margin: 0 0 12px;
657
+ font-size: 16px;
658
+ font-weight: 600;
659
+ color: #f4f4f5;
660
+ }
661
+ p { margin: 0 0 16px; color: #a1a1aa; }
632
662
  form { display: flex; flex-direction: column; gap: 16px; }
633
663
  label { display: flex; flex-direction: column; gap: 6px; }
634
664
  .field-label {
@@ -653,7 +683,7 @@ function renderLoginPage(basePath) {
653
683
  font-size: 12px;
654
684
  color: #fb7185;
655
685
  }
656
- button {
686
+ button, .button {
657
687
  margin-top: 8px;
658
688
  border-radius: 4px;
659
689
  border: 1px solid rgb(52 211 153 / 0.4);
@@ -666,14 +696,27 @@ function renderLoginPage(basePath) {
666
696
  text-transform: uppercase;
667
697
  letter-spacing: 0.05em;
668
698
  cursor: pointer;
699
+ display: inline-block;
700
+ text-align: center;
701
+ text-decoration: none;
702
+ box-sizing: border-box;
669
703
  }
670
- button:hover:not(:disabled) { background: rgb(52 211 153 / 0.2); }
704
+ button:hover:not(:disabled), .button:hover { background: rgb(52 211 153 / 0.2); }
671
705
  button:disabled { opacity: 0.6; cursor: default; }
672
706
  </style>
673
707
  </head>
674
708
  <body>
675
709
  <div class="card">
676
- <p class="brand">Durable</p>
710
+ ${content}
711
+ </div>
712
+ </body>
713
+ </html>`;
714
+ }
715
+ __name(page, "page");
716
+ function renderLoginPage(basePath) {
717
+ const loginAction = `${basePath}/login`;
718
+ const defaultReturnTo = basePath;
719
+ return page(`<p class="brand">Durable</p>
677
720
  <form id="login-form" autocomplete="on">
678
721
  <label>
679
722
  <span class="field-label">Username</span>
@@ -686,7 +729,6 @@ function renderLoginPage(basePath) {
686
729
  <p id="error" role="alert">Invalid username or password.</p>
687
730
  <button id="submit" type="submit">Sign in</button>
688
731
  </form>
689
- </div>
690
732
  <script>
691
733
  (function () {
692
734
  var LOGIN_ACTION = ${JSON.stringify(loginAction)};
@@ -733,11 +775,15 @@ function renderLoginPage(basePath) {
733
775
  });
734
776
  });
735
777
  })();
736
- </script>
737
- </body>
738
- </html>`;
778
+ </script>`, "Sign in \u2014 Durable");
739
779
  }
740
780
  __name(renderLoginPage, "renderLoginPage");
781
+ function renderSessionRequiredPage(basePath) {
782
+ return page(`<h1>Open this console from your application</h1>
783
+ <p>Your session is minted by the host app. Use its console launcher to sign in, then reload.</p>
784
+ <a class="button" href="${basePath}">Retry</a>`);
785
+ }
786
+ __name(renderSessionRequiredPage, "renderSessionRequiredPage");
741
787
 
742
788
  // src/server/auth/request.ts
743
789
  function isRecord(value) {
@@ -838,6 +884,15 @@ function redirectRaw(response, location, status = 302) {
838
884
  raw.end();
839
885
  }
840
886
  __name(redirectRaw, "redirectRaw");
887
+ function writeHtmlRaw(response, html, status = 200) {
888
+ const raw = resolveRedirectResponse(response);
889
+ if (!raw) return;
890
+ raw.statusCode = status;
891
+ raw.setHeader("content-type", "text/html; charset=utf-8");
892
+ raw.setHeader("cache-control", "no-store, must-revalidate");
893
+ raw.end(html);
894
+ }
895
+ __name(writeHtmlRaw, "writeHtmlRaw");
841
896
 
842
897
  // src/server/auth/cookie-header.ts
843
898
  function parseCookieHeader(header) {
@@ -993,21 +1048,37 @@ function readSessionFromRequest(auth, request) {
993
1048
  });
994
1049
  }
995
1050
  __name(readSessionFromRequest, "readSessionFromRequest");
996
- function maybeRenewSession(auth, session, request, response) {
997
- const now = Date.now();
998
- if (now - session.iat <= auth.ttlMs / 2) return;
999
- issueSessionCookie({
1051
+ async function maybeRenewSession(auth, session, request, response, now = Date.now()) {
1052
+ if (now - session.iat <= auth.ttlMs / 2) return true;
1053
+ const user = {
1000
1054
  id: session.sub,
1001
1055
  ...session.name !== void 0 ? {
1002
1056
  name: session.name
1003
1057
  } : {},
1004
1058
  roles: session.roles
1005
- }, {
1059
+ };
1060
+ if (auth.revalidate) {
1061
+ let allowed;
1062
+ try {
1063
+ allowed = await auth.revalidate(user);
1064
+ } catch {
1065
+ allowed = false;
1066
+ }
1067
+ if (!allowed) {
1068
+ clearSessionCookie({
1069
+ request,
1070
+ response
1071
+ });
1072
+ return false;
1073
+ }
1074
+ }
1075
+ issueSessionCookie(user, {
1006
1076
  auth,
1007
1077
  request,
1008
1078
  response,
1009
1079
  now
1010
1080
  });
1081
+ return true;
1011
1082
  }
1012
1083
  __name(maybeRenewSession, "maybeRenewSession");
1013
1084
 
@@ -1015,7 +1086,7 @@ __name(maybeRenewSession, "maybeRenewSession");
1015
1086
  var import_node_fs = require("fs");
1016
1087
  var import_node_path = require("path");
1017
1088
  var import_node_url = require("url");
1018
- var import_common4 = require("@nestjs/common");
1089
+ var import_common5 = require("@nestjs/common");
1019
1090
 
1020
1091
  // src/server/auth/login-redirect.exception.ts
1021
1092
  var import_common3 = require("@nestjs/common");
@@ -1050,7 +1121,8 @@ DashboardLoginRedirectFilter = _ts_decorate3([
1050
1121
  (0, import_common3.Catch)(DashboardLoginRedirectException)
1051
1122
  ], DashboardLoginRedirectFilter);
1052
1123
 
1053
- // src/server/durable-ui.controller.ts
1124
+ // src/server/auth/session-required.exception.ts
1125
+ var import_common4 = require("@nestjs/common");
1054
1126
  function _ts_decorate4(decorators, target, key, desc) {
1055
1127
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1056
1128
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -1058,6 +1130,38 @@ function _ts_decorate4(decorators, target, key, desc) {
1058
1130
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1059
1131
  }
1060
1132
  __name(_ts_decorate4, "_ts_decorate");
1133
+ var DashboardSessionRequiredException = class extends import_common4.HttpException {
1134
+ static {
1135
+ __name(this, "DashboardSessionRequiredException");
1136
+ }
1137
+ basePath;
1138
+ constructor(basePath) {
1139
+ super({
1140
+ basePath
1141
+ }, import_common4.HttpStatus.UNAUTHORIZED), this.basePath = basePath;
1142
+ }
1143
+ };
1144
+ var DashboardSessionRequiredFilter = class {
1145
+ static {
1146
+ __name(this, "DashboardSessionRequiredFilter");
1147
+ }
1148
+ catch(exception, host) {
1149
+ const response = host.switchToHttp().getResponse();
1150
+ writeHtmlRaw(response, renderSessionRequiredPage(exception.basePath), import_common4.HttpStatus.UNAUTHORIZED);
1151
+ }
1152
+ };
1153
+ DashboardSessionRequiredFilter = _ts_decorate4([
1154
+ (0, import_common4.Catch)(DashboardSessionRequiredException)
1155
+ ], DashboardSessionRequiredFilter);
1156
+
1157
+ // src/server/durable-ui.controller.ts
1158
+ function _ts_decorate5(decorators, target, key, desc) {
1159
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1160
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1161
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1162
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1163
+ }
1164
+ __name(_ts_decorate5, "_ts_decorate");
1061
1165
  function _ts_metadata3(k, v) {
1062
1166
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1063
1167
  }
@@ -1100,7 +1204,7 @@ var DurableUiController = class {
1100
1204
  index() {
1101
1205
  const indexPath = (0, import_node_path.join)(this.dir, "index.html");
1102
1206
  if (!(0, import_node_fs.existsSync)(indexPath)) {
1103
- throw new import_common4.NotFoundException("Dashboard is not built. Run the package build.");
1207
+ throw new import_common5.NotFoundException("Dashboard is not built. Run the package build.");
1104
1208
  }
1105
1209
  const html = (0, import_node_fs.readFileSync)(indexPath, "utf8").replaceAll(`="${BUILD_BASE}/`, `="${this.basePath}/`);
1106
1210
  const inject = `<script>window.__DURABLE_BASE__='${this.basePath}';window.__DURABLE_API__='${this.apiBasePath}';</script>`;
@@ -1108,41 +1212,41 @@ var DurableUiController = class {
1108
1212
  }
1109
1213
  asset(file) {
1110
1214
  const safe = (0, import_node_path.basename)(file);
1111
- if (safe !== file) throw new import_common4.NotFoundException();
1215
+ if (safe !== file) throw new import_common5.NotFoundException();
1112
1216
  const root = (0, import_node_path.resolve)(this.dir, "assets");
1113
1217
  const assetPath = (0, import_node_path.resolve)(root, safe);
1114
1218
  if (!assetPath.startsWith(root + import_node_path.sep) || !(0, import_node_fs.existsSync)(assetPath)) {
1115
- throw new import_common4.NotFoundException();
1219
+ throw new import_common5.NotFoundException();
1116
1220
  }
1117
1221
  const type = CONTENT_TYPES[(0, import_node_path.extname)(safe)] ?? "application/octet-stream";
1118
- return new import_common4.StreamableFile((0, import_node_fs.readFileSync)(assetPath), {
1222
+ return new import_common5.StreamableFile((0, import_node_fs.readFileSync)(assetPath), {
1119
1223
  type
1120
1224
  });
1121
1225
  }
1122
1226
  };
1123
- _ts_decorate4([
1124
- (0, import_common4.Get)(),
1125
- (0, import_common4.Header)("Content-Type", "text/html; charset=utf-8"),
1126
- (0, import_common4.Header)("Cache-Control", "no-store, must-revalidate"),
1227
+ _ts_decorate5([
1228
+ (0, import_common5.Get)(),
1229
+ (0, import_common5.Header)("Content-Type", "text/html; charset=utf-8"),
1230
+ (0, import_common5.Header)("Cache-Control", "no-store, must-revalidate"),
1127
1231
  _ts_metadata3("design:type", Function),
1128
1232
  _ts_metadata3("design:paramtypes", []),
1129
1233
  _ts_metadata3("design:returntype", String)
1130
1234
  ], DurableUiController.prototype, "index", null);
1131
- _ts_decorate4([
1132
- (0, import_common4.Get)("assets/:file"),
1133
- (0, import_common4.Header)("Cache-Control", "public, max-age=31536000, immutable"),
1134
- _ts_param3(0, (0, import_common4.Param)("file")),
1235
+ _ts_decorate5([
1236
+ (0, import_common5.Get)("assets/:file"),
1237
+ (0, import_common5.Header)("Cache-Control", "public, max-age=31536000, immutable"),
1238
+ _ts_param3(0, (0, import_common5.Param)("file")),
1135
1239
  _ts_metadata3("design:type", Function),
1136
1240
  _ts_metadata3("design:paramtypes", [
1137
1241
  String
1138
1242
  ]),
1139
- _ts_metadata3("design:returntype", typeof import_common4.StreamableFile === "undefined" ? Object : import_common4.StreamableFile)
1243
+ _ts_metadata3("design:returntype", typeof import_common5.StreamableFile === "undefined" ? Object : import_common5.StreamableFile)
1140
1244
  ], DurableUiController.prototype, "asset", null);
1141
- DurableUiController = _ts_decorate4([
1142
- (0, import_common4.UseFilters)(DashboardLoginRedirectFilter),
1143
- (0, import_common4.Controller)(),
1144
- _ts_param3(0, (0, import_common4.Inject)(DASHBOARD_BASE_PATH)),
1145
- _ts_param3(1, (0, import_common4.Inject)(DASHBOARD_API_PATH)),
1245
+ DurableUiController = _ts_decorate5([
1246
+ (0, import_common5.UseFilters)(DashboardLoginRedirectFilter, DashboardSessionRequiredFilter),
1247
+ (0, import_common5.Controller)(),
1248
+ _ts_param3(0, (0, import_common5.Inject)(DASHBOARD_BASE_PATH)),
1249
+ _ts_param3(1, (0, import_common5.Inject)(DASHBOARD_API_PATH)),
1146
1250
  _ts_metadata3("design:type", Function),
1147
1251
  _ts_metadata3("design:paramtypes", [
1148
1252
  String,
@@ -1151,13 +1255,13 @@ DurableUiController = _ts_decorate4([
1151
1255
  ], DurableUiController);
1152
1256
 
1153
1257
  // src/server/durable-auth.controller.ts
1154
- function _ts_decorate5(decorators, target, key, desc) {
1258
+ function _ts_decorate6(decorators, target, key, desc) {
1155
1259
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1156
1260
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1157
1261
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1158
1262
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1159
1263
  }
1160
- __name(_ts_decorate5, "_ts_decorate");
1264
+ __name(_ts_decorate6, "_ts_decorate");
1161
1265
  function _ts_metadata4(k, v) {
1162
1266
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1163
1267
  }
@@ -1178,9 +1282,9 @@ var DurableAuthController = class _DurableAuthController {
1178
1282
  }
1179
1283
  auth;
1180
1284
  basePath;
1181
- logger = new import_common5.Logger(_DurableAuthController.name);
1182
- /** Warn once so a throwing `login` hook doesn't spam logs on every failed attempt. */
1183
- warnedOnHookThrow = false;
1285
+ logger = new import_common6.Logger(_DurableAuthController.name);
1286
+ /** Warn once per hook kind so a throwing hook doesn't spam logs on every failed attempt. */
1287
+ warnedHooks = /* @__PURE__ */ new Set();
1184
1288
  constructor(auth, basePath) {
1185
1289
  this.auth = auth;
1186
1290
  this.basePath = basePath;
@@ -1189,7 +1293,8 @@ var DurableAuthController = class _DurableAuthController {
1189
1293
  // `login-page.ts`), so this response never varies per request and needs no templating beyond
1190
1294
  // the developer-controlled `basePath`.
1191
1295
  loginPage() {
1192
- if (!this.auth) throw new import_common5.NotFoundException();
1296
+ const auth = this.requireAuth();
1297
+ if (!auth.login) throw new import_common6.NotFoundException();
1193
1298
  return renderLoginPage(this.basePath);
1194
1299
  }
1195
1300
  // Called by the login page's own fetch (JSON, not a classic form POST — see `login-page.ts` for
@@ -1198,11 +1303,13 @@ var DurableAuthController = class _DurableAuthController {
1198
1303
  // (unknown user, wrong password, throwing hook) — no user-enumeration.
1199
1304
  async login(body, request, response) {
1200
1305
  const auth = this.requireAuth();
1306
+ if (!auth.login) throw new import_common6.NotFoundException();
1201
1307
  if (!isString(body?.username) || !isString(body?.password)) {
1202
- throw new import_common5.BadRequestException("Body must include string `username` and `password`.");
1308
+ throw new import_common6.BadRequestException("Body must include string `username` and `password`.");
1203
1309
  }
1204
- const user = await this.runLoginHook(auth, body.username, body.password);
1205
- if (!user) throw new import_common5.UnauthorizedException({
1310
+ const { username, password } = body;
1311
+ const user = await this.runHook("login", () => auth.login?.(username, password) ?? null);
1312
+ if (!user) throw new import_common6.UnauthorizedException({
1206
1313
  message: "Invalid username or password."
1207
1314
  });
1208
1315
  issueSessionCookie(user, {
@@ -1214,6 +1321,20 @@ var DurableAuthController = class _DurableAuthController {
1214
1321
  redirectTo: sanitizeReturnTo(body.returnTo, this.basePath)
1215
1322
  };
1216
1323
  }
1324
+ // Mode A: the host frontend (carrying its own auth) POSTs here; the host hook validates the raw
1325
+ // request and returns the session user, or `null` to deny. This is the SSO path — the host's own
1326
+ // guard decides, and no credential ever reaches this library.
1327
+ async session(request, response) {
1328
+ const auth = this.requireAuth();
1329
+ if (!auth.session) throw new import_common6.NotFoundException();
1330
+ const user = await this.runHook("session", () => auth.session?.(request) ?? null);
1331
+ if (!user) throw new import_common6.UnauthorizedException();
1332
+ issueSessionCookie(user, {
1333
+ auth,
1334
+ request,
1335
+ response
1336
+ });
1337
+ }
1217
1338
  // Plain `GET` (not `POST`): logging out only ever destroys the CALLER's own session, so it's
1218
1339
  // safe/idempotent from the caller's perspective — a simple `<a href>` (no JS, no CSRF token)
1219
1340
  // works. Non-passthrough `@Res()`: this handler owns the response outright (clears the cookie,
@@ -1223,43 +1344,43 @@ var DurableAuthController = class _DurableAuthController {
1223
1344
  request,
1224
1345
  response
1225
1346
  });
1226
- redirectRaw(response, `${this.basePath}/login`);
1347
+ redirectRaw(response, this.auth?.login ? `${this.basePath}/login` : this.basePath);
1227
1348
  }
1228
1349
  requireAuth() {
1229
- if (!this.auth) throw new import_common5.NotFoundException();
1350
+ if (!this.auth) throw new import_common6.NotFoundException();
1230
1351
  return this.auth;
1231
1352
  }
1232
1353
  /**
1233
- * Run the host's `login` hook defensively: a throw is treated as a denial (uniform failure) and
1234
- * warn-logged once, so a buggy hook never 500s the endpoint into a stack-trace leak nor floods
1235
- * the logs on repeated bad attempts.
1354
+ * Run a host hook (`login` or `session`) defensively: a throw is treated as a denial (uniform
1355
+ * failure) and warn-logged once PER KIND, so a buggy hook never 500s the endpoint into a
1356
+ * stack-trace leak nor floods the logs on repeated bad attempts.
1236
1357
  */
1237
- async runLoginHook(auth, username, password) {
1358
+ async runHook(kind, run) {
1238
1359
  try {
1239
- return await auth.login(username, password) ?? null;
1360
+ return await run() ?? null;
1240
1361
  } catch (error) {
1241
- if (!this.warnedOnHookThrow) {
1242
- this.warnedOnHookThrow = true;
1243
- this.logger.warn(`dashboardAuth login hook threw; treating as denial. ${error instanceof Error ? error.message : String(error)}`);
1362
+ if (!this.warnedHooks.has(kind)) {
1363
+ this.warnedHooks.add(kind);
1364
+ this.logger.warn(`dashboardAuth ${kind} hook threw; treating as denial. ${error instanceof Error ? error.message : String(error)}`);
1244
1365
  }
1245
1366
  return null;
1246
1367
  }
1247
1368
  }
1248
1369
  };
1249
- _ts_decorate5([
1250
- (0, import_common5.Get)("login"),
1251
- (0, import_common5.Header)("Content-Type", "text/html; charset=utf-8"),
1252
- (0, import_common5.Header)("Cache-Control", "no-store, must-revalidate"),
1370
+ _ts_decorate6([
1371
+ (0, import_common6.Get)("login"),
1372
+ (0, import_common6.Header)("Content-Type", "text/html; charset=utf-8"),
1373
+ (0, import_common6.Header)("Cache-Control", "no-store, must-revalidate"),
1253
1374
  _ts_metadata4("design:type", Function),
1254
1375
  _ts_metadata4("design:paramtypes", []),
1255
1376
  _ts_metadata4("design:returntype", String)
1256
1377
  ], DurableAuthController.prototype, "loginPage", null);
1257
- _ts_decorate5([
1258
- (0, import_common5.Post)("login"),
1259
- (0, import_common5.HttpCode)(200),
1260
- _ts_param4(0, (0, import_common5.Body)()),
1261
- _ts_param4(1, (0, import_common5.Req)()),
1262
- _ts_param4(2, (0, import_common5.Res)({
1378
+ _ts_decorate6([
1379
+ (0, import_common6.Post)("login"),
1380
+ (0, import_common6.HttpCode)(200),
1381
+ _ts_param4(0, (0, import_common6.Body)()),
1382
+ _ts_param4(1, (0, import_common6.Req)()),
1383
+ _ts_param4(2, (0, import_common6.Res)({
1263
1384
  passthrough: true
1264
1385
  })),
1265
1386
  _ts_metadata4("design:type", Function),
@@ -1270,10 +1391,24 @@ _ts_decorate5([
1270
1391
  ]),
1271
1392
  _ts_metadata4("design:returntype", Promise)
1272
1393
  ], DurableAuthController.prototype, "login", null);
1273
- _ts_decorate5([
1274
- (0, import_common5.Get)("logout"),
1275
- _ts_param4(0, (0, import_common5.Req)()),
1276
- _ts_param4(1, (0, import_common5.Res)()),
1394
+ _ts_decorate6([
1395
+ (0, import_common6.Post)("session"),
1396
+ (0, import_common6.HttpCode)(204),
1397
+ _ts_param4(0, (0, import_common6.Req)()),
1398
+ _ts_param4(1, (0, import_common6.Res)({
1399
+ passthrough: true
1400
+ })),
1401
+ _ts_metadata4("design:type", Function),
1402
+ _ts_metadata4("design:paramtypes", [
1403
+ Object,
1404
+ Object
1405
+ ]),
1406
+ _ts_metadata4("design:returntype", Promise)
1407
+ ], DurableAuthController.prototype, "session", null);
1408
+ _ts_decorate6([
1409
+ (0, import_common6.Get)("logout"),
1410
+ _ts_param4(0, (0, import_common6.Req)()),
1411
+ _ts_param4(1, (0, import_common6.Res)()),
1277
1412
  _ts_metadata4("design:type", Function),
1278
1413
  _ts_metadata4("design:paramtypes", [
1279
1414
  Object,
@@ -1281,11 +1416,11 @@ _ts_decorate5([
1281
1416
  ]),
1282
1417
  _ts_metadata4("design:returntype", void 0)
1283
1418
  ], DurableAuthController.prototype, "logout", null);
1284
- DurableAuthController = _ts_decorate5([
1285
- (0, import_common5.Controller)(),
1286
- _ts_param4(0, (0, import_common5.Optional)()),
1287
- _ts_param4(0, (0, import_common5.Inject)(DASHBOARD_AUTH)),
1288
- _ts_param4(1, (0, import_common5.Inject)(DASHBOARD_BASE_PATH)),
1419
+ DurableAuthController = _ts_decorate6([
1420
+ (0, import_common6.Controller)(),
1421
+ _ts_param4(0, (0, import_common6.Optional)()),
1422
+ _ts_param4(0, (0, import_common6.Inject)(DASHBOARD_AUTH)),
1423
+ _ts_param4(1, (0, import_common6.Inject)(DASHBOARD_BASE_PATH)),
1289
1424
  _ts_metadata4("design:type", Function),
1290
1425
  _ts_metadata4("design:paramtypes", [
1291
1426
  Object,
@@ -1295,18 +1430,18 @@ DurableAuthController = _ts_decorate5([
1295
1430
 
1296
1431
  // src/server/durable-dashboard.module.ts
1297
1432
  var import_reflect_metadata = require("reflect-metadata");
1298
- var import_common7 = require("@nestjs/common");
1433
+ var import_common8 = require("@nestjs/common");
1299
1434
  var import_core = require("@nestjs/core");
1300
1435
 
1301
1436
  // src/server/durable-session.guard.ts
1302
- var import_common6 = require("@nestjs/common");
1303
- function _ts_decorate6(decorators, target, key, desc) {
1437
+ var import_common7 = require("@nestjs/common");
1438
+ function _ts_decorate7(decorators, target, key, desc) {
1304
1439
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1305
1440
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1306
1441
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1307
1442
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1308
1443
  }
1309
- __name(_ts_decorate6, "_ts_decorate");
1444
+ __name(_ts_decorate7, "_ts_decorate");
1310
1445
  function _ts_metadata5(k, v) {
1311
1446
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1312
1447
  }
@@ -1327,23 +1462,40 @@ var DurableUiSessionGuard = class {
1327
1462
  this.auth = auth;
1328
1463
  this.basePath = basePath;
1329
1464
  }
1330
- canActivate(context) {
1465
+ async canActivate(context) {
1331
1466
  if (!this.auth) return true;
1467
+ const auth = this.auth;
1332
1468
  const http = context.switchToHttp();
1333
1469
  const request = http.getRequest();
1334
- const session = readSessionFromRequest(this.auth, request);
1470
+ const session = readSessionFromRequest(auth, request);
1335
1471
  if (!session) {
1336
- const returnTo = encodeURIComponent(originalRequestUrl(request));
1337
- throw new DashboardLoginRedirectException(`${this.basePath}/login?returnTo=${returnTo}`);
1472
+ this.denyUnauthenticated(auth, request);
1473
+ }
1474
+ if (!await maybeRenewSession(auth, session, request, http.getResponse())) {
1475
+ this.denyUnauthenticated(auth, request);
1338
1476
  }
1339
- maybeRenewSession(this.auth, session, request, http.getResponse());
1340
1477
  return true;
1341
1478
  }
1479
+ /**
1480
+ * A revoked session (the host's `revalidate` hook says no) is denied exactly like an absent
1481
+ * one — by design, there is nothing left to distinguish "never had a session" from "had one,
1482
+ * then lost it" once the cookie is cleared, so both get the same Mode-aware treatment.
1483
+ *
1484
+ * Takes `auth` explicitly rather than reading `this.auth`: `canActivate`'s `!this.auth` early
1485
+ * return narrows the field within that method only — TS doesn't carry it across a method call
1486
+ * — so callers pass the already-narrowed value instead of this method re-deriving (or
1487
+ * asserting away) the nullability.
1488
+ */
1489
+ denyUnauthenticated(auth, request) {
1490
+ if (!auth.login) throw new DashboardSessionRequiredException(this.basePath);
1491
+ const returnTo = encodeURIComponent(originalRequestUrl(request));
1492
+ throw new DashboardLoginRedirectException(`${this.basePath}/login?returnTo=${returnTo}`);
1493
+ }
1342
1494
  };
1343
- DurableUiSessionGuard = _ts_decorate6([
1344
- (0, import_common6.Injectable)(),
1345
- _ts_param5(0, (0, import_common6.Inject)(DASHBOARD_AUTH)),
1346
- _ts_param5(1, (0, import_common6.Inject)(DASHBOARD_BASE_PATH)),
1495
+ DurableUiSessionGuard = _ts_decorate7([
1496
+ (0, import_common7.Injectable)(),
1497
+ _ts_param5(0, (0, import_common7.Inject)(DASHBOARD_AUTH)),
1498
+ _ts_param5(1, (0, import_common7.Inject)(DASHBOARD_BASE_PATH)),
1347
1499
  _ts_metadata5("design:type", Function),
1348
1500
  _ts_metadata5("design:paramtypes", [
1349
1501
  Object,
@@ -1358,19 +1510,21 @@ var DurableApiSessionGuard = class {
1358
1510
  constructor(auth) {
1359
1511
  this.auth = auth;
1360
1512
  }
1361
- canActivate(context) {
1513
+ async canActivate(context) {
1362
1514
  if (!this.auth) return true;
1363
1515
  const http = context.switchToHttp();
1364
1516
  const request = http.getRequest();
1365
1517
  const session = readSessionFromRequest(this.auth, request);
1366
- if (!session) throw new import_common6.UnauthorizedException();
1367
- maybeRenewSession(this.auth, session, request, http.getResponse());
1518
+ if (!session) throw new import_common7.UnauthorizedException();
1519
+ if (!await maybeRenewSession(this.auth, session, request, http.getResponse())) {
1520
+ throw new import_common7.UnauthorizedException();
1521
+ }
1368
1522
  return true;
1369
1523
  }
1370
1524
  };
1371
- DurableApiSessionGuard = _ts_decorate6([
1372
- (0, import_common6.Injectable)(),
1373
- _ts_param5(0, (0, import_common6.Inject)(DASHBOARD_AUTH)),
1525
+ DurableApiSessionGuard = _ts_decorate7([
1526
+ (0, import_common7.Injectable)(),
1527
+ _ts_param5(0, (0, import_common7.Inject)(DASHBOARD_AUTH)),
1374
1528
  _ts_metadata5("design:type", Function),
1375
1529
  _ts_metadata5("design:paramtypes", [
1376
1530
  Object
@@ -1378,13 +1532,13 @@ DurableApiSessionGuard = _ts_decorate6([
1378
1532
  ], DurableApiSessionGuard);
1379
1533
 
1380
1534
  // src/server/durable-dashboard.module.ts
1381
- function _ts_decorate7(decorators, target, key, desc) {
1535
+ function _ts_decorate8(decorators, target, key, desc) {
1382
1536
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1383
1537
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1384
1538
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1385
1539
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1386
1540
  }
1387
- __name(_ts_decorate7, "_ts_decorate");
1541
+ __name(_ts_decorate8, "_ts_decorate");
1388
1542
  var GUARDS_METADATA = "__guards__";
1389
1543
  function normalize(path) {
1390
1544
  return `/${path.replace(/^\/+|\/+$/g, "")}`;
@@ -1426,8 +1580,8 @@ var DurableApiModule = class _DurableApiModule {
1426
1580
  };
1427
1581
  }
1428
1582
  };
1429
- DurableApiModule = _ts_decorate7([
1430
- (0, import_common7.Module)({})
1583
+ DurableApiModule = _ts_decorate8([
1584
+ (0, import_common8.Module)({})
1431
1585
  ], DurableApiModule);
1432
1586
  var DurableDashboardModule = class _DurableDashboardModule {
1433
1587
  static {
@@ -1512,8 +1666,8 @@ var DurableDashboardModule = class _DurableDashboardModule {
1512
1666
  };
1513
1667
  }
1514
1668
  };
1515
- DurableDashboardModule = _ts_decorate7([
1516
- (0, import_common7.Module)({})
1669
+ DurableDashboardModule = _ts_decorate8([
1670
+ (0, import_common8.Module)({})
1517
1671
  ], DurableDashboardModule);
1518
1672
  // Annotate the CommonJS export names for ESM import in node:
1519
1673
  0 && (module.exports = {