@dudousxd/nestjs-durable-dashboard 0.37.0 → 0.37.1
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 +44 -23
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +12 -3
- package/dist/server/index.d.ts +12 -3
- package/dist/server/index.js +44 -23
- 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,9 +18,11 @@ 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;
|
|
26
28
|
type AuthMode = 'session' | 'login';
|
|
@@ -418,6 +420,13 @@ declare class DurableApiSessionGuard implements CanActivate {
|
|
|
418
420
|
private readonly auth;
|
|
419
421
|
constructor(auth: ResolvedDashboardAuth | null);
|
|
420
422
|
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
423
|
+
/**
|
|
424
|
+
* A bare 401 body carrying `{ auth: { modes } }` — mirrors `@dudousxd/nestjs-telescope`'s
|
|
425
|
+
* dashboardAuth 401. The API is fetched (never navigated), so this is the ONLY way the console
|
|
426
|
+
* SPA (`durable-client.ts`) learns which auth surface to send the operator to on a mid-session
|
|
427
|
+
* 401; `DurableUiSessionGuard`'s redirect/session-required exceptions above don't reach it.
|
|
428
|
+
*/
|
|
429
|
+
private unauthorized;
|
|
421
430
|
}
|
|
422
431
|
|
|
423
432
|
/** DI token carrying the UI mount base (e.g. `/durable`). */
|
package/dist/server/index.d.ts
CHANGED
|
@@ -18,9 +18,11 @@ 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;
|
|
26
28
|
type AuthMode = 'session' | 'login';
|
|
@@ -418,6 +420,13 @@ declare class DurableApiSessionGuard implements CanActivate {
|
|
|
418
420
|
private readonly auth;
|
|
419
421
|
constructor(auth: ResolvedDashboardAuth | null);
|
|
420
422
|
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
423
|
+
/**
|
|
424
|
+
* A bare 401 body carrying `{ auth: { modes } }` — mirrors `@dudousxd/nestjs-telescope`'s
|
|
425
|
+
* dashboardAuth 401. The API is fetched (never navigated), so this is the ONLY way the console
|
|
426
|
+
* SPA (`durable-client.ts`) learns which auth surface to send the operator to on a mid-session
|
|
427
|
+
* 401; `DurableUiSessionGuard`'s redirect/session-required exceptions above don't reach it.
|
|
428
|
+
*/
|
|
429
|
+
private unauthorized;
|
|
421
430
|
}
|
|
422
431
|
|
|
423
432
|
/** DI token carrying the UI mount base (e.g. `/durable`). */
|
package/dist/server/index.js
CHANGED
|
@@ -1014,6 +1014,20 @@ function readSessionFromRequest(auth, request) {
|
|
|
1014
1014
|
});
|
|
1015
1015
|
}
|
|
1016
1016
|
__name(readSessionFromRequest, "readSessionFromRequest");
|
|
1017
|
+
var inFlightRevalidations = /* @__PURE__ */ new Map();
|
|
1018
|
+
function revalidateOnce(auth, user) {
|
|
1019
|
+
const revalidate = auth.revalidate;
|
|
1020
|
+
if (!revalidate) return Promise.resolve(true);
|
|
1021
|
+
const key = `${auth.secret}:${user.id}`;
|
|
1022
|
+
const inFlight = inFlightRevalidations.get(key);
|
|
1023
|
+
if (inFlight) return inFlight;
|
|
1024
|
+
const call = Promise.resolve().then(() => revalidate(user)).catch(() => false).finally(() => {
|
|
1025
|
+
inFlightRevalidations.delete(key);
|
|
1026
|
+
});
|
|
1027
|
+
inFlightRevalidations.set(key, call);
|
|
1028
|
+
return call;
|
|
1029
|
+
}
|
|
1030
|
+
__name(revalidateOnce, "revalidateOnce");
|
|
1017
1031
|
async function maybeRenewSession(auth, session, request, response, now = Date.now()) {
|
|
1018
1032
|
if (now - session.iat <= auth.ttlMs / 2) return true;
|
|
1019
1033
|
const user = {
|
|
@@ -1023,20 +1037,13 @@ async function maybeRenewSession(auth, session, request, response, now = Date.no
|
|
|
1023
1037
|
} : {},
|
|
1024
1038
|
roles: session.roles
|
|
1025
1039
|
};
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
if (!allowed) {
|
|
1034
|
-
clearSessionCookie({
|
|
1035
|
-
request,
|
|
1036
|
-
response
|
|
1037
|
-
});
|
|
1038
|
-
return false;
|
|
1039
|
-
}
|
|
1040
|
+
const allowed = await revalidateOnce(auth, user);
|
|
1041
|
+
if (!allowed) {
|
|
1042
|
+
clearSessionCookie({
|
|
1043
|
+
request,
|
|
1044
|
+
response
|
|
1045
|
+
});
|
|
1046
|
+
return false;
|
|
1040
1047
|
}
|
|
1041
1048
|
issueSessionCookie(user, {
|
|
1042
1049
|
auth,
|
|
@@ -1260,7 +1267,7 @@ var DurableAuthController = class _DurableAuthController {
|
|
|
1260
1267
|
// the developer-controlled `basePath`.
|
|
1261
1268
|
loginPage() {
|
|
1262
1269
|
const auth = this.requireAuth();
|
|
1263
|
-
if (!auth.login) throw new NotFoundException3();
|
|
1270
|
+
if (!auth.modes.includes("login")) throw new NotFoundException3();
|
|
1264
1271
|
return renderLoginPage(this.basePath);
|
|
1265
1272
|
}
|
|
1266
1273
|
// Called by the login page's own fetch (JSON, not a classic form POST — see `login-page.ts` for
|
|
@@ -1269,7 +1276,7 @@ var DurableAuthController = class _DurableAuthController {
|
|
|
1269
1276
|
// (unknown user, wrong password, throwing hook) — no user-enumeration.
|
|
1270
1277
|
async login(body, request, response) {
|
|
1271
1278
|
const auth = this.requireAuth();
|
|
1272
|
-
if (!auth.login) throw new NotFoundException3();
|
|
1279
|
+
if (!auth.modes.includes("login")) throw new NotFoundException3();
|
|
1273
1280
|
if (!isString(body?.username) || !isString(body?.password)) {
|
|
1274
1281
|
throw new BadRequestException("Body must include string `username` and `password`.");
|
|
1275
1282
|
}
|
|
@@ -1292,7 +1299,7 @@ var DurableAuthController = class _DurableAuthController {
|
|
|
1292
1299
|
// guard decides, and no credential ever reaches this library.
|
|
1293
1300
|
async session(request, response) {
|
|
1294
1301
|
const auth = this.requireAuth();
|
|
1295
|
-
if (!auth.session) throw new NotFoundException3();
|
|
1302
|
+
if (!auth.modes.includes("session")) throw new NotFoundException3();
|
|
1296
1303
|
const user = await this.runHook("session", () => auth.session?.(request) ?? null);
|
|
1297
1304
|
if (!user) throw new UnauthorizedException();
|
|
1298
1305
|
issueSessionCookie(user, {
|
|
@@ -1310,7 +1317,7 @@ var DurableAuthController = class _DurableAuthController {
|
|
|
1310
1317
|
request,
|
|
1311
1318
|
response
|
|
1312
1319
|
});
|
|
1313
|
-
redirectRaw(response, this.auth?.login ? `${this.basePath}/login` : this.basePath);
|
|
1320
|
+
redirectRaw(response, this.auth?.modes.includes("login") ? `${this.basePath}/login` : this.basePath);
|
|
1314
1321
|
}
|
|
1315
1322
|
requireAuth() {
|
|
1316
1323
|
if (!this.auth) throw new NotFoundException3();
|
|
@@ -1453,7 +1460,7 @@ var DurableUiSessionGuard = class {
|
|
|
1453
1460
|
* asserting away) the nullability.
|
|
1454
1461
|
*/
|
|
1455
1462
|
denyUnauthenticated(auth, request) {
|
|
1456
|
-
if (!auth.login) throw new DashboardSessionRequiredException(this.basePath);
|
|
1463
|
+
if (!auth.modes.includes("login")) throw new DashboardSessionRequiredException(this.basePath);
|
|
1457
1464
|
const returnTo = encodeURIComponent(originalRequestUrl(request));
|
|
1458
1465
|
throw new DashboardLoginRedirectException(`${this.basePath}/login?returnTo=${returnTo}`);
|
|
1459
1466
|
}
|
|
@@ -1478,15 +1485,29 @@ var DurableApiSessionGuard = class {
|
|
|
1478
1485
|
}
|
|
1479
1486
|
async canActivate(context) {
|
|
1480
1487
|
if (!this.auth) return true;
|
|
1488
|
+
const auth = this.auth;
|
|
1481
1489
|
const http = context.switchToHttp();
|
|
1482
1490
|
const request = http.getRequest();
|
|
1483
|
-
const session = readSessionFromRequest(
|
|
1484
|
-
if (!session) throw
|
|
1485
|
-
if (!await maybeRenewSession(
|
|
1486
|
-
throw
|
|
1491
|
+
const session = readSessionFromRequest(auth, request);
|
|
1492
|
+
if (!session) throw this.unauthorized(auth);
|
|
1493
|
+
if (!await maybeRenewSession(auth, session, request, http.getResponse())) {
|
|
1494
|
+
throw this.unauthorized(auth);
|
|
1487
1495
|
}
|
|
1488
1496
|
return true;
|
|
1489
1497
|
}
|
|
1498
|
+
/**
|
|
1499
|
+
* A bare 401 body carrying `{ auth: { modes } }` — mirrors `@dudousxd/nestjs-telescope`'s
|
|
1500
|
+
* dashboardAuth 401. The API is fetched (never navigated), so this is the ONLY way the console
|
|
1501
|
+
* SPA (`durable-client.ts`) learns which auth surface to send the operator to on a mid-session
|
|
1502
|
+
* 401; `DurableUiSessionGuard`'s redirect/session-required exceptions above don't reach it.
|
|
1503
|
+
*/
|
|
1504
|
+
unauthorized(auth) {
|
|
1505
|
+
return new UnauthorizedException2({
|
|
1506
|
+
auth: {
|
|
1507
|
+
modes: auth.modes
|
|
1508
|
+
}
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1490
1511
|
};
|
|
1491
1512
|
DurableApiSessionGuard = _ts_decorate7([
|
|
1492
1513
|
Injectable2(),
|