@dudousxd/nestjs-agent-dashboard 0.10.0 → 0.10.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/server/index.cjs +37 -20
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +13 -11
- package/dist/server/index.d.ts +13 -11
- package/dist/server/index.js +37 -20
- package/dist/server/index.js.map +1 -1
- package/package.json +2 -2
package/dist/server/index.d.cts
CHANGED
|
@@ -203,14 +203,15 @@ type SessionHook = (request: unknown) => Promise<DashboardSessionUser | null> |
|
|
|
203
203
|
type LoginHook = (username: string, password: string) => Promise<DashboardSessionUser | null> | DashboardSessionUser | null;
|
|
204
204
|
/**
|
|
205
205
|
* Re-checks a LIVE session when the cookie is slid forward. Runs at most once per `ttl/2` PER
|
|
206
|
-
* COOKIE GENERATION
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
* cap —
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
206
|
+
* COOKIE GENERATION — the gate is on the inbound cookie's `iat`, so every request still carrying a
|
|
207
|
+
* not-yet-renewed cookie would, on its own, invoke this hook again; `maybeRenewSession` de-dupes
|
|
208
|
+
* concurrent calls for the same session (`sub` + `iat`) into a single in-flight call, so a page
|
|
209
|
+
* load that fires N parallel API calls still costs exactly ONE host round-trip, not N. Still not a
|
|
210
|
+
* per-session-lifetime cap — this can run again every `ttl/2` as the session keeps sliding, so a
|
|
211
|
+
* slow or expensive check should still debounce/cache across renewal windows on its own. Return
|
|
212
|
+
* `false` to revoke (the cookie is cleared and the request denied). Distinct from `session`: that
|
|
213
|
+
* hook reads the host's auth off a fresh request, which a console XHR does not carry — this one
|
|
214
|
+
* receives the already-minted session.
|
|
214
215
|
*/
|
|
215
216
|
type RevalidateHook = (session: DashboardSessionUser) => Promise<boolean> | boolean;
|
|
216
217
|
/** Which `dashboardAuth` hook(s) a resolved config was given — see `resolveDashboardAuth`. */
|
|
@@ -289,9 +290,10 @@ interface LoginFormBody {
|
|
|
289
290
|
* hook decides — or Mode B (`login`), a dependency-free, server-rendered login form (GET/POST) —
|
|
290
291
|
* the bundled React SPA in this package is a built Vite artifact with no auth-aware UI of its own,
|
|
291
292
|
* so gating the actual page navigation needs a self-contained flow that doesn't touch it. Each
|
|
292
|
-
* mode's routes 404 when that mode isn't
|
|
293
|
-
*
|
|
294
|
-
* `null`) — no dangling login page
|
|
293
|
+
* mode's routes 404 when that mode isn't listed in `auth.modes` — the single source of truth for
|
|
294
|
+
* which mode(s) are configured, not hook-presence truthiness — and every route 404s outright when
|
|
295
|
+
* `dashboardAuth` is unconfigured (`DASHBOARD_AUTH` resolves to `null`) — no dangling login page
|
|
296
|
+
* when the feature is off.
|
|
295
297
|
*/
|
|
296
298
|
declare class AgentDashboardAuthController {
|
|
297
299
|
private readonly auth;
|
package/dist/server/index.d.ts
CHANGED
|
@@ -203,14 +203,15 @@ type SessionHook = (request: unknown) => Promise<DashboardSessionUser | null> |
|
|
|
203
203
|
type LoginHook = (username: string, password: string) => Promise<DashboardSessionUser | null> | DashboardSessionUser | null;
|
|
204
204
|
/**
|
|
205
205
|
* Re-checks a LIVE session when the cookie is slid forward. Runs at most once per `ttl/2` PER
|
|
206
|
-
* COOKIE GENERATION
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
* cap —
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
206
|
+
* COOKIE GENERATION — the gate is on the inbound cookie's `iat`, so every request still carrying a
|
|
207
|
+
* not-yet-renewed cookie would, on its own, invoke this hook again; `maybeRenewSession` de-dupes
|
|
208
|
+
* concurrent calls for the same session (`sub` + `iat`) into a single in-flight call, so a page
|
|
209
|
+
* load that fires N parallel API calls still costs exactly ONE host round-trip, not N. Still not a
|
|
210
|
+
* per-session-lifetime cap — this can run again every `ttl/2` as the session keeps sliding, so a
|
|
211
|
+
* slow or expensive check should still debounce/cache across renewal windows on its own. Return
|
|
212
|
+
* `false` to revoke (the cookie is cleared and the request denied). Distinct from `session`: that
|
|
213
|
+
* hook reads the host's auth off a fresh request, which a console XHR does not carry — this one
|
|
214
|
+
* receives the already-minted session.
|
|
214
215
|
*/
|
|
215
216
|
type RevalidateHook = (session: DashboardSessionUser) => Promise<boolean> | boolean;
|
|
216
217
|
/** Which `dashboardAuth` hook(s) a resolved config was given — see `resolveDashboardAuth`. */
|
|
@@ -289,9 +290,10 @@ interface LoginFormBody {
|
|
|
289
290
|
* hook decides — or Mode B (`login`), a dependency-free, server-rendered login form (GET/POST) —
|
|
290
291
|
* the bundled React SPA in this package is a built Vite artifact with no auth-aware UI of its own,
|
|
291
292
|
* so gating the actual page navigation needs a self-contained flow that doesn't touch it. Each
|
|
292
|
-
* mode's routes 404 when that mode isn't
|
|
293
|
-
*
|
|
294
|
-
* `null`) — no dangling login page
|
|
293
|
+
* mode's routes 404 when that mode isn't listed in `auth.modes` — the single source of truth for
|
|
294
|
+
* which mode(s) are configured, not hook-presence truthiness — and every route 404s outright when
|
|
295
|
+
* `dashboardAuth` is unconfigured (`DASHBOARD_AUTH` resolves to `null`) — no dangling login page
|
|
296
|
+
* when the feature is off.
|
|
295
297
|
*/
|
|
296
298
|
declare class AgentDashboardAuthController {
|
|
297
299
|
private readonly auth;
|
package/dist/server/index.js
CHANGED
|
@@ -261,6 +261,30 @@ function clearSessionCookie(context) {
|
|
|
261
261
|
appendSetCookie(context.response, cookie);
|
|
262
262
|
}
|
|
263
263
|
__name(clearSessionCookie, "clearSessionCookie");
|
|
264
|
+
var inFlightRevalidations = /* @__PURE__ */ new Map();
|
|
265
|
+
function revalidationKey(session) {
|
|
266
|
+
return `${session.sub}:${session.iat}`;
|
|
267
|
+
}
|
|
268
|
+
__name(revalidationKey, "revalidationKey");
|
|
269
|
+
function getOrStartRevalidation(auth, session, user) {
|
|
270
|
+
const hook = auth.revalidate;
|
|
271
|
+
if (!hook) return Promise.resolve(true);
|
|
272
|
+
const key = revalidationKey(session);
|
|
273
|
+
const inFlight = inFlightRevalidations.get(key);
|
|
274
|
+
if (inFlight) return inFlight;
|
|
275
|
+
const outcome = (async () => {
|
|
276
|
+
try {
|
|
277
|
+
return await hook(user);
|
|
278
|
+
} catch {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
})().finally(() => {
|
|
282
|
+
inFlightRevalidations.delete(key);
|
|
283
|
+
});
|
|
284
|
+
inFlightRevalidations.set(key, outcome);
|
|
285
|
+
return outcome;
|
|
286
|
+
}
|
|
287
|
+
__name(getOrStartRevalidation, "getOrStartRevalidation");
|
|
264
288
|
async function maybeRenewSession(auth, session, request, response, now = Date.now()) {
|
|
265
289
|
if (now - session.iat <= auth.ttlMs / 2) return true;
|
|
266
290
|
const user = {
|
|
@@ -270,20 +294,13 @@ async function maybeRenewSession(auth, session, request, response, now = Date.no
|
|
|
270
294
|
} : {},
|
|
271
295
|
roles: session.roles
|
|
272
296
|
};
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
if (!allowed) {
|
|
281
|
-
clearSessionCookie({
|
|
282
|
-
request,
|
|
283
|
-
response
|
|
284
|
-
});
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
297
|
+
const allowed = await getOrStartRevalidation(auth, session, user);
|
|
298
|
+
if (!allowed) {
|
|
299
|
+
clearSessionCookie({
|
|
300
|
+
request,
|
|
301
|
+
response
|
|
302
|
+
});
|
|
303
|
+
return false;
|
|
287
304
|
}
|
|
288
305
|
issueSessionCookie(user, {
|
|
289
306
|
auth,
|
|
@@ -1305,7 +1322,7 @@ var AgentDashboardAuthController = class _AgentDashboardAuthController {
|
|
|
1305
1322
|
}
|
|
1306
1323
|
loginPage(req, res, returnToQuery, error) {
|
|
1307
1324
|
const auth = this.requireAuth();
|
|
1308
|
-
if (!auth.login) throw new NotFoundException();
|
|
1325
|
+
if (!auth.modes.includes("login")) throw new NotFoundException();
|
|
1309
1326
|
const returnTo = sanitizeReturnTo(this.basePath, returnToQuery);
|
|
1310
1327
|
if (this.currentSession(req)) {
|
|
1311
1328
|
res.status(302);
|
|
@@ -1320,7 +1337,7 @@ var AgentDashboardAuthController = class _AgentDashboardAuthController {
|
|
|
1320
1337
|
}
|
|
1321
1338
|
async login(body, req, res) {
|
|
1322
1339
|
const auth = this.requireAuth();
|
|
1323
|
-
if (!auth.login) throw new NotFoundException();
|
|
1340
|
+
if (!auth.modes.includes("login")) throw new NotFoundException();
|
|
1324
1341
|
const returnTo = sanitizeReturnTo(this.basePath, body?.returnTo);
|
|
1325
1342
|
const username = body?.username;
|
|
1326
1343
|
const password = body?.password;
|
|
@@ -1348,14 +1365,14 @@ var AgentDashboardAuthController = class _AgentDashboardAuthController {
|
|
|
1348
1365
|
response: res
|
|
1349
1366
|
});
|
|
1350
1367
|
res.status(302);
|
|
1351
|
-
res.setHeader("Location", this.auth && !this.auth.login ? `${this.basePath}/auth/session-required` : `${this.basePath}/auth/login`);
|
|
1368
|
+
res.setHeader("Location", this.auth && !this.auth.modes.includes("login") ? `${this.basePath}/auth/session-required` : `${this.basePath}/auth/login`);
|
|
1352
1369
|
return "";
|
|
1353
1370
|
}
|
|
1354
1371
|
// Mode A: the host frontend (carrying its own auth) POSTs here; the host hook validates the raw
|
|
1355
1372
|
// request and returns the session user, or `null` to deny. No credential reaches this library.
|
|
1356
1373
|
async session(request, response) {
|
|
1357
1374
|
const auth = this.requireAuth();
|
|
1358
|
-
if (!auth.session) throw new NotFoundException();
|
|
1375
|
+
if (!auth.modes.includes("session")) throw new NotFoundException();
|
|
1359
1376
|
const user = await this.runHook("session", () => auth.session?.(request) ?? null);
|
|
1360
1377
|
if (!user) throw new UnauthorizedException2();
|
|
1361
1378
|
issueSessionCookie(user, {
|
|
@@ -1369,7 +1386,7 @@ var AgentDashboardAuthController = class _AgentDashboardAuthController {
|
|
|
1369
1386
|
// 404s under Mode A (see `loginPage` above).
|
|
1370
1387
|
sessionRequiredPage() {
|
|
1371
1388
|
const auth = this.requireAuth();
|
|
1372
|
-
if (auth.login) throw new NotFoundException();
|
|
1389
|
+
if (auth.modes.includes("login")) throw new NotFoundException();
|
|
1373
1390
|
return renderSessionRequiredPage();
|
|
1374
1391
|
}
|
|
1375
1392
|
/** Uniform failure: same redirect (generic error flag) whether the user is unknown or the password is wrong. */
|
|
@@ -1615,7 +1632,7 @@ var DashboardAuthPageGuard = class {
|
|
|
1615
1632
|
* asserting away) the nullability.
|
|
1616
1633
|
*/
|
|
1617
1634
|
deny(auth, request) {
|
|
1618
|
-
if (!auth.login) {
|
|
1635
|
+
if (!auth.modes.includes("login")) {
|
|
1619
1636
|
throw new DashboardAuthRedirect(`${this.basePath}/auth/session-required`);
|
|
1620
1637
|
}
|
|
1621
1638
|
const returnTo = readOriginalUrl(request);
|