@dudousxd/nestjs-durable-dashboard 0.35.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.
@@ -543,28 +543,51 @@ function resolveDashboardAuth(options) {
543
543
  if (typeof options.secret !== "string" || options.secret === "") {
544
544
  throw new Error("DurableDashboardModule: dashboardAuth.secret is required and must be a non-empty string (HMAC-SHA256 signing key, 32+ bytes recommended). Failing closed.");
545
545
  }
546
- if (typeof options.login !== "function") {
547
- throw new Error("DurableDashboardModule: dashboardAuth.login is required (a login hook).");
546
+ const modes = [];
547
+ if (options.session !== void 0) {
548
+ if (typeof options.session !== "function") {
549
+ throw new Error("DurableDashboardModule: dashboardAuth.session must be a function.");
550
+ }
551
+ modes.push("session");
552
+ }
553
+ if (options.login !== void 0) {
554
+ if (typeof options.login !== "function") {
555
+ throw new Error("DurableDashboardModule: dashboardAuth.login must be a function.");
556
+ }
557
+ modes.push("login");
558
+ }
559
+ if (options.revalidate !== void 0 && typeof options.revalidate !== "function") {
560
+ throw new Error("DurableDashboardModule: dashboardAuth.revalidate must be a function.");
561
+ }
562
+ if (modes.length === 0) {
563
+ throw new Error("DurableDashboardModule: dashboardAuth needs at least one of `session` or `login` (otherwise the cookie can never be minted). Failing closed.");
548
564
  }
549
565
  return {
550
566
  secret: options.secret,
551
567
  ttlMs: durationToMs(options.ttl ?? DEFAULT_TTL),
552
- login: options.login
568
+ modes,
569
+ ...options.session !== void 0 ? {
570
+ session: options.session
571
+ } : {},
572
+ ...options.login !== void 0 ? {
573
+ login: options.login
574
+ } : {},
575
+ ...options.revalidate !== void 0 ? {
576
+ revalidate: options.revalidate
577
+ } : {}
553
578
  };
554
579
  }
555
580
  __name(resolveDashboardAuth, "resolveDashboardAuth");
556
581
 
557
582
  // src/server/auth/login-page.ts
558
- function renderLoginPage(basePath) {
559
- const loginAction = `${basePath}/login`;
560
- const defaultReturnTo = basePath;
583
+ function page(content, title = "Durable") {
561
584
  return `<!doctype html>
562
585
  <html lang="en">
563
586
  <head>
564
587
  <meta charset="utf-8" />
565
588
  <meta name="viewport" content="width=device-width, initial-scale=1" />
566
589
  <meta name="robots" content="noindex, nofollow" />
567
- <title>Sign in \u2014 Durable</title>
590
+ <title>${title}</title>
568
591
  <style>
569
592
  :root { color-scheme: dark; }
570
593
  * { box-sizing: border-box; }
@@ -595,6 +618,13 @@ function renderLoginPage(basePath) {
595
618
  font-weight: 600;
596
619
  color: #34d399;
597
620
  }
621
+ h1 {
622
+ margin: 0 0 12px;
623
+ font-size: 16px;
624
+ font-weight: 600;
625
+ color: #f4f4f5;
626
+ }
627
+ p { margin: 0 0 16px; color: #a1a1aa; }
598
628
  form { display: flex; flex-direction: column; gap: 16px; }
599
629
  label { display: flex; flex-direction: column; gap: 6px; }
600
630
  .field-label {
@@ -619,7 +649,7 @@ function renderLoginPage(basePath) {
619
649
  font-size: 12px;
620
650
  color: #fb7185;
621
651
  }
622
- button {
652
+ button, .button {
623
653
  margin-top: 8px;
624
654
  border-radius: 4px;
625
655
  border: 1px solid rgb(52 211 153 / 0.4);
@@ -632,14 +662,27 @@ function renderLoginPage(basePath) {
632
662
  text-transform: uppercase;
633
663
  letter-spacing: 0.05em;
634
664
  cursor: pointer;
665
+ display: inline-block;
666
+ text-align: center;
667
+ text-decoration: none;
668
+ box-sizing: border-box;
635
669
  }
636
- button:hover:not(:disabled) { background: rgb(52 211 153 / 0.2); }
670
+ button:hover:not(:disabled), .button:hover { background: rgb(52 211 153 / 0.2); }
637
671
  button:disabled { opacity: 0.6; cursor: default; }
638
672
  </style>
639
673
  </head>
640
674
  <body>
641
675
  <div class="card">
642
- <p class="brand">Durable</p>
676
+ ${content}
677
+ </div>
678
+ </body>
679
+ </html>`;
680
+ }
681
+ __name(page, "page");
682
+ function renderLoginPage(basePath) {
683
+ const loginAction = `${basePath}/login`;
684
+ const defaultReturnTo = basePath;
685
+ return page(`<p class="brand">Durable</p>
643
686
  <form id="login-form" autocomplete="on">
644
687
  <label>
645
688
  <span class="field-label">Username</span>
@@ -652,7 +695,6 @@ function renderLoginPage(basePath) {
652
695
  <p id="error" role="alert">Invalid username or password.</p>
653
696
  <button id="submit" type="submit">Sign in</button>
654
697
  </form>
655
- </div>
656
698
  <script>
657
699
  (function () {
658
700
  var LOGIN_ACTION = ${JSON.stringify(loginAction)};
@@ -699,11 +741,15 @@ function renderLoginPage(basePath) {
699
741
  });
700
742
  });
701
743
  })();
702
- </script>
703
- </body>
704
- </html>`;
744
+ </script>`, "Sign in \u2014 Durable");
705
745
  }
706
746
  __name(renderLoginPage, "renderLoginPage");
747
+ function renderSessionRequiredPage(basePath) {
748
+ return page(`<h1>Open this console from your application</h1>
749
+ <p>Your session is minted by the host app. Use its console launcher to sign in, then reload.</p>
750
+ <a class="button" href="${basePath}">Retry</a>`);
751
+ }
752
+ __name(renderSessionRequiredPage, "renderSessionRequiredPage");
707
753
 
708
754
  // src/server/auth/request.ts
709
755
  function isRecord(value) {
@@ -804,6 +850,15 @@ function redirectRaw(response, location, status = 302) {
804
850
  raw.end();
805
851
  }
806
852
  __name(redirectRaw, "redirectRaw");
853
+ function writeHtmlRaw(response, html, status = 200) {
854
+ const raw = resolveRedirectResponse(response);
855
+ if (!raw) return;
856
+ raw.statusCode = status;
857
+ raw.setHeader("content-type", "text/html; charset=utf-8");
858
+ raw.setHeader("cache-control", "no-store, must-revalidate");
859
+ raw.end(html);
860
+ }
861
+ __name(writeHtmlRaw, "writeHtmlRaw");
807
862
 
808
863
  // src/server/auth/cookie-header.ts
809
864
  function parseCookieHeader(header) {
@@ -959,21 +1014,37 @@ function readSessionFromRequest(auth, request) {
959
1014
  });
960
1015
  }
961
1016
  __name(readSessionFromRequest, "readSessionFromRequest");
962
- function maybeRenewSession(auth, session, request, response) {
963
- const now = Date.now();
964
- if (now - session.iat <= auth.ttlMs / 2) return;
965
- issueSessionCookie({
1017
+ async function maybeRenewSession(auth, session, request, response, now = Date.now()) {
1018
+ if (now - session.iat <= auth.ttlMs / 2) return true;
1019
+ const user = {
966
1020
  id: session.sub,
967
1021
  ...session.name !== void 0 ? {
968
1022
  name: session.name
969
1023
  } : {},
970
1024
  roles: session.roles
971
- }, {
1025
+ };
1026
+ if (auth.revalidate) {
1027
+ let allowed;
1028
+ try {
1029
+ allowed = await auth.revalidate(user);
1030
+ } catch {
1031
+ allowed = false;
1032
+ }
1033
+ if (!allowed) {
1034
+ clearSessionCookie({
1035
+ request,
1036
+ response
1037
+ });
1038
+ return false;
1039
+ }
1040
+ }
1041
+ issueSessionCookie(user, {
972
1042
  auth,
973
1043
  request,
974
1044
  response,
975
1045
  now
976
1046
  });
1047
+ return true;
977
1048
  }
978
1049
  __name(maybeRenewSession, "maybeRenewSession");
979
1050
 
@@ -1016,7 +1087,8 @@ DashboardLoginRedirectFilter = _ts_decorate3([
1016
1087
  Catch(DashboardLoginRedirectException)
1017
1088
  ], DashboardLoginRedirectFilter);
1018
1089
 
1019
- // src/server/durable-ui.controller.ts
1090
+ // src/server/auth/session-required.exception.ts
1091
+ import { Catch as Catch2, HttpException as HttpException2, HttpStatus as HttpStatus2 } from "@nestjs/common";
1020
1092
  function _ts_decorate4(decorators, target, key, desc) {
1021
1093
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1022
1094
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -1024,6 +1096,38 @@ function _ts_decorate4(decorators, target, key, desc) {
1024
1096
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1025
1097
  }
1026
1098
  __name(_ts_decorate4, "_ts_decorate");
1099
+ var DashboardSessionRequiredException = class extends HttpException2 {
1100
+ static {
1101
+ __name(this, "DashboardSessionRequiredException");
1102
+ }
1103
+ basePath;
1104
+ constructor(basePath) {
1105
+ super({
1106
+ basePath
1107
+ }, HttpStatus2.UNAUTHORIZED), this.basePath = basePath;
1108
+ }
1109
+ };
1110
+ var DashboardSessionRequiredFilter = class {
1111
+ static {
1112
+ __name(this, "DashboardSessionRequiredFilter");
1113
+ }
1114
+ catch(exception, host) {
1115
+ const response = host.switchToHttp().getResponse();
1116
+ writeHtmlRaw(response, renderSessionRequiredPage(exception.basePath), HttpStatus2.UNAUTHORIZED);
1117
+ }
1118
+ };
1119
+ DashboardSessionRequiredFilter = _ts_decorate4([
1120
+ Catch2(DashboardSessionRequiredException)
1121
+ ], DashboardSessionRequiredFilter);
1122
+
1123
+ // src/server/durable-ui.controller.ts
1124
+ function _ts_decorate5(decorators, target, key, desc) {
1125
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1126
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1127
+ 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;
1128
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1129
+ }
1130
+ __name(_ts_decorate5, "_ts_decorate");
1027
1131
  function _ts_metadata3(k, v) {
1028
1132
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1029
1133
  }
@@ -1086,7 +1190,7 @@ var DurableUiController = class {
1086
1190
  });
1087
1191
  }
1088
1192
  };
1089
- _ts_decorate4([
1193
+ _ts_decorate5([
1090
1194
  Get2(),
1091
1195
  Header2("Content-Type", "text/html; charset=utf-8"),
1092
1196
  Header2("Cache-Control", "no-store, must-revalidate"),
@@ -1094,7 +1198,7 @@ _ts_decorate4([
1094
1198
  _ts_metadata3("design:paramtypes", []),
1095
1199
  _ts_metadata3("design:returntype", String)
1096
1200
  ], DurableUiController.prototype, "index", null);
1097
- _ts_decorate4([
1201
+ _ts_decorate5([
1098
1202
  Get2("assets/:file"),
1099
1203
  Header2("Cache-Control", "public, max-age=31536000, immutable"),
1100
1204
  _ts_param3(0, Param2("file")),
@@ -1104,8 +1208,8 @@ _ts_decorate4([
1104
1208
  ]),
1105
1209
  _ts_metadata3("design:returntype", typeof StreamableFile === "undefined" ? Object : StreamableFile)
1106
1210
  ], DurableUiController.prototype, "asset", null);
1107
- DurableUiController = _ts_decorate4([
1108
- UseFilters(DashboardLoginRedirectFilter),
1211
+ DurableUiController = _ts_decorate5([
1212
+ UseFilters(DashboardLoginRedirectFilter, DashboardSessionRequiredFilter),
1109
1213
  Controller2(),
1110
1214
  _ts_param3(0, Inject2(DASHBOARD_BASE_PATH)),
1111
1215
  _ts_param3(1, Inject2(DASHBOARD_API_PATH)),
@@ -1117,13 +1221,13 @@ DurableUiController = _ts_decorate4([
1117
1221
  ], DurableUiController);
1118
1222
 
1119
1223
  // src/server/durable-auth.controller.ts
1120
- function _ts_decorate5(decorators, target, key, desc) {
1224
+ function _ts_decorate6(decorators, target, key, desc) {
1121
1225
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1122
1226
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1123
1227
  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;
1124
1228
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1125
1229
  }
1126
- __name(_ts_decorate5, "_ts_decorate");
1230
+ __name(_ts_decorate6, "_ts_decorate");
1127
1231
  function _ts_metadata4(k, v) {
1128
1232
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1129
1233
  }
@@ -1145,8 +1249,8 @@ var DurableAuthController = class _DurableAuthController {
1145
1249
  auth;
1146
1250
  basePath;
1147
1251
  logger = new Logger(_DurableAuthController.name);
1148
- /** Warn once so a throwing `login` hook doesn't spam logs on every failed attempt. */
1149
- warnedOnHookThrow = false;
1252
+ /** Warn once per hook kind so a throwing hook doesn't spam logs on every failed attempt. */
1253
+ warnedHooks = /* @__PURE__ */ new Set();
1150
1254
  constructor(auth, basePath) {
1151
1255
  this.auth = auth;
1152
1256
  this.basePath = basePath;
@@ -1155,7 +1259,8 @@ var DurableAuthController = class _DurableAuthController {
1155
1259
  // `login-page.ts`), so this response never varies per request and needs no templating beyond
1156
1260
  // the developer-controlled `basePath`.
1157
1261
  loginPage() {
1158
- if (!this.auth) throw new NotFoundException3();
1262
+ const auth = this.requireAuth();
1263
+ if (!auth.login) throw new NotFoundException3();
1159
1264
  return renderLoginPage(this.basePath);
1160
1265
  }
1161
1266
  // Called by the login page's own fetch (JSON, not a classic form POST — see `login-page.ts` for
@@ -1164,10 +1269,12 @@ var DurableAuthController = class _DurableAuthController {
1164
1269
  // (unknown user, wrong password, throwing hook) — no user-enumeration.
1165
1270
  async login(body, request, response) {
1166
1271
  const auth = this.requireAuth();
1272
+ if (!auth.login) throw new NotFoundException3();
1167
1273
  if (!isString(body?.username) || !isString(body?.password)) {
1168
1274
  throw new BadRequestException("Body must include string `username` and `password`.");
1169
1275
  }
1170
- const user = await this.runLoginHook(auth, body.username, body.password);
1276
+ const { username, password } = body;
1277
+ const user = await this.runHook("login", () => auth.login?.(username, password) ?? null);
1171
1278
  if (!user) throw new UnauthorizedException({
1172
1279
  message: "Invalid username or password."
1173
1280
  });
@@ -1180,6 +1287,20 @@ var DurableAuthController = class _DurableAuthController {
1180
1287
  redirectTo: sanitizeReturnTo(body.returnTo, this.basePath)
1181
1288
  };
1182
1289
  }
1290
+ // Mode A: the host frontend (carrying its own auth) POSTs here; the host hook validates the raw
1291
+ // request and returns the session user, or `null` to deny. This is the SSO path — the host's own
1292
+ // guard decides, and no credential ever reaches this library.
1293
+ async session(request, response) {
1294
+ const auth = this.requireAuth();
1295
+ if (!auth.session) throw new NotFoundException3();
1296
+ const user = await this.runHook("session", () => auth.session?.(request) ?? null);
1297
+ if (!user) throw new UnauthorizedException();
1298
+ issueSessionCookie(user, {
1299
+ auth,
1300
+ request,
1301
+ response
1302
+ });
1303
+ }
1183
1304
  // Plain `GET` (not `POST`): logging out only ever destroys the CALLER's own session, so it's
1184
1305
  // safe/idempotent from the caller's perspective — a simple `<a href>` (no JS, no CSRF token)
1185
1306
  // works. Non-passthrough `@Res()`: this handler owns the response outright (clears the cookie,
@@ -1189,30 +1310,30 @@ var DurableAuthController = class _DurableAuthController {
1189
1310
  request,
1190
1311
  response
1191
1312
  });
1192
- redirectRaw(response, `${this.basePath}/login`);
1313
+ redirectRaw(response, this.auth?.login ? `${this.basePath}/login` : this.basePath);
1193
1314
  }
1194
1315
  requireAuth() {
1195
1316
  if (!this.auth) throw new NotFoundException3();
1196
1317
  return this.auth;
1197
1318
  }
1198
1319
  /**
1199
- * Run the host's `login` hook defensively: a throw is treated as a denial (uniform failure) and
1200
- * warn-logged once, so a buggy hook never 500s the endpoint into a stack-trace leak nor floods
1201
- * the logs on repeated bad attempts.
1320
+ * Run a host hook (`login` or `session`) defensively: a throw is treated as a denial (uniform
1321
+ * failure) and warn-logged once PER KIND, so a buggy hook never 500s the endpoint into a
1322
+ * stack-trace leak nor floods the logs on repeated bad attempts.
1202
1323
  */
1203
- async runLoginHook(auth, username, password) {
1324
+ async runHook(kind, run) {
1204
1325
  try {
1205
- return await auth.login(username, password) ?? null;
1326
+ return await run() ?? null;
1206
1327
  } catch (error) {
1207
- if (!this.warnedOnHookThrow) {
1208
- this.warnedOnHookThrow = true;
1209
- this.logger.warn(`dashboardAuth login hook threw; treating as denial. ${error instanceof Error ? error.message : String(error)}`);
1328
+ if (!this.warnedHooks.has(kind)) {
1329
+ this.warnedHooks.add(kind);
1330
+ this.logger.warn(`dashboardAuth ${kind} hook threw; treating as denial. ${error instanceof Error ? error.message : String(error)}`);
1210
1331
  }
1211
1332
  return null;
1212
1333
  }
1213
1334
  }
1214
1335
  };
1215
- _ts_decorate5([
1336
+ _ts_decorate6([
1216
1337
  Get3("login"),
1217
1338
  Header3("Content-Type", "text/html; charset=utf-8"),
1218
1339
  Header3("Cache-Control", "no-store, must-revalidate"),
@@ -1220,7 +1341,7 @@ _ts_decorate5([
1220
1341
  _ts_metadata4("design:paramtypes", []),
1221
1342
  _ts_metadata4("design:returntype", String)
1222
1343
  ], DurableAuthController.prototype, "loginPage", null);
1223
- _ts_decorate5([
1344
+ _ts_decorate6([
1224
1345
  Post2("login"),
1225
1346
  HttpCode(200),
1226
1347
  _ts_param4(0, Body2()),
@@ -1236,7 +1357,21 @@ _ts_decorate5([
1236
1357
  ]),
1237
1358
  _ts_metadata4("design:returntype", Promise)
1238
1359
  ], DurableAuthController.prototype, "login", null);
1239
- _ts_decorate5([
1360
+ _ts_decorate6([
1361
+ Post2("session"),
1362
+ HttpCode(204),
1363
+ _ts_param4(0, Req()),
1364
+ _ts_param4(1, Res({
1365
+ passthrough: true
1366
+ })),
1367
+ _ts_metadata4("design:type", Function),
1368
+ _ts_metadata4("design:paramtypes", [
1369
+ Object,
1370
+ Object
1371
+ ]),
1372
+ _ts_metadata4("design:returntype", Promise)
1373
+ ], DurableAuthController.prototype, "session", null);
1374
+ _ts_decorate6([
1240
1375
  Get3("logout"),
1241
1376
  _ts_param4(0, Req()),
1242
1377
  _ts_param4(1, Res()),
@@ -1247,7 +1382,7 @@ _ts_decorate5([
1247
1382
  ]),
1248
1383
  _ts_metadata4("design:returntype", void 0)
1249
1384
  ], DurableAuthController.prototype, "logout", null);
1250
- DurableAuthController = _ts_decorate5([
1385
+ DurableAuthController = _ts_decorate6([
1251
1386
  Controller3(),
1252
1387
  _ts_param4(0, Optional2()),
1253
1388
  _ts_param4(0, Inject3(DASHBOARD_AUTH)),
@@ -1266,13 +1401,13 @@ import { RouterModule } from "@nestjs/core";
1266
1401
 
1267
1402
  // src/server/durable-session.guard.ts
1268
1403
  import { Inject as Inject4, Injectable as Injectable2, UnauthorizedException as UnauthorizedException2 } from "@nestjs/common";
1269
- function _ts_decorate6(decorators, target, key, desc) {
1404
+ function _ts_decorate7(decorators, target, key, desc) {
1270
1405
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1271
1406
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1272
1407
  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;
1273
1408
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1274
1409
  }
1275
- __name(_ts_decorate6, "_ts_decorate");
1410
+ __name(_ts_decorate7, "_ts_decorate");
1276
1411
  function _ts_metadata5(k, v) {
1277
1412
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1278
1413
  }
@@ -1293,20 +1428,37 @@ var DurableUiSessionGuard = class {
1293
1428
  this.auth = auth;
1294
1429
  this.basePath = basePath;
1295
1430
  }
1296
- canActivate(context) {
1431
+ async canActivate(context) {
1297
1432
  if (!this.auth) return true;
1433
+ const auth = this.auth;
1298
1434
  const http = context.switchToHttp();
1299
1435
  const request = http.getRequest();
1300
- const session = readSessionFromRequest(this.auth, request);
1436
+ const session = readSessionFromRequest(auth, request);
1301
1437
  if (!session) {
1302
- const returnTo = encodeURIComponent(originalRequestUrl(request));
1303
- throw new DashboardLoginRedirectException(`${this.basePath}/login?returnTo=${returnTo}`);
1438
+ this.denyUnauthenticated(auth, request);
1439
+ }
1440
+ if (!await maybeRenewSession(auth, session, request, http.getResponse())) {
1441
+ this.denyUnauthenticated(auth, request);
1304
1442
  }
1305
- maybeRenewSession(this.auth, session, request, http.getResponse());
1306
1443
  return true;
1307
1444
  }
1445
+ /**
1446
+ * A revoked session (the host's `revalidate` hook says no) is denied exactly like an absent
1447
+ * one — by design, there is nothing left to distinguish "never had a session" from "had one,
1448
+ * then lost it" once the cookie is cleared, so both get the same Mode-aware treatment.
1449
+ *
1450
+ * Takes `auth` explicitly rather than reading `this.auth`: `canActivate`'s `!this.auth` early
1451
+ * return narrows the field within that method only — TS doesn't carry it across a method call
1452
+ * — so callers pass the already-narrowed value instead of this method re-deriving (or
1453
+ * asserting away) the nullability.
1454
+ */
1455
+ denyUnauthenticated(auth, request) {
1456
+ if (!auth.login) throw new DashboardSessionRequiredException(this.basePath);
1457
+ const returnTo = encodeURIComponent(originalRequestUrl(request));
1458
+ throw new DashboardLoginRedirectException(`${this.basePath}/login?returnTo=${returnTo}`);
1459
+ }
1308
1460
  };
1309
- DurableUiSessionGuard = _ts_decorate6([
1461
+ DurableUiSessionGuard = _ts_decorate7([
1310
1462
  Injectable2(),
1311
1463
  _ts_param5(0, Inject4(DASHBOARD_AUTH)),
1312
1464
  _ts_param5(1, Inject4(DASHBOARD_BASE_PATH)),
@@ -1324,17 +1476,19 @@ var DurableApiSessionGuard = class {
1324
1476
  constructor(auth) {
1325
1477
  this.auth = auth;
1326
1478
  }
1327
- canActivate(context) {
1479
+ async canActivate(context) {
1328
1480
  if (!this.auth) return true;
1329
1481
  const http = context.switchToHttp();
1330
1482
  const request = http.getRequest();
1331
1483
  const session = readSessionFromRequest(this.auth, request);
1332
1484
  if (!session) throw new UnauthorizedException2();
1333
- maybeRenewSession(this.auth, session, request, http.getResponse());
1485
+ if (!await maybeRenewSession(this.auth, session, request, http.getResponse())) {
1486
+ throw new UnauthorizedException2();
1487
+ }
1334
1488
  return true;
1335
1489
  }
1336
1490
  };
1337
- DurableApiSessionGuard = _ts_decorate6([
1491
+ DurableApiSessionGuard = _ts_decorate7([
1338
1492
  Injectable2(),
1339
1493
  _ts_param5(0, Inject4(DASHBOARD_AUTH)),
1340
1494
  _ts_metadata5("design:type", Function),
@@ -1344,13 +1498,13 @@ DurableApiSessionGuard = _ts_decorate6([
1344
1498
  ], DurableApiSessionGuard);
1345
1499
 
1346
1500
  // src/server/durable-dashboard.module.ts
1347
- function _ts_decorate7(decorators, target, key, desc) {
1501
+ function _ts_decorate8(decorators, target, key, desc) {
1348
1502
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1349
1503
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1350
1504
  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;
1351
1505
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1352
1506
  }
1353
- __name(_ts_decorate7, "_ts_decorate");
1507
+ __name(_ts_decorate8, "_ts_decorate");
1354
1508
  var GUARDS_METADATA = "__guards__";
1355
1509
  function normalize(path) {
1356
1510
  return `/${path.replace(/^\/+|\/+$/g, "")}`;
@@ -1392,7 +1546,7 @@ var DurableApiModule = class _DurableApiModule {
1392
1546
  };
1393
1547
  }
1394
1548
  };
1395
- DurableApiModule = _ts_decorate7([
1549
+ DurableApiModule = _ts_decorate8([
1396
1550
  Module({})
1397
1551
  ], DurableApiModule);
1398
1552
  var DurableDashboardModule = class _DurableDashboardModule {
@@ -1478,7 +1632,7 @@ var DurableDashboardModule = class _DurableDashboardModule {
1478
1632
  };
1479
1633
  }
1480
1634
  };
1481
- DurableDashboardModule = _ts_decorate7([
1635
+ DurableDashboardModule = _ts_decorate8([
1482
1636
  Module({})
1483
1637
  ], DurableDashboardModule);
1484
1638
  export {