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