UNPKG

@dudousxd/nestjs-telescope

Version:

Laravel Telescope-style observability console for NestJS — core: watchers, recorder, correlation, SQLite store, headless API.

169 lines 7.47 kB
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