@dudousxd/nestjs-telescope
Version:
Laravel Telescope-style observability console for NestJS — core: watchers, recorder, correlation, SQLite store, headless API.
169 lines • 7.47 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); }
};
var TelescopeAuthController_1;
// packages/core/src/nest/telescope-auth.controller.ts
import { BadRequestException, Body, Controller, Get, HttpCode, Inject, Logger, NotFoundException, Post, Req, Res, UnauthorizedException, } from '@nestjs/common';
import { 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, toTelescopeHttpRequest, } from './telescope.options.js';
function isString(value) {
return typeof value === 'string';
}
/**
* Mints/clears the dashboard session cookie. Mounted on a SEPARATE controller
* from the gated API so it is NOT behind `TelescopeGuard` — these endpoints
* CREATE the session the gate checks for.
*/
let TelescopeAuthController = TelescopeAuthController_1 = class TelescopeAuthController {
auth;
config;
logger = new Logger(TelescopeAuthController_1.name);
/** One warn per hook kind, so a flaky hook doesn't spam logs every request. */
warnedHooks = new Set();
constructor(auth, config) {
this.auth = auth;
this.config = config;
}
// Mode A: the host frontend (carrying its own auth) POSTs here; the host hook
// validates the raw request and returns the session user (or null to deny).
async session(request, response) {
const auth = this.requireAuth();
if (!auth.session) {
// Mode A not configured => the endpoint doesn't exist for this host.
throw new NotFoundException();
}
const user = await this.runHook('session', () => auth.session?.(toTelescopeHttpRequest(request)) ?? null);
if (!user)
throw new UnauthorizedException();
this.mint(user, request, response);
}
// Mode B: built-in login. Validates the body shape, runs the host hook with a
// uniform 401 (no user-enumeration: same response for unknown user / bad pass).
async login(body, request, response) {
const auth = this.requireAuth();
if (!auth.login) {
throw new NotFoundException();
}
if (body === null ||
typeof body !== 'object' ||
!isString(body.username) ||
!isString(body.password)) {
throw new BadRequestException('Body must include string `username` and `password`.');
}
const username = body.username;
const password = body.password;
const user = await this.runHook('login', () => auth.login?.(username, password) ?? null);
if (!user)
throw new UnauthorizedException({ message: 'Invalid credentials' });
this.mint(user, request, response);
}
logout(request, response) {
// Best-effort: even without dashboardAuth configured, clearing is harmless.
clearSessionCookie({ telescopePath: this.config.path, request, response });
}
// The UNauthenticated SPA learns which AuthScreen to render from the 401 body
// here (meta stays behind the gate). A valid cookie returns the user.
me(request) {
const auth = this.requireAuth();
const cookieValue = parseCookieHeader(readCookieHeader(request))[SESSION_COOKIE_NAME];
const session = cookieValue !== undefined ? verifySessionCookie(cookieValue, { secret: auth.secret }) : null;
if (!session) {
throw new UnauthorizedException({ auth: { modes: auth.modes } });
}
return {
user: {
id: session.sub,
...(session.name !== undefined ? { name: session.name } : {}),
roles: session.roles,
},
};
}
requireAuth() {
// The auth controller is only registered when dashboardAuth is configured,
// so this is a defensive guard rather than a reachable runtime path.
if (!this.auth)
throw new NotFoundException();
return this.auth;
}
mint(user, request, response) {
const auth = this.requireAuth();
issueSessionCookie(user, {
auth,
telescopePath: this.config.path,
request,
response,
});
}
/**
* Run a host hook defensively: a throw is treated as a denial (null) and
* warn-logged once per kind, so a buggy hook never 500s the endpoint into a
* stack leak nor floods the logs.
*/
async runHook(kind, run) {
try {
return (await run()) ?? null;
}
catch (error) {
if (!this.warnedHooks.has(kind)) {
this.warnedHooks.add(kind);
this.logger.warn(`Telescope dashboardAuth ${kind} hook threw; treating as denial. ${error instanceof Error ? error.message : String(error)}`);
}
return null;
}
}
};
__decorate([
Post('session'),
HttpCode(204),
__param(0, Req()),
__param(1, Res({ passthrough: true })),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], TelescopeAuthController.prototype, "session", null);
__decorate([
Post('login'),
HttpCode(204),
__param(0, Body()),
__param(1, Req()),
__param(2, Res({ passthrough: true })),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object, Object]),
__metadata("design:returntype", Promise)
], TelescopeAuthController.prototype, "login", null);
__decorate([
Post('logout'),
HttpCode(204),
__param(0, Req()),
__param(1, Res({ passthrough: true })),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", void 0)
], TelescopeAuthController.prototype, "logout", null);
__decorate([
Get('me'),
__param(0, Req()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Object)
], TelescopeAuthController.prototype, "me", null);
TelescopeAuthController = TelescopeAuthController_1 = __decorate([
Controller('telescope/api/auth'),
__param(0, Inject(TELESCOPE_DASHBOARD_AUTH)),
__param(1, Inject(TELESCOPE_CONFIG)),
__metadata("design:paramtypes", [Object, Object])
], TelescopeAuthController);
export { TelescopeAuthController };
//# sourceMappingURL=telescope-auth.controller.js.map