UNPKG

@dollhousemcp/mcp-server

Version:

DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.

187 lines 27.4 kB
/** * TOTP (authenticator) enrollment HTTP routes — Phase 2 of #1780 (#1794). * * Provides: * - GET /api/console/totp/status — enrollment state (no secrets) * - POST /api/console/totp/enroll/begin — generate secret, return QR + otpauth URI * - POST /api/console/totp/enroll/confirm — verify code, persist, return backup codes (once) * - POST /api/console/totp/disable — verify code, clear enrollment * * Security model: * - All endpoints require a valid existing console token. The caller must * prove they already hold the token before they can enroll a second * factor — otherwise an attacker with local port access could pre-enroll * their own authenticator and lock the legitimate user out. * - Enforcement happens via an always-on `createAuthMiddleware` instance * mounted at the top of this router, independent of the global * DOLLHOUSE_WEB_AUTH_ENABLED flag. * - Backup codes are returned in plaintext exactly once (confirm response) * and only their sha256 hashes are retained by the store. * - A sliding-window rate limit throttles confirm/disable attempts on a * per-IP basis so a bad actor with a live session can't brute-force a * TOTP window by flooding requests. * * @since v2.1.0 — Issue #1794 */ import express, { Router } from 'express'; import QRCode from 'qrcode'; import { TotpError } from '../console/consoleToken.js'; import { createAuthMiddleware } from '../middleware/authMiddleware.js'; import { SlidingWindowRateLimiter } from '../../utils/SlidingWindowRateLimiter.js'; import { httpStatusForStoreError, sendStoreError, getNormalizedStringField } from './consoleRouteHelpers.js'; import { logger } from '../../utils/logger.js'; /** JSON body size limit — TOTP requests are tiny, cap hard. */ const BODY_LIMIT = '1kb'; /** * Default rate limit for code-verification endpoints: 10 attempts per minute. * TOTP codes are 6 digits (1-in-10^6 guess rate) and the ±30s window gives * an attacker up to 3 valid codes per minute. 10 attempts caps brute-force * success probability per minute at ~3e-5 even before network latency. * * Limiters are per-endpoint (confirm vs disable) because they protect * different secrets: confirm tests the in-memory pending enrollment secret * (bounded lifetime, attacker must also know the pendingId), disable tests * the persisted enrollment secret (long-lived). A single shared limiter * would let traffic on one endpoint exhaust budget on the other. * * Limiters are global (not per-IP) because the server binds to 127.0.0.1 * only — every request comes from the same loopback address, so keying on * IP would collapse to a single bucket anyway. * * Tests construct a fresh router per `buildApp`, which yields fresh * limiters, so no cross-test pollution. */ const DEFAULT_RATE_LIMIT_MAX = 10; const DEFAULT_RATE_LIMIT_WINDOW_MS = 60_000; /** * Render an otpauth URI as an SVG data URL suitable for direct embedding * in an <img src> or background-image. Separated into a helper so the * request handler stays readable. */ async function renderQrDataUrl(otpauthUri) { // errorCorrectionLevel 'M' is the default and balances size vs robustness. // We emit SVG (not PNG) because it scales to any container size and is // smaller on the wire for this particular payload. const svg = await QRCode.toString(otpauthUri, { type: 'svg', margin: 1 }); return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`; } /** * Build the Express router exposing TOTP endpoints. The returned router * should be mounted at `/api/console/totp`; the caller does not need to * add additional auth middleware — this router enforces its own auth * regardless of the global feature flag. */ export function createTotpRoutes(options) { const { store } = options; const router = Router(); const jsonParser = express.json({ limit: BODY_LIMIT, type: 'application/json' }); // Fresh per-endpoint limiters per router instance. Separate buckets for // confirm vs disable — they protect different secrets, so traffic on one // should not exhaust the budget of the other. Fresh instances per call // keep tests isolated. const rateLimitMax = options.rateLimitMax ?? DEFAULT_RATE_LIMIT_MAX; const rateLimitWindowMs = options.rateLimitWindowMs ?? DEFAULT_RATE_LIMIT_WINDOW_MS; const confirmLimiter = new SlidingWindowRateLimiter(rateLimitMax, rateLimitWindowMs); const disableLimiter = new SlidingWindowRateLimiter(rateLimitMax, rateLimitWindowMs); // Always-on auth — the global feature flag does not apply here. Even // during Phase 1 rollout (flag off), the TOTP endpoints must verify that // the caller already holds the console token before letting them enroll // a second factor. Otherwise an attacker with local port access could // enroll their own authenticator and lock the real user out. const auth = createAuthMiddleware({ store, enabled: true, label: 'totp', }); router.use(auth); /** GET /status — enrollment state (no secret material). */ router.get('/status', (_req, res) => { res.json(store.getTotpStatus()); }); /** POST /enroll/begin — generate pending secret, return QR + URI. */ router.post('/enroll/begin', jsonParser, async (req, res) => { // Optional label override — lets the UI label the authenticator entry // differently if the user has renamed the console token. const label = getNormalizedStringField(req.body, 'label') ?? undefined; try { const begin = store.beginTotpEnrollment(label); const qrSvgDataUrl = await renderQrDataUrl(begin.otpauthUri); res.json({ pendingId: begin.pendingId, secret: begin.secret, otpauthUri: begin.otpauthUri, qrSvgDataUrl, expiresAt: begin.expiresAt, }); } catch (err) { const message = err instanceof Error ? err.message : 'Enrollment could not be started'; logger.debug(`[TOTP] begin failed: ${message}`); if (err instanceof TotpError) { sendStoreError(res, httpStatusForStoreError(err.code), err.code, err.message); } else { sendStoreError(res, 500, 'INTERNAL', message); } } }); /** POST /enroll/confirm — verify code, persist, return backup codes (once). */ router.post('/enroll/confirm', jsonParser, async (req, res) => { if (!confirmLimiter.tryAcquire()) { sendStoreError(res, 429, 'RATE_LIMITED', 'Too many confirmation attempts — slow down'); return; } const pendingId = getNormalizedStringField(req.body, 'pendingId'); const code = getNormalizedStringField(req.body, 'code'); if (!pendingId || !code) { sendStoreError(res, 400, 'MISSING_FIELDS', 'pendingId and code are required'); return; } try { const result = await store.confirmTotpEnrollment(pendingId, code); res.json({ enrolled: true, enrolledAt: result.enrolledAt, backupCodes: result.backupCodes, }); } catch (err) { const message = err instanceof Error ? err.message : 'Confirmation failed'; logger.debug(`[TOTP] confirm failed: ${message}`); if (err instanceof TotpError) { sendStoreError(res, httpStatusForStoreError(err.code), err.code, err.message); } else { sendStoreError(res, 500, 'INTERNAL', message); } } }); /** POST /disable — verify code, clear enrollment. */ router.post('/disable', jsonParser, async (req, res) => { if (!disableLimiter.tryAcquire()) { sendStoreError(res, 429, 'RATE_LIMITED', 'Too many disable attempts — slow down'); return; } const code = getNormalizedStringField(req.body, 'code'); if (!code) { sendStoreError(res, 400, 'MISSING_FIELDS', 'code is required'); return; } try { await store.disableTotp(code); res.json({ enrolled: false }); } catch (err) { const message = err instanceof Error ? err.message : 'Disable failed'; logger.debug(`[TOTP] disable failed: ${message}`); if (err instanceof TotpError) { sendStoreError(res, httpStatusForStoreError(err.code), err.code, err.message); } else { sendStoreError(res, 500, 'INTERNAL', message); } } }); return router; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG90cFJvdXRlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy93ZWIvcm91dGVzL3RvdHBSb3V0ZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXdCRztBQUVILE9BQU8sT0FBTyxFQUFFLEVBQUUsTUFBTSxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBRTFDLE9BQU8sTUFBTSxNQUFNLFFBQVEsQ0FBQztBQUM1QixPQUFPLEVBQUUsU0FBUyxFQUEwQixNQUFNLDRCQUE0QixDQUFDO0FBQy9FLE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ3ZFLE9BQU8sRUFBRSx3QkFBd0IsRUFBRSxNQUFNLHlDQUF5QyxDQUFDO0FBQ25GLE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxjQUFjLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUM3RyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFFL0MsK0RBQStEO0FBQy9ELE1BQU0sVUFBVSxHQUFHLEtBQUssQ0FBQztBQUV6Qjs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBa0JHO0FBQ0gsTUFBTSxzQkFBc0IsR0FBRyxFQUFFLENBQUM7QUFDbEMsTUFBTSw0QkFBNEIsR0FBRyxNQUFNLENBQUM7QUFFNUM7Ozs7R0FJRztBQUNILEtBQUssVUFBVSxlQUFlLENBQUMsVUFBa0I7SUFDL0MsMkVBQTJFO0lBQzNFLHVFQUF1RTtJQUN2RSxtREFBbUQ7SUFDbkQsTUFBTSxHQUFHLEdBQUcsTUFBTSxNQUFNLENBQUMsUUFBUSxDQUFDLFVBQVUsRUFBRSxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUM7SUFDMUUsT0FBTywyQkFBMkIsa0JBQWtCLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztBQUM5RCxDQUFDO0FBYUQ7Ozs7O0dBS0c7QUFDSCxN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LENBQUMsQ0FBQyxnQkFBZ0IsQ0FBQztZQUN0RSxNQUFNLENBQUMsS0FBSyxDQUFDLDBCQUEwQixPQUFPLEVBQUUsQ0FBQyxDQUFDO1lBQ2xELElBQUksR0FBRyxZQUFZLFNBQVMsRUFBRSxDQUFDO2dCQUM3QixjQUFjLENBQUMsR0FBRyxFQUFFLHVCQUF1QixDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxHQUFHLENBQUMsSUFBSSxFQUFFLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUNoRixDQUFDO2lCQUFNLENBQUM7Z0JBQ04sY0FBYyxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsVUFBVSxFQUFFLE9BQU8sQ0FBQyxDQUFDO1lBQ2hELENBQUM7UUFDSCxDQUFDO0lBQ0gsQ0FBQyxDQUFDLENBQUM7SUFFSCxPQUFPLE1BQU0sQ0FBQztBQUNoQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBUT1RQIChhdXRoZW50aWNhdG9yKSBlbnJvbGxtZW50IEhUVFAgcm91dGVzIOKAlCBQaGFzZSAyIG9mICMxNzgwICgjMTc5NCkuXG4gKlxuICogUHJvdmlkZXM6XG4gKiAtIEdFVCAgICAvYXBpL2NvbnNvbGUvdG90cC9zdGF0dXMgICAgICAgIOKAlCBlbnJvbGxtZW50IHN0YXRlIChubyBzZWNyZXRzKVxuICogLSBQT1NUICAgL2FwaS9jb25zb2xlL3RvdHAvZW5yb2xsL2JlZ2luICDigJQgZ2VuZXJhdGUgc2VjcmV0LCByZXR1cm4gUVIgKyBvdHBhdXRoIFVSSVxuICogLSBQT1NUICAgL2FwaS9jb25zb2xlL3RvdHAvZW5yb2xsL2NvbmZpcm0g4oCUIHZlcmlmeSBjb2RlLCBwZXJzaXN0LCByZXR1cm4gYmFja3VwIGNvZGVzIChvbmNlKVxuICogLSBQT1NUICAgL2FwaS9jb25zb2xlL3RvdHAvZGlzYWJsZSAgICAgICDigJQgdmVyaWZ5IGNvZGUsIGNsZWFyIGVucm9sbG1lbnRcbiAqXG4gKiBTZWN1cml0eSBtb2RlbDpcbiAqIC0gQWxsIGVuZHBvaW50cyByZXF1aXJlIGEgdmFsaWQgZXhpc3RpbmcgY29uc29sZSB0b2tlbi4gVGhlIGNhbGxlciBtdXN0XG4gKiAgIHByb3ZlIHRoZXkgYWxyZWFkeSBob2xkIHRoZSB0b2tlbiBiZWZvcmUgdGhleSBjYW4gZW5yb2xsIGEgc2Vjb25kXG4gKiAgIGZhY3RvciDigJQgb3RoZXJ3aXNlIGFuIGF0dGFja2VyIHdpdGggbG9jYWwgcG9ydCBhY2Nlc3MgY291bGQgcHJlLWVucm9sbFxuICogICB0aGVpciBvd24gYXV0aGVudGljYXRvciBhbmQgbG9jayB0aGUgbGVnaXRpbWF0ZSB1c2VyIG91dC5cbiAqIC0gRW5mb3JjZW1lbnQgaGFwcGVucyB2aWEgYW4gYWx3YXlzLW9uIGBjcmVhdGVBdXRoTWlkZGxld2FyZWAgaW5zdGFuY2VcbiAqICAgbW91bnRlZCBhdCB0aGUgdG9wIG9mIHRoaXMgcm91dGVyLCBpbmRlcGVuZGVudCBvZiB0aGUgZ2xvYmFsXG4gKiAgIERPTExIT1VTRV9XRUJfQVVUSF9FTkFCTEVEIGZsYWcuXG4gKiAtIEJhY2t1cCBjb2RlcyBhcmUgcmV0dXJuZWQgaW4gcGxhaW50ZXh0IGV4YWN0bHkgb25jZSAoY29uZmlybSByZXNwb25zZSlcbiAqICAgYW5kIG9ubHkgdGhlaXIgc2hhMjU2IGhhc2hlcyBhcmUgcmV0YWluZWQgYnkgdGhlIHN0b3JlLlxuICogLSBBIHNsaWRpbmctd2luZG93IHJhdGUgbGltaXQgdGhyb3R0bGVzIGNvbmZpcm0vZGlzYWJsZSBhdHRlbXB0cyBvbiBhXG4gKiAgIHBlci1JUCBiYXNpcyBzbyBhIGJhZCBhY3RvciB3aXRoIGEgbGl2ZSBzZXNzaW9uIGNhbid0IGJydXRlLWZvcmNlIGFcbiAqICAgVE9UUCB3aW5kb3cgYnkgZmxvb2RpbmcgcmVxdWVzdHMuXG4gKlxuICogQHNpbmNlIHYyLjEuMCDigJQgSXNzdWUgIzE3OTRcbiAqL1xuXG5pbXBvcnQgZXhwcmVzcywgeyBSb3V0ZXIgfSBmcm9tICdleHByZXNzJztcbmltcG9ydCB0eXBlIHsgUmVxdWVzdCwgUmVzcG9uc2UgfSBmcm9tICdleHByZXNzJztcbmltcG9ydCBRUkNvZGUgZnJvbSAncXJjb2RlJztcbmltcG9ydCB7IFRvdHBFcnJvciwgdHlwZSBDb25zb2xlVG9rZW5TdG9yZSB9IGZyb20gJy4uL2NvbnNvbGUvY29uc29sZVRva2VuLmpzJztcbmltcG9ydCB7IGNyZWF0ZUF1dGhNaWRkbGV3YXJlIH0gZnJvbSAnLi4vbWlkZGxld2FyZS9hdXRoTWlkZGxld2FyZS5qcyc7XG5pbXBvcnQgeyBTbGlkaW5nV2luZG93UmF0ZUxpbWl0ZXIgfSBmcm9tICcuLi8uLi91dGlscy9TbGlkaW5nV2luZG93UmF0ZUxpbWl0ZXIuanMnO1xuaW1wb3J0IHsgaHR0cFN0YXR1c0ZvclN0b3JlRXJyb3IsIHNlbmRTdG9yZUVycm9yLCBnZXROb3JtYWxpemVkU3RyaW5nRmllbGQgfSBmcm9tICcuL2NvbnNvbGVSb3V0ZUhlbHBlcnMuanMnO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vLi4vdXRpbHMvbG9nZ2VyLmpzJztcblxuLyoqIEpTT04gYm9keSBzaXplIGxpbWl0IOKAlCBUT1RQIHJlcXVlc3RzIGFyZSB0aW55LCBjYXAgaGFyZC4gKi9cbmNvbnN0IEJPRFlfTElNSVQgPSAnMWtiJztcblxuLyoqXG4gKiBEZWZhdWx0IHJhdGUgbGltaXQgZm9yIGNvZGUtdmVyaWZpY2F0aW9uIGVuZHBvaW50czogMTAgYXR0ZW1wdHMgcGVyIG1pbnV0ZS5cbiAqIFRPVFAgY29kZXMgYXJlIDYgZGlnaXRzICgxLWluLTEwXjYgZ3Vlc3MgcmF0ZSkgYW5kIHRoZSDCsTMwcyB3aW5kb3cgZ2l2ZXNcbiAqIGFuIGF0dGFja2VyIHVwIHRvIDMgdmFsaWQgY29kZXMgcGVyIG1pbnV0ZS4gMTAgYXR0ZW1wdHMgY2FwcyBicnV0ZS1mb3JjZVxuICogc3VjY2VzcyBwcm9iYWJpbGl0eSBwZXIgbWludXRlIGF0IH4zZS01IGV2ZW4gYmVmb3JlIG5ldHdvcmsgbGF0ZW5jeS5cbiAqXG4gKiBMaW1pdGVycyBhcmUgcGVyLWVuZHBvaW50IChjb25maXJtIHZzIGRpc2FibGUpIGJlY2F1c2UgdGhleSBwcm90ZWN0XG4gKiBkaWZmZXJlbnQgc2VjcmV0czogY29uZmlybSB0ZXN0cyB0aGUgaW4tbWVtb3J5IHBlbmRpbmcgZW5yb2xsbWVudCBzZWNyZXRcbiAqIChib3VuZGVkIGxpZmV0aW1lLCBhdHRhY2tlciBtdXN0IGFsc28ga25vdyB0aGUgcGVuZGluZ0lkKSwgZGlzYWJsZSB0ZXN0c1xuICogdGhlIHBlcnNpc3RlZCBlbnJvbGxtZW50IHNlY3JldCAobG9uZy1saXZlZCkuIEEgc2luZ2xlIHNoYXJlZCBsaW1pdGVyXG4gKiB3b3VsZCBsZXQgdHJhZmZpYyBvbiBvbmUgZW5kcG9pbnQgZXhoYXVzdCBidWRnZXQgb24gdGhlIG90aGVyLlxuICpcbiAqIExpbWl0ZXJzIGFyZSBnbG9iYWwgKG5vdCBwZXItSVApIGJlY2F1c2UgdGhlIHNlcnZlciBiaW5kcyB0byAxMjcuMC4wLjFcbiAqIG9ubHkg4oCUIGV2ZXJ5IHJlcXVlc3QgY29tZXMgZnJvbSB0aGUgc2FtZSBsb29wYmFjayBhZGRyZXNzLCBzbyBrZXlpbmcgb25cbiAqIElQIHdvdWxkIGNvbGxhcHNlIHRvIGEgc2luZ2xlIGJ1Y2tldCBhbnl3YXkuXG4gKlxuICogVGVzdHMgY29uc3RydWN0IGEgZnJlc2ggcm91dGVyIHBlciBgYnVpbGRBcHBgLCB3aGljaCB5aWVsZHMgZnJlc2hcbiAqIGxpbWl0ZXJzLCBzbyBubyBjcm9zcy10ZXN0IHBvbGx1dGlvbi5cbiAqL1xuY29uc3QgREVGQVVMVF9SQVRFX0xJTUlUX01BWCA9IDEwO1xuY29uc3QgREVGQVVMVF9SQVRFX0xJTUlUX1dJTkRPV19NUyA9IDYwXzAwMDtcblxuLyoqXG4gKiBSZW5kZXIgYW4gb3RwYXV0aCBVUkkgYXMgYW4gU1ZHIGRhdGEgVVJMIHN1aXRhYmxlIGZvciBkaXJlY3QgZW1iZWRkaW5nXG4gKiBpbiBhbiA8aW1nIHNyYz4gb3IgYmFja2dyb3VuZC1pbWFnZS4gU2VwYXJhdGVkIGludG8gYSBoZWxwZXIgc28gdGhlXG4gKiByZXF1ZXN0IGhhbmRsZXIgc3RheXMgcmVhZGFibGUuXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIHJlbmRlclFyRGF0YVVybChvdHBhdXRoVXJpOiBzdHJpbmcpOiBQcm9taXNlPHN0cmluZz4ge1xuICAvLyBlcnJvckNvcnJlY3Rpb25MZXZlbCAnTScgaXMgdGhlIGRlZmF1bHQgYW5kIGJhbGFuY2VzIHNpemUgdnMgcm9idXN0bmVzcy5cbiAgLy8gV2UgZW1pdCBTVkcgKG5vdCBQTkcpIGJlY2F1c2UgaXQgc2NhbGVzIHRvIGFueSBjb250YWluZXIgc2l6ZSBhbmQgaXNcbiAgLy8gc21hbGxlciBvbiB0aGUgd2lyZSBmb3IgdGhpcyBwYXJ0aWN1bGFyIHBheWxvYWQuXG4gIGNvbnN0IHN2ZyA9IGF3YWl0IFFSQ29kZS50b1N0cmluZyhvdHBhdXRoVXJpLCB7IHR5cGU6ICdzdmcnLCBtYXJnaW46IDEgfSk7XG4gIHJldHVybiBgZGF0YTppbWFnZS9zdmcreG1sO3V0ZjgsJHtlbmNvZGVVUklDb21wb25lbnQoc3ZnKX1gO1xufVxuXG4vKipcbiAqIE9wdGlvbnMgZm9yIHRoZSBUT1RQIHJvdXRlcyBmYWN0b3J5LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFRvdHBSb3V0ZXNPcHRpb25zIHtcbiAgc3RvcmU6IENvbnNvbGVUb2tlblN0b3JlO1xuICAvKiogTWF4aW11bSBjb2RlLXZlcmlmaWNhdGlvbiBhdHRlbXB0cyBwZXIgd2luZG93LiBEZWZhdWx0OiAxMC4gKi9cbiAgcmF0ZUxpbWl0TWF4PzogbnVtYmVyO1xuICAvKiogUmF0ZSBsaW1pdCB3aW5kb3cgaW4gbWlsbGlzZWNvbmRzLiBEZWZhdWx0OiA2MF8wMDAgKDEgbWludXRlKS4gKi9cbiAgcmF0ZUxpbWl0V2luZG93TXM/OiBudW1iZXI7XG59XG5cbi8qKlxuICogQnVpbGQgdGhlIEV4cHJlc3Mgcm91dGVyIGV4cG9zaW5nIFRPVFAgZW5kcG9pbnRzLiBUaGUgcmV0dXJuZWQgcm91dGVyXG4gKiBzaG91bGQgYmUgbW91bnRlZCBhdCBgL2FwaS9jb25zb2xlL3RvdHBgOyB0aGUgY2FsbGVyIGRvZXMgbm90IG5lZWQgdG9cbiAqIGFkZCBhZGRpdGlvbmFsIGF1dGggbWlkZGxld2FyZSDigJQgdGhpcyByb3V0ZXIgZW5mb3JjZXMgaXRzIG93biBhdXRoXG4gKiByZWdhcmRsZXNzIG9mIHRoZSBnbG9iYWwgZmVhdHVyZSBmbGFnLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlVG90cFJvdXRlcyhvcHRpb25zOiBUb3RwUm91dGVzT3B0aW9ucyk6IFJvdXRlciB7XG4gIGNvbnN0IHsgc3RvcmUgfSA9IG9wdGlvbnM7XG4gIGNvbnN0IHJvdXRlciA9IFJvdXRlcigpO1xuICBjb25zdCBqc29uUGFyc2VyID0gZXhwcmVzcy5qc29uKHsgbGltaXQ6IEJPRFlfTElNSVQsIHR5cGU6ICdhcHBsaWNhdGlvbi9qc29uJyB9KTtcbiAgLy8gRnJlc2ggcGVyLWVuZHBvaW50IGxpbWl0ZXJzIHBlciByb3V0ZXIgaW5zdGFuY2UuIFNlcGFyYXRlIGJ1Y2tldHMgZm9yXG4gIC8vIGNvbmZpcm0gdnMgZGlzYWJsZSDigJQgdGhleSBwcm90ZWN0IGRpZmZlcmVudCBzZWNyZXRzLCBzbyB0cmFmZmljIG9uIG9uZVxuICAvLyBzaG91bGQgbm90IGV4aGF1c3QgdGhlIGJ1ZGdldCBvZiB0aGUgb3RoZXIuIEZyZXNoIGluc3RhbmNlcyBwZXIgY2FsbFxuICAvLyBrZWVwIHRlc3RzIGlzb2xhdGVkLlxuICBjb25zdCByYXRlTGltaXRNYXggPSBvcHRpb25zLnJhdGVMaW1pdE1heCA/PyBERUZBVUxUX1JBVEVfTElNSVRfTUFYO1xuICBjb25zdCByYXRlTGltaXRXaW5kb3dNcyA9IG9wdGlvbnMucmF0ZUxpbWl0V2luZG93TXMgPz8gREVGQVVMVF9SQVRFX0xJTUlUX1dJTkRPV19NUztcbiAgY29uc3QgY29uZmlybUxpbWl0ZXIgPSBuZXcgU2xpZGluZ1dpbmRvd1JhdGVMaW1pdGVyKHJhdGVMaW1pdE1heCwgcmF0ZUxpbWl0V2luZG93TXMpO1xuICBjb25zdCBkaXNhYmxlTGltaXRlciA9IG5ldyBTbGlkaW5nV2luZG93UmF0ZUxpbWl0ZXIocmF0ZUxpbWl0TWF4LCByYXRlTGltaXRXaW5kb3dNcyk7XG5cbiAgLy8gQWx3YXlzLW9uIGF1dGgg4oCUIHRoZSBnbG9iYWwgZmVhdHVyZSBmbGFnIGRvZXMgbm90IGFwcGx5IGhlcmUuIEV2ZW5cbiAgLy8gZHVyaW5nIFBoYXNlIDEgcm9sbG91dCAoZmxhZyBvZmYpLCB0aGUgVE9UUCBlbmRwb2ludHMgbXVzdCB2ZXJpZnkgdGhhdFxuICAvLyB0aGUgY2FsbGVyIGFscmVhZHkgaG9sZHMgdGhlIGNvbnNvbGUgdG9rZW4gYmVmb3JlIGxldHRpbmcgdGhlbSBlbnJvbGxcbiAgLy8gYSBzZWNvbmQgZmFjdG9yLiBPdGhlcndpc2UgYW4gYXR0YWNrZXIgd2l0aCBsb2NhbCBwb3J0IGFjY2VzcyBjb3VsZFxuICAvLyBlbnJvbGwgdGhlaXIgb3duIGF1dGhlbnRpY2F0b3IgYW5kIGxvY2sgdGhlIHJlYWwgdXNlciBvdXQuXG4gIGNvbnN0IGF1dGggPSBjcmVhdGVBdXRoTWlkZGxld2FyZSh7XG4gICAgc3RvcmUsXG4gICAgZW5hYmxlZDogdHJ1ZSxcbiAgICBsYWJlbDogJ3RvdHAnLFxuICB9KTtcbiAgcm91dGVyLnVzZShhdXRoKTtcblxuICAvKiogR0VUIC9zdGF0dXMg4oCUIGVucm9sbG1lbnQgc3RhdGUgKG5vIHNlY3JldCBtYXRlcmlhbCkuICovXG4gIHJvdXRlci5nZXQoJy9zdGF0dXMnLCAoX3JlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSkgPT4ge1xuICAgIHJlcy5qc29uKHN0b3JlLmdldFRvdHBTdGF0dXMoKSk7XG4gIH0pO1xuXG4gIC8qKiBQT1NUIC9lbnJvbGwvYmVnaW4g4oCUIGdlbmVyYXRlIHBlbmRpbmcgc2VjcmV0LCByZXR1cm4gUVIgKyBVUkkuICovXG4gIHJvdXRlci5wb3N0KCcvZW5yb2xsL2JlZ2luJywganNvblBhcnNlciwgYXN5bmMgKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSkgPT4ge1xuICAgIC8vIE9wdGlvbmFsIGxhYmVsIG92ZXJyaWRlIOKAlCBsZXRzIHRoZSBVSSBsYWJlbCB0aGUgYXV0aGVudGljYXRvciBlbnRyeVxuICAgIC8vIGRpZmZlcmVudGx5IGlmIHRoZSB1c2VyIGhhcyByZW5hbWVkIHRoZSBjb25zb2xlIHRva2VuLlxuICAgIGNvbnN0IGxhYmVsID0gZ2V0Tm9ybWFsaXplZFN0cmluZ0ZpZWxkKHJlcS5ib2R5LCAnbGFiZWwnKSA/PyB1bmRlZmluZWQ7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IGJlZ2luID0gc3RvcmUuYmVnaW5Ub3RwRW5yb2xsbWVudChsYWJlbCk7XG4gICAgICBjb25zdCBxclN2Z0RhdGFVcmwgPSBhd2FpdCByZW5kZXJRckRhdGFVcmwoYmVnaW4ub3RwYXV0aFVyaSk7XG4gICAgICByZXMuanNvbih7XG4gICAgICAgIHBlbmRpbmdJZDogYmVnaW4ucGVuZGluZ0lkLFxuICAgICAgICBzZWNyZXQ6IGJlZ2luLnNlY3JldCxcbiAgICAgICAgb3RwYXV0aFVyaTogYmVnaW4ub3RwYXV0aFVyaSxcbiAgICAgICAgcXJTdmdEYXRhVXJsLFxuICAgICAgICBleHBpcmVzQXQ6IGJlZ2luLmV4cGlyZXNBdCxcbiAgICAgIH0pO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgY29uc3QgbWVzc2FnZSA9IGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiAnRW5yb2xsbWVudCBjb3VsZCBub3QgYmUgc3RhcnRlZCc7XG4gICAgICBsb2dnZXIuZGVidWcoYFtUT1RQXSBiZWdpbiBmYWlsZWQ6ICR7bWVzc2FnZX1gKTtcbiAgICAgIGlmIChlcnIgaW5zdGFuY2VvZiBUb3RwRXJyb3IpIHtcbiAgICAgICAgc2VuZFN0b3JlRXJyb3IocmVzLCBodHRwU3RhdHVzRm9yU3RvcmVFcnJvcihlcnIuY29kZSksIGVyci5jb2RlLCBlcnIubWVzc2FnZSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBzZW5kU3RvcmVFcnJvcihyZXMsIDUwMCwgJ0lOVEVSTkFMJywgbWVzc2FnZSk7XG4gICAgICB9XG4gICAgfVxuICB9KTtcblxuICAvKiogUE9TVCAvZW5yb2xsL2NvbmZpcm0g4oCUIHZlcmlmeSBjb2RlLCBwZXJzaXN0LCByZXR1cm4gYmFja3VwIGNvZGVzIChvbmNlKS4gKi9cbiAgcm91dGVyLnBvc3QoJy9lbnJvbGwvY29uZmlybScsIGpzb25QYXJzZXIsIGFzeW5jIChyZXE6IFJlcXVlc3QsIHJlczogUmVzcG9uc2UpID0+IHtcbiAgICBpZiAoIWNvbmZpcm1MaW1pdGVyLnRyeUFjcXVpcmUoKSkge1xuICAgICAgc2VuZFN0b3JlRXJyb3IocmVzLCA0MjksICdSQVRFX0xJTUlURUQnLCAnVG9vIG1hbnkgY29uZmlybWF0aW9uIGF0dGVtcHRzIOKAlCBzbG93IGRvd24nKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgcGVuZGluZ0lkID0gZ2V0Tm9ybWFsaXplZFN0cmluZ0ZpZWxkKHJlcS5ib2R5LCAncGVuZGluZ0lkJyk7XG4gICAgY29uc3QgY29kZSA9IGdldE5vcm1hbGl6ZWRTdHJpbmdGaWVsZChyZXEuYm9keSwgJ2NvZGUnKTtcbiAgICBpZiAoIXBlbmRpbmdJZCB8fCAhY29kZSkge1xuICAgICAgc2VuZFN0b3JlRXJyb3IocmVzLCA0MDAsICdNSVNTSU5HX0ZJRUxEUycsICdwZW5kaW5nSWQgYW5kIGNvZGUgYXJlIHJlcXVpcmVkJyk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHRyeSB7XG4gICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCBzdG9yZS5jb25maXJtVG90cEVucm9sbG1lbnQocGVuZGluZ0lkLCBjb2RlKTtcbiAgICAgIHJlcy5qc29uKHtcbiAgICAgICAgZW5yb2xsZWQ6IHRydWUsXG4gICAgICAgIGVucm9sbGVkQXQ6IHJlc3VsdC5lbnJvbGxlZEF0LFxuICAgICAgICBiYWNrdXBDb2RlczogcmVzdWx0LmJhY2t1cENvZGVzLFxuICAgICAgfSk7XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICBjb25zdCBtZXNzYWdlID0gZXJyIGluc3RhbmNlb2YgRXJyb3IgPyBlcnIubWVzc2FnZSA6ICdDb25maXJtYXRpb24gZmFpbGVkJztcbiAgICAgIGxvZ2dlci5kZWJ1ZyhgW1RPVFBdIGNvbmZpcm0gZmFpbGVkOiAke21lc3NhZ2V9YCk7XG4gICAgICBpZiAoZXJyIGluc3RhbmNlb2YgVG90cEVycm9yKSB7XG4gICAgICAgIHNlbmRTdG9yZUVycm9yKHJlcywgaHR0cFN0YXR1c0ZvclN0b3JlRXJyb3IoZXJyLmNvZGUpLCBlcnIuY29kZSwgZXJyLm1lc3NhZ2UpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgc2VuZFN0b3JlRXJyb3IocmVzLCA1MDAsICdJTlRFUk5BTCcsIG1lc3NhZ2UpO1xuICAgICAgfVxuICAgIH1cbiAgfSk7XG5cbiAgLyoqIFBPU1QgL2Rpc2FibGUg4oCUIHZlcmlmeSBjb2RlLCBjbGVhciBlbnJvbGxtZW50LiAqL1xuICByb3V0ZXIucG9zdCgnL2Rpc2FibGUnLCBqc29uUGFyc2VyLCBhc3luYyAocmVxOiBSZXF1ZXN0LCByZXM6IFJlc3BvbnNlKSA9PiB7XG4gICAgaWYgKCFkaXNhYmxlTGltaXRlci50cnlBY3F1aXJlKCkpIHtcbiAgICAgIHNlbmRTdG9yZUVycm9yKHJlcywgNDI5LCAnUkFURV9MSU1JVEVEJywgJ1RvbyBtYW55IGRpc2FibGUgYXR0ZW1wdHMg4oCUIHNsb3cgZG93bicpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBjb2RlID0gZ2V0Tm9ybWFsaXplZFN0cmluZ0ZpZWxkKHJlcS5ib2R5LCAnY29kZScpO1xuICAgIGlmICghY29kZSkge1xuICAgICAgc2VuZFN0b3JlRXJyb3IocmVzLCA0MDAsICdNSVNTSU5HX0ZJRUxEUycsICdjb2RlIGlzIHJlcXVpcmVkJyk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHRyeSB7XG4gICAgICBhd2FpdCBzdG9yZS5kaXNhYmxlVG90cChjb2RlKTtcbiAgICAgIHJlcy5qc29uKHsgZW5yb2xsZWQ6IGZhbHNlIH0pO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgY29uc3QgbWVzc2FnZSA9IGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiAnRGlzYWJsZSBmYWlsZWQnO1xuICAgICAgbG9nZ2VyLmRlYnVnKGBbVE9UUF0gZGlzYWJsZSBmYWlsZWQ6ICR7bWVzc2FnZX1gKTtcbiAgICAgIGlmIChlcnIgaW5zdGFuY2VvZiBUb3RwRXJyb3IpIHtcbiAgICAgICAgc2VuZFN0b3JlRXJyb3IocmVzLCBodHRwU3RhdHVzRm9yU3RvcmVFcnJvcihlcnIuY29kZSksIGVyci5jb2RlLCBlcnIubWVzc2FnZSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBzZW5kU3RvcmVFcnJvcihyZXMsIDUwMCwgJ0lOVEVSTkFMJywgbWVzc2FnZSk7XG4gICAgICB9XG4gICAgfVxuICB9KTtcblxuICByZXR1cm4gcm91dGVyO1xufVxuIl19