@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.cjs
CHANGED
|
@@ -552,7 +552,7 @@ DurableApiController = _ts_decorate2([
|
|
|
552
552
|
], DurableApiController);
|
|
553
553
|
|
|
554
554
|
// src/server/durable-auth.controller.ts
|
|
555
|
-
var
|
|
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
|
-
|
|
581
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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,44 @@ function readSessionFromRequest(auth, request) {
|
|
|
993
1048
|
});
|
|
994
1049
|
}
|
|
995
1050
|
__name(readSessionFromRequest, "readSessionFromRequest");
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1051
|
+
var inFlightRevalidations = /* @__PURE__ */ new Map();
|
|
1052
|
+
function revalidateOnce(auth, user) {
|
|
1053
|
+
const revalidate = auth.revalidate;
|
|
1054
|
+
if (!revalidate) return Promise.resolve(true);
|
|
1055
|
+
const key = `${auth.secret}:${user.id}`;
|
|
1056
|
+
const inFlight = inFlightRevalidations.get(key);
|
|
1057
|
+
if (inFlight) return inFlight;
|
|
1058
|
+
const call = Promise.resolve().then(() => revalidate(user)).catch(() => false).finally(() => {
|
|
1059
|
+
inFlightRevalidations.delete(key);
|
|
1060
|
+
});
|
|
1061
|
+
inFlightRevalidations.set(key, call);
|
|
1062
|
+
return call;
|
|
1063
|
+
}
|
|
1064
|
+
__name(revalidateOnce, "revalidateOnce");
|
|
1065
|
+
async function maybeRenewSession(auth, session, request, response, now = Date.now()) {
|
|
1066
|
+
if (now - session.iat <= auth.ttlMs / 2) return true;
|
|
1067
|
+
const user = {
|
|
1000
1068
|
id: session.sub,
|
|
1001
1069
|
...session.name !== void 0 ? {
|
|
1002
1070
|
name: session.name
|
|
1003
1071
|
} : {},
|
|
1004
1072
|
roles: session.roles
|
|
1005
|
-
}
|
|
1073
|
+
};
|
|
1074
|
+
const allowed = await revalidateOnce(auth, user);
|
|
1075
|
+
if (!allowed) {
|
|
1076
|
+
clearSessionCookie({
|
|
1077
|
+
request,
|
|
1078
|
+
response
|
|
1079
|
+
});
|
|
1080
|
+
return false;
|
|
1081
|
+
}
|
|
1082
|
+
issueSessionCookie(user, {
|
|
1006
1083
|
auth,
|
|
1007
1084
|
request,
|
|
1008
1085
|
response,
|
|
1009
1086
|
now
|
|
1010
1087
|
});
|
|
1088
|
+
return true;
|
|
1011
1089
|
}
|
|
1012
1090
|
__name(maybeRenewSession, "maybeRenewSession");
|
|
1013
1091
|
|
|
@@ -1015,7 +1093,7 @@ __name(maybeRenewSession, "maybeRenewSession");
|
|
|
1015
1093
|
var import_node_fs = require("fs");
|
|
1016
1094
|
var import_node_path = require("path");
|
|
1017
1095
|
var import_node_url = require("url");
|
|
1018
|
-
var
|
|
1096
|
+
var import_common5 = require("@nestjs/common");
|
|
1019
1097
|
|
|
1020
1098
|
// src/server/auth/login-redirect.exception.ts
|
|
1021
1099
|
var import_common3 = require("@nestjs/common");
|
|
@@ -1050,7 +1128,8 @@ DashboardLoginRedirectFilter = _ts_decorate3([
|
|
|
1050
1128
|
(0, import_common3.Catch)(DashboardLoginRedirectException)
|
|
1051
1129
|
], DashboardLoginRedirectFilter);
|
|
1052
1130
|
|
|
1053
|
-
// src/server/
|
|
1131
|
+
// src/server/auth/session-required.exception.ts
|
|
1132
|
+
var import_common4 = require("@nestjs/common");
|
|
1054
1133
|
function _ts_decorate4(decorators, target, key, desc) {
|
|
1055
1134
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1056
1135
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -1058,6 +1137,38 @@ function _ts_decorate4(decorators, target, key, desc) {
|
|
|
1058
1137
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1059
1138
|
}
|
|
1060
1139
|
__name(_ts_decorate4, "_ts_decorate");
|
|
1140
|
+
var DashboardSessionRequiredException = class extends import_common4.HttpException {
|
|
1141
|
+
static {
|
|
1142
|
+
__name(this, "DashboardSessionRequiredException");
|
|
1143
|
+
}
|
|
1144
|
+
basePath;
|
|
1145
|
+
constructor(basePath) {
|
|
1146
|
+
super({
|
|
1147
|
+
basePath
|
|
1148
|
+
}, import_common4.HttpStatus.UNAUTHORIZED), this.basePath = basePath;
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
var DashboardSessionRequiredFilter = class {
|
|
1152
|
+
static {
|
|
1153
|
+
__name(this, "DashboardSessionRequiredFilter");
|
|
1154
|
+
}
|
|
1155
|
+
catch(exception, host) {
|
|
1156
|
+
const response = host.switchToHttp().getResponse();
|
|
1157
|
+
writeHtmlRaw(response, renderSessionRequiredPage(exception.basePath), import_common4.HttpStatus.UNAUTHORIZED);
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
DashboardSessionRequiredFilter = _ts_decorate4([
|
|
1161
|
+
(0, import_common4.Catch)(DashboardSessionRequiredException)
|
|
1162
|
+
], DashboardSessionRequiredFilter);
|
|
1163
|
+
|
|
1164
|
+
// src/server/durable-ui.controller.ts
|
|
1165
|
+
function _ts_decorate5(decorators, target, key, desc) {
|
|
1166
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1167
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1168
|
+
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;
|
|
1169
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1170
|
+
}
|
|
1171
|
+
__name(_ts_decorate5, "_ts_decorate");
|
|
1061
1172
|
function _ts_metadata3(k, v) {
|
|
1062
1173
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1063
1174
|
}
|
|
@@ -1100,7 +1211,7 @@ var DurableUiController = class {
|
|
|
1100
1211
|
index() {
|
|
1101
1212
|
const indexPath = (0, import_node_path.join)(this.dir, "index.html");
|
|
1102
1213
|
if (!(0, import_node_fs.existsSync)(indexPath)) {
|
|
1103
|
-
throw new
|
|
1214
|
+
throw new import_common5.NotFoundException("Dashboard is not built. Run the package build.");
|
|
1104
1215
|
}
|
|
1105
1216
|
const html = (0, import_node_fs.readFileSync)(indexPath, "utf8").replaceAll(`="${BUILD_BASE}/`, `="${this.basePath}/`);
|
|
1106
1217
|
const inject = `<script>window.__DURABLE_BASE__='${this.basePath}';window.__DURABLE_API__='${this.apiBasePath}';</script>`;
|
|
@@ -1108,41 +1219,41 @@ var DurableUiController = class {
|
|
|
1108
1219
|
}
|
|
1109
1220
|
asset(file) {
|
|
1110
1221
|
const safe = (0, import_node_path.basename)(file);
|
|
1111
|
-
if (safe !== file) throw new
|
|
1222
|
+
if (safe !== file) throw new import_common5.NotFoundException();
|
|
1112
1223
|
const root = (0, import_node_path.resolve)(this.dir, "assets");
|
|
1113
1224
|
const assetPath = (0, import_node_path.resolve)(root, safe);
|
|
1114
1225
|
if (!assetPath.startsWith(root + import_node_path.sep) || !(0, import_node_fs.existsSync)(assetPath)) {
|
|
1115
|
-
throw new
|
|
1226
|
+
throw new import_common5.NotFoundException();
|
|
1116
1227
|
}
|
|
1117
1228
|
const type = CONTENT_TYPES[(0, import_node_path.extname)(safe)] ?? "application/octet-stream";
|
|
1118
|
-
return new
|
|
1229
|
+
return new import_common5.StreamableFile((0, import_node_fs.readFileSync)(assetPath), {
|
|
1119
1230
|
type
|
|
1120
1231
|
});
|
|
1121
1232
|
}
|
|
1122
1233
|
};
|
|
1123
|
-
|
|
1124
|
-
(0,
|
|
1125
|
-
(0,
|
|
1126
|
-
(0,
|
|
1234
|
+
_ts_decorate5([
|
|
1235
|
+
(0, import_common5.Get)(),
|
|
1236
|
+
(0, import_common5.Header)("Content-Type", "text/html; charset=utf-8"),
|
|
1237
|
+
(0, import_common5.Header)("Cache-Control", "no-store, must-revalidate"),
|
|
1127
1238
|
_ts_metadata3("design:type", Function),
|
|
1128
1239
|
_ts_metadata3("design:paramtypes", []),
|
|
1129
1240
|
_ts_metadata3("design:returntype", String)
|
|
1130
1241
|
], DurableUiController.prototype, "index", null);
|
|
1131
|
-
|
|
1132
|
-
(0,
|
|
1133
|
-
(0,
|
|
1134
|
-
_ts_param3(0, (0,
|
|
1242
|
+
_ts_decorate5([
|
|
1243
|
+
(0, import_common5.Get)("assets/:file"),
|
|
1244
|
+
(0, import_common5.Header)("Cache-Control", "public, max-age=31536000, immutable"),
|
|
1245
|
+
_ts_param3(0, (0, import_common5.Param)("file")),
|
|
1135
1246
|
_ts_metadata3("design:type", Function),
|
|
1136
1247
|
_ts_metadata3("design:paramtypes", [
|
|
1137
1248
|
String
|
|
1138
1249
|
]),
|
|
1139
|
-
_ts_metadata3("design:returntype", typeof
|
|
1250
|
+
_ts_metadata3("design:returntype", typeof import_common5.StreamableFile === "undefined" ? Object : import_common5.StreamableFile)
|
|
1140
1251
|
], DurableUiController.prototype, "asset", null);
|
|
1141
|
-
DurableUiController =
|
|
1142
|
-
(0,
|
|
1143
|
-
(0,
|
|
1144
|
-
_ts_param3(0, (0,
|
|
1145
|
-
_ts_param3(1, (0,
|
|
1252
|
+
DurableUiController = _ts_decorate5([
|
|
1253
|
+
(0, import_common5.UseFilters)(DashboardLoginRedirectFilter, DashboardSessionRequiredFilter),
|
|
1254
|
+
(0, import_common5.Controller)(),
|
|
1255
|
+
_ts_param3(0, (0, import_common5.Inject)(DASHBOARD_BASE_PATH)),
|
|
1256
|
+
_ts_param3(1, (0, import_common5.Inject)(DASHBOARD_API_PATH)),
|
|
1146
1257
|
_ts_metadata3("design:type", Function),
|
|
1147
1258
|
_ts_metadata3("design:paramtypes", [
|
|
1148
1259
|
String,
|
|
@@ -1151,13 +1262,13 @@ DurableUiController = _ts_decorate4([
|
|
|
1151
1262
|
], DurableUiController);
|
|
1152
1263
|
|
|
1153
1264
|
// src/server/durable-auth.controller.ts
|
|
1154
|
-
function
|
|
1265
|
+
function _ts_decorate6(decorators, target, key, desc) {
|
|
1155
1266
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1156
1267
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1157
1268
|
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
1269
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1159
1270
|
}
|
|
1160
|
-
__name(
|
|
1271
|
+
__name(_ts_decorate6, "_ts_decorate");
|
|
1161
1272
|
function _ts_metadata4(k, v) {
|
|
1162
1273
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1163
1274
|
}
|
|
@@ -1178,9 +1289,9 @@ var DurableAuthController = class _DurableAuthController {
|
|
|
1178
1289
|
}
|
|
1179
1290
|
auth;
|
|
1180
1291
|
basePath;
|
|
1181
|
-
logger = new
|
|
1182
|
-
/** Warn once so a throwing
|
|
1183
|
-
|
|
1292
|
+
logger = new import_common6.Logger(_DurableAuthController.name);
|
|
1293
|
+
/** Warn once per hook kind so a throwing hook doesn't spam logs on every failed attempt. */
|
|
1294
|
+
warnedHooks = /* @__PURE__ */ new Set();
|
|
1184
1295
|
constructor(auth, basePath) {
|
|
1185
1296
|
this.auth = auth;
|
|
1186
1297
|
this.basePath = basePath;
|
|
@@ -1189,7 +1300,8 @@ var DurableAuthController = class _DurableAuthController {
|
|
|
1189
1300
|
// `login-page.ts`), so this response never varies per request and needs no templating beyond
|
|
1190
1301
|
// the developer-controlled `basePath`.
|
|
1191
1302
|
loginPage() {
|
|
1192
|
-
|
|
1303
|
+
const auth = this.requireAuth();
|
|
1304
|
+
if (!auth.modes.includes("login")) throw new import_common6.NotFoundException();
|
|
1193
1305
|
return renderLoginPage(this.basePath);
|
|
1194
1306
|
}
|
|
1195
1307
|
// Called by the login page's own fetch (JSON, not a classic form POST — see `login-page.ts` for
|
|
@@ -1198,11 +1310,13 @@ var DurableAuthController = class _DurableAuthController {
|
|
|
1198
1310
|
// (unknown user, wrong password, throwing hook) — no user-enumeration.
|
|
1199
1311
|
async login(body, request, response) {
|
|
1200
1312
|
const auth = this.requireAuth();
|
|
1313
|
+
if (!auth.modes.includes("login")) throw new import_common6.NotFoundException();
|
|
1201
1314
|
if (!isString(body?.username) || !isString(body?.password)) {
|
|
1202
|
-
throw new
|
|
1315
|
+
throw new import_common6.BadRequestException("Body must include string `username` and `password`.");
|
|
1203
1316
|
}
|
|
1204
|
-
const
|
|
1205
|
-
|
|
1317
|
+
const { username, password } = body;
|
|
1318
|
+
const user = await this.runHook("login", () => auth.login?.(username, password) ?? null);
|
|
1319
|
+
if (!user) throw new import_common6.UnauthorizedException({
|
|
1206
1320
|
message: "Invalid username or password."
|
|
1207
1321
|
});
|
|
1208
1322
|
issueSessionCookie(user, {
|
|
@@ -1214,6 +1328,20 @@ var DurableAuthController = class _DurableAuthController {
|
|
|
1214
1328
|
redirectTo: sanitizeReturnTo(body.returnTo, this.basePath)
|
|
1215
1329
|
};
|
|
1216
1330
|
}
|
|
1331
|
+
// Mode A: the host frontend (carrying its own auth) POSTs here; the host hook validates the raw
|
|
1332
|
+
// request and returns the session user, or `null` to deny. This is the SSO path — the host's own
|
|
1333
|
+
// guard decides, and no credential ever reaches this library.
|
|
1334
|
+
async session(request, response) {
|
|
1335
|
+
const auth = this.requireAuth();
|
|
1336
|
+
if (!auth.modes.includes("session")) throw new import_common6.NotFoundException();
|
|
1337
|
+
const user = await this.runHook("session", () => auth.session?.(request) ?? null);
|
|
1338
|
+
if (!user) throw new import_common6.UnauthorizedException();
|
|
1339
|
+
issueSessionCookie(user, {
|
|
1340
|
+
auth,
|
|
1341
|
+
request,
|
|
1342
|
+
response
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1217
1345
|
// Plain `GET` (not `POST`): logging out only ever destroys the CALLER's own session, so it's
|
|
1218
1346
|
// safe/idempotent from the caller's perspective — a simple `<a href>` (no JS, no CSRF token)
|
|
1219
1347
|
// works. Non-passthrough `@Res()`: this handler owns the response outright (clears the cookie,
|
|
@@ -1223,43 +1351,43 @@ var DurableAuthController = class _DurableAuthController {
|
|
|
1223
1351
|
request,
|
|
1224
1352
|
response
|
|
1225
1353
|
});
|
|
1226
|
-
redirectRaw(response, `${this.basePath}/login`);
|
|
1354
|
+
redirectRaw(response, this.auth?.modes.includes("login") ? `${this.basePath}/login` : this.basePath);
|
|
1227
1355
|
}
|
|
1228
1356
|
requireAuth() {
|
|
1229
|
-
if (!this.auth) throw new
|
|
1357
|
+
if (!this.auth) throw new import_common6.NotFoundException();
|
|
1230
1358
|
return this.auth;
|
|
1231
1359
|
}
|
|
1232
1360
|
/**
|
|
1233
|
-
* Run
|
|
1234
|
-
* warn-logged once, so a buggy hook never 500s the endpoint into a
|
|
1235
|
-
* the logs on repeated bad attempts.
|
|
1361
|
+
* Run a host hook (`login` or `session`) defensively: a throw is treated as a denial (uniform
|
|
1362
|
+
* failure) and warn-logged once PER KIND, so a buggy hook never 500s the endpoint into a
|
|
1363
|
+
* stack-trace leak nor floods the logs on repeated bad attempts.
|
|
1236
1364
|
*/
|
|
1237
|
-
async
|
|
1365
|
+
async runHook(kind, run) {
|
|
1238
1366
|
try {
|
|
1239
|
-
return await
|
|
1367
|
+
return await run() ?? null;
|
|
1240
1368
|
} catch (error) {
|
|
1241
|
-
if (!this.
|
|
1242
|
-
this.
|
|
1243
|
-
this.logger.warn(`dashboardAuth
|
|
1369
|
+
if (!this.warnedHooks.has(kind)) {
|
|
1370
|
+
this.warnedHooks.add(kind);
|
|
1371
|
+
this.logger.warn(`dashboardAuth ${kind} hook threw; treating as denial. ${error instanceof Error ? error.message : String(error)}`);
|
|
1244
1372
|
}
|
|
1245
1373
|
return null;
|
|
1246
1374
|
}
|
|
1247
1375
|
}
|
|
1248
1376
|
};
|
|
1249
|
-
|
|
1250
|
-
(0,
|
|
1251
|
-
(0,
|
|
1252
|
-
(0,
|
|
1377
|
+
_ts_decorate6([
|
|
1378
|
+
(0, import_common6.Get)("login"),
|
|
1379
|
+
(0, import_common6.Header)("Content-Type", "text/html; charset=utf-8"),
|
|
1380
|
+
(0, import_common6.Header)("Cache-Control", "no-store, must-revalidate"),
|
|
1253
1381
|
_ts_metadata4("design:type", Function),
|
|
1254
1382
|
_ts_metadata4("design:paramtypes", []),
|
|
1255
1383
|
_ts_metadata4("design:returntype", String)
|
|
1256
1384
|
], DurableAuthController.prototype, "loginPage", null);
|
|
1257
|
-
|
|
1258
|
-
(0,
|
|
1259
|
-
(0,
|
|
1260
|
-
_ts_param4(0, (0,
|
|
1261
|
-
_ts_param4(1, (0,
|
|
1262
|
-
_ts_param4(2, (0,
|
|
1385
|
+
_ts_decorate6([
|
|
1386
|
+
(0, import_common6.Post)("login"),
|
|
1387
|
+
(0, import_common6.HttpCode)(200),
|
|
1388
|
+
_ts_param4(0, (0, import_common6.Body)()),
|
|
1389
|
+
_ts_param4(1, (0, import_common6.Req)()),
|
|
1390
|
+
_ts_param4(2, (0, import_common6.Res)({
|
|
1263
1391
|
passthrough: true
|
|
1264
1392
|
})),
|
|
1265
1393
|
_ts_metadata4("design:type", Function),
|
|
@@ -1270,10 +1398,24 @@ _ts_decorate5([
|
|
|
1270
1398
|
]),
|
|
1271
1399
|
_ts_metadata4("design:returntype", Promise)
|
|
1272
1400
|
], DurableAuthController.prototype, "login", null);
|
|
1273
|
-
|
|
1274
|
-
(0,
|
|
1275
|
-
|
|
1276
|
-
_ts_param4(
|
|
1401
|
+
_ts_decorate6([
|
|
1402
|
+
(0, import_common6.Post)("session"),
|
|
1403
|
+
(0, import_common6.HttpCode)(204),
|
|
1404
|
+
_ts_param4(0, (0, import_common6.Req)()),
|
|
1405
|
+
_ts_param4(1, (0, import_common6.Res)({
|
|
1406
|
+
passthrough: true
|
|
1407
|
+
})),
|
|
1408
|
+
_ts_metadata4("design:type", Function),
|
|
1409
|
+
_ts_metadata4("design:paramtypes", [
|
|
1410
|
+
Object,
|
|
1411
|
+
Object
|
|
1412
|
+
]),
|
|
1413
|
+
_ts_metadata4("design:returntype", Promise)
|
|
1414
|
+
], DurableAuthController.prototype, "session", null);
|
|
1415
|
+
_ts_decorate6([
|
|
1416
|
+
(0, import_common6.Get)("logout"),
|
|
1417
|
+
_ts_param4(0, (0, import_common6.Req)()),
|
|
1418
|
+
_ts_param4(1, (0, import_common6.Res)()),
|
|
1277
1419
|
_ts_metadata4("design:type", Function),
|
|
1278
1420
|
_ts_metadata4("design:paramtypes", [
|
|
1279
1421
|
Object,
|
|
@@ -1281,11 +1423,11 @@ _ts_decorate5([
|
|
|
1281
1423
|
]),
|
|
1282
1424
|
_ts_metadata4("design:returntype", void 0)
|
|
1283
1425
|
], DurableAuthController.prototype, "logout", null);
|
|
1284
|
-
DurableAuthController =
|
|
1285
|
-
(0,
|
|
1286
|
-
_ts_param4(0, (0,
|
|
1287
|
-
_ts_param4(0, (0,
|
|
1288
|
-
_ts_param4(1, (0,
|
|
1426
|
+
DurableAuthController = _ts_decorate6([
|
|
1427
|
+
(0, import_common6.Controller)(),
|
|
1428
|
+
_ts_param4(0, (0, import_common6.Optional)()),
|
|
1429
|
+
_ts_param4(0, (0, import_common6.Inject)(DASHBOARD_AUTH)),
|
|
1430
|
+
_ts_param4(1, (0, import_common6.Inject)(DASHBOARD_BASE_PATH)),
|
|
1289
1431
|
_ts_metadata4("design:type", Function),
|
|
1290
1432
|
_ts_metadata4("design:paramtypes", [
|
|
1291
1433
|
Object,
|
|
@@ -1295,18 +1437,18 @@ DurableAuthController = _ts_decorate5([
|
|
|
1295
1437
|
|
|
1296
1438
|
// src/server/durable-dashboard.module.ts
|
|
1297
1439
|
var import_reflect_metadata = require("reflect-metadata");
|
|
1298
|
-
var
|
|
1440
|
+
var import_common8 = require("@nestjs/common");
|
|
1299
1441
|
var import_core = require("@nestjs/core");
|
|
1300
1442
|
|
|
1301
1443
|
// src/server/durable-session.guard.ts
|
|
1302
|
-
var
|
|
1303
|
-
function
|
|
1444
|
+
var import_common7 = require("@nestjs/common");
|
|
1445
|
+
function _ts_decorate7(decorators, target, key, desc) {
|
|
1304
1446
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1305
1447
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1306
1448
|
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
1449
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1308
1450
|
}
|
|
1309
|
-
__name(
|
|
1451
|
+
__name(_ts_decorate7, "_ts_decorate");
|
|
1310
1452
|
function _ts_metadata5(k, v) {
|
|
1311
1453
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1312
1454
|
}
|
|
@@ -1327,23 +1469,40 @@ var DurableUiSessionGuard = class {
|
|
|
1327
1469
|
this.auth = auth;
|
|
1328
1470
|
this.basePath = basePath;
|
|
1329
1471
|
}
|
|
1330
|
-
canActivate(context) {
|
|
1472
|
+
async canActivate(context) {
|
|
1331
1473
|
if (!this.auth) return true;
|
|
1474
|
+
const auth = this.auth;
|
|
1332
1475
|
const http = context.switchToHttp();
|
|
1333
1476
|
const request = http.getRequest();
|
|
1334
|
-
const session = readSessionFromRequest(
|
|
1477
|
+
const session = readSessionFromRequest(auth, request);
|
|
1335
1478
|
if (!session) {
|
|
1336
|
-
|
|
1337
|
-
|
|
1479
|
+
this.denyUnauthenticated(auth, request);
|
|
1480
|
+
}
|
|
1481
|
+
if (!await maybeRenewSession(auth, session, request, http.getResponse())) {
|
|
1482
|
+
this.denyUnauthenticated(auth, request);
|
|
1338
1483
|
}
|
|
1339
|
-
maybeRenewSession(this.auth, session, request, http.getResponse());
|
|
1340
1484
|
return true;
|
|
1341
1485
|
}
|
|
1486
|
+
/**
|
|
1487
|
+
* A revoked session (the host's `revalidate` hook says no) is denied exactly like an absent
|
|
1488
|
+
* one — by design, there is nothing left to distinguish "never had a session" from "had one,
|
|
1489
|
+
* then lost it" once the cookie is cleared, so both get the same Mode-aware treatment.
|
|
1490
|
+
*
|
|
1491
|
+
* Takes `auth` explicitly rather than reading `this.auth`: `canActivate`'s `!this.auth` early
|
|
1492
|
+
* return narrows the field within that method only — TS doesn't carry it across a method call
|
|
1493
|
+
* — so callers pass the already-narrowed value instead of this method re-deriving (or
|
|
1494
|
+
* asserting away) the nullability.
|
|
1495
|
+
*/
|
|
1496
|
+
denyUnauthenticated(auth, request) {
|
|
1497
|
+
if (!auth.modes.includes("login")) throw new DashboardSessionRequiredException(this.basePath);
|
|
1498
|
+
const returnTo = encodeURIComponent(originalRequestUrl(request));
|
|
1499
|
+
throw new DashboardLoginRedirectException(`${this.basePath}/login?returnTo=${returnTo}`);
|
|
1500
|
+
}
|
|
1342
1501
|
};
|
|
1343
|
-
DurableUiSessionGuard =
|
|
1344
|
-
(0,
|
|
1345
|
-
_ts_param5(0, (0,
|
|
1346
|
-
_ts_param5(1, (0,
|
|
1502
|
+
DurableUiSessionGuard = _ts_decorate7([
|
|
1503
|
+
(0, import_common7.Injectable)(),
|
|
1504
|
+
_ts_param5(0, (0, import_common7.Inject)(DASHBOARD_AUTH)),
|
|
1505
|
+
_ts_param5(1, (0, import_common7.Inject)(DASHBOARD_BASE_PATH)),
|
|
1347
1506
|
_ts_metadata5("design:type", Function),
|
|
1348
1507
|
_ts_metadata5("design:paramtypes", [
|
|
1349
1508
|
Object,
|
|
@@ -1358,19 +1517,35 @@ var DurableApiSessionGuard = class {
|
|
|
1358
1517
|
constructor(auth) {
|
|
1359
1518
|
this.auth = auth;
|
|
1360
1519
|
}
|
|
1361
|
-
canActivate(context) {
|
|
1520
|
+
async canActivate(context) {
|
|
1362
1521
|
if (!this.auth) return true;
|
|
1522
|
+
const auth = this.auth;
|
|
1363
1523
|
const http = context.switchToHttp();
|
|
1364
1524
|
const request = http.getRequest();
|
|
1365
|
-
const session = readSessionFromRequest(
|
|
1366
|
-
if (!session) throw
|
|
1367
|
-
maybeRenewSession(
|
|
1525
|
+
const session = readSessionFromRequest(auth, request);
|
|
1526
|
+
if (!session) throw this.unauthorized(auth);
|
|
1527
|
+
if (!await maybeRenewSession(auth, session, request, http.getResponse())) {
|
|
1528
|
+
throw this.unauthorized(auth);
|
|
1529
|
+
}
|
|
1368
1530
|
return true;
|
|
1369
1531
|
}
|
|
1532
|
+
/**
|
|
1533
|
+
* A bare 401 body carrying `{ auth: { modes } }` — mirrors `@dudousxd/nestjs-telescope`'s
|
|
1534
|
+
* dashboardAuth 401. The API is fetched (never navigated), so this is the ONLY way the console
|
|
1535
|
+
* SPA (`durable-client.ts`) learns which auth surface to send the operator to on a mid-session
|
|
1536
|
+
* 401; `DurableUiSessionGuard`'s redirect/session-required exceptions above don't reach it.
|
|
1537
|
+
*/
|
|
1538
|
+
unauthorized(auth) {
|
|
1539
|
+
return new import_common7.UnauthorizedException({
|
|
1540
|
+
auth: {
|
|
1541
|
+
modes: auth.modes
|
|
1542
|
+
}
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1370
1545
|
};
|
|
1371
|
-
DurableApiSessionGuard =
|
|
1372
|
-
(0,
|
|
1373
|
-
_ts_param5(0, (0,
|
|
1546
|
+
DurableApiSessionGuard = _ts_decorate7([
|
|
1547
|
+
(0, import_common7.Injectable)(),
|
|
1548
|
+
_ts_param5(0, (0, import_common7.Inject)(DASHBOARD_AUTH)),
|
|
1374
1549
|
_ts_metadata5("design:type", Function),
|
|
1375
1550
|
_ts_metadata5("design:paramtypes", [
|
|
1376
1551
|
Object
|
|
@@ -1378,13 +1553,13 @@ DurableApiSessionGuard = _ts_decorate6([
|
|
|
1378
1553
|
], DurableApiSessionGuard);
|
|
1379
1554
|
|
|
1380
1555
|
// src/server/durable-dashboard.module.ts
|
|
1381
|
-
function
|
|
1556
|
+
function _ts_decorate8(decorators, target, key, desc) {
|
|
1382
1557
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1383
1558
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1384
1559
|
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
1560
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1386
1561
|
}
|
|
1387
|
-
__name(
|
|
1562
|
+
__name(_ts_decorate8, "_ts_decorate");
|
|
1388
1563
|
var GUARDS_METADATA = "__guards__";
|
|
1389
1564
|
function normalize(path) {
|
|
1390
1565
|
return `/${path.replace(/^\/+|\/+$/g, "")}`;
|
|
@@ -1426,8 +1601,8 @@ var DurableApiModule = class _DurableApiModule {
|
|
|
1426
1601
|
};
|
|
1427
1602
|
}
|
|
1428
1603
|
};
|
|
1429
|
-
DurableApiModule =
|
|
1430
|
-
(0,
|
|
1604
|
+
DurableApiModule = _ts_decorate8([
|
|
1605
|
+
(0, import_common8.Module)({})
|
|
1431
1606
|
], DurableApiModule);
|
|
1432
1607
|
var DurableDashboardModule = class _DurableDashboardModule {
|
|
1433
1608
|
static {
|
|
@@ -1512,8 +1687,8 @@ var DurableDashboardModule = class _DurableDashboardModule {
|
|
|
1512
1687
|
};
|
|
1513
1688
|
}
|
|
1514
1689
|
};
|
|
1515
|
-
DurableDashboardModule =
|
|
1516
|
-
(0,
|
|
1690
|
+
DurableDashboardModule = _ts_decorate8([
|
|
1691
|
+
(0, import_common8.Module)({})
|
|
1517
1692
|
], DurableDashboardModule);
|
|
1518
1693
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1519
1694
|
0 && (module.exports = {
|