@dudousxd/nestjs-durable-dashboard 0.30.0 → 0.32.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/server/index.cjs +825 -42
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +205 -5
- package/dist/server/index.d.ts +205 -5
- package/dist/server/index.js +807 -27
- package/dist/server/index.js.map +1 -1
- package/package.json +2 -2
package/dist/server/index.cjs
CHANGED
|
@@ -27,8 +27,11 @@ __export(index_exports, {
|
|
|
27
27
|
DashboardService: () => DashboardService,
|
|
28
28
|
DurableApiController: () => DurableApiController,
|
|
29
29
|
DurableApiModule: () => DurableApiModule,
|
|
30
|
+
DurableApiSessionGuard: () => DurableApiSessionGuard,
|
|
31
|
+
DurableAuthController: () => DurableAuthController,
|
|
30
32
|
DurableDashboardModule: () => DurableDashboardModule,
|
|
31
|
-
DurableUiController: () => DurableUiController
|
|
33
|
+
DurableUiController: () => DurableUiController,
|
|
34
|
+
DurableUiSessionGuard: () => DurableUiSessionGuard
|
|
32
35
|
});
|
|
33
36
|
module.exports = __toCommonJS(index_exports);
|
|
34
37
|
|
|
@@ -554,14 +557,468 @@ DurableApiController = _ts_decorate2([
|
|
|
554
557
|
])
|
|
555
558
|
], DurableApiController);
|
|
556
559
|
|
|
557
|
-
// src/server/durable-
|
|
558
|
-
var
|
|
559
|
-
|
|
560
|
+
// src/server/durable-auth.controller.ts
|
|
561
|
+
var import_common5 = require("@nestjs/common");
|
|
562
|
+
|
|
563
|
+
// src/server/auth/dashboard-auth-config.ts
|
|
564
|
+
var DASHBOARD_AUTH = Symbol("DASHBOARD_AUTH");
|
|
565
|
+
var DEFAULT_TTL = "8h";
|
|
566
|
+
var DEFAULT_TTL_MS = 8 * 60 * 60 * 1e3;
|
|
567
|
+
var DURATION_UNITS = {
|
|
568
|
+
s: 1e3,
|
|
569
|
+
m: 60 * 1e3,
|
|
570
|
+
h: 60 * 60 * 1e3,
|
|
571
|
+
d: 24 * 60 * 60 * 1e3
|
|
572
|
+
};
|
|
573
|
+
function durationToMs(ttl) {
|
|
574
|
+
const match = /^(\d+)([smhd])$/.exec(ttl.trim());
|
|
575
|
+
if (!match) return DEFAULT_TTL_MS;
|
|
576
|
+
const unit = DURATION_UNITS[match[2] ?? ""];
|
|
577
|
+
if (unit === void 0) return DEFAULT_TTL_MS;
|
|
578
|
+
return Number(match[1]) * unit;
|
|
579
|
+
}
|
|
580
|
+
__name(durationToMs, "durationToMs");
|
|
581
|
+
function resolveDashboardAuth(options) {
|
|
582
|
+
if (options === void 0) return null;
|
|
583
|
+
if (typeof options.secret !== "string" || options.secret === "") {
|
|
584
|
+
throw new Error("DurableDashboardModule: dashboardAuth.secret is required and must be a non-empty string (HMAC-SHA256 signing key, 32+ bytes recommended). Failing closed.");
|
|
585
|
+
}
|
|
586
|
+
if (typeof options.login !== "function") {
|
|
587
|
+
throw new Error("DurableDashboardModule: dashboardAuth.login is required (a login hook).");
|
|
588
|
+
}
|
|
589
|
+
return {
|
|
590
|
+
secret: options.secret,
|
|
591
|
+
ttlMs: durationToMs(options.ttl ?? DEFAULT_TTL),
|
|
592
|
+
login: options.login
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
__name(resolveDashboardAuth, "resolveDashboardAuth");
|
|
596
|
+
|
|
597
|
+
// src/server/auth/login-page.ts
|
|
598
|
+
function renderLoginPage(basePath) {
|
|
599
|
+
const loginAction = `${basePath}/login`;
|
|
600
|
+
const defaultReturnTo = basePath;
|
|
601
|
+
return `<!doctype html>
|
|
602
|
+
<html lang="en">
|
|
603
|
+
<head>
|
|
604
|
+
<meta charset="utf-8" />
|
|
605
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
606
|
+
<meta name="robots" content="noindex, nofollow" />
|
|
607
|
+
<title>Sign in \u2014 Durable</title>
|
|
608
|
+
<style>
|
|
609
|
+
:root { color-scheme: light dark; }
|
|
610
|
+
* { box-sizing: border-box; }
|
|
611
|
+
body {
|
|
612
|
+
margin: 0;
|
|
613
|
+
min-height: 100vh;
|
|
614
|
+
display: flex;
|
|
615
|
+
align-items: center;
|
|
616
|
+
justify-content: center;
|
|
617
|
+
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
|
618
|
+
background: #0b0f14;
|
|
619
|
+
color: #e5e7eb;
|
|
620
|
+
}
|
|
621
|
+
main {
|
|
622
|
+
width: 100%;
|
|
623
|
+
max-width: 360px;
|
|
624
|
+
padding: 32px;
|
|
625
|
+
}
|
|
626
|
+
h1 {
|
|
627
|
+
font-size: 18px;
|
|
628
|
+
font-weight: 600;
|
|
629
|
+
margin: 0 0 4px;
|
|
630
|
+
}
|
|
631
|
+
p.subtitle {
|
|
632
|
+
margin: 0 0 24px;
|
|
633
|
+
color: #9ca3af;
|
|
634
|
+
font-size: 13px;
|
|
635
|
+
}
|
|
636
|
+
label {
|
|
637
|
+
display: block;
|
|
638
|
+
font-size: 12px;
|
|
639
|
+
font-weight: 500;
|
|
640
|
+
margin-bottom: 6px;
|
|
641
|
+
color: #cbd5e1;
|
|
642
|
+
}
|
|
643
|
+
input {
|
|
644
|
+
width: 100%;
|
|
645
|
+
padding: 8px 10px;
|
|
646
|
+
margin-bottom: 16px;
|
|
647
|
+
border-radius: 6px;
|
|
648
|
+
border: 1px solid #374151;
|
|
649
|
+
background: #111827;
|
|
650
|
+
color: #e5e7eb;
|
|
651
|
+
font-size: 14px;
|
|
652
|
+
}
|
|
653
|
+
input:focus { outline: 2px solid #3b82f6; outline-offset: 1px; }
|
|
654
|
+
button {
|
|
655
|
+
width: 100%;
|
|
656
|
+
padding: 9px 10px;
|
|
657
|
+
border-radius: 6px;
|
|
658
|
+
border: none;
|
|
659
|
+
background: #3b82f6;
|
|
660
|
+
color: white;
|
|
661
|
+
font-size: 14px;
|
|
662
|
+
font-weight: 600;
|
|
663
|
+
cursor: pointer;
|
|
664
|
+
}
|
|
665
|
+
button:disabled { opacity: 0.6; cursor: default; }
|
|
666
|
+
button:hover:not(:disabled) { background: #2563eb; }
|
|
667
|
+
#error {
|
|
668
|
+
display: none;
|
|
669
|
+
margin-bottom: 16px;
|
|
670
|
+
padding: 8px 10px;
|
|
671
|
+
border-radius: 6px;
|
|
672
|
+
background: #7f1d1d;
|
|
673
|
+
color: #fecaca;
|
|
674
|
+
font-size: 13px;
|
|
675
|
+
}
|
|
676
|
+
</style>
|
|
677
|
+
</head>
|
|
678
|
+
<body>
|
|
679
|
+
<main>
|
|
680
|
+
<h1>Durable control plane</h1>
|
|
681
|
+
<p class="subtitle">Sign in to continue.</p>
|
|
682
|
+
<div id="error" role="alert">Invalid username or password.</div>
|
|
683
|
+
<form id="login-form" autocomplete="on">
|
|
684
|
+
<label for="username">Username</label>
|
|
685
|
+
<input id="username" name="username" type="text" autocomplete="username" required autofocus />
|
|
686
|
+
<label for="password">Password</label>
|
|
687
|
+
<input id="password" name="password" type="password" autocomplete="current-password" required />
|
|
688
|
+
<button id="submit" type="submit">Sign in</button>
|
|
689
|
+
</form>
|
|
690
|
+
</main>
|
|
691
|
+
<script>
|
|
692
|
+
(function () {
|
|
693
|
+
var LOGIN_ACTION = ${JSON.stringify(loginAction)};
|
|
694
|
+
var DEFAULT_RETURN_TO = ${JSON.stringify(defaultReturnTo)};
|
|
695
|
+
var params = new URLSearchParams(window.location.search);
|
|
696
|
+
var errorBox = document.getElementById('error');
|
|
697
|
+
if (params.get('error')) errorBox.style.display = 'block';
|
|
698
|
+
|
|
699
|
+
function sameOriginReturnTo(value) {
|
|
700
|
+
if (typeof value !== 'string' || value === '') return DEFAULT_RETURN_TO;
|
|
701
|
+
if (value.charAt(0) !== '/' || value.charAt(1) === '/' || value.indexOf('://') !== -1) {
|
|
702
|
+
return DEFAULT_RETURN_TO;
|
|
703
|
+
}
|
|
704
|
+
return value;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
var form = document.getElementById('login-form');
|
|
708
|
+
var submitButton = document.getElementById('submit');
|
|
709
|
+
form.addEventListener('submit', function (event) {
|
|
710
|
+
event.preventDefault();
|
|
711
|
+
submitButton.disabled = true;
|
|
712
|
+
errorBox.style.display = 'none';
|
|
713
|
+
var returnTo = sameOriginReturnTo(params.get('returnTo'));
|
|
714
|
+
fetch(LOGIN_ACTION, {
|
|
715
|
+
method: 'POST',
|
|
716
|
+
headers: { 'content-type': 'application/json' },
|
|
717
|
+
credentials: 'same-origin',
|
|
718
|
+
body: JSON.stringify({
|
|
719
|
+
username: document.getElementById('username').value,
|
|
720
|
+
password: document.getElementById('password').value,
|
|
721
|
+
returnTo: returnTo,
|
|
722
|
+
}),
|
|
723
|
+
})
|
|
724
|
+
.then(function (response) {
|
|
725
|
+
if (!response.ok) throw new Error('unauthorized');
|
|
726
|
+
return response.json();
|
|
727
|
+
})
|
|
728
|
+
.then(function (data) {
|
|
729
|
+
window.location.href = data.redirectTo || DEFAULT_RETURN_TO;
|
|
730
|
+
})
|
|
731
|
+
.catch(function () {
|
|
732
|
+
errorBox.style.display = 'block';
|
|
733
|
+
submitButton.disabled = false;
|
|
734
|
+
});
|
|
735
|
+
});
|
|
736
|
+
})();
|
|
737
|
+
</script>
|
|
738
|
+
</body>
|
|
739
|
+
</html>`;
|
|
740
|
+
}
|
|
741
|
+
__name(renderLoginPage, "renderLoginPage");
|
|
742
|
+
|
|
743
|
+
// src/server/auth/request.ts
|
|
744
|
+
function isRecord(value) {
|
|
745
|
+
return typeof value === "object" && value !== null;
|
|
746
|
+
}
|
|
747
|
+
__name(isRecord, "isRecord");
|
|
748
|
+
function readHeaders(request) {
|
|
749
|
+
if (!isRecord(request)) return {};
|
|
750
|
+
const headers = request.headers;
|
|
751
|
+
return isRecord(headers) ? headers : {};
|
|
752
|
+
}
|
|
753
|
+
__name(readHeaders, "readHeaders");
|
|
754
|
+
function headerToString(value) {
|
|
755
|
+
if (typeof value === "string") return value;
|
|
756
|
+
if (Array.isArray(value) && value.every((part) => typeof part === "string")) {
|
|
757
|
+
return value.join(",");
|
|
758
|
+
}
|
|
759
|
+
return void 0;
|
|
760
|
+
}
|
|
761
|
+
__name(headerToString, "headerToString");
|
|
762
|
+
function readCookieHeader(request) {
|
|
763
|
+
return headerToString(readHeaders(request).cookie);
|
|
764
|
+
}
|
|
765
|
+
__name(readCookieHeader, "readCookieHeader");
|
|
766
|
+
function isHttpsRequest(request) {
|
|
767
|
+
if (isRecord(request) && request.protocol === "https") return true;
|
|
768
|
+
const forwarded = headerToString(readHeaders(request)["x-forwarded-proto"]);
|
|
769
|
+
if (forwarded === void 0) return false;
|
|
770
|
+
return forwarded.split(",").map((proto) => proto.trim().toLowerCase()).includes("https");
|
|
771
|
+
}
|
|
772
|
+
__name(isHttpsRequest, "isHttpsRequest");
|
|
773
|
+
function originalRequestUrl(request) {
|
|
774
|
+
if (!isRecord(request)) return "/";
|
|
775
|
+
if (typeof request.originalUrl === "string" && request.originalUrl !== "") {
|
|
776
|
+
return request.originalUrl;
|
|
777
|
+
}
|
|
778
|
+
if (typeof request.url === "string" && request.url !== "") return request.url;
|
|
779
|
+
return "/";
|
|
780
|
+
}
|
|
781
|
+
__name(originalRequestUrl, "originalRequestUrl");
|
|
782
|
+
function sanitizeReturnTo(candidate, fallback) {
|
|
783
|
+
if (typeof candidate !== "string") return fallback;
|
|
784
|
+
if (!candidate.startsWith("/")) return fallback;
|
|
785
|
+
if (candidate.startsWith("//")) return fallback;
|
|
786
|
+
if (candidate.includes("://")) return fallback;
|
|
787
|
+
return candidate;
|
|
788
|
+
}
|
|
789
|
+
__name(sanitizeReturnTo, "sanitizeReturnTo");
|
|
790
|
+
|
|
791
|
+
// src/server/auth/response.ts
|
|
792
|
+
function isRecord2(value) {
|
|
793
|
+
return typeof value === "object" && value !== null;
|
|
794
|
+
}
|
|
795
|
+
__name(isRecord2, "isRecord");
|
|
796
|
+
function isRawCookieResponse(value) {
|
|
797
|
+
return isRecord2(value) && typeof value.getHeader === "function" && typeof value.setHeader === "function";
|
|
798
|
+
}
|
|
799
|
+
__name(isRawCookieResponse, "isRawCookieResponse");
|
|
800
|
+
function resolveCookieResponse(response) {
|
|
801
|
+
if (isRawCookieResponse(response)) return response;
|
|
802
|
+
if (isRecord2(response) && isRawCookieResponse(response.raw)) return response.raw;
|
|
803
|
+
return null;
|
|
804
|
+
}
|
|
805
|
+
__name(resolveCookieResponse, "resolveCookieResponse");
|
|
806
|
+
function existingSetCookies(response) {
|
|
807
|
+
const current = response.getHeader("set-cookie");
|
|
808
|
+
if (Array.isArray(current)) return current;
|
|
809
|
+
if (typeof current === "string") return [
|
|
810
|
+
current
|
|
811
|
+
];
|
|
812
|
+
return [];
|
|
813
|
+
}
|
|
814
|
+
__name(existingSetCookies, "existingSetCookies");
|
|
815
|
+
function appendSetCookie(response, cookie) {
|
|
816
|
+
const raw = resolveCookieResponse(response);
|
|
817
|
+
if (!raw) return;
|
|
818
|
+
raw.setHeader("set-cookie", [
|
|
819
|
+
...existingSetCookies(raw),
|
|
820
|
+
cookie
|
|
821
|
+
]);
|
|
822
|
+
}
|
|
823
|
+
__name(appendSetCookie, "appendSetCookie");
|
|
824
|
+
function isRawRedirectResponse(value) {
|
|
825
|
+
return isRecord2(value) && typeof value.getHeader === "function" && typeof value.setHeader === "function" && typeof value.end === "function";
|
|
826
|
+
}
|
|
827
|
+
__name(isRawRedirectResponse, "isRawRedirectResponse");
|
|
828
|
+
function resolveRedirectResponse(response) {
|
|
829
|
+
if (isRawRedirectResponse(response)) return response;
|
|
830
|
+
if (isRecord2(response) && isRawRedirectResponse(response.raw)) return response.raw;
|
|
831
|
+
return null;
|
|
832
|
+
}
|
|
833
|
+
__name(resolveRedirectResponse, "resolveRedirectResponse");
|
|
834
|
+
function redirectRaw(response, location, status = 302) {
|
|
835
|
+
const raw = resolveRedirectResponse(response);
|
|
836
|
+
if (!raw) return;
|
|
837
|
+
raw.statusCode = status;
|
|
838
|
+
raw.setHeader("location", location);
|
|
839
|
+
raw.end();
|
|
840
|
+
}
|
|
841
|
+
__name(redirectRaw, "redirectRaw");
|
|
842
|
+
|
|
843
|
+
// src/server/auth/cookie-header.ts
|
|
844
|
+
function parseCookieHeader(header) {
|
|
845
|
+
const cookies = {};
|
|
846
|
+
if (typeof header !== "string" || header === "") return cookies;
|
|
847
|
+
for (const segment of header.split(";")) {
|
|
848
|
+
const eq = segment.indexOf("=");
|
|
849
|
+
if (eq <= 0) continue;
|
|
850
|
+
const name = segment.slice(0, eq).trim();
|
|
851
|
+
if (name === "") continue;
|
|
852
|
+
let rawValue = segment.slice(eq + 1).trim();
|
|
853
|
+
if (rawValue.length >= 2 && rawValue.startsWith('"') && rawValue.endsWith('"')) {
|
|
854
|
+
rawValue = rawValue.slice(1, -1);
|
|
855
|
+
}
|
|
856
|
+
if (name in cookies) continue;
|
|
857
|
+
try {
|
|
858
|
+
cookies[name] = decodeURIComponent(rawValue);
|
|
859
|
+
} catch {
|
|
860
|
+
cookies[name] = rawValue;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
return cookies;
|
|
864
|
+
}
|
|
865
|
+
__name(parseCookieHeader, "parseCookieHeader");
|
|
866
|
+
function serializeSetCookie(name, value, options) {
|
|
867
|
+
const parts = [
|
|
868
|
+
`${name}=${options.clear ? "" : encodeURIComponent(value)}`
|
|
869
|
+
];
|
|
870
|
+
parts.push(`Path=${options.path}`);
|
|
871
|
+
parts.push("HttpOnly");
|
|
872
|
+
parts.push("SameSite=Lax");
|
|
873
|
+
if (options.secure) parts.push("Secure");
|
|
874
|
+
if (options.clear) {
|
|
875
|
+
parts.push("Max-Age=0");
|
|
876
|
+
parts.push("Expires=Thu, 01 Jan 1970 00:00:00 GMT");
|
|
877
|
+
} else {
|
|
878
|
+
parts.push(`Max-Age=${options.maxAgeSeconds}`);
|
|
879
|
+
}
|
|
880
|
+
return parts.join("; ");
|
|
881
|
+
}
|
|
882
|
+
__name(serializeSetCookie, "serializeSetCookie");
|
|
883
|
+
|
|
884
|
+
// src/server/auth/session-cookie.ts
|
|
885
|
+
var import_node_crypto = require("crypto");
|
|
886
|
+
var EXP_GRACE_MS = 3e4;
|
|
887
|
+
function base64urlEncode(value) {
|
|
888
|
+
return Buffer.from(value, "utf8").toString("base64url");
|
|
889
|
+
}
|
|
890
|
+
__name(base64urlEncode, "base64urlEncode");
|
|
891
|
+
function base64urlDecode(value) {
|
|
892
|
+
return Buffer.from(value, "base64url").toString("utf8");
|
|
893
|
+
}
|
|
894
|
+
__name(base64urlDecode, "base64urlDecode");
|
|
895
|
+
function hmac(secret, payload) {
|
|
896
|
+
return (0, import_node_crypto.createHmac)("sha256", secret).update(payload).digest("base64url");
|
|
897
|
+
}
|
|
898
|
+
__name(hmac, "hmac");
|
|
899
|
+
function signSessionCookie(user, options) {
|
|
900
|
+
const issuedAt = options.now ?? Date.now();
|
|
901
|
+
const session = {
|
|
902
|
+
sub: user.id,
|
|
903
|
+
...user.name !== void 0 ? {
|
|
904
|
+
name: user.name
|
|
905
|
+
} : {},
|
|
906
|
+
roles: user.roles ?? [],
|
|
907
|
+
iat: issuedAt,
|
|
908
|
+
exp: issuedAt + options.ttlMs
|
|
909
|
+
};
|
|
910
|
+
const encodedPayload = base64urlEncode(JSON.stringify(session));
|
|
911
|
+
const signature = hmac(options.secret, encodedPayload);
|
|
912
|
+
return `${encodedPayload}.${signature}`;
|
|
913
|
+
}
|
|
914
|
+
__name(signSessionCookie, "signSessionCookie");
|
|
915
|
+
function isSessionPayload(value) {
|
|
916
|
+
if (typeof value !== "object" || value === null) return false;
|
|
917
|
+
const record = {
|
|
918
|
+
...value
|
|
919
|
+
};
|
|
920
|
+
if (typeof record.sub !== "string") return false;
|
|
921
|
+
if (record.name !== void 0 && typeof record.name !== "string") return false;
|
|
922
|
+
if (!Array.isArray(record.roles)) return false;
|
|
923
|
+
if (!record.roles.every((role) => typeof role === "string")) return false;
|
|
924
|
+
if (typeof record.iat !== "number" || !Number.isFinite(record.iat)) return false;
|
|
925
|
+
if (typeof record.exp !== "number" || !Number.isFinite(record.exp)) return false;
|
|
926
|
+
return true;
|
|
927
|
+
}
|
|
928
|
+
__name(isSessionPayload, "isSessionPayload");
|
|
929
|
+
function verifySessionCookie(value, options) {
|
|
930
|
+
try {
|
|
931
|
+
const dot = value.indexOf(".");
|
|
932
|
+
if (dot <= 0 || dot === value.length - 1) return null;
|
|
933
|
+
const encodedPayload = value.slice(0, dot);
|
|
934
|
+
const providedSignature = value.slice(dot + 1);
|
|
935
|
+
const expectedSignature = hmac(options.secret, encodedPayload);
|
|
936
|
+
const provided = Buffer.from(providedSignature, "base64url");
|
|
937
|
+
const expected = Buffer.from(expectedSignature, "base64url");
|
|
938
|
+
if (provided.length !== expected.length) return null;
|
|
939
|
+
if (!(0, import_node_crypto.timingSafeEqual)(provided, expected)) return null;
|
|
940
|
+
const decoded = JSON.parse(base64urlDecode(encodedPayload));
|
|
941
|
+
if (!isSessionPayload(decoded)) return null;
|
|
942
|
+
const now = options.now ?? Date.now();
|
|
943
|
+
if (now > decoded.exp + EXP_GRACE_MS) return null;
|
|
944
|
+
const session = {
|
|
945
|
+
sub: decoded.sub,
|
|
946
|
+
...decoded.name !== void 0 ? {
|
|
947
|
+
name: decoded.name
|
|
948
|
+
} : {},
|
|
949
|
+
roles: decoded.roles,
|
|
950
|
+
iat: decoded.iat,
|
|
951
|
+
exp: decoded.exp
|
|
952
|
+
};
|
|
953
|
+
return session;
|
|
954
|
+
} catch {
|
|
955
|
+
return null;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
__name(verifySessionCookie, "verifySessionCookie");
|
|
959
|
+
|
|
960
|
+
// src/server/auth/session-cookie-io.ts
|
|
961
|
+
var SESSION_COOKIE_NAME = "durable_dashboard_session";
|
|
962
|
+
var COOKIE_PATH = "/";
|
|
963
|
+
function issueSessionCookie(user, context) {
|
|
964
|
+
const value = signSessionCookie(user, {
|
|
965
|
+
secret: context.auth.secret,
|
|
966
|
+
ttlMs: context.auth.ttlMs,
|
|
967
|
+
...context.now !== void 0 ? {
|
|
968
|
+
now: context.now
|
|
969
|
+
} : {}
|
|
970
|
+
});
|
|
971
|
+
const cookie = serializeSetCookie(SESSION_COOKIE_NAME, value, {
|
|
972
|
+
path: COOKIE_PATH,
|
|
973
|
+
maxAgeSeconds: Math.floor(context.auth.ttlMs / 1e3),
|
|
974
|
+
secure: isHttpsRequest(context.request)
|
|
975
|
+
});
|
|
976
|
+
appendSetCookie(context.response, cookie);
|
|
977
|
+
}
|
|
978
|
+
__name(issueSessionCookie, "issueSessionCookie");
|
|
979
|
+
function clearSessionCookie(context) {
|
|
980
|
+
const cookie = serializeSetCookie(SESSION_COOKIE_NAME, "", {
|
|
981
|
+
path: COOKIE_PATH,
|
|
982
|
+
maxAgeSeconds: 0,
|
|
983
|
+
secure: isHttpsRequest(context.request),
|
|
984
|
+
clear: true
|
|
985
|
+
});
|
|
986
|
+
appendSetCookie(context.response, cookie);
|
|
987
|
+
}
|
|
988
|
+
__name(clearSessionCookie, "clearSessionCookie");
|
|
989
|
+
function readSessionFromRequest(auth, request) {
|
|
990
|
+
const cookieValue = parseCookieHeader(readCookieHeader(request))[SESSION_COOKIE_NAME];
|
|
991
|
+
if (cookieValue === void 0) return null;
|
|
992
|
+
return verifySessionCookie(cookieValue, {
|
|
993
|
+
secret: auth.secret
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
__name(readSessionFromRequest, "readSessionFromRequest");
|
|
997
|
+
function maybeRenewSession(auth, session, request, response) {
|
|
998
|
+
const now = Date.now();
|
|
999
|
+
if (now - session.iat <= auth.ttlMs / 2) return;
|
|
1000
|
+
issueSessionCookie({
|
|
1001
|
+
id: session.sub,
|
|
1002
|
+
...session.name !== void 0 ? {
|
|
1003
|
+
name: session.name
|
|
1004
|
+
} : {},
|
|
1005
|
+
roles: session.roles
|
|
1006
|
+
}, {
|
|
1007
|
+
auth,
|
|
1008
|
+
request,
|
|
1009
|
+
response,
|
|
1010
|
+
now
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
__name(maybeRenewSession, "maybeRenewSession");
|
|
560
1014
|
|
|
561
1015
|
// src/server/durable-ui.controller.ts
|
|
562
1016
|
var import_node_fs = require("fs");
|
|
563
1017
|
var import_node_path = require("path");
|
|
564
1018
|
var import_node_url = require("url");
|
|
1019
|
+
var import_common4 = require("@nestjs/common");
|
|
1020
|
+
|
|
1021
|
+
// src/server/auth/login-redirect.exception.ts
|
|
565
1022
|
var import_common3 = require("@nestjs/common");
|
|
566
1023
|
function _ts_decorate3(decorators, target, key, desc) {
|
|
567
1024
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -570,6 +1027,38 @@ function _ts_decorate3(decorators, target, key, desc) {
|
|
|
570
1027
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
571
1028
|
}
|
|
572
1029
|
__name(_ts_decorate3, "_ts_decorate");
|
|
1030
|
+
var DashboardLoginRedirectException = class extends import_common3.HttpException {
|
|
1031
|
+
static {
|
|
1032
|
+
__name(this, "DashboardLoginRedirectException");
|
|
1033
|
+
}
|
|
1034
|
+
location;
|
|
1035
|
+
constructor(location) {
|
|
1036
|
+
super({
|
|
1037
|
+
location
|
|
1038
|
+
}, import_common3.HttpStatus.FOUND), this.location = location;
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
var DashboardLoginRedirectFilter = class {
|
|
1042
|
+
static {
|
|
1043
|
+
__name(this, "DashboardLoginRedirectFilter");
|
|
1044
|
+
}
|
|
1045
|
+
catch(exception, host) {
|
|
1046
|
+
const response = host.switchToHttp().getResponse();
|
|
1047
|
+
redirectRaw(response, exception.location);
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
DashboardLoginRedirectFilter = _ts_decorate3([
|
|
1051
|
+
(0, import_common3.Catch)(DashboardLoginRedirectException)
|
|
1052
|
+
], DashboardLoginRedirectFilter);
|
|
1053
|
+
|
|
1054
|
+
// src/server/durable-ui.controller.ts
|
|
1055
|
+
function _ts_decorate4(decorators, target, key, desc) {
|
|
1056
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1057
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1058
|
+
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;
|
|
1059
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1060
|
+
}
|
|
1061
|
+
__name(_ts_decorate4, "_ts_decorate");
|
|
573
1062
|
function _ts_metadata3(k, v) {
|
|
574
1063
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
575
1064
|
}
|
|
@@ -612,7 +1101,7 @@ var DurableUiController = class {
|
|
|
612
1101
|
index() {
|
|
613
1102
|
const indexPath = (0, import_node_path.join)(this.dir, "index.html");
|
|
614
1103
|
if (!(0, import_node_fs.existsSync)(indexPath)) {
|
|
615
|
-
throw new
|
|
1104
|
+
throw new import_common4.NotFoundException("Dashboard is not built. Run the package build.");
|
|
616
1105
|
}
|
|
617
1106
|
const html = (0, import_node_fs.readFileSync)(indexPath, "utf8").replaceAll(`="${BUILD_BASE}/`, `="${this.basePath}/`);
|
|
618
1107
|
const inject = `<script>window.__DURABLE_BASE__='${this.basePath}';window.__DURABLE_API__='${this.apiBasePath}';</script>`;
|
|
@@ -620,40 +1109,41 @@ var DurableUiController = class {
|
|
|
620
1109
|
}
|
|
621
1110
|
asset(file) {
|
|
622
1111
|
const safe = (0, import_node_path.basename)(file);
|
|
623
|
-
if (safe !== file) throw new
|
|
1112
|
+
if (safe !== file) throw new import_common4.NotFoundException();
|
|
624
1113
|
const root = (0, import_node_path.resolve)(this.dir, "assets");
|
|
625
1114
|
const assetPath = (0, import_node_path.resolve)(root, safe);
|
|
626
1115
|
if (!assetPath.startsWith(root + import_node_path.sep) || !(0, import_node_fs.existsSync)(assetPath)) {
|
|
627
|
-
throw new
|
|
1116
|
+
throw new import_common4.NotFoundException();
|
|
628
1117
|
}
|
|
629
1118
|
const type = CONTENT_TYPES[(0, import_node_path.extname)(safe)] ?? "application/octet-stream";
|
|
630
|
-
return new
|
|
1119
|
+
return new import_common4.StreamableFile((0, import_node_fs.readFileSync)(assetPath), {
|
|
631
1120
|
type
|
|
632
1121
|
});
|
|
633
1122
|
}
|
|
634
1123
|
};
|
|
635
|
-
|
|
636
|
-
(0,
|
|
637
|
-
(0,
|
|
638
|
-
(0,
|
|
1124
|
+
_ts_decorate4([
|
|
1125
|
+
(0, import_common4.Get)(),
|
|
1126
|
+
(0, import_common4.Header)("Content-Type", "text/html; charset=utf-8"),
|
|
1127
|
+
(0, import_common4.Header)("Cache-Control", "no-store, must-revalidate"),
|
|
639
1128
|
_ts_metadata3("design:type", Function),
|
|
640
1129
|
_ts_metadata3("design:paramtypes", []),
|
|
641
1130
|
_ts_metadata3("design:returntype", String)
|
|
642
1131
|
], DurableUiController.prototype, "index", null);
|
|
643
|
-
|
|
644
|
-
(0,
|
|
645
|
-
(0,
|
|
646
|
-
_ts_param3(0, (0,
|
|
1132
|
+
_ts_decorate4([
|
|
1133
|
+
(0, import_common4.Get)("assets/:file"),
|
|
1134
|
+
(0, import_common4.Header)("Cache-Control", "public, max-age=31536000, immutable"),
|
|
1135
|
+
_ts_param3(0, (0, import_common4.Param)("file")),
|
|
647
1136
|
_ts_metadata3("design:type", Function),
|
|
648
1137
|
_ts_metadata3("design:paramtypes", [
|
|
649
1138
|
String
|
|
650
1139
|
]),
|
|
651
|
-
_ts_metadata3("design:returntype", typeof
|
|
1140
|
+
_ts_metadata3("design:returntype", typeof import_common4.StreamableFile === "undefined" ? Object : import_common4.StreamableFile)
|
|
652
1141
|
], DurableUiController.prototype, "asset", null);
|
|
653
|
-
DurableUiController =
|
|
654
|
-
(0,
|
|
655
|
-
|
|
656
|
-
_ts_param3(
|
|
1142
|
+
DurableUiController = _ts_decorate4([
|
|
1143
|
+
(0, import_common4.UseFilters)(DashboardLoginRedirectFilter),
|
|
1144
|
+
(0, import_common4.Controller)(),
|
|
1145
|
+
_ts_param3(0, (0, import_common4.Inject)(DASHBOARD_BASE_PATH)),
|
|
1146
|
+
_ts_param3(1, (0, import_common4.Inject)(DASHBOARD_API_PATH)),
|
|
657
1147
|
_ts_metadata3("design:type", Function),
|
|
658
1148
|
_ts_metadata3("design:paramtypes", [
|
|
659
1149
|
String,
|
|
@@ -661,47 +1151,326 @@ DurableUiController = _ts_decorate3([
|
|
|
661
1151
|
])
|
|
662
1152
|
], DurableUiController);
|
|
663
1153
|
|
|
1154
|
+
// src/server/durable-auth.controller.ts
|
|
1155
|
+
function _ts_decorate5(decorators, target, key, desc) {
|
|
1156
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1157
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1158
|
+
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;
|
|
1159
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1160
|
+
}
|
|
1161
|
+
__name(_ts_decorate5, "_ts_decorate");
|
|
1162
|
+
function _ts_metadata4(k, v) {
|
|
1163
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1164
|
+
}
|
|
1165
|
+
__name(_ts_metadata4, "_ts_metadata");
|
|
1166
|
+
function _ts_param4(paramIndex, decorator) {
|
|
1167
|
+
return function(target, key) {
|
|
1168
|
+
decorator(target, key, paramIndex);
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
__name(_ts_param4, "_ts_param");
|
|
1172
|
+
function isString(value) {
|
|
1173
|
+
return typeof value === "string";
|
|
1174
|
+
}
|
|
1175
|
+
__name(isString, "isString");
|
|
1176
|
+
var DurableAuthController = class _DurableAuthController {
|
|
1177
|
+
static {
|
|
1178
|
+
__name(this, "DurableAuthController");
|
|
1179
|
+
}
|
|
1180
|
+
auth;
|
|
1181
|
+
basePath;
|
|
1182
|
+
logger = new import_common5.Logger(_DurableAuthController.name);
|
|
1183
|
+
/** Warn once so a throwing `login` hook doesn't spam logs on every failed attempt. */
|
|
1184
|
+
warnedOnHookThrow = false;
|
|
1185
|
+
constructor(auth, basePath) {
|
|
1186
|
+
this.auth = auth;
|
|
1187
|
+
this.basePath = basePath;
|
|
1188
|
+
}
|
|
1189
|
+
// The login page reads `returnTo`/`error` from `location.search` client-side (see
|
|
1190
|
+
// `login-page.ts`), so this response never varies per request and needs no templating beyond
|
|
1191
|
+
// the developer-controlled `basePath`.
|
|
1192
|
+
loginPage() {
|
|
1193
|
+
if (!this.auth) throw new import_common5.NotFoundException();
|
|
1194
|
+
return renderLoginPage(this.basePath);
|
|
1195
|
+
}
|
|
1196
|
+
// Called by the login page's own fetch (JSON, not a classic form POST — see `login-page.ts` for
|
|
1197
|
+
// why: it keeps the login page working with no host-configured body parser beyond the
|
|
1198
|
+
// JSON one Nest wires in by default on both Express and Fastify). Uniform `401` on any failure
|
|
1199
|
+
// (unknown user, wrong password, throwing hook) — no user-enumeration.
|
|
1200
|
+
async login(body, request, response) {
|
|
1201
|
+
const auth = this.requireAuth();
|
|
1202
|
+
if (!isString(body?.username) || !isString(body?.password)) {
|
|
1203
|
+
throw new import_common5.BadRequestException("Body must include string `username` and `password`.");
|
|
1204
|
+
}
|
|
1205
|
+
const user = await this.runLoginHook(auth, body.username, body.password);
|
|
1206
|
+
if (!user) throw new import_common5.UnauthorizedException({
|
|
1207
|
+
message: "Invalid username or password."
|
|
1208
|
+
});
|
|
1209
|
+
issueSessionCookie(user, {
|
|
1210
|
+
auth,
|
|
1211
|
+
request,
|
|
1212
|
+
response
|
|
1213
|
+
});
|
|
1214
|
+
return {
|
|
1215
|
+
redirectTo: sanitizeReturnTo(body.returnTo, this.basePath)
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
// Plain `GET` (not `POST`): logging out only ever destroys the CALLER's own session, so it's
|
|
1219
|
+
// safe/idempotent from the caller's perspective — a simple `<a href>` (no JS, no CSRF token)
|
|
1220
|
+
// works. Non-passthrough `@Res()`: this handler owns the response outright (clears the cookie,
|
|
1221
|
+
// then redirects), so Nest must not also try to send a return value afterwards.
|
|
1222
|
+
logout(request, response) {
|
|
1223
|
+
clearSessionCookie({
|
|
1224
|
+
request,
|
|
1225
|
+
response
|
|
1226
|
+
});
|
|
1227
|
+
redirectRaw(response, `${this.basePath}/login`);
|
|
1228
|
+
}
|
|
1229
|
+
requireAuth() {
|
|
1230
|
+
if (!this.auth) throw new import_common5.NotFoundException();
|
|
1231
|
+
return this.auth;
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Run the host's `login` hook defensively: a throw is treated as a denial (uniform failure) and
|
|
1235
|
+
* warn-logged once, so a buggy hook never 500s the endpoint into a stack-trace leak nor floods
|
|
1236
|
+
* the logs on repeated bad attempts.
|
|
1237
|
+
*/
|
|
1238
|
+
async runLoginHook(auth, username, password) {
|
|
1239
|
+
try {
|
|
1240
|
+
return await auth.login(username, password) ?? null;
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
if (!this.warnedOnHookThrow) {
|
|
1243
|
+
this.warnedOnHookThrow = true;
|
|
1244
|
+
this.logger.warn(`dashboardAuth login hook threw; treating as denial. ${error instanceof Error ? error.message : String(error)}`);
|
|
1245
|
+
}
|
|
1246
|
+
return null;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
};
|
|
1250
|
+
_ts_decorate5([
|
|
1251
|
+
(0, import_common5.Get)("login"),
|
|
1252
|
+
(0, import_common5.Header)("Content-Type", "text/html; charset=utf-8"),
|
|
1253
|
+
(0, import_common5.Header)("Cache-Control", "no-store, must-revalidate"),
|
|
1254
|
+
_ts_metadata4("design:type", Function),
|
|
1255
|
+
_ts_metadata4("design:paramtypes", []),
|
|
1256
|
+
_ts_metadata4("design:returntype", String)
|
|
1257
|
+
], DurableAuthController.prototype, "loginPage", null);
|
|
1258
|
+
_ts_decorate5([
|
|
1259
|
+
(0, import_common5.Post)("login"),
|
|
1260
|
+
(0, import_common5.HttpCode)(200),
|
|
1261
|
+
_ts_param4(0, (0, import_common5.Body)()),
|
|
1262
|
+
_ts_param4(1, (0, import_common5.Req)()),
|
|
1263
|
+
_ts_param4(2, (0, import_common5.Res)({
|
|
1264
|
+
passthrough: true
|
|
1265
|
+
})),
|
|
1266
|
+
_ts_metadata4("design:type", Function),
|
|
1267
|
+
_ts_metadata4("design:paramtypes", [
|
|
1268
|
+
typeof LoginBody === "undefined" ? Object : LoginBody,
|
|
1269
|
+
Object,
|
|
1270
|
+
Object
|
|
1271
|
+
]),
|
|
1272
|
+
_ts_metadata4("design:returntype", Promise)
|
|
1273
|
+
], DurableAuthController.prototype, "login", null);
|
|
1274
|
+
_ts_decorate5([
|
|
1275
|
+
(0, import_common5.Get)("logout"),
|
|
1276
|
+
_ts_param4(0, (0, import_common5.Req)()),
|
|
1277
|
+
_ts_param4(1, (0, import_common5.Res)()),
|
|
1278
|
+
_ts_metadata4("design:type", Function),
|
|
1279
|
+
_ts_metadata4("design:paramtypes", [
|
|
1280
|
+
Object,
|
|
1281
|
+
Object
|
|
1282
|
+
]),
|
|
1283
|
+
_ts_metadata4("design:returntype", void 0)
|
|
1284
|
+
], DurableAuthController.prototype, "logout", null);
|
|
1285
|
+
DurableAuthController = _ts_decorate5([
|
|
1286
|
+
(0, import_common5.Controller)(),
|
|
1287
|
+
_ts_param4(0, (0, import_common5.Optional)()),
|
|
1288
|
+
_ts_param4(0, (0, import_common5.Inject)(DASHBOARD_AUTH)),
|
|
1289
|
+
_ts_param4(1, (0, import_common5.Inject)(DASHBOARD_BASE_PATH)),
|
|
1290
|
+
_ts_metadata4("design:type", Function),
|
|
1291
|
+
_ts_metadata4("design:paramtypes", [
|
|
1292
|
+
Object,
|
|
1293
|
+
String
|
|
1294
|
+
])
|
|
1295
|
+
], DurableAuthController);
|
|
1296
|
+
|
|
664
1297
|
// src/server/durable-dashboard.module.ts
|
|
665
|
-
|
|
1298
|
+
var import_reflect_metadata = require("reflect-metadata");
|
|
1299
|
+
var import_common7 = require("@nestjs/common");
|
|
1300
|
+
var import_core = require("@nestjs/core");
|
|
1301
|
+
|
|
1302
|
+
// src/server/durable-session.guard.ts
|
|
1303
|
+
var import_common6 = require("@nestjs/common");
|
|
1304
|
+
function _ts_decorate6(decorators, target, key, desc) {
|
|
666
1305
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
667
1306
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
668
1307
|
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;
|
|
669
1308
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
670
1309
|
}
|
|
671
|
-
__name(
|
|
1310
|
+
__name(_ts_decorate6, "_ts_decorate");
|
|
1311
|
+
function _ts_metadata5(k, v) {
|
|
1312
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1313
|
+
}
|
|
1314
|
+
__name(_ts_metadata5, "_ts_metadata");
|
|
1315
|
+
function _ts_param5(paramIndex, decorator) {
|
|
1316
|
+
return function(target, key) {
|
|
1317
|
+
decorator(target, key, paramIndex);
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
__name(_ts_param5, "_ts_param");
|
|
1321
|
+
var DurableUiSessionGuard = class {
|
|
1322
|
+
static {
|
|
1323
|
+
__name(this, "DurableUiSessionGuard");
|
|
1324
|
+
}
|
|
1325
|
+
auth;
|
|
1326
|
+
basePath;
|
|
1327
|
+
constructor(auth, basePath) {
|
|
1328
|
+
this.auth = auth;
|
|
1329
|
+
this.basePath = basePath;
|
|
1330
|
+
}
|
|
1331
|
+
canActivate(context) {
|
|
1332
|
+
if (!this.auth) return true;
|
|
1333
|
+
const http = context.switchToHttp();
|
|
1334
|
+
const request = http.getRequest();
|
|
1335
|
+
const session = readSessionFromRequest(this.auth, request);
|
|
1336
|
+
if (!session) {
|
|
1337
|
+
const returnTo = encodeURIComponent(originalRequestUrl(request));
|
|
1338
|
+
throw new DashboardLoginRedirectException(`${this.basePath}/login?returnTo=${returnTo}`);
|
|
1339
|
+
}
|
|
1340
|
+
maybeRenewSession(this.auth, session, request, http.getResponse());
|
|
1341
|
+
return true;
|
|
1342
|
+
}
|
|
1343
|
+
};
|
|
1344
|
+
DurableUiSessionGuard = _ts_decorate6([
|
|
1345
|
+
(0, import_common6.Injectable)(),
|
|
1346
|
+
_ts_param5(0, (0, import_common6.Inject)(DASHBOARD_AUTH)),
|
|
1347
|
+
_ts_param5(1, (0, import_common6.Inject)(DASHBOARD_BASE_PATH)),
|
|
1348
|
+
_ts_metadata5("design:type", Function),
|
|
1349
|
+
_ts_metadata5("design:paramtypes", [
|
|
1350
|
+
Object,
|
|
1351
|
+
String
|
|
1352
|
+
])
|
|
1353
|
+
], DurableUiSessionGuard);
|
|
1354
|
+
var DurableApiSessionGuard = class {
|
|
1355
|
+
static {
|
|
1356
|
+
__name(this, "DurableApiSessionGuard");
|
|
1357
|
+
}
|
|
1358
|
+
auth;
|
|
1359
|
+
constructor(auth) {
|
|
1360
|
+
this.auth = auth;
|
|
1361
|
+
}
|
|
1362
|
+
canActivate(context) {
|
|
1363
|
+
if (!this.auth) return true;
|
|
1364
|
+
const http = context.switchToHttp();
|
|
1365
|
+
const request = http.getRequest();
|
|
1366
|
+
const session = readSessionFromRequest(this.auth, request);
|
|
1367
|
+
if (!session) throw new import_common6.UnauthorizedException();
|
|
1368
|
+
maybeRenewSession(this.auth, session, request, http.getResponse());
|
|
1369
|
+
return true;
|
|
1370
|
+
}
|
|
1371
|
+
};
|
|
1372
|
+
DurableApiSessionGuard = _ts_decorate6([
|
|
1373
|
+
(0, import_common6.Injectable)(),
|
|
1374
|
+
_ts_param5(0, (0, import_common6.Inject)(DASHBOARD_AUTH)),
|
|
1375
|
+
_ts_metadata5("design:type", Function),
|
|
1376
|
+
_ts_metadata5("design:paramtypes", [
|
|
1377
|
+
Object
|
|
1378
|
+
])
|
|
1379
|
+
], DurableApiSessionGuard);
|
|
1380
|
+
|
|
1381
|
+
// src/server/durable-dashboard.module.ts
|
|
1382
|
+
function _ts_decorate7(decorators, target, key, desc) {
|
|
1383
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1384
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1385
|
+
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;
|
|
1386
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1387
|
+
}
|
|
1388
|
+
__name(_ts_decorate7, "_ts_decorate");
|
|
1389
|
+
var GUARDS_METADATA = "__guards__";
|
|
672
1390
|
function normalize(path) {
|
|
673
1391
|
return `/${path.replace(/^\/+|\/+$/g, "")}`;
|
|
674
1392
|
}
|
|
675
1393
|
__name(normalize, "normalize");
|
|
676
|
-
|
|
1394
|
+
function stampGuards(hostGuards, builtIn, controller) {
|
|
1395
|
+
const merged = [
|
|
1396
|
+
...builtIn ? [
|
|
1397
|
+
builtIn
|
|
1398
|
+
] : [],
|
|
1399
|
+
...hostGuards ?? []
|
|
1400
|
+
];
|
|
1401
|
+
Reflect.defineMetadata(GUARDS_METADATA, merged, controller);
|
|
1402
|
+
}
|
|
1403
|
+
__name(stampGuards, "stampGuards");
|
|
1404
|
+
var DurableApiModule = class _DurableApiModule {
|
|
677
1405
|
static {
|
|
678
1406
|
__name(this, "DurableApiModule");
|
|
679
1407
|
}
|
|
1408
|
+
static register(options) {
|
|
1409
|
+
stampGuards(options.guards, options.authMayBeConfigured ? DurableApiSessionGuard : void 0, DurableApiController);
|
|
1410
|
+
return {
|
|
1411
|
+
module: _DurableApiModule,
|
|
1412
|
+
imports: [
|
|
1413
|
+
...options.imports ?? []
|
|
1414
|
+
],
|
|
1415
|
+
controllers: [
|
|
1416
|
+
DurableApiController
|
|
1417
|
+
],
|
|
1418
|
+
providers: [
|
|
1419
|
+
DashboardService,
|
|
1420
|
+
options.authProvider,
|
|
1421
|
+
DurableApiSessionGuard,
|
|
1422
|
+
...options.guards ?? []
|
|
1423
|
+
],
|
|
1424
|
+
exports: [
|
|
1425
|
+
DashboardService
|
|
1426
|
+
]
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
680
1429
|
};
|
|
681
|
-
DurableApiModule =
|
|
682
|
-
(0,
|
|
683
|
-
controllers: [
|
|
684
|
-
DurableApiController
|
|
685
|
-
],
|
|
686
|
-
providers: [
|
|
687
|
-
DashboardService
|
|
688
|
-
],
|
|
689
|
-
exports: [
|
|
690
|
-
DashboardService
|
|
691
|
-
]
|
|
692
|
-
})
|
|
1430
|
+
DurableApiModule = _ts_decorate7([
|
|
1431
|
+
(0, import_common7.Module)({})
|
|
693
1432
|
], DurableApiModule);
|
|
694
1433
|
var DurableDashboardModule = class _DurableDashboardModule {
|
|
695
1434
|
static {
|
|
696
1435
|
__name(this, "DurableDashboardModule");
|
|
697
1436
|
}
|
|
698
1437
|
static forRoot(options = {}) {
|
|
1438
|
+
const authProvider = {
|
|
1439
|
+
provide: DASHBOARD_AUTH,
|
|
1440
|
+
useValue: resolveDashboardAuth(options.dashboardAuth)
|
|
1441
|
+
};
|
|
1442
|
+
return _DurableDashboardModule.build(options, authProvider, options.dashboardAuth !== void 0);
|
|
1443
|
+
}
|
|
1444
|
+
static forRootAsync(options) {
|
|
1445
|
+
const authProvider = {
|
|
1446
|
+
provide: DASHBOARD_AUTH,
|
|
1447
|
+
inject: options.inject ?? [],
|
|
1448
|
+
useFactory: /* @__PURE__ */ __name(async (...deps) => resolveDashboardAuth(await options.useDashboardAuth(...deps)), "useFactory")
|
|
1449
|
+
};
|
|
1450
|
+
return _DurableDashboardModule.build(options, authProvider, true);
|
|
1451
|
+
}
|
|
1452
|
+
/** Shared wiring: static routing + the API module, with `dashboardAuth` supplied by `authProvider`. */
|
|
1453
|
+
static build(options, authProvider, authMayBeConfigured) {
|
|
699
1454
|
const basePath = normalize(options.basePath ?? "/durable");
|
|
700
1455
|
const apiBasePath = normalize(options.apiBasePath ?? `${basePath}/api`);
|
|
1456
|
+
stampGuards(options.guards, authMayBeConfigured ? DurableUiSessionGuard : void 0, DurableUiController);
|
|
701
1457
|
return {
|
|
702
1458
|
module: _DurableDashboardModule,
|
|
703
1459
|
imports: [
|
|
704
|
-
|
|
1460
|
+
...options.imports ?? [],
|
|
1461
|
+
// Guards + host imports must reach the API controller's HOST module — enhancers resolve
|
|
1462
|
+
// from their controller's own module, never from a parent (see DurableApiModule.register).
|
|
1463
|
+
// Spread-only-when-set: exactOptionalPropertyTypes rejects an explicit `undefined`.
|
|
1464
|
+
DurableApiModule.register({
|
|
1465
|
+
...options.imports ? {
|
|
1466
|
+
imports: options.imports
|
|
1467
|
+
} : {},
|
|
1468
|
+
...options.guards ? {
|
|
1469
|
+
guards: options.guards
|
|
1470
|
+
} : {},
|
|
1471
|
+
authProvider,
|
|
1472
|
+
authMayBeConfigured
|
|
1473
|
+
}),
|
|
705
1474
|
import_core.RouterModule.register([
|
|
706
1475
|
{
|
|
707
1476
|
path: basePath,
|
|
@@ -714,7 +1483,13 @@ var DurableDashboardModule = class _DurableDashboardModule {
|
|
|
714
1483
|
])
|
|
715
1484
|
],
|
|
716
1485
|
controllers: [
|
|
717
|
-
DurableUiController
|
|
1486
|
+
DurableUiController,
|
|
1487
|
+
// The auth controller (login page + logout) only exists when dashboardAuth might be
|
|
1488
|
+
// configured — mounting it unconditionally would add a new `/login` route even for hosts
|
|
1489
|
+
// that never set `dashboardAuth`, breaking "absent -> today's behavior byte-for-byte".
|
|
1490
|
+
...authMayBeConfigured ? [
|
|
1491
|
+
DurableAuthController
|
|
1492
|
+
] : []
|
|
718
1493
|
],
|
|
719
1494
|
providers: [
|
|
720
1495
|
{
|
|
@@ -724,7 +1499,12 @@ var DurableDashboardModule = class _DurableDashboardModule {
|
|
|
724
1499
|
{
|
|
725
1500
|
provide: DASHBOARD_API_PATH,
|
|
726
1501
|
useValue: apiBasePath
|
|
727
|
-
}
|
|
1502
|
+
},
|
|
1503
|
+
authProvider,
|
|
1504
|
+
DurableUiSessionGuard,
|
|
1505
|
+
// DurableUiController (and DurableAuthController, when present) are hosted HERE, so their
|
|
1506
|
+
// guards DI-instantiate from this module.
|
|
1507
|
+
...options.guards ?? []
|
|
728
1508
|
],
|
|
729
1509
|
// Re-export the API module so its DashboardService reaches importers (e.g. flip's own controllers).
|
|
730
1510
|
exports: [
|
|
@@ -733,8 +1513,8 @@ var DurableDashboardModule = class _DurableDashboardModule {
|
|
|
733
1513
|
};
|
|
734
1514
|
}
|
|
735
1515
|
};
|
|
736
|
-
DurableDashboardModule =
|
|
737
|
-
(0,
|
|
1516
|
+
DurableDashboardModule = _ts_decorate7([
|
|
1517
|
+
(0, import_common7.Module)({})
|
|
738
1518
|
], DurableDashboardModule);
|
|
739
1519
|
// Annotate the CommonJS export names for ESM import in node:
|
|
740
1520
|
0 && (module.exports = {
|
|
@@ -743,7 +1523,10 @@ DurableDashboardModule = _ts_decorate4([
|
|
|
743
1523
|
DashboardService,
|
|
744
1524
|
DurableApiController,
|
|
745
1525
|
DurableApiModule,
|
|
1526
|
+
DurableApiSessionGuard,
|
|
1527
|
+
DurableAuthController,
|
|
746
1528
|
DurableDashboardModule,
|
|
747
|
-
DurableUiController
|
|
1529
|
+
DurableUiController,
|
|
1530
|
+
DurableUiSessionGuard
|
|
748
1531
|
});
|
|
749
1532
|
//# sourceMappingURL=index.cjs.map
|