@dudousxd/nestjs-durable-dashboard 0.36.0 → 0.37.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/durable-client.d.ts.map +1 -1
- package/dist/client/durable-client.js +57 -2
- package/dist/client/durable-client.js.map +1 -1
- package/dist/server/index.cjs +279 -104
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +110 -40
- package/dist/server/index.d.ts +110 -40
- package/dist/server/index.js +235 -60
- package/dist/server/index.js.map +1 -1
- package/dist/spa/assets/{index-tjU3_joS.js → index-AKaTbEKr.js} +8 -8
- package/dist/spa/assets/{index-BgWYZ0wi.js → index-kCrHKxZp.js} +1 -1
- package/dist/spa/assets/{preview-CoXYGpK-.js → preview-G8TH2d5R.js} +1 -1
- package/dist/spa/index.html +2 -2
- package/dist/spa/preview.html +2 -2
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -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
|
-
|
|
547
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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,44 @@ function readSessionFromRequest(auth, request) {
|
|
|
959
1014
|
});
|
|
960
1015
|
}
|
|
961
1016
|
__name(readSessionFromRequest, "readSessionFromRequest");
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1017
|
+
var inFlightRevalidations = /* @__PURE__ */ new Map();
|
|
1018
|
+
function revalidateOnce(auth, user) {
|
|
1019
|
+
const revalidate = auth.revalidate;
|
|
1020
|
+
if (!revalidate) return Promise.resolve(true);
|
|
1021
|
+
const key = `${auth.secret}:${user.id}`;
|
|
1022
|
+
const inFlight = inFlightRevalidations.get(key);
|
|
1023
|
+
if (inFlight) return inFlight;
|
|
1024
|
+
const call = Promise.resolve().then(() => revalidate(user)).catch(() => false).finally(() => {
|
|
1025
|
+
inFlightRevalidations.delete(key);
|
|
1026
|
+
});
|
|
1027
|
+
inFlightRevalidations.set(key, call);
|
|
1028
|
+
return call;
|
|
1029
|
+
}
|
|
1030
|
+
__name(revalidateOnce, "revalidateOnce");
|
|
1031
|
+
async function maybeRenewSession(auth, session, request, response, now = Date.now()) {
|
|
1032
|
+
if (now - session.iat <= auth.ttlMs / 2) return true;
|
|
1033
|
+
const user = {
|
|
966
1034
|
id: session.sub,
|
|
967
1035
|
...session.name !== void 0 ? {
|
|
968
1036
|
name: session.name
|
|
969
1037
|
} : {},
|
|
970
1038
|
roles: session.roles
|
|
971
|
-
}
|
|
1039
|
+
};
|
|
1040
|
+
const allowed = await revalidateOnce(auth, user);
|
|
1041
|
+
if (!allowed) {
|
|
1042
|
+
clearSessionCookie({
|
|
1043
|
+
request,
|
|
1044
|
+
response
|
|
1045
|
+
});
|
|
1046
|
+
return false;
|
|
1047
|
+
}
|
|
1048
|
+
issueSessionCookie(user, {
|
|
972
1049
|
auth,
|
|
973
1050
|
request,
|
|
974
1051
|
response,
|
|
975
1052
|
now
|
|
976
1053
|
});
|
|
1054
|
+
return true;
|
|
977
1055
|
}
|
|
978
1056
|
__name(maybeRenewSession, "maybeRenewSession");
|
|
979
1057
|
|
|
@@ -1016,7 +1094,8 @@ DashboardLoginRedirectFilter = _ts_decorate3([
|
|
|
1016
1094
|
Catch(DashboardLoginRedirectException)
|
|
1017
1095
|
], DashboardLoginRedirectFilter);
|
|
1018
1096
|
|
|
1019
|
-
// src/server/
|
|
1097
|
+
// src/server/auth/session-required.exception.ts
|
|
1098
|
+
import { Catch as Catch2, HttpException as HttpException2, HttpStatus as HttpStatus2 } from "@nestjs/common";
|
|
1020
1099
|
function _ts_decorate4(decorators, target, key, desc) {
|
|
1021
1100
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1022
1101
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -1024,6 +1103,38 @@ function _ts_decorate4(decorators, target, key, desc) {
|
|
|
1024
1103
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1025
1104
|
}
|
|
1026
1105
|
__name(_ts_decorate4, "_ts_decorate");
|
|
1106
|
+
var DashboardSessionRequiredException = class extends HttpException2 {
|
|
1107
|
+
static {
|
|
1108
|
+
__name(this, "DashboardSessionRequiredException");
|
|
1109
|
+
}
|
|
1110
|
+
basePath;
|
|
1111
|
+
constructor(basePath) {
|
|
1112
|
+
super({
|
|
1113
|
+
basePath
|
|
1114
|
+
}, HttpStatus2.UNAUTHORIZED), this.basePath = basePath;
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
1117
|
+
var DashboardSessionRequiredFilter = class {
|
|
1118
|
+
static {
|
|
1119
|
+
__name(this, "DashboardSessionRequiredFilter");
|
|
1120
|
+
}
|
|
1121
|
+
catch(exception, host) {
|
|
1122
|
+
const response = host.switchToHttp().getResponse();
|
|
1123
|
+
writeHtmlRaw(response, renderSessionRequiredPage(exception.basePath), HttpStatus2.UNAUTHORIZED);
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
DashboardSessionRequiredFilter = _ts_decorate4([
|
|
1127
|
+
Catch2(DashboardSessionRequiredException)
|
|
1128
|
+
], DashboardSessionRequiredFilter);
|
|
1129
|
+
|
|
1130
|
+
// src/server/durable-ui.controller.ts
|
|
1131
|
+
function _ts_decorate5(decorators, target, key, desc) {
|
|
1132
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1133
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1134
|
+
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;
|
|
1135
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1136
|
+
}
|
|
1137
|
+
__name(_ts_decorate5, "_ts_decorate");
|
|
1027
1138
|
function _ts_metadata3(k, v) {
|
|
1028
1139
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1029
1140
|
}
|
|
@@ -1086,7 +1197,7 @@ var DurableUiController = class {
|
|
|
1086
1197
|
});
|
|
1087
1198
|
}
|
|
1088
1199
|
};
|
|
1089
|
-
|
|
1200
|
+
_ts_decorate5([
|
|
1090
1201
|
Get2(),
|
|
1091
1202
|
Header2("Content-Type", "text/html; charset=utf-8"),
|
|
1092
1203
|
Header2("Cache-Control", "no-store, must-revalidate"),
|
|
@@ -1094,7 +1205,7 @@ _ts_decorate4([
|
|
|
1094
1205
|
_ts_metadata3("design:paramtypes", []),
|
|
1095
1206
|
_ts_metadata3("design:returntype", String)
|
|
1096
1207
|
], DurableUiController.prototype, "index", null);
|
|
1097
|
-
|
|
1208
|
+
_ts_decorate5([
|
|
1098
1209
|
Get2("assets/:file"),
|
|
1099
1210
|
Header2("Cache-Control", "public, max-age=31536000, immutable"),
|
|
1100
1211
|
_ts_param3(0, Param2("file")),
|
|
@@ -1104,8 +1215,8 @@ _ts_decorate4([
|
|
|
1104
1215
|
]),
|
|
1105
1216
|
_ts_metadata3("design:returntype", typeof StreamableFile === "undefined" ? Object : StreamableFile)
|
|
1106
1217
|
], DurableUiController.prototype, "asset", null);
|
|
1107
|
-
DurableUiController =
|
|
1108
|
-
UseFilters(DashboardLoginRedirectFilter),
|
|
1218
|
+
DurableUiController = _ts_decorate5([
|
|
1219
|
+
UseFilters(DashboardLoginRedirectFilter, DashboardSessionRequiredFilter),
|
|
1109
1220
|
Controller2(),
|
|
1110
1221
|
_ts_param3(0, Inject2(DASHBOARD_BASE_PATH)),
|
|
1111
1222
|
_ts_param3(1, Inject2(DASHBOARD_API_PATH)),
|
|
@@ -1117,13 +1228,13 @@ DurableUiController = _ts_decorate4([
|
|
|
1117
1228
|
], DurableUiController);
|
|
1118
1229
|
|
|
1119
1230
|
// src/server/durable-auth.controller.ts
|
|
1120
|
-
function
|
|
1231
|
+
function _ts_decorate6(decorators, target, key, desc) {
|
|
1121
1232
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1122
1233
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1123
1234
|
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
1235
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1125
1236
|
}
|
|
1126
|
-
__name(
|
|
1237
|
+
__name(_ts_decorate6, "_ts_decorate");
|
|
1127
1238
|
function _ts_metadata4(k, v) {
|
|
1128
1239
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1129
1240
|
}
|
|
@@ -1145,8 +1256,8 @@ var DurableAuthController = class _DurableAuthController {
|
|
|
1145
1256
|
auth;
|
|
1146
1257
|
basePath;
|
|
1147
1258
|
logger = new Logger(_DurableAuthController.name);
|
|
1148
|
-
/** Warn once so a throwing
|
|
1149
|
-
|
|
1259
|
+
/** Warn once per hook kind so a throwing hook doesn't spam logs on every failed attempt. */
|
|
1260
|
+
warnedHooks = /* @__PURE__ */ new Set();
|
|
1150
1261
|
constructor(auth, basePath) {
|
|
1151
1262
|
this.auth = auth;
|
|
1152
1263
|
this.basePath = basePath;
|
|
@@ -1155,7 +1266,8 @@ var DurableAuthController = class _DurableAuthController {
|
|
|
1155
1266
|
// `login-page.ts`), so this response never varies per request and needs no templating beyond
|
|
1156
1267
|
// the developer-controlled `basePath`.
|
|
1157
1268
|
loginPage() {
|
|
1158
|
-
|
|
1269
|
+
const auth = this.requireAuth();
|
|
1270
|
+
if (!auth.modes.includes("login")) throw new NotFoundException3();
|
|
1159
1271
|
return renderLoginPage(this.basePath);
|
|
1160
1272
|
}
|
|
1161
1273
|
// Called by the login page's own fetch (JSON, not a classic form POST — see `login-page.ts` for
|
|
@@ -1164,10 +1276,12 @@ var DurableAuthController = class _DurableAuthController {
|
|
|
1164
1276
|
// (unknown user, wrong password, throwing hook) — no user-enumeration.
|
|
1165
1277
|
async login(body, request, response) {
|
|
1166
1278
|
const auth = this.requireAuth();
|
|
1279
|
+
if (!auth.modes.includes("login")) throw new NotFoundException3();
|
|
1167
1280
|
if (!isString(body?.username) || !isString(body?.password)) {
|
|
1168
1281
|
throw new BadRequestException("Body must include string `username` and `password`.");
|
|
1169
1282
|
}
|
|
1170
|
-
const
|
|
1283
|
+
const { username, password } = body;
|
|
1284
|
+
const user = await this.runHook("login", () => auth.login?.(username, password) ?? null);
|
|
1171
1285
|
if (!user) throw new UnauthorizedException({
|
|
1172
1286
|
message: "Invalid username or password."
|
|
1173
1287
|
});
|
|
@@ -1180,6 +1294,20 @@ var DurableAuthController = class _DurableAuthController {
|
|
|
1180
1294
|
redirectTo: sanitizeReturnTo(body.returnTo, this.basePath)
|
|
1181
1295
|
};
|
|
1182
1296
|
}
|
|
1297
|
+
// Mode A: the host frontend (carrying its own auth) POSTs here; the host hook validates the raw
|
|
1298
|
+
// request and returns the session user, or `null` to deny. This is the SSO path — the host's own
|
|
1299
|
+
// guard decides, and no credential ever reaches this library.
|
|
1300
|
+
async session(request, response) {
|
|
1301
|
+
const auth = this.requireAuth();
|
|
1302
|
+
if (!auth.modes.includes("session")) throw new NotFoundException3();
|
|
1303
|
+
const user = await this.runHook("session", () => auth.session?.(request) ?? null);
|
|
1304
|
+
if (!user) throw new UnauthorizedException();
|
|
1305
|
+
issueSessionCookie(user, {
|
|
1306
|
+
auth,
|
|
1307
|
+
request,
|
|
1308
|
+
response
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1183
1311
|
// Plain `GET` (not `POST`): logging out only ever destroys the CALLER's own session, so it's
|
|
1184
1312
|
// safe/idempotent from the caller's perspective — a simple `<a href>` (no JS, no CSRF token)
|
|
1185
1313
|
// works. Non-passthrough `@Res()`: this handler owns the response outright (clears the cookie,
|
|
@@ -1189,30 +1317,30 @@ var DurableAuthController = class _DurableAuthController {
|
|
|
1189
1317
|
request,
|
|
1190
1318
|
response
|
|
1191
1319
|
});
|
|
1192
|
-
redirectRaw(response, `${this.basePath}/login`);
|
|
1320
|
+
redirectRaw(response, this.auth?.modes.includes("login") ? `${this.basePath}/login` : this.basePath);
|
|
1193
1321
|
}
|
|
1194
1322
|
requireAuth() {
|
|
1195
1323
|
if (!this.auth) throw new NotFoundException3();
|
|
1196
1324
|
return this.auth;
|
|
1197
1325
|
}
|
|
1198
1326
|
/**
|
|
1199
|
-
* Run
|
|
1200
|
-
* warn-logged once, so a buggy hook never 500s the endpoint into a
|
|
1201
|
-
* the logs on repeated bad attempts.
|
|
1327
|
+
* Run a host hook (`login` or `session`) defensively: a throw is treated as a denial (uniform
|
|
1328
|
+
* failure) and warn-logged once PER KIND, so a buggy hook never 500s the endpoint into a
|
|
1329
|
+
* stack-trace leak nor floods the logs on repeated bad attempts.
|
|
1202
1330
|
*/
|
|
1203
|
-
async
|
|
1331
|
+
async runHook(kind, run) {
|
|
1204
1332
|
try {
|
|
1205
|
-
return await
|
|
1333
|
+
return await run() ?? null;
|
|
1206
1334
|
} catch (error) {
|
|
1207
|
-
if (!this.
|
|
1208
|
-
this.
|
|
1209
|
-
this.logger.warn(`dashboardAuth
|
|
1335
|
+
if (!this.warnedHooks.has(kind)) {
|
|
1336
|
+
this.warnedHooks.add(kind);
|
|
1337
|
+
this.logger.warn(`dashboardAuth ${kind} hook threw; treating as denial. ${error instanceof Error ? error.message : String(error)}`);
|
|
1210
1338
|
}
|
|
1211
1339
|
return null;
|
|
1212
1340
|
}
|
|
1213
1341
|
}
|
|
1214
1342
|
};
|
|
1215
|
-
|
|
1343
|
+
_ts_decorate6([
|
|
1216
1344
|
Get3("login"),
|
|
1217
1345
|
Header3("Content-Type", "text/html; charset=utf-8"),
|
|
1218
1346
|
Header3("Cache-Control", "no-store, must-revalidate"),
|
|
@@ -1220,7 +1348,7 @@ _ts_decorate5([
|
|
|
1220
1348
|
_ts_metadata4("design:paramtypes", []),
|
|
1221
1349
|
_ts_metadata4("design:returntype", String)
|
|
1222
1350
|
], DurableAuthController.prototype, "loginPage", null);
|
|
1223
|
-
|
|
1351
|
+
_ts_decorate6([
|
|
1224
1352
|
Post2("login"),
|
|
1225
1353
|
HttpCode(200),
|
|
1226
1354
|
_ts_param4(0, Body2()),
|
|
@@ -1236,7 +1364,21 @@ _ts_decorate5([
|
|
|
1236
1364
|
]),
|
|
1237
1365
|
_ts_metadata4("design:returntype", Promise)
|
|
1238
1366
|
], DurableAuthController.prototype, "login", null);
|
|
1239
|
-
|
|
1367
|
+
_ts_decorate6([
|
|
1368
|
+
Post2("session"),
|
|
1369
|
+
HttpCode(204),
|
|
1370
|
+
_ts_param4(0, Req()),
|
|
1371
|
+
_ts_param4(1, Res({
|
|
1372
|
+
passthrough: true
|
|
1373
|
+
})),
|
|
1374
|
+
_ts_metadata4("design:type", Function),
|
|
1375
|
+
_ts_metadata4("design:paramtypes", [
|
|
1376
|
+
Object,
|
|
1377
|
+
Object
|
|
1378
|
+
]),
|
|
1379
|
+
_ts_metadata4("design:returntype", Promise)
|
|
1380
|
+
], DurableAuthController.prototype, "session", null);
|
|
1381
|
+
_ts_decorate6([
|
|
1240
1382
|
Get3("logout"),
|
|
1241
1383
|
_ts_param4(0, Req()),
|
|
1242
1384
|
_ts_param4(1, Res()),
|
|
@@ -1247,7 +1389,7 @@ _ts_decorate5([
|
|
|
1247
1389
|
]),
|
|
1248
1390
|
_ts_metadata4("design:returntype", void 0)
|
|
1249
1391
|
], DurableAuthController.prototype, "logout", null);
|
|
1250
|
-
DurableAuthController =
|
|
1392
|
+
DurableAuthController = _ts_decorate6([
|
|
1251
1393
|
Controller3(),
|
|
1252
1394
|
_ts_param4(0, Optional2()),
|
|
1253
1395
|
_ts_param4(0, Inject3(DASHBOARD_AUTH)),
|
|
@@ -1266,13 +1408,13 @@ import { RouterModule } from "@nestjs/core";
|
|
|
1266
1408
|
|
|
1267
1409
|
// src/server/durable-session.guard.ts
|
|
1268
1410
|
import { Inject as Inject4, Injectable as Injectable2, UnauthorizedException as UnauthorizedException2 } from "@nestjs/common";
|
|
1269
|
-
function
|
|
1411
|
+
function _ts_decorate7(decorators, target, key, desc) {
|
|
1270
1412
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1271
1413
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1272
1414
|
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
1415
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1274
1416
|
}
|
|
1275
|
-
__name(
|
|
1417
|
+
__name(_ts_decorate7, "_ts_decorate");
|
|
1276
1418
|
function _ts_metadata5(k, v) {
|
|
1277
1419
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1278
1420
|
}
|
|
@@ -1293,20 +1435,37 @@ var DurableUiSessionGuard = class {
|
|
|
1293
1435
|
this.auth = auth;
|
|
1294
1436
|
this.basePath = basePath;
|
|
1295
1437
|
}
|
|
1296
|
-
canActivate(context) {
|
|
1438
|
+
async canActivate(context) {
|
|
1297
1439
|
if (!this.auth) return true;
|
|
1440
|
+
const auth = this.auth;
|
|
1298
1441
|
const http = context.switchToHttp();
|
|
1299
1442
|
const request = http.getRequest();
|
|
1300
|
-
const session = readSessionFromRequest(
|
|
1443
|
+
const session = readSessionFromRequest(auth, request);
|
|
1301
1444
|
if (!session) {
|
|
1302
|
-
|
|
1303
|
-
|
|
1445
|
+
this.denyUnauthenticated(auth, request);
|
|
1446
|
+
}
|
|
1447
|
+
if (!await maybeRenewSession(auth, session, request, http.getResponse())) {
|
|
1448
|
+
this.denyUnauthenticated(auth, request);
|
|
1304
1449
|
}
|
|
1305
|
-
maybeRenewSession(this.auth, session, request, http.getResponse());
|
|
1306
1450
|
return true;
|
|
1307
1451
|
}
|
|
1452
|
+
/**
|
|
1453
|
+
* A revoked session (the host's `revalidate` hook says no) is denied exactly like an absent
|
|
1454
|
+
* one — by design, there is nothing left to distinguish "never had a session" from "had one,
|
|
1455
|
+
* then lost it" once the cookie is cleared, so both get the same Mode-aware treatment.
|
|
1456
|
+
*
|
|
1457
|
+
* Takes `auth` explicitly rather than reading `this.auth`: `canActivate`'s `!this.auth` early
|
|
1458
|
+
* return narrows the field within that method only — TS doesn't carry it across a method call
|
|
1459
|
+
* — so callers pass the already-narrowed value instead of this method re-deriving (or
|
|
1460
|
+
* asserting away) the nullability.
|
|
1461
|
+
*/
|
|
1462
|
+
denyUnauthenticated(auth, request) {
|
|
1463
|
+
if (!auth.modes.includes("login")) throw new DashboardSessionRequiredException(this.basePath);
|
|
1464
|
+
const returnTo = encodeURIComponent(originalRequestUrl(request));
|
|
1465
|
+
throw new DashboardLoginRedirectException(`${this.basePath}/login?returnTo=${returnTo}`);
|
|
1466
|
+
}
|
|
1308
1467
|
};
|
|
1309
|
-
DurableUiSessionGuard =
|
|
1468
|
+
DurableUiSessionGuard = _ts_decorate7([
|
|
1310
1469
|
Injectable2(),
|
|
1311
1470
|
_ts_param5(0, Inject4(DASHBOARD_AUTH)),
|
|
1312
1471
|
_ts_param5(1, Inject4(DASHBOARD_BASE_PATH)),
|
|
@@ -1324,17 +1483,33 @@ var DurableApiSessionGuard = class {
|
|
|
1324
1483
|
constructor(auth) {
|
|
1325
1484
|
this.auth = auth;
|
|
1326
1485
|
}
|
|
1327
|
-
canActivate(context) {
|
|
1486
|
+
async canActivate(context) {
|
|
1328
1487
|
if (!this.auth) return true;
|
|
1488
|
+
const auth = this.auth;
|
|
1329
1489
|
const http = context.switchToHttp();
|
|
1330
1490
|
const request = http.getRequest();
|
|
1331
|
-
const session = readSessionFromRequest(
|
|
1332
|
-
if (!session) throw
|
|
1333
|
-
maybeRenewSession(
|
|
1491
|
+
const session = readSessionFromRequest(auth, request);
|
|
1492
|
+
if (!session) throw this.unauthorized(auth);
|
|
1493
|
+
if (!await maybeRenewSession(auth, session, request, http.getResponse())) {
|
|
1494
|
+
throw this.unauthorized(auth);
|
|
1495
|
+
}
|
|
1334
1496
|
return true;
|
|
1335
1497
|
}
|
|
1498
|
+
/**
|
|
1499
|
+
* A bare 401 body carrying `{ auth: { modes } }` — mirrors `@dudousxd/nestjs-telescope`'s
|
|
1500
|
+
* dashboardAuth 401. The API is fetched (never navigated), so this is the ONLY way the console
|
|
1501
|
+
* SPA (`durable-client.ts`) learns which auth surface to send the operator to on a mid-session
|
|
1502
|
+
* 401; `DurableUiSessionGuard`'s redirect/session-required exceptions above don't reach it.
|
|
1503
|
+
*/
|
|
1504
|
+
unauthorized(auth) {
|
|
1505
|
+
return new UnauthorizedException2({
|
|
1506
|
+
auth: {
|
|
1507
|
+
modes: auth.modes
|
|
1508
|
+
}
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1336
1511
|
};
|
|
1337
|
-
DurableApiSessionGuard =
|
|
1512
|
+
DurableApiSessionGuard = _ts_decorate7([
|
|
1338
1513
|
Injectable2(),
|
|
1339
1514
|
_ts_param5(0, Inject4(DASHBOARD_AUTH)),
|
|
1340
1515
|
_ts_metadata5("design:type", Function),
|
|
@@ -1344,13 +1519,13 @@ DurableApiSessionGuard = _ts_decorate6([
|
|
|
1344
1519
|
], DurableApiSessionGuard);
|
|
1345
1520
|
|
|
1346
1521
|
// src/server/durable-dashboard.module.ts
|
|
1347
|
-
function
|
|
1522
|
+
function _ts_decorate8(decorators, target, key, desc) {
|
|
1348
1523
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1349
1524
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1350
1525
|
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
1526
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1352
1527
|
}
|
|
1353
|
-
__name(
|
|
1528
|
+
__name(_ts_decorate8, "_ts_decorate");
|
|
1354
1529
|
var GUARDS_METADATA = "__guards__";
|
|
1355
1530
|
function normalize(path) {
|
|
1356
1531
|
return `/${path.replace(/^\/+|\/+$/g, "")}`;
|
|
@@ -1392,7 +1567,7 @@ var DurableApiModule = class _DurableApiModule {
|
|
|
1392
1567
|
};
|
|
1393
1568
|
}
|
|
1394
1569
|
};
|
|
1395
|
-
DurableApiModule =
|
|
1570
|
+
DurableApiModule = _ts_decorate8([
|
|
1396
1571
|
Module({})
|
|
1397
1572
|
], DurableApiModule);
|
|
1398
1573
|
var DurableDashboardModule = class _DurableDashboardModule {
|
|
@@ -1478,7 +1653,7 @@ var DurableDashboardModule = class _DurableDashboardModule {
|
|
|
1478
1653
|
};
|
|
1479
1654
|
}
|
|
1480
1655
|
};
|
|
1481
|
-
DurableDashboardModule =
|
|
1656
|
+
DurableDashboardModule = _ts_decorate8([
|
|
1482
1657
|
Module({})
|
|
1483
1658
|
], DurableDashboardModule);
|
|
1484
1659
|
export {
|