@dudousxd/nestjs-media-dashboard 0.9.0 → 0.11.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/console-session.d.ts +69 -0
- package/dist/client/console-session.d.ts.map +1 -0
- package/dist/client/console-session.js +120 -0
- package/dist/client/console-session.js.map +1 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +3 -0
- package/dist/client/index.js.map +1 -1
- package/dist/server/index.cjs +99 -11
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +35 -0
- package/dist/server/index.d.ts +35 -0
- package/dist/server/index.js +100 -12
- package/dist/server/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless client for opening the console from YOUR app.
|
|
3
|
+
*
|
|
4
|
+
* The console SPA is served at a literal path and knows nothing about your app's auth, so a plain
|
|
5
|
+
* browser navigation to it carries no identity. Mode A closes that gap: an XHR from inside your app
|
|
6
|
+
* — which DOES carry your auth — posts to the console's session endpoint, the `session` hook
|
|
7
|
+
* decides, and the library answers with its own signed cookie. The navigation that follows rides it.
|
|
8
|
+
*
|
|
9
|
+
* No UI here on purpose: you own the button, the page and the copy. This module owns the two things
|
|
10
|
+
* a host would otherwise have to rediscover — where the session endpoint actually lives, and how to
|
|
11
|
+
* call it without the failure mode below.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Where `POST` mints the console session cookie.
|
|
15
|
+
*
|
|
16
|
+
* Note this derives from `apiBasePath`, NOT `basePath`: the auth controller is mounted with the
|
|
17
|
+
* console's JSON API, which `MediaDashboardModule` mounts separately from the SPA. Deriving it here
|
|
18
|
+
* rather than at the call site means a host cannot get that split wrong, and cannot be silently
|
|
19
|
+
* broken by the route moving under a version bump — the break would only show up as a runtime 404.
|
|
20
|
+
*/
|
|
21
|
+
export declare function mediaConsoleSessionUrl(apiBasePath?: string): string;
|
|
22
|
+
/** Where the console SPA itself is served. */
|
|
23
|
+
export declare function mediaConsoleUrl(basePath?: string): string;
|
|
24
|
+
export interface OpenConsoleOptions {
|
|
25
|
+
/** Where the console SPA is mounted. MUST match `MediaDashboardModule.forRoot({ basePath })`. */
|
|
26
|
+
basePath?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Where the console's JSON API — and with it the session endpoint — is mounted. MUST match
|
|
29
|
+
* `MediaDashboardModule.forRoot({ apiBasePath })`. Defaults to `${basePath}/api`, the same
|
|
30
|
+
* defaulting the module itself does, so a host that set neither can pass neither.
|
|
31
|
+
*/
|
|
32
|
+
apiBasePath?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Headers for the mint request — in practice your app's `Authorization` header. A function (sync
|
|
35
|
+
* or async) is accepted so a token can be read at call time rather than captured at wiring time,
|
|
36
|
+
* which is what a refreshing token needs.
|
|
37
|
+
*/
|
|
38
|
+
headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
39
|
+
/** Injected for tests and non-browser callers. Defaults to the global `fetch`. */
|
|
40
|
+
fetch?: typeof globalThis.fetch;
|
|
41
|
+
signal?: AbortSignal;
|
|
42
|
+
/**
|
|
43
|
+
* Performs the navigation after a successful mint. Defaults to `location.assign`. Override to
|
|
44
|
+
* route through your own router, or to open in a new tab.
|
|
45
|
+
*/
|
|
46
|
+
navigate?: (url: string) => void;
|
|
47
|
+
}
|
|
48
|
+
/** Thrown when the session could not be minted. Never thrown after a successful mint. */
|
|
49
|
+
export declare class ConsoleSessionError extends Error {
|
|
50
|
+
readonly url: string;
|
|
51
|
+
/** The HTTP status, or `undefined` when the request never produced one (network error). */
|
|
52
|
+
readonly status?: number | undefined;
|
|
53
|
+
constructor(message: string, url: string,
|
|
54
|
+
/** The HTTP status, or `undefined` when the request never produced one (network error). */
|
|
55
|
+
status?: number | undefined);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Mint the console session cookie. Resolves on success; throws {@link ConsoleSessionError} on
|
|
59
|
+
* refusal. Use this directly when you want to mint without navigating (a pre-flight check, or a
|
|
60
|
+
* link the user opens later).
|
|
61
|
+
*/
|
|
62
|
+
export declare function mintMediaConsoleSession(options?: OpenConsoleOptions): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Mint the session, then navigate to the console. Throws without navigating when the mint is
|
|
65
|
+
* refused, so a denied user gets a real error instead of landing on the console's
|
|
66
|
+
* "no session" page — which reads as a bug rather than a permission decision.
|
|
67
|
+
*/
|
|
68
|
+
export declare function openMediaConsole(options?: OpenConsoleOptions): Promise<void>;
|
|
69
|
+
//# sourceMappingURL=console-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"console-session.d.ts","sourceRoot":"","sources":["../../src/client/console-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAaH;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,GAAE,MAA8B,GAAG,MAAM,CAE1F;AAED,8CAA8C;AAC9C,wBAAgB,eAAe,CAAC,QAAQ,GAAE,MAA0B,GAAG,MAAM,CAE5E;AAED,MAAM,WAAW,kBAAkB;IACjC,iGAAiG;IACjG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,OAAO,CAAC,EAAE,WAAW,GAAG,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;IACnE,kFAAkF;IAClF,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED,yFAAyF;AACzF,qBAAa,mBAAoB,SAAQ,KAAK;IAG1C,QAAQ,CAAC,GAAG,EAAE,MAAM;IACpB,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;gBAHxB,OAAO,EAAE,MAAM,EACN,GAAG,EAAE,MAAM;IACpB,2FAA2F;IAClF,MAAM,CAAC,EAAE,MAAM,YAAA;CAK3B;AAiBD;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkD7F;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKtF"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Headless client for opening the console from YOUR app.
|
|
3
|
+
*
|
|
4
|
+
* The console SPA is served at a literal path and knows nothing about your app's auth, so a plain
|
|
5
|
+
* browser navigation to it carries no identity. Mode A closes that gap: an XHR from inside your app
|
|
6
|
+
* — which DOES carry your auth — posts to the console's session endpoint, the `session` hook
|
|
7
|
+
* decides, and the library answers with its own signed cookie. The navigation that follows rides it.
|
|
8
|
+
*
|
|
9
|
+
* No UI here on purpose: you own the button, the page and the copy. This module owns the two things
|
|
10
|
+
* a host would otherwise have to rediscover — where the session endpoint actually lives, and how to
|
|
11
|
+
* call it without the failure mode below.
|
|
12
|
+
*/
|
|
13
|
+
/** The mount default; matches `DurableDashboardModule.forRoot()`'s own `basePath` default. */
|
|
14
|
+
const DEFAULT_BASE_PATH = '/media';
|
|
15
|
+
/** Matches `MediaDashboardModule`'s own `apiBasePath` default of `${basePath}/api`. */
|
|
16
|
+
const DEFAULT_API_BASE_PATH = '/media/api';
|
|
17
|
+
function normalizeBasePath(basePath) {
|
|
18
|
+
const trimmed = basePath.trim().replace(/\/+$/, '');
|
|
19
|
+
if (trimmed === '')
|
|
20
|
+
return '';
|
|
21
|
+
return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Where `POST` mints the console session cookie.
|
|
25
|
+
*
|
|
26
|
+
* Note this derives from `apiBasePath`, NOT `basePath`: the auth controller is mounted with the
|
|
27
|
+
* console's JSON API, which `MediaDashboardModule` mounts separately from the SPA. Deriving it here
|
|
28
|
+
* rather than at the call site means a host cannot get that split wrong, and cannot be silently
|
|
29
|
+
* broken by the route moving under a version bump — the break would only show up as a runtime 404.
|
|
30
|
+
*/
|
|
31
|
+
export function mediaConsoleSessionUrl(apiBasePath = DEFAULT_API_BASE_PATH) {
|
|
32
|
+
return `${normalizeBasePath(apiBasePath)}/session`;
|
|
33
|
+
}
|
|
34
|
+
/** Where the console SPA itself is served. */
|
|
35
|
+
export function mediaConsoleUrl(basePath = DEFAULT_BASE_PATH) {
|
|
36
|
+
return normalizeBasePath(basePath) || '/';
|
|
37
|
+
}
|
|
38
|
+
/** Thrown when the session could not be minted. Never thrown after a successful mint. */
|
|
39
|
+
export class ConsoleSessionError extends Error {
|
|
40
|
+
url;
|
|
41
|
+
status;
|
|
42
|
+
constructor(message, url,
|
|
43
|
+
/** The HTTP status, or `undefined` when the request never produced one (network error). */
|
|
44
|
+
status) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.url = url;
|
|
47
|
+
this.status = status;
|
|
48
|
+
this.name = 'ConsoleSessionError';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Mirror the module's own defaulting: an explicit `apiBasePath` wins, otherwise it hangs off
|
|
53
|
+
* `basePath`, so a host that only customised the SPA mount still hits the right session endpoint.
|
|
54
|
+
*/
|
|
55
|
+
function resolveApiBasePath(options) {
|
|
56
|
+
if (options.apiBasePath !== undefined)
|
|
57
|
+
return options.apiBasePath;
|
|
58
|
+
if (options.basePath !== undefined)
|
|
59
|
+
return `${normalizeBasePath(options.basePath)}/api`;
|
|
60
|
+
return DEFAULT_API_BASE_PATH;
|
|
61
|
+
}
|
|
62
|
+
async function resolveHeaders(headers) {
|
|
63
|
+
if (headers === undefined)
|
|
64
|
+
return {};
|
|
65
|
+
return typeof headers === 'function' ? await headers() : headers;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Mint the console session cookie. Resolves on success; throws {@link ConsoleSessionError} on
|
|
69
|
+
* refusal. Use this directly when you want to mint without navigating (a pre-flight check, or a
|
|
70
|
+
* link the user opens later).
|
|
71
|
+
*/
|
|
72
|
+
export async function mintMediaConsoleSession(options = {}) {
|
|
73
|
+
const url = mediaConsoleSessionUrl(resolveApiBasePath(options));
|
|
74
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
75
|
+
if (typeof doFetch !== 'function') {
|
|
76
|
+
throw new ConsoleSessionError('No `fetch` available; pass one via `options.fetch`.', url);
|
|
77
|
+
}
|
|
78
|
+
let response;
|
|
79
|
+
try {
|
|
80
|
+
response = await doFetch(url, {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
// The whole point: the response's Set-Cookie must stick, and the cookie must ride the
|
|
83
|
+
// navigation that follows.
|
|
84
|
+
credentials: 'include',
|
|
85
|
+
headers: await resolveHeaders(options.headers),
|
|
86
|
+
// Load-bearing, and the reason this helper exists rather than three lines at the call site.
|
|
87
|
+
// `fetch` FOLLOWS redirects by default, so an app whose auth layer rewrites a 401 into a
|
|
88
|
+
// "go to /signin" redirect makes this request resolve 200 against the sign-in HTML —
|
|
89
|
+
// `response.ok` reads true, the caller navigates, and the user lands in a console with no
|
|
90
|
+
// session, which looks exactly like a permissions bug. Handling the redirect explicitly turns
|
|
91
|
+
// that into a clear error instead of a silent false success.
|
|
92
|
+
redirect: 'manual',
|
|
93
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (cause) {
|
|
97
|
+
throw new ConsoleSessionError(`Could not reach the console session endpoint at ${url}: ${String(cause)}`, url);
|
|
98
|
+
}
|
|
99
|
+
// `redirect: 'manual'` surfaces differently per runtime: browsers give an opaque response
|
|
100
|
+
// (`type: 'opaqueredirect'`, status 0), Node/undici gives the real 3xx. Both mean the same thing.
|
|
101
|
+
const redirected = response.type === 'opaqueredirect' || (response.status >= 300 && response.status < 400);
|
|
102
|
+
if (redirected) {
|
|
103
|
+
throw new ConsoleSessionError(`The console session endpoint at ${url} answered with a redirect instead of minting a session. Something in front of it — usually an auth middleware or exception filter that rewrites 401s into a sign-in redirect — is intercepting the response. Exempt this path from that rewrite so the real status reaches this caller.`, url, response.status || undefined);
|
|
104
|
+
}
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
throw new ConsoleSessionError(`The console refused to open (HTTP ${response.status}).`, url, response.status);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Mint the session, then navigate to the console. Throws without navigating when the mint is
|
|
111
|
+
* refused, so a denied user gets a real error instead of landing on the console's
|
|
112
|
+
* "no session" page — which reads as a bug rather than a permission decision.
|
|
113
|
+
*/
|
|
114
|
+
export async function openMediaConsole(options = {}) {
|
|
115
|
+
await mintMediaConsoleSession(options);
|
|
116
|
+
const target = mediaConsoleUrl(options.basePath);
|
|
117
|
+
const navigate = options.navigate ?? ((url) => globalThis.location?.assign(url));
|
|
118
|
+
navigate(target);
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=console-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"console-session.js","sourceRoot":"","sources":["../../src/client/console-session.ts"],"names":[],"mappings":"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"}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { ConsoleSessionError, mediaConsoleSessionUrl, mediaConsoleUrl, mintMediaConsoleSession, openMediaConsole, type OpenConsoleOptions, } from './console-session.js';
|
|
1
2
|
export * from './types.js';
|
|
2
3
|
export { apiBase, mediaConsoleClient, type MediaConsoleClient } from './media-console-client.js';
|
|
3
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EACf,uBAAuB,EACvB,gBAAgB,EAChB,KAAK,kBAAkB,GACxB,MAAM,sBAAsB,CAAC;AAE9B,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC"}
|
package/dist/client/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Headless console-launcher primitives (path derivation + mint-then-navigate). Re-exported here
|
|
2
|
+
// because `./client` resolves to this file — see the package's `exports` map.
|
|
3
|
+
export { ConsoleSessionError, mediaConsoleSessionUrl, mediaConsoleUrl, mintMediaConsoleSession, openMediaConsole, } from './console-session.js';
|
|
1
4
|
export * from './types.js';
|
|
2
5
|
export { apiBase, mediaConsoleClient } from './media-console-client.js';
|
|
3
6
|
//# sourceMappingURL=index.js.map
|
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAA2B,MAAM,2BAA2B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,8EAA8E;AAC9E,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EACf,uBAAuB,EACvB,gBAAgB,GAEjB,MAAM,sBAAsB,CAAC;AAE9B,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAA2B,MAAM,2BAA2B,CAAC"}
|
package/dist/server/index.cjs
CHANGED
|
@@ -66,6 +66,9 @@ function resolveConsoleAuth(options) {
|
|
|
66
66
|
if (modes.length === 0) {
|
|
67
67
|
throw new Error("MediaDashboardModule: auth needs a `login` and/or `session` hook.");
|
|
68
68
|
}
|
|
69
|
+
if (options.unauthenticatedPage !== void 0 && typeof options.unauthenticatedPage !== "function") {
|
|
70
|
+
throw new Error("MediaDashboardModule: auth.unauthenticatedPage must be a function.");
|
|
71
|
+
}
|
|
69
72
|
return {
|
|
70
73
|
secret: options.secret,
|
|
71
74
|
ttlMs: durationToMs(options.ttl),
|
|
@@ -78,6 +81,9 @@ function resolveConsoleAuth(options) {
|
|
|
78
81
|
} : {},
|
|
79
82
|
...options.revalidate ? {
|
|
80
83
|
revalidate: options.revalidate
|
|
84
|
+
} : {},
|
|
85
|
+
...options.unauthenticatedPage ? {
|
|
86
|
+
unauthenticatedPage: options.unauthenticatedPage
|
|
81
87
|
} : {}
|
|
82
88
|
};
|
|
83
89
|
}
|
|
@@ -224,6 +230,26 @@ function appendSetCookie(response, cookie) {
|
|
|
224
230
|
]);
|
|
225
231
|
}
|
|
226
232
|
__name(appendSetCookie, "appendSetCookie");
|
|
233
|
+
function resolveEndableResponse(response) {
|
|
234
|
+
const isEndable = /* @__PURE__ */ __name((value) => isRawNodeResponse(value) && typeof value.end === "function", "isEndable");
|
|
235
|
+
if (isEndable(response)) return response;
|
|
236
|
+
if (isRecord2(response) && isEndable(response.raw)) return response.raw;
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
__name(resolveEndableResponse, "resolveEndableResponse");
|
|
240
|
+
function responseAlreadyWritten(response) {
|
|
241
|
+
return resolveEndableResponse(response)?.headersSent === true;
|
|
242
|
+
}
|
|
243
|
+
__name(responseAlreadyWritten, "responseAlreadyWritten");
|
|
244
|
+
function sendHtml(response, status, html) {
|
|
245
|
+
const raw = resolveEndableResponse(response);
|
|
246
|
+
if (!raw) return;
|
|
247
|
+
raw.statusCode = status;
|
|
248
|
+
raw.setHeader("content-type", "text/html; charset=utf-8");
|
|
249
|
+
raw.setHeader("cache-control", "no-store, must-revalidate");
|
|
250
|
+
raw.end(html);
|
|
251
|
+
}
|
|
252
|
+
__name(sendHtml, "sendHtml");
|
|
227
253
|
|
|
228
254
|
// src/server/auth/session-cookie.ts
|
|
229
255
|
var import_node_crypto = require("crypto");
|
|
@@ -1483,8 +1509,14 @@ var MediaConsoleApiModule = class _MediaConsoleApiModule {
|
|
|
1483
1509
|
// none — it's used as-is.
|
|
1484
1510
|
...(options.guards ?? []).filter(isGuardClass)
|
|
1485
1511
|
],
|
|
1512
|
+
// MEDIA_CONSOLE_AUTH is exported (not just provided) because MediaDashboardUiController —
|
|
1513
|
+
// hosted in MediaDashboardModule, which imports THIS module — reads it to decide whether to
|
|
1514
|
+
// hand an unauthenticated navigation to the host's `unauthenticatedPage`. Exporting keeps the
|
|
1515
|
+
// single `authProvider` instance shared; re-providing the same factory in both modules would
|
|
1516
|
+
// run a `forRootAsync` host's `useAuth` twice.
|
|
1486
1517
|
exports: [
|
|
1487
|
-
MediaConsoleService
|
|
1518
|
+
MediaConsoleService,
|
|
1519
|
+
MEDIA_CONSOLE_AUTH
|
|
1488
1520
|
]
|
|
1489
1521
|
};
|
|
1490
1522
|
}
|
|
@@ -1529,27 +1561,77 @@ var CONTENT_TYPES = {
|
|
|
1529
1561
|
".woff2": "font/woff2",
|
|
1530
1562
|
".ico": "image/x-icon"
|
|
1531
1563
|
};
|
|
1532
|
-
var MediaDashboardUiController = class {
|
|
1564
|
+
var MediaDashboardUiController = class _MediaDashboardUiController {
|
|
1533
1565
|
static {
|
|
1534
1566
|
__name(this, "MediaDashboardUiController");
|
|
1535
1567
|
}
|
|
1536
1568
|
basePath;
|
|
1537
1569
|
apiBasePath;
|
|
1570
|
+
auth;
|
|
1538
1571
|
dir = spaDir();
|
|
1539
|
-
|
|
1572
|
+
logger = new import_common7.Logger(_MediaDashboardUiController.name);
|
|
1573
|
+
warned = false;
|
|
1574
|
+
constructor(basePath, apiBasePath, auth = null) {
|
|
1540
1575
|
this.basePath = basePath;
|
|
1541
1576
|
this.apiBasePath = apiBasePath;
|
|
1577
|
+
this.auth = auth;
|
|
1542
1578
|
}
|
|
1543
1579
|
// index.html references hash-named bundles, so it MUST NOT be cached (stale bundle = "stuck
|
|
1544
|
-
// loading after a deploy"). The hashed assets below are immutable.
|
|
1545
|
-
|
|
1580
|
+
// loading after a deploy"). The hashed assets below are immutable. `sendHtml` sets both headers;
|
|
1581
|
+
// they are no longer `@Header` decorators because this route is now non-passthrough (see below).
|
|
1582
|
+
//
|
|
1583
|
+
// Non-passthrough `@Res()` so a host's `auth.unauthenticatedPage` can OWN the response and serve
|
|
1584
|
+
// its own page in place of the SPA. Without a hook this behaves exactly as before: the shell is
|
|
1585
|
+
// served to everyone, and the SPA's own auth screen renders once its API calls come back 401.
|
|
1586
|
+
async index(req, res) {
|
|
1587
|
+
if (await this.serveUnauthenticatedPage(req, res)) return;
|
|
1546
1588
|
const indexPath = (0, import_node_path.join)(this.dir, "index.html");
|
|
1547
1589
|
if (!(0, import_node_fs.existsSync)(indexPath)) {
|
|
1548
1590
|
throw new import_common7.NotFoundException("Console SPA is not built. Run the package build.");
|
|
1549
1591
|
}
|
|
1550
1592
|
const html = (0, import_node_fs.readFileSync)(indexPath, "utf8").replaceAll(`="${BUILD_BASE}/`, `="${this.basePath}/`);
|
|
1551
1593
|
const inject = `<script>window.__MEDIA_BASE__='${this.basePath}';window.__MEDIA_API__='${this.apiBasePath}';</script>`;
|
|
1552
|
-
|
|
1594
|
+
sendHtml(res, 200, html.includes("</head>") ? html.replace("</head>", `${inject}</head>`) : inject + html);
|
|
1595
|
+
}
|
|
1596
|
+
/**
|
|
1597
|
+
* Hand the response to the host's `unauthenticatedPage` when this navigation has no valid session.
|
|
1598
|
+
* Returns `true` when the host wrote a page (the caller must not also serve the SPA).
|
|
1599
|
+
*
|
|
1600
|
+
* Mode-A-only, deliberately: with `login` configured the SPA's own login form is the way in, so
|
|
1601
|
+
* replacing the shell would lock a Mode B host out of its own console.
|
|
1602
|
+
*/
|
|
1603
|
+
async serveUnauthenticatedPage(req, res) {
|
|
1604
|
+
const auth = this.auth;
|
|
1605
|
+
const hook = auth?.unauthenticatedPage;
|
|
1606
|
+
if (!auth || !hook) return false;
|
|
1607
|
+
if (auth.modes.includes("login")) return false;
|
|
1608
|
+
if (this.hasValidSession(auth, req)) return false;
|
|
1609
|
+
try {
|
|
1610
|
+
await hook({
|
|
1611
|
+
request: req,
|
|
1612
|
+
response: res,
|
|
1613
|
+
basePath: this.basePath
|
|
1614
|
+
});
|
|
1615
|
+
if (responseAlreadyWritten(res)) return true;
|
|
1616
|
+
this.warnOnce("auth.unauthenticatedPage returned without writing a response; serving the console SPA instead. The hook must write AND end the response it is given.");
|
|
1617
|
+
} catch (error) {
|
|
1618
|
+
this.warnOnce(`auth.unauthenticatedPage threw; serving the console SPA instead. ${String(error)}`);
|
|
1619
|
+
if (responseAlreadyWritten(res)) return true;
|
|
1620
|
+
}
|
|
1621
|
+
return false;
|
|
1622
|
+
}
|
|
1623
|
+
hasValidSession(auth, req) {
|
|
1624
|
+
const cookieValue = parseCookieHeader(readCookieHeader(req))[SESSION_COOKIE_NAME];
|
|
1625
|
+
if (cookieValue === void 0) return false;
|
|
1626
|
+
return verifySessionCookie(cookieValue, {
|
|
1627
|
+
secret: auth.secret
|
|
1628
|
+
}) !== null;
|
|
1629
|
+
}
|
|
1630
|
+
/** Once per process, not once per request: a broken hook fires on every unauthenticated visit. */
|
|
1631
|
+
warnOnce(message) {
|
|
1632
|
+
if (this.warned) return;
|
|
1633
|
+
this.warned = true;
|
|
1634
|
+
this.logger.warn(message);
|
|
1553
1635
|
}
|
|
1554
1636
|
asset(file) {
|
|
1555
1637
|
const safe = (0, import_node_path.basename)(file);
|
|
@@ -1567,11 +1649,14 @@ var MediaDashboardUiController = class {
|
|
|
1567
1649
|
};
|
|
1568
1650
|
_ts_decorate7([
|
|
1569
1651
|
(0, import_common7.Get)(),
|
|
1570
|
-
(0, import_common7.
|
|
1571
|
-
(0, import_common7.
|
|
1652
|
+
_ts_param6(0, (0, import_common7.Req)()),
|
|
1653
|
+
_ts_param6(1, (0, import_common7.Res)()),
|
|
1572
1654
|
_ts_metadata6("design:type", Function),
|
|
1573
|
-
_ts_metadata6("design:paramtypes", [
|
|
1574
|
-
|
|
1655
|
+
_ts_metadata6("design:paramtypes", [
|
|
1656
|
+
Object,
|
|
1657
|
+
Object
|
|
1658
|
+
]),
|
|
1659
|
+
_ts_metadata6("design:returntype", Promise)
|
|
1575
1660
|
], MediaDashboardUiController.prototype, "index", null);
|
|
1576
1661
|
_ts_decorate7([
|
|
1577
1662
|
(0, import_common7.Get)("assets/:file"),
|
|
@@ -1587,10 +1672,13 @@ MediaDashboardUiController = _ts_decorate7([
|
|
|
1587
1672
|
(0, import_common7.Controller)(),
|
|
1588
1673
|
_ts_param6(0, (0, import_common7.Inject)(MEDIA_DASHBOARD_BASE_PATH)),
|
|
1589
1674
|
_ts_param6(1, (0, import_common7.Inject)(MEDIA_DASHBOARD_API_PATH)),
|
|
1675
|
+
_ts_param6(2, (0, import_common7.Optional)()),
|
|
1676
|
+
_ts_param6(2, (0, import_common7.Inject)(MEDIA_CONSOLE_AUTH)),
|
|
1590
1677
|
_ts_metadata6("design:type", Function),
|
|
1591
1678
|
_ts_metadata6("design:paramtypes", [
|
|
1592
1679
|
String,
|
|
1593
|
-
String
|
|
1680
|
+
String,
|
|
1681
|
+
Object
|
|
1594
1682
|
])
|
|
1595
1683
|
], MediaDashboardUiController);
|
|
1596
1684
|
|