@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.
- package/dist/client/durable-client.d.ts.map +1 -1
- package/dist/client/durable-client.js +7 -0
- package/dist/client/durable-client.js.map +1 -1
- package/dist/server/index.cjs +257 -103
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +101 -40
- package/dist/server/index.d.ts +101 -40
- package/dist/server/index.js +212 -58
- package/dist/server/index.js.map +1 -1
- package/dist/spa/assets/{index-DwESVU5p.js → index-BgWYZ0wi.js} +1 -1
- package/dist/spa/assets/{index-vYlHyRGm.js → index-tjU3_joS.js} +1 -1
- package/dist/spa/assets/{preview-Da-rD4KB.js → preview-CoXYGpK-.js} +1 -1
- package/dist/spa/index.html +2 -2
- package/dist/spa/preview.html +2 -2
- package/package.json +2 -2
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,37 @@ function readSessionFromRequest(auth, request) {
|
|
|
993
1048
|
});
|
|
994
1049
|
}
|
|
995
1050
|
__name(readSessionFromRequest, "readSessionFromRequest");
|
|
996
|
-
function maybeRenewSession(auth, session, request, response) {
|
|
997
|
-
|
|
998
|
-
|
|
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
|
|
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/
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1222
|
+
return new import_common5.StreamableFile((0, import_node_fs.readFileSync)(assetPath), {
|
|
1119
1223
|
type
|
|
1120
1224
|
});
|
|
1121
1225
|
}
|
|
1122
1226
|
};
|
|
1123
|
-
|
|
1124
|
-
(0,
|
|
1125
|
-
(0,
|
|
1126
|
-
(0,
|
|
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
|
-
|
|
1132
|
-
(0,
|
|
1133
|
-
(0,
|
|
1134
|
-
_ts_param3(0, (0,
|
|
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
|
|
1243
|
+
_ts_metadata3("design:returntype", typeof import_common5.StreamableFile === "undefined" ? Object : import_common5.StreamableFile)
|
|
1140
1244
|
], DurableUiController.prototype, "asset", null);
|
|
1141
|
-
DurableUiController =
|
|
1142
|
-
(0,
|
|
1143
|
-
(0,
|
|
1144
|
-
_ts_param3(0, (0,
|
|
1145
|
-
_ts_param3(1, (0,
|
|
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
|
|
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(
|
|
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
|
|
1182
|
-
/** Warn once so a throwing
|
|
1183
|
-
|
|
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
|
-
|
|
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
|
|
1308
|
+
throw new import_common6.BadRequestException("Body must include string `username` and `password`.");
|
|
1203
1309
|
}
|
|
1204
|
-
const
|
|
1205
|
-
|
|
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
|
|
1350
|
+
if (!this.auth) throw new import_common6.NotFoundException();
|
|
1230
1351
|
return this.auth;
|
|
1231
1352
|
}
|
|
1232
1353
|
/**
|
|
1233
|
-
* Run
|
|
1234
|
-
* warn-logged once, so a buggy hook never 500s the endpoint into a
|
|
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
|
|
1358
|
+
async runHook(kind, run) {
|
|
1238
1359
|
try {
|
|
1239
|
-
return await
|
|
1360
|
+
return await run() ?? null;
|
|
1240
1361
|
} catch (error) {
|
|
1241
|
-
if (!this.
|
|
1242
|
-
this.
|
|
1243
|
-
this.logger.warn(`dashboardAuth
|
|
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
|
-
|
|
1250
|
-
(0,
|
|
1251
|
-
(0,
|
|
1252
|
-
(0,
|
|
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
|
-
|
|
1258
|
-
(0,
|
|
1259
|
-
(0,
|
|
1260
|
-
_ts_param4(0, (0,
|
|
1261
|
-
_ts_param4(1, (0,
|
|
1262
|
-
_ts_param4(2, (0,
|
|
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
|
-
|
|
1274
|
-
(0,
|
|
1275
|
-
|
|
1276
|
-
_ts_param4(
|
|
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 =
|
|
1285
|
-
(0,
|
|
1286
|
-
_ts_param4(0, (0,
|
|
1287
|
-
_ts_param4(0, (0,
|
|
1288
|
-
_ts_param4(1, (0,
|
|
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
|
|
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
|
|
1303
|
-
function
|
|
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(
|
|
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(
|
|
1470
|
+
const session = readSessionFromRequest(auth, request);
|
|
1335
1471
|
if (!session) {
|
|
1336
|
-
|
|
1337
|
-
|
|
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 =
|
|
1344
|
-
(0,
|
|
1345
|
-
_ts_param5(0, (0,
|
|
1346
|
-
_ts_param5(1, (0,
|
|
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
|
|
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 =
|
|
1372
|
-
(0,
|
|
1373
|
-
_ts_param5(0, (0,
|
|
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
|
|
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(
|
|
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 =
|
|
1430
|
-
(0,
|
|
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 =
|
|
1516
|
-
(0,
|
|
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 = {
|