@dudousxd/nestjs-telescope
Version:
Laravel Telescope-style observability console for NestJS — core: watchers, recorder, correlation, SQLite store, headless API.
127 lines • 5.64 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
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;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
// packages/core/src/nest/telescope.guard.ts
import { Inject, Injectable, UnauthorizedException, } from '@nestjs/common';
import { attachSession, readCookieHeader } from '../auth/auth-request.js';
import { parseCookieHeader } from '../auth/cookie-header.js';
import { SESSION_COOKIE_NAME, clearSessionCookie, issueSessionCookie, } from '../auth/session-cookie-io.js';
import { verifySessionCookie } from '../auth/session-cookie.js';
import { TELESCOPE_CONFIG, TELESCOPE_DASHBOARD_AUTH, TELESCOPE_OPTIONS, } from './telescope.options.js';
let TelescopeGuard = class TelescopeGuard {
options;
dashboardAuth;
config;
constructor(options, dashboardAuth = null, config = null) {
this.options = options;
this.dashboardAuth = dashboardAuth;
this.config = config;
}
async canActivate(context) {
const http = context.switchToHttp();
const request = http.getRequest();
// Cookie-session gate: when configured, a valid session cookie is REQUIRED
// for every guarded route (the auth endpoints live on a separate, ungated
// controller). The session is attached to the request, then `authorizer`
// still runs (AND semantics) below.
if (this.dashboardAuth) {
const session = this.verifyRequestSession(request);
if (!session) {
// Absent/invalid/expired cookie => 401 (not 403): the SPA reads this as
// "log in", distinct from authorizer's 403 "logged in but forbidden".
throw new UnauthorizedException();
}
attachSession(request, session);
if (!(await this.maybeRenew(http.getResponse(), request, session))) {
// Revoked mid-session: same 401 as an absent cookie.
throw new UnauthorizedException();
}
return this.runAuthorizer(request);
}
if (this.options.authorizer) {
return this.runAuthorizer(request);
}
// Safe default: open in dev, closed in production. An unset NODE_ENV is
// treated as non-production (local/dev context).
return process.env.NODE_ENV !== 'production';
}
verifyRequestSession(request) {
const auth = this.dashboardAuth;
if (!auth)
return null;
const cookieValue = parseCookieHeader(readCookieHeader(request))[SESSION_COOKIE_NAME];
if (cookieValue === undefined)
return null;
return verifySessionCookie(cookieValue, { secret: auth.secret });
}
/**
* Sliding renewal + revalidation: when a valid cookie is past 50% of its TTL, re-issue a fresh one
* so active users never get logged out mid-session — but first let the host's `revalidate` hook
* re-check the user, so a deactivated or demoted operator loses access instead of riding a
* self-renewing cookie. Returns `false` when the session was revoked (cookie already cleared).
*/
async maybeRenew(response, request, session) {
const auth = this.dashboardAuth;
if (!auth)
return true;
const now = Date.now();
if (now - session.iat <= auth.ttlMs / 2)
return true;
const user = {
id: session.sub,
...(session.name !== undefined ? { name: session.name } : {}),
roles: session.roles,
};
if (auth.revalidate) {
let allowed;
try {
allowed = await auth.revalidate(user);
}
catch {
allowed = false; // Fail closed.
}
if (!allowed) {
clearSessionCookie({ telescopePath: this.config?.path ?? 'telescope', request, response });
return false;
}
}
issueSessionCookie(user, {
auth,
telescopePath: this.config?.path ?? 'telescope',
request,
response,
now,
});
return true;
}
async runAuthorizer(request) {
if (!this.options.authorizer)
return true;
try {
return await this.options.authorizer({ request });
}
catch {
// Fail closed: a throwing authorizer denies access (clean 403),
// never accidentally grants it or surfaces a 500.
return false;
}
}
};
TelescopeGuard = __decorate([
Injectable(),
__param(0, Inject(TELESCOPE_OPTIONS)),
__param(1, Inject(TELESCOPE_DASHBOARD_AUTH)),
__param(2, Inject(TELESCOPE_CONFIG)),
__metadata("design:paramtypes", [Object, Object, Object])
], TelescopeGuard);
export { TelescopeGuard };
//# sourceMappingURL=telescope.guard.js.map