@dudousxd/nestjs-durable-dashboard 0.37.0 → 0.38.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 +57 -2
- package/dist/client/durable-client.js.map +1 -1
- package/dist/server/index.cjs +157 -81
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +52 -4
- package/dist/server/index.d.ts +52 -4
- package/dist/server/index.js +162 -86
- package/dist/server/index.js.map +1 -1
- package/dist/spa/assets/{index-tjU3_joS.js → index-AKaTbEKr.js} +8 -8
- package/dist/spa/assets/{index-BgWYZ0wi.js → index-kCrHKxZp.js} +1 -1
- package/dist/spa/assets/{preview-CoXYGpK-.js → preview-G8TH2d5R.js} +1 -1
- package/dist/spa/index.html +2 -2
- package/dist/spa/preview.html +2 -2
- package/package.json +1 -1
package/dist/server/index.d.cts
CHANGED
|
@@ -18,11 +18,48 @@ type SessionHook = (request: unknown) => Promise<DashboardSessionUser | null> |
|
|
|
18
18
|
type LoginHook = (username: string, password: string) => Promise<DashboardSessionUser | null> | DashboardSessionUser | null;
|
|
19
19
|
/**
|
|
20
20
|
* Re-checks a LIVE session when the cookie is slid forward. Runs at most once per `ttl/2` per
|
|
21
|
-
* session, so a DB round-trip here is cheap
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* session, so a DB round-trip here is cheap — this holds even under concurrent traffic: N parallel
|
|
22
|
+
* requests carrying the same past-half-life cookie share ONE call (see `revalidateOnce` in
|
|
23
|
+
* `session-cookie-io.ts`), not N. Return `false` to revoke (the cookie is cleared and the request
|
|
24
|
+
* denied). Distinct from `session`: that hook reads the host's auth off a fresh request, which a
|
|
25
|
+
* console XHR does not carry — this one receives the already-minted session.
|
|
24
26
|
*/
|
|
25
27
|
type RevalidateHook = (session: DashboardSessionUser) => Promise<boolean> | boolean;
|
|
28
|
+
/** What `unauthenticatedPage` receives. An object (not positional args) so fields can be added later. */
|
|
29
|
+
interface UnauthenticatedPageContext {
|
|
30
|
+
/**
|
|
31
|
+
* The platform-native request — Express' `Request`, Fastify's `FastifyRequest`. Typed `unknown`
|
|
32
|
+
* for the same reason `SessionHook`'s is: this package refuses to depend on either platform.
|
|
33
|
+
* Cast it to whatever your app actually runs on.
|
|
34
|
+
*/
|
|
35
|
+
request: unknown;
|
|
36
|
+
/**
|
|
37
|
+
* The platform-native response. The hook OWNS it: it must write AND end it (`res.render(...)`,
|
|
38
|
+
* `res.status(401).send(...)`, an Inertia render, ...). If the hook returns without writing, the
|
|
39
|
+
* library falls back to its own built-in page rather than leaving the request hanging.
|
|
40
|
+
*/
|
|
41
|
+
response: unknown;
|
|
42
|
+
/** Where this console is mounted (e.g. `/durable`) — useful for a "back to the console" link. */
|
|
43
|
+
basePath: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Host-owned page for an unauthenticated full-page navigation to the console.
|
|
47
|
+
*
|
|
48
|
+
* The built-in page cannot know who hosts the console, so it can only say "open this console from
|
|
49
|
+
* your application" in the abstract — it can't name the launcher, link to it, or look like the rest
|
|
50
|
+
* of the host's product. This hook hands the whole response to the host instead: render through
|
|
51
|
+
* your own template engine, your own Inertia page, whatever. The page is served AT the console's
|
|
52
|
+
* own URL, so `/durable` stays `/durable`.
|
|
53
|
+
*
|
|
54
|
+
* Deliberately NOT a replacement for Mode B's built-in login form. A host that wants its own login
|
|
55
|
+
* UI uses Mode A plus this hook, and posts to `<basePath>/session` from its own page — the mint
|
|
56
|
+
* endpoint is the supported primitive for that.
|
|
57
|
+
*
|
|
58
|
+
* Fail-closed by construction: it only ever runs on a request that has ALREADY been denied. A hook
|
|
59
|
+
* that throws, or that returns without writing, falls back to the built-in page — it cannot let
|
|
60
|
+
* anyone in.
|
|
61
|
+
*/
|
|
62
|
+
type UnauthenticatedPageHook = (context: UnauthenticatedPageContext) => void | Promise<void>;
|
|
26
63
|
type AuthMode = 'session' | 'login';
|
|
27
64
|
/**
|
|
28
65
|
* Author-facing `dashboardAuth` option on `DurableDashboardModule.forRoot`/`forRootAsync`. Gates
|
|
@@ -50,6 +87,9 @@ interface DashboardAuthOptions {
|
|
|
50
87
|
/** Re-checks a live session on sliding renewal; see `RevalidateHook`. Not a mode — it cannot
|
|
51
88
|
* mint a session, only revoke one already minted by `session`/`login`. */
|
|
52
89
|
revalidate?: RevalidateHook;
|
|
90
|
+
/** Renders the host's own page for an unauthenticated navigation, in place of the built-in
|
|
91
|
+
* "open this console from your application" card; see `UnauthenticatedPageHook`. */
|
|
92
|
+
unauthenticatedPage?: UnauthenticatedPageHook;
|
|
53
93
|
}
|
|
54
94
|
/** Resolved, validated `dashboardAuth` config shared by the guards, auth controller, and login page. */
|
|
55
95
|
interface ResolvedDashboardAuth {
|
|
@@ -59,6 +99,7 @@ interface ResolvedDashboardAuth {
|
|
|
59
99
|
session?: SessionHook;
|
|
60
100
|
login?: LoginHook;
|
|
61
101
|
revalidate?: RevalidateHook;
|
|
102
|
+
unauthenticatedPage?: UnauthenticatedPageHook;
|
|
62
103
|
}
|
|
63
104
|
|
|
64
105
|
/**
|
|
@@ -418,6 +459,13 @@ declare class DurableApiSessionGuard implements CanActivate {
|
|
|
418
459
|
private readonly auth;
|
|
419
460
|
constructor(auth: ResolvedDashboardAuth | null);
|
|
420
461
|
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
462
|
+
/**
|
|
463
|
+
* A bare 401 body carrying `{ auth: { modes } }` — mirrors `@dudousxd/nestjs-telescope`'s
|
|
464
|
+
* dashboardAuth 401. The API is fetched (never navigated), so this is the ONLY way the console
|
|
465
|
+
* SPA (`durable-client.ts`) learns which auth surface to send the operator to on a mid-session
|
|
466
|
+
* 401; `DurableUiSessionGuard`'s redirect/session-required exceptions above don't reach it.
|
|
467
|
+
*/
|
|
468
|
+
private unauthorized;
|
|
421
469
|
}
|
|
422
470
|
|
|
423
471
|
/** DI token carrying the UI mount base (e.g. `/durable`). */
|
|
@@ -446,4 +494,4 @@ declare class DurableUiController {
|
|
|
446
494
|
asset(file: string): StreamableFile;
|
|
447
495
|
}
|
|
448
496
|
|
|
449
|
-
export { DASHBOARD_API_PATH, DASHBOARD_BASE_PATH, type DashboardAuthOptions, DashboardService, type DashboardSessionUser, DurableApiController, DurableApiModule, DurableApiSessionGuard, DurableAuthController, type DurableDashboardAsyncOptions, DurableDashboardModule, type DurableDashboardOptions, DurableUiController, DurableUiSessionGuard };
|
|
497
|
+
export { DASHBOARD_API_PATH, DASHBOARD_BASE_PATH, type DashboardAuthOptions, DashboardService, type DashboardSessionUser, DurableApiController, DurableApiModule, DurableApiSessionGuard, DurableAuthController, type DurableDashboardAsyncOptions, DurableDashboardModule, type DurableDashboardOptions, DurableUiController, DurableUiSessionGuard, type UnauthenticatedPageContext, type UnauthenticatedPageHook };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -18,11 +18,48 @@ type SessionHook = (request: unknown) => Promise<DashboardSessionUser | null> |
|
|
|
18
18
|
type LoginHook = (username: string, password: string) => Promise<DashboardSessionUser | null> | DashboardSessionUser | null;
|
|
19
19
|
/**
|
|
20
20
|
* Re-checks a LIVE session when the cookie is slid forward. Runs at most once per `ttl/2` per
|
|
21
|
-
* session, so a DB round-trip here is cheap
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* session, so a DB round-trip here is cheap — this holds even under concurrent traffic: N parallel
|
|
22
|
+
* requests carrying the same past-half-life cookie share ONE call (see `revalidateOnce` in
|
|
23
|
+
* `session-cookie-io.ts`), not N. Return `false` to revoke (the cookie is cleared and the request
|
|
24
|
+
* denied). Distinct from `session`: that hook reads the host's auth off a fresh request, which a
|
|
25
|
+
* console XHR does not carry — this one receives the already-minted session.
|
|
24
26
|
*/
|
|
25
27
|
type RevalidateHook = (session: DashboardSessionUser) => Promise<boolean> | boolean;
|
|
28
|
+
/** What `unauthenticatedPage` receives. An object (not positional args) so fields can be added later. */
|
|
29
|
+
interface UnauthenticatedPageContext {
|
|
30
|
+
/**
|
|
31
|
+
* The platform-native request — Express' `Request`, Fastify's `FastifyRequest`. Typed `unknown`
|
|
32
|
+
* for the same reason `SessionHook`'s is: this package refuses to depend on either platform.
|
|
33
|
+
* Cast it to whatever your app actually runs on.
|
|
34
|
+
*/
|
|
35
|
+
request: unknown;
|
|
36
|
+
/**
|
|
37
|
+
* The platform-native response. The hook OWNS it: it must write AND end it (`res.render(...)`,
|
|
38
|
+
* `res.status(401).send(...)`, an Inertia render, ...). If the hook returns without writing, the
|
|
39
|
+
* library falls back to its own built-in page rather than leaving the request hanging.
|
|
40
|
+
*/
|
|
41
|
+
response: unknown;
|
|
42
|
+
/** Where this console is mounted (e.g. `/durable`) — useful for a "back to the console" link. */
|
|
43
|
+
basePath: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Host-owned page for an unauthenticated full-page navigation to the console.
|
|
47
|
+
*
|
|
48
|
+
* The built-in page cannot know who hosts the console, so it can only say "open this console from
|
|
49
|
+
* your application" in the abstract — it can't name the launcher, link to it, or look like the rest
|
|
50
|
+
* of the host's product. This hook hands the whole response to the host instead: render through
|
|
51
|
+
* your own template engine, your own Inertia page, whatever. The page is served AT the console's
|
|
52
|
+
* own URL, so `/durable` stays `/durable`.
|
|
53
|
+
*
|
|
54
|
+
* Deliberately NOT a replacement for Mode B's built-in login form. A host that wants its own login
|
|
55
|
+
* UI uses Mode A plus this hook, and posts to `<basePath>/session` from its own page — the mint
|
|
56
|
+
* endpoint is the supported primitive for that.
|
|
57
|
+
*
|
|
58
|
+
* Fail-closed by construction: it only ever runs on a request that has ALREADY been denied. A hook
|
|
59
|
+
* that throws, or that returns without writing, falls back to the built-in page — it cannot let
|
|
60
|
+
* anyone in.
|
|
61
|
+
*/
|
|
62
|
+
type UnauthenticatedPageHook = (context: UnauthenticatedPageContext) => void | Promise<void>;
|
|
26
63
|
type AuthMode = 'session' | 'login';
|
|
27
64
|
/**
|
|
28
65
|
* Author-facing `dashboardAuth` option on `DurableDashboardModule.forRoot`/`forRootAsync`. Gates
|
|
@@ -50,6 +87,9 @@ interface DashboardAuthOptions {
|
|
|
50
87
|
/** Re-checks a live session on sliding renewal; see `RevalidateHook`. Not a mode — it cannot
|
|
51
88
|
* mint a session, only revoke one already minted by `session`/`login`. */
|
|
52
89
|
revalidate?: RevalidateHook;
|
|
90
|
+
/** Renders the host's own page for an unauthenticated navigation, in place of the built-in
|
|
91
|
+
* "open this console from your application" card; see `UnauthenticatedPageHook`. */
|
|
92
|
+
unauthenticatedPage?: UnauthenticatedPageHook;
|
|
53
93
|
}
|
|
54
94
|
/** Resolved, validated `dashboardAuth` config shared by the guards, auth controller, and login page. */
|
|
55
95
|
interface ResolvedDashboardAuth {
|
|
@@ -59,6 +99,7 @@ interface ResolvedDashboardAuth {
|
|
|
59
99
|
session?: SessionHook;
|
|
60
100
|
login?: LoginHook;
|
|
61
101
|
revalidate?: RevalidateHook;
|
|
102
|
+
unauthenticatedPage?: UnauthenticatedPageHook;
|
|
62
103
|
}
|
|
63
104
|
|
|
64
105
|
/**
|
|
@@ -418,6 +459,13 @@ declare class DurableApiSessionGuard implements CanActivate {
|
|
|
418
459
|
private readonly auth;
|
|
419
460
|
constructor(auth: ResolvedDashboardAuth | null);
|
|
420
461
|
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
462
|
+
/**
|
|
463
|
+
* A bare 401 body carrying `{ auth: { modes } }` — mirrors `@dudousxd/nestjs-telescope`'s
|
|
464
|
+
* dashboardAuth 401. The API is fetched (never navigated), so this is the ONLY way the console
|
|
465
|
+
* SPA (`durable-client.ts`) learns which auth surface to send the operator to on a mid-session
|
|
466
|
+
* 401; `DurableUiSessionGuard`'s redirect/session-required exceptions above don't reach it.
|
|
467
|
+
*/
|
|
468
|
+
private unauthorized;
|
|
421
469
|
}
|
|
422
470
|
|
|
423
471
|
/** DI token carrying the UI mount base (e.g. `/durable`). */
|
|
@@ -446,4 +494,4 @@ declare class DurableUiController {
|
|
|
446
494
|
asset(file: string): StreamableFile;
|
|
447
495
|
}
|
|
448
496
|
|
|
449
|
-
export { DASHBOARD_API_PATH, DASHBOARD_BASE_PATH, type DashboardAuthOptions, DashboardService, type DashboardSessionUser, DurableApiController, DurableApiModule, DurableApiSessionGuard, DurableAuthController, type DurableDashboardAsyncOptions, DurableDashboardModule, type DurableDashboardOptions, DurableUiController, DurableUiSessionGuard };
|
|
497
|
+
export { DASHBOARD_API_PATH, DASHBOARD_BASE_PATH, type DashboardAuthOptions, DashboardService, type DashboardSessionUser, DurableApiController, DurableApiModule, DurableApiSessionGuard, DurableAuthController, type DurableDashboardAsyncOptions, DurableDashboardModule, type DurableDashboardOptions, DurableUiController, DurableUiSessionGuard, type UnauthenticatedPageContext, type UnauthenticatedPageHook };
|
package/dist/server/index.js
CHANGED
|
@@ -518,7 +518,7 @@ DurableApiController = _ts_decorate2([
|
|
|
518
518
|
], DurableApiController);
|
|
519
519
|
|
|
520
520
|
// src/server/durable-auth.controller.ts
|
|
521
|
-
import { BadRequestException, Body as Body2, Controller as Controller3, Get as Get3, Header as Header3, HttpCode, Inject as
|
|
521
|
+
import { BadRequestException, Body as Body2, Controller as Controller3, Get as Get3, Header as Header3, HttpCode, Inject as Inject4, Logger as Logger2, NotFoundException as NotFoundException3, Optional as Optional3, Post as Post2, Req, Res, UnauthorizedException } from "@nestjs/common";
|
|
522
522
|
|
|
523
523
|
// src/server/auth/dashboard-auth-config.ts
|
|
524
524
|
var DASHBOARD_AUTH = Symbol("DASHBOARD_AUTH");
|
|
@@ -559,6 +559,9 @@ function resolveDashboardAuth(options) {
|
|
|
559
559
|
if (options.revalidate !== void 0 && typeof options.revalidate !== "function") {
|
|
560
560
|
throw new Error("DurableDashboardModule: dashboardAuth.revalidate must be a function.");
|
|
561
561
|
}
|
|
562
|
+
if (options.unauthenticatedPage !== void 0 && typeof options.unauthenticatedPage !== "function") {
|
|
563
|
+
throw new Error("DurableDashboardModule: dashboardAuth.unauthenticatedPage must be a function.");
|
|
564
|
+
}
|
|
562
565
|
if (modes.length === 0) {
|
|
563
566
|
throw new Error("DurableDashboardModule: dashboardAuth needs at least one of `session` or `login` (otherwise the cookie can never be minted). Failing closed.");
|
|
564
567
|
}
|
|
@@ -574,6 +577,9 @@ function resolveDashboardAuth(options) {
|
|
|
574
577
|
} : {},
|
|
575
578
|
...options.revalidate !== void 0 ? {
|
|
576
579
|
revalidate: options.revalidate
|
|
580
|
+
} : {},
|
|
581
|
+
...options.unauthenticatedPage !== void 0 ? {
|
|
582
|
+
unauthenticatedPage: options.unauthenticatedPage
|
|
577
583
|
} : {}
|
|
578
584
|
};
|
|
579
585
|
}
|
|
@@ -850,6 +856,11 @@ function redirectRaw(response, location, status = 302) {
|
|
|
850
856
|
raw.end();
|
|
851
857
|
}
|
|
852
858
|
__name(redirectRaw, "redirectRaw");
|
|
859
|
+
function responseAlreadyWritten(response) {
|
|
860
|
+
const raw = resolveRedirectResponse(response);
|
|
861
|
+
return raw?.headersSent === true;
|
|
862
|
+
}
|
|
863
|
+
__name(responseAlreadyWritten, "responseAlreadyWritten");
|
|
853
864
|
function writeHtmlRaw(response, html, status = 200) {
|
|
854
865
|
const raw = resolveRedirectResponse(response);
|
|
855
866
|
if (!raw) return;
|
|
@@ -1014,6 +1025,20 @@ function readSessionFromRequest(auth, request) {
|
|
|
1014
1025
|
});
|
|
1015
1026
|
}
|
|
1016
1027
|
__name(readSessionFromRequest, "readSessionFromRequest");
|
|
1028
|
+
var inFlightRevalidations = /* @__PURE__ */ new Map();
|
|
1029
|
+
function revalidateOnce(auth, user) {
|
|
1030
|
+
const revalidate = auth.revalidate;
|
|
1031
|
+
if (!revalidate) return Promise.resolve(true);
|
|
1032
|
+
const key = `${auth.secret}:${user.id}`;
|
|
1033
|
+
const inFlight = inFlightRevalidations.get(key);
|
|
1034
|
+
if (inFlight) return inFlight;
|
|
1035
|
+
const call = Promise.resolve().then(() => revalidate(user)).catch(() => false).finally(() => {
|
|
1036
|
+
inFlightRevalidations.delete(key);
|
|
1037
|
+
});
|
|
1038
|
+
inFlightRevalidations.set(key, call);
|
|
1039
|
+
return call;
|
|
1040
|
+
}
|
|
1041
|
+
__name(revalidateOnce, "revalidateOnce");
|
|
1017
1042
|
async function maybeRenewSession(auth, session, request, response, now = Date.now()) {
|
|
1018
1043
|
if (now - session.iat <= auth.ttlMs / 2) return true;
|
|
1019
1044
|
const user = {
|
|
@@ -1023,20 +1048,13 @@ async function maybeRenewSession(auth, session, request, response, now = Date.no
|
|
|
1023
1048
|
} : {},
|
|
1024
1049
|
roles: session.roles
|
|
1025
1050
|
};
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
if (!allowed) {
|
|
1034
|
-
clearSessionCookie({
|
|
1035
|
-
request,
|
|
1036
|
-
response
|
|
1037
|
-
});
|
|
1038
|
-
return false;
|
|
1039
|
-
}
|
|
1051
|
+
const allowed = await revalidateOnce(auth, user);
|
|
1052
|
+
if (!allowed) {
|
|
1053
|
+
clearSessionCookie({
|
|
1054
|
+
request,
|
|
1055
|
+
response
|
|
1056
|
+
});
|
|
1057
|
+
return false;
|
|
1040
1058
|
}
|
|
1041
1059
|
issueSessionCookie(user, {
|
|
1042
1060
|
auth,
|
|
@@ -1052,7 +1070,7 @@ __name(maybeRenewSession, "maybeRenewSession");
|
|
|
1052
1070
|
import { existsSync, readFileSync } from "node:fs";
|
|
1053
1071
|
import { basename, extname, join, resolve, sep } from "node:path";
|
|
1054
1072
|
import { fileURLToPath } from "node:url";
|
|
1055
|
-
import { Controller as Controller2, Get as Get2, Header as Header2, Inject as
|
|
1073
|
+
import { Controller as Controller2, Get as Get2, Header as Header2, Inject as Inject3, NotFoundException as NotFoundException2, Param as Param2, StreamableFile, UseFilters } from "@nestjs/common";
|
|
1056
1074
|
|
|
1057
1075
|
// src/server/auth/login-redirect.exception.ts
|
|
1058
1076
|
import { Catch, HttpException, HttpStatus } from "@nestjs/common";
|
|
@@ -1088,7 +1106,7 @@ DashboardLoginRedirectFilter = _ts_decorate3([
|
|
|
1088
1106
|
], DashboardLoginRedirectFilter);
|
|
1089
1107
|
|
|
1090
1108
|
// src/server/auth/session-required.exception.ts
|
|
1091
|
-
import { Catch as Catch2, HttpException as HttpException2, HttpStatus as HttpStatus2 } from "@nestjs/common";
|
|
1109
|
+
import { Catch as Catch2, HttpException as HttpException2, HttpStatus as HttpStatus2, Inject as Inject2, Logger, Optional as Optional2 } from "@nestjs/common";
|
|
1092
1110
|
function _ts_decorate4(decorators, target, key, desc) {
|
|
1093
1111
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1094
1112
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -1096,6 +1114,16 @@ function _ts_decorate4(decorators, target, key, desc) {
|
|
|
1096
1114
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1097
1115
|
}
|
|
1098
1116
|
__name(_ts_decorate4, "_ts_decorate");
|
|
1117
|
+
function _ts_metadata3(k, v) {
|
|
1118
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1119
|
+
}
|
|
1120
|
+
__name(_ts_metadata3, "_ts_metadata");
|
|
1121
|
+
function _ts_param3(paramIndex, decorator) {
|
|
1122
|
+
return function(target, key) {
|
|
1123
|
+
decorator(target, key, paramIndex);
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
__name(_ts_param3, "_ts_param");
|
|
1099
1127
|
var DashboardSessionRequiredException = class extends HttpException2 {
|
|
1100
1128
|
static {
|
|
1101
1129
|
__name(this, "DashboardSessionRequiredException");
|
|
@@ -1107,17 +1135,51 @@ var DashboardSessionRequiredException = class extends HttpException2 {
|
|
|
1107
1135
|
}, HttpStatus2.UNAUTHORIZED), this.basePath = basePath;
|
|
1108
1136
|
}
|
|
1109
1137
|
};
|
|
1110
|
-
var DashboardSessionRequiredFilter = class {
|
|
1138
|
+
var DashboardSessionRequiredFilter = class _DashboardSessionRequiredFilter {
|
|
1111
1139
|
static {
|
|
1112
1140
|
__name(this, "DashboardSessionRequiredFilter");
|
|
1113
1141
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1142
|
+
auth;
|
|
1143
|
+
logger = new Logger(_DashboardSessionRequiredFilter.name);
|
|
1144
|
+
warned = false;
|
|
1145
|
+
constructor(auth = null) {
|
|
1146
|
+
this.auth = auth;
|
|
1147
|
+
}
|
|
1148
|
+
async catch(exception, host) {
|
|
1149
|
+
const http = host.switchToHttp();
|
|
1150
|
+
const response = http.getResponse();
|
|
1151
|
+
const hook = this.auth?.unauthenticatedPage;
|
|
1152
|
+
if (hook) {
|
|
1153
|
+
try {
|
|
1154
|
+
await hook({
|
|
1155
|
+
request: http.getRequest(),
|
|
1156
|
+
response,
|
|
1157
|
+
basePath: exception.basePath
|
|
1158
|
+
});
|
|
1159
|
+
if (responseAlreadyWritten(response)) return;
|
|
1160
|
+
this.warnOnce("dashboardAuth.unauthenticatedPage returned without writing a response; falling back to the built-in page. The hook must write AND end the response it is given.");
|
|
1161
|
+
} catch (error) {
|
|
1162
|
+
this.warnOnce(`dashboardAuth.unauthenticatedPage threw; falling back to the built-in page. ${String(error)}`);
|
|
1163
|
+
if (responseAlreadyWritten(response)) return;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1116
1166
|
writeHtmlRaw(response, renderSessionRequiredPage(exception.basePath), HttpStatus2.UNAUTHORIZED);
|
|
1117
1167
|
}
|
|
1168
|
+
/** Once per process, not once per request: a broken hook fires on every denied navigation. */
|
|
1169
|
+
warnOnce(message) {
|
|
1170
|
+
if (this.warned) return;
|
|
1171
|
+
this.warned = true;
|
|
1172
|
+
this.logger.warn(message);
|
|
1173
|
+
}
|
|
1118
1174
|
};
|
|
1119
1175
|
DashboardSessionRequiredFilter = _ts_decorate4([
|
|
1120
|
-
Catch2(DashboardSessionRequiredException)
|
|
1176
|
+
Catch2(DashboardSessionRequiredException),
|
|
1177
|
+
_ts_param3(0, Optional2()),
|
|
1178
|
+
_ts_param3(0, Inject2(DASHBOARD_AUTH)),
|
|
1179
|
+
_ts_metadata3("design:type", Function),
|
|
1180
|
+
_ts_metadata3("design:paramtypes", [
|
|
1181
|
+
Object
|
|
1182
|
+
])
|
|
1121
1183
|
], DashboardSessionRequiredFilter);
|
|
1122
1184
|
|
|
1123
1185
|
// src/server/durable-ui.controller.ts
|
|
@@ -1128,16 +1190,16 @@ function _ts_decorate5(decorators, target, key, desc) {
|
|
|
1128
1190
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1129
1191
|
}
|
|
1130
1192
|
__name(_ts_decorate5, "_ts_decorate");
|
|
1131
|
-
function
|
|
1193
|
+
function _ts_metadata4(k, v) {
|
|
1132
1194
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1133
1195
|
}
|
|
1134
|
-
__name(
|
|
1135
|
-
function
|
|
1196
|
+
__name(_ts_metadata4, "_ts_metadata");
|
|
1197
|
+
function _ts_param4(paramIndex, decorator) {
|
|
1136
1198
|
return function(target, key) {
|
|
1137
1199
|
decorator(target, key, paramIndex);
|
|
1138
1200
|
};
|
|
1139
1201
|
}
|
|
1140
|
-
__name(
|
|
1202
|
+
__name(_ts_param4, "_ts_param");
|
|
1141
1203
|
var DASHBOARD_BASE_PATH = Symbol("DASHBOARD_BASE_PATH");
|
|
1142
1204
|
var DASHBOARD_API_PATH = Symbol("DASHBOARD_API_PATH");
|
|
1143
1205
|
var BUILD_BASE = "/durable";
|
|
@@ -1194,27 +1256,27 @@ _ts_decorate5([
|
|
|
1194
1256
|
Get2(),
|
|
1195
1257
|
Header2("Content-Type", "text/html; charset=utf-8"),
|
|
1196
1258
|
Header2("Cache-Control", "no-store, must-revalidate"),
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1259
|
+
_ts_metadata4("design:type", Function),
|
|
1260
|
+
_ts_metadata4("design:paramtypes", []),
|
|
1261
|
+
_ts_metadata4("design:returntype", String)
|
|
1200
1262
|
], DurableUiController.prototype, "index", null);
|
|
1201
1263
|
_ts_decorate5([
|
|
1202
1264
|
Get2("assets/:file"),
|
|
1203
1265
|
Header2("Cache-Control", "public, max-age=31536000, immutable"),
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1266
|
+
_ts_param4(0, Param2("file")),
|
|
1267
|
+
_ts_metadata4("design:type", Function),
|
|
1268
|
+
_ts_metadata4("design:paramtypes", [
|
|
1207
1269
|
String
|
|
1208
1270
|
]),
|
|
1209
|
-
|
|
1271
|
+
_ts_metadata4("design:returntype", typeof StreamableFile === "undefined" ? Object : StreamableFile)
|
|
1210
1272
|
], DurableUiController.prototype, "asset", null);
|
|
1211
1273
|
DurableUiController = _ts_decorate5([
|
|
1212
1274
|
UseFilters(DashboardLoginRedirectFilter, DashboardSessionRequiredFilter),
|
|
1213
1275
|
Controller2(),
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1276
|
+
_ts_param4(0, Inject3(DASHBOARD_BASE_PATH)),
|
|
1277
|
+
_ts_param4(1, Inject3(DASHBOARD_API_PATH)),
|
|
1278
|
+
_ts_metadata4("design:type", Function),
|
|
1279
|
+
_ts_metadata4("design:paramtypes", [
|
|
1218
1280
|
String,
|
|
1219
1281
|
String
|
|
1220
1282
|
])
|
|
@@ -1228,16 +1290,16 @@ function _ts_decorate6(decorators, target, key, desc) {
|
|
|
1228
1290
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1229
1291
|
}
|
|
1230
1292
|
__name(_ts_decorate6, "_ts_decorate");
|
|
1231
|
-
function
|
|
1293
|
+
function _ts_metadata5(k, v) {
|
|
1232
1294
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1233
1295
|
}
|
|
1234
|
-
__name(
|
|
1235
|
-
function
|
|
1296
|
+
__name(_ts_metadata5, "_ts_metadata");
|
|
1297
|
+
function _ts_param5(paramIndex, decorator) {
|
|
1236
1298
|
return function(target, key) {
|
|
1237
1299
|
decorator(target, key, paramIndex);
|
|
1238
1300
|
};
|
|
1239
1301
|
}
|
|
1240
|
-
__name(
|
|
1302
|
+
__name(_ts_param5, "_ts_param");
|
|
1241
1303
|
function isString(value) {
|
|
1242
1304
|
return typeof value === "string";
|
|
1243
1305
|
}
|
|
@@ -1248,7 +1310,7 @@ var DurableAuthController = class _DurableAuthController {
|
|
|
1248
1310
|
}
|
|
1249
1311
|
auth;
|
|
1250
1312
|
basePath;
|
|
1251
|
-
logger = new
|
|
1313
|
+
logger = new Logger2(_DurableAuthController.name);
|
|
1252
1314
|
/** Warn once per hook kind so a throwing hook doesn't spam logs on every failed attempt. */
|
|
1253
1315
|
warnedHooks = /* @__PURE__ */ new Set();
|
|
1254
1316
|
constructor(auth, basePath) {
|
|
@@ -1260,7 +1322,7 @@ var DurableAuthController = class _DurableAuthController {
|
|
|
1260
1322
|
// the developer-controlled `basePath`.
|
|
1261
1323
|
loginPage() {
|
|
1262
1324
|
const auth = this.requireAuth();
|
|
1263
|
-
if (!auth.login) throw new NotFoundException3();
|
|
1325
|
+
if (!auth.modes.includes("login")) throw new NotFoundException3();
|
|
1264
1326
|
return renderLoginPage(this.basePath);
|
|
1265
1327
|
}
|
|
1266
1328
|
// Called by the login page's own fetch (JSON, not a classic form POST — see `login-page.ts` for
|
|
@@ -1269,7 +1331,7 @@ var DurableAuthController = class _DurableAuthController {
|
|
|
1269
1331
|
// (unknown user, wrong password, throwing hook) — no user-enumeration.
|
|
1270
1332
|
async login(body, request, response) {
|
|
1271
1333
|
const auth = this.requireAuth();
|
|
1272
|
-
if (!auth.login) throw new NotFoundException3();
|
|
1334
|
+
if (!auth.modes.includes("login")) throw new NotFoundException3();
|
|
1273
1335
|
if (!isString(body?.username) || !isString(body?.password)) {
|
|
1274
1336
|
throw new BadRequestException("Body must include string `username` and `password`.");
|
|
1275
1337
|
}
|
|
@@ -1292,7 +1354,7 @@ var DurableAuthController = class _DurableAuthController {
|
|
|
1292
1354
|
// guard decides, and no credential ever reaches this library.
|
|
1293
1355
|
async session(request, response) {
|
|
1294
1356
|
const auth = this.requireAuth();
|
|
1295
|
-
if (!auth.session) throw new NotFoundException3();
|
|
1357
|
+
if (!auth.modes.includes("session")) throw new NotFoundException3();
|
|
1296
1358
|
const user = await this.runHook("session", () => auth.session?.(request) ?? null);
|
|
1297
1359
|
if (!user) throw new UnauthorizedException();
|
|
1298
1360
|
issueSessionCookie(user, {
|
|
@@ -1310,7 +1372,7 @@ var DurableAuthController = class _DurableAuthController {
|
|
|
1310
1372
|
request,
|
|
1311
1373
|
response
|
|
1312
1374
|
});
|
|
1313
|
-
redirectRaw(response, this.auth?.login ? `${this.basePath}/login` : this.basePath);
|
|
1375
|
+
redirectRaw(response, this.auth?.modes.includes("login") ? `${this.basePath}/login` : this.basePath);
|
|
1314
1376
|
}
|
|
1315
1377
|
requireAuth() {
|
|
1316
1378
|
if (!this.auth) throw new NotFoundException3();
|
|
@@ -1337,58 +1399,58 @@ _ts_decorate6([
|
|
|
1337
1399
|
Get3("login"),
|
|
1338
1400
|
Header3("Content-Type", "text/html; charset=utf-8"),
|
|
1339
1401
|
Header3("Cache-Control", "no-store, must-revalidate"),
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1402
|
+
_ts_metadata5("design:type", Function),
|
|
1403
|
+
_ts_metadata5("design:paramtypes", []),
|
|
1404
|
+
_ts_metadata5("design:returntype", String)
|
|
1343
1405
|
], DurableAuthController.prototype, "loginPage", null);
|
|
1344
1406
|
_ts_decorate6([
|
|
1345
1407
|
Post2("login"),
|
|
1346
1408
|
HttpCode(200),
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1409
|
+
_ts_param5(0, Body2()),
|
|
1410
|
+
_ts_param5(1, Req()),
|
|
1411
|
+
_ts_param5(2, Res({
|
|
1350
1412
|
passthrough: true
|
|
1351
1413
|
})),
|
|
1352
|
-
|
|
1353
|
-
|
|
1414
|
+
_ts_metadata5("design:type", Function),
|
|
1415
|
+
_ts_metadata5("design:paramtypes", [
|
|
1354
1416
|
typeof LoginBody === "undefined" ? Object : LoginBody,
|
|
1355
1417
|
Object,
|
|
1356
1418
|
Object
|
|
1357
1419
|
]),
|
|
1358
|
-
|
|
1420
|
+
_ts_metadata5("design:returntype", Promise)
|
|
1359
1421
|
], DurableAuthController.prototype, "login", null);
|
|
1360
1422
|
_ts_decorate6([
|
|
1361
1423
|
Post2("session"),
|
|
1362
1424
|
HttpCode(204),
|
|
1363
|
-
|
|
1364
|
-
|
|
1425
|
+
_ts_param5(0, Req()),
|
|
1426
|
+
_ts_param5(1, Res({
|
|
1365
1427
|
passthrough: true
|
|
1366
1428
|
})),
|
|
1367
|
-
|
|
1368
|
-
|
|
1429
|
+
_ts_metadata5("design:type", Function),
|
|
1430
|
+
_ts_metadata5("design:paramtypes", [
|
|
1369
1431
|
Object,
|
|
1370
1432
|
Object
|
|
1371
1433
|
]),
|
|
1372
|
-
|
|
1434
|
+
_ts_metadata5("design:returntype", Promise)
|
|
1373
1435
|
], DurableAuthController.prototype, "session", null);
|
|
1374
1436
|
_ts_decorate6([
|
|
1375
1437
|
Get3("logout"),
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1438
|
+
_ts_param5(0, Req()),
|
|
1439
|
+
_ts_param5(1, Res()),
|
|
1440
|
+
_ts_metadata5("design:type", Function),
|
|
1441
|
+
_ts_metadata5("design:paramtypes", [
|
|
1380
1442
|
Object,
|
|
1381
1443
|
Object
|
|
1382
1444
|
]),
|
|
1383
|
-
|
|
1445
|
+
_ts_metadata5("design:returntype", void 0)
|
|
1384
1446
|
], DurableAuthController.prototype, "logout", null);
|
|
1385
1447
|
DurableAuthController = _ts_decorate6([
|
|
1386
1448
|
Controller3(),
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1449
|
+
_ts_param5(0, Optional3()),
|
|
1450
|
+
_ts_param5(0, Inject4(DASHBOARD_AUTH)),
|
|
1451
|
+
_ts_param5(1, Inject4(DASHBOARD_BASE_PATH)),
|
|
1452
|
+
_ts_metadata5("design:type", Function),
|
|
1453
|
+
_ts_metadata5("design:paramtypes", [
|
|
1392
1454
|
Object,
|
|
1393
1455
|
String
|
|
1394
1456
|
])
|
|
@@ -1400,7 +1462,7 @@ import { Module } from "@nestjs/common";
|
|
|
1400
1462
|
import { RouterModule } from "@nestjs/core";
|
|
1401
1463
|
|
|
1402
1464
|
// src/server/durable-session.guard.ts
|
|
1403
|
-
import { Inject as
|
|
1465
|
+
import { Inject as Inject5, Injectable as Injectable2, UnauthorizedException as UnauthorizedException2 } from "@nestjs/common";
|
|
1404
1466
|
function _ts_decorate7(decorators, target, key, desc) {
|
|
1405
1467
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1406
1468
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -1408,16 +1470,16 @@ function _ts_decorate7(decorators, target, key, desc) {
|
|
|
1408
1470
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1409
1471
|
}
|
|
1410
1472
|
__name(_ts_decorate7, "_ts_decorate");
|
|
1411
|
-
function
|
|
1473
|
+
function _ts_metadata6(k, v) {
|
|
1412
1474
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1413
1475
|
}
|
|
1414
|
-
__name(
|
|
1415
|
-
function
|
|
1476
|
+
__name(_ts_metadata6, "_ts_metadata");
|
|
1477
|
+
function _ts_param6(paramIndex, decorator) {
|
|
1416
1478
|
return function(target, key) {
|
|
1417
1479
|
decorator(target, key, paramIndex);
|
|
1418
1480
|
};
|
|
1419
1481
|
}
|
|
1420
|
-
__name(
|
|
1482
|
+
__name(_ts_param6, "_ts_param");
|
|
1421
1483
|
var DurableUiSessionGuard = class {
|
|
1422
1484
|
static {
|
|
1423
1485
|
__name(this, "DurableUiSessionGuard");
|
|
@@ -1453,17 +1515,17 @@ var DurableUiSessionGuard = class {
|
|
|
1453
1515
|
* asserting away) the nullability.
|
|
1454
1516
|
*/
|
|
1455
1517
|
denyUnauthenticated(auth, request) {
|
|
1456
|
-
if (!auth.login) throw new DashboardSessionRequiredException(this.basePath);
|
|
1518
|
+
if (!auth.modes.includes("login")) throw new DashboardSessionRequiredException(this.basePath);
|
|
1457
1519
|
const returnTo = encodeURIComponent(originalRequestUrl(request));
|
|
1458
1520
|
throw new DashboardLoginRedirectException(`${this.basePath}/login?returnTo=${returnTo}`);
|
|
1459
1521
|
}
|
|
1460
1522
|
};
|
|
1461
1523
|
DurableUiSessionGuard = _ts_decorate7([
|
|
1462
1524
|
Injectable2(),
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1525
|
+
_ts_param6(0, Inject5(DASHBOARD_AUTH)),
|
|
1526
|
+
_ts_param6(1, Inject5(DASHBOARD_BASE_PATH)),
|
|
1527
|
+
_ts_metadata6("design:type", Function),
|
|
1528
|
+
_ts_metadata6("design:paramtypes", [
|
|
1467
1529
|
Object,
|
|
1468
1530
|
String
|
|
1469
1531
|
])
|
|
@@ -1478,21 +1540,35 @@ var DurableApiSessionGuard = class {
|
|
|
1478
1540
|
}
|
|
1479
1541
|
async canActivate(context) {
|
|
1480
1542
|
if (!this.auth) return true;
|
|
1543
|
+
const auth = this.auth;
|
|
1481
1544
|
const http = context.switchToHttp();
|
|
1482
1545
|
const request = http.getRequest();
|
|
1483
|
-
const session = readSessionFromRequest(
|
|
1484
|
-
if (!session) throw
|
|
1485
|
-
if (!await maybeRenewSession(
|
|
1486
|
-
throw
|
|
1546
|
+
const session = readSessionFromRequest(auth, request);
|
|
1547
|
+
if (!session) throw this.unauthorized(auth);
|
|
1548
|
+
if (!await maybeRenewSession(auth, session, request, http.getResponse())) {
|
|
1549
|
+
throw this.unauthorized(auth);
|
|
1487
1550
|
}
|
|
1488
1551
|
return true;
|
|
1489
1552
|
}
|
|
1553
|
+
/**
|
|
1554
|
+
* A bare 401 body carrying `{ auth: { modes } }` — mirrors `@dudousxd/nestjs-telescope`'s
|
|
1555
|
+
* dashboardAuth 401. The API is fetched (never navigated), so this is the ONLY way the console
|
|
1556
|
+
* SPA (`durable-client.ts`) learns which auth surface to send the operator to on a mid-session
|
|
1557
|
+
* 401; `DurableUiSessionGuard`'s redirect/session-required exceptions above don't reach it.
|
|
1558
|
+
*/
|
|
1559
|
+
unauthorized(auth) {
|
|
1560
|
+
return new UnauthorizedException2({
|
|
1561
|
+
auth: {
|
|
1562
|
+
modes: auth.modes
|
|
1563
|
+
}
|
|
1564
|
+
});
|
|
1565
|
+
}
|
|
1490
1566
|
};
|
|
1491
1567
|
DurableApiSessionGuard = _ts_decorate7([
|
|
1492
1568
|
Injectable2(),
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1569
|
+
_ts_param6(0, Inject5(DASHBOARD_AUTH)),
|
|
1570
|
+
_ts_metadata6("design:type", Function),
|
|
1571
|
+
_ts_metadata6("design:paramtypes", [
|
|
1496
1572
|
Object
|
|
1497
1573
|
])
|
|
1498
1574
|
], DurableApiSessionGuard);
|