@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.
95 lines • 13.4 kB
JavaScript
/**
* Console token management HTTP routes — #1795.
*
* Provides:
* - POST /api/console/token/rotate — rotate the primary token with TOTP confirmation
*
* Security model:
* - All endpoints require a valid existing console token. Enforcement
* happens via an always-on `createAuthMiddleware` instance mounted at the
* top of this router, independent of `DOLLHOUSE_WEB_AUTH_ENABLED`.
* - Rotation additionally requires TOTP confirmation (Pattern B). Pattern A
* (OS dialog fallback) is deferred to a follow-up issue.
* - A sliding-window rate limit throttles rotation attempts so a bad actor
* with a live session can't brute-force TOTP codes by flooding rotations.
*
* @since v2.1.0 — Issue #1795
*/
import express, { Router } from 'express';
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 — rotation requests are tiny. */
const BODY_LIMIT = '1kb';
/**
* Rate limit for the rotation endpoint: 10 attempts per minute.
* Same rationale as the TOTP enrollment confirm limiter — brute-force
* success probability stays well below 5e-5/min.
*/
const DEFAULT_RATE_LIMIT_MAX = 10;
const DEFAULT_RATE_LIMIT_WINDOW_MS = 60_000;
/**
* Build the Express router exposing token management endpoints. The returned
* router should be mounted at `/api/console/token`; the caller does not need
* to add additional auth middleware — this router enforces its own auth
* regardless of the global feature flag.
*/
export function createTokenRoutes(options) {
const { store } = options;
const router = Router();
const jsonParser = express.json({ limit: BODY_LIMIT, type: 'application/json' });
const rateLimitMax = options.rateLimitMax ?? DEFAULT_RATE_LIMIT_MAX;
const rateLimitWindowMs = options.rateLimitWindowMs ?? DEFAULT_RATE_LIMIT_WINDOW_MS;
const rotateLimiter = new SlidingWindowRateLimiter(rateLimitMax, rateLimitWindowMs);
// Always-on auth — same pattern as the TOTP router.
const auth = createAuthMiddleware({
store,
enabled: true,
label: 'token',
});
router.use(auth);
/** GET /info — token metadata + TOTP status for the Security tab UI (#1791). */
router.get('/info', (_req, res) => {
const masked = store.listMasked();
const totpStatus = store.getTotpStatus();
res.json({
tokens: masked,
totp: totpStatus,
filePath: store.getFilePath(),
});
});
/** POST /rotate — rotate the primary console token with TOTP confirmation. */
router.post('/rotate', jsonParser, async (req, res) => {
if (!rotateLimiter.tryAcquire()) {
sendStoreError(res, 429, 'RATE_LIMITED', 'Too many rotation attempts — slow down');
return;
}
const confirmationCode = getNormalizedStringField(req.body, 'confirmationCode');
if (!confirmationCode) {
sendStoreError(res, 400, 'MISSING_FIELDS', 'confirmationCode is required');
return;
}
try {
const result = await store.rotatePrimary(confirmationCode);
res.json({
token: result.token,
rotatedAt: result.rotatedAt,
graceUntil: result.graceUntil,
});
}
catch (err) {
const message = err instanceof Error ? err.message : 'Rotation failed';
logger.debug(`[Token] rotate 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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9rZW5Sb3V0ZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvd2ViL3JvdXRlcy90b2tlblJvdXRlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7OztHQWdCRztBQUVILE9BQU8sT0FBTyxFQUFFLEVBQUUsTUFBTSxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBRTFDLE9BQU8sRUFBRSxTQUFTLEVBQTBCLE1BQU0sNEJBQTRCLENBQUM7QUFDL0UsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDdkUsT0FBTyxFQUFFLHdCQUF3QixFQUFFLE1BQU0seUNBQXlDLENBQUM7QUFDbkYsT0FBTyxFQUFFLHVCQUF1QixFQUFFLGNBQWMsRUFBRSx3QkFBd0IsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQzdHLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUUvQyx5REFBeUQ7QUFDekQsTUFBTSxVQUFVLEdBQUcsS0FBSyxDQUFDO0FBRXpCOzs7O0dBSUc7QUFDSCxNQUFNLHNCQUFzQixHQUFHLEVBQUUsQ0FBQztBQUNsQyxNQUFNLDRCQUE0QixHQUFHLE1BQU0sQ0FBQztBQWE1Qzs7Ozs7R0FLRztBQUNILE1BQU0sVUFBVSxpQkFBaUIsQ0FBQyxPQUEyQjtJQUMzRCxNQUFNLEVBQUUsS0FBSyxFQUFFLEdBQUcsT0FBTyxDQUFDO0lBQzFCLE1BQU0sTUFBTSxHQUFHLE1BQU0sRUFBRSxDQUFDO0lBQ3hCLE1BQU0sVUFBVSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsRUFBRSxLQUFLLEVBQUUsVUFBVSxFQUFFLElBQUksRUFBRSxrQkFBa0IsRUFBRSxDQUFDLENBQUM7SUFDakYsTUFBTSxZQUFZLEdBQUcsT0FBTyxDQUFDLFlBQVksSUFBSSxzQkFBc0IsQ0FBQztJQUNwRSxNQUFNLGlCQUFpQixHQUFHLE9BQU8sQ0FBQyxpQkFBaUIsSUFBSSw0QkFBNEIsQ0FBQztJQUNwRixNQUFNLGFBQWEsR0FBRyxJQUFJLHdCQUF3QixDQUFDLFlBQVksRUFBRSxpQkFBaUIsQ0FBQyxDQUFDO0lBRXBGLG9EQUFvRDtJQUNwRCxNQUFNLElBQUksR0FBRyxvQkFBb0IsQ0FBQztRQUNoQyxLQUFLO1FBQ0wsT0FBTyxFQUFFLElBQUk7UUFDYixLQUFLLEVBQUUsT0FBTztLQUNmLENBQUMsQ0FBQztJQUNILE1BQU0sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7SUFFakIsZ0ZBQWdGO0lBQ2hGLE1BQU0sQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLENBQUMsSUFBYSxFQUFFLEdBQWEsRUFBRSxFQUFFO1FBQ25ELE1BQU0sTUFBTSxHQUFHLEtBQUssQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUNsQyxNQUFNLFVBQVUsR0FBRyxLQUFLLENBQUMsYUFBYSxFQUFFLENBQUM7UUFDekMsR0FBRyxDQUFDLElBQUksQ0FBQztZQUNQLE1BQU0sRUFBRSxNQUFNO1lBQ2QsSUFBSSxFQUFFLFVBQVU7WUFDaEIsUUFBUSxFQUFFLEtBQUssQ0FBQyxXQUFXLEVBQUU7U0FDOUIsQ0FBQyxDQUFDO0lBQ0wsQ0FBQyxDQUFDLENBQUM7SUFFSCw4RUFBOEU7SUFDOUUsTUFBTSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsVUFBVSxFQUFFLEtBQUssRUFBRSxHQUFZLEVBQUUsR0FBYSxFQUFFLEVBQUU7UUFDdkUsSUFBSSxDQUFDLGFBQWEsQ0FBQyxVQUFVLEVBQUUsRUFBRSxDQUFDO1lBQ2hDLGNBQWMsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLGNBQWMsRUFBRSx3Q0FBd0MsQ0FBQyxDQUFDO1lBQ25GLE9BQU87UUFDVCxDQUFDO1FBQ0QsTUFBTSxnQkFBZ0IsR0FBRyx3QkFBd0IsQ0FBQyxHQUFHLENBQUMsSUFBSSxFQUFFLGtCQUFrQixDQUFDLENBQUM7UUFDaEYsSUFBSSxDQUFDLGdCQUFnQixFQUFFLENBQUM7WUFDdEIsY0FBYyxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsZ0JBQWdCLEVBQUUsOEJBQThCLENBQUMsQ0FBQztZQUMzRSxPQUFPO1FBQ1QsQ0FBQztRQUNELElBQUksQ0FBQztZQUNILE1BQU0sTUFBTSxHQUFHLE1BQU0sS0FBSyxDQUFDLGFBQWEsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO1lBQzNELEdBQUcsQ0FBQyxJQUFJLENBQUM7Z0JBQ1AsS0FBSyxFQUFFLE1BQU0sQ0FBQyxLQUFLO2dCQUNuQixTQUFTLEVBQUUsTUFBTSxDQUFDLFNBQVM7Z0JBQzNCLFVBQVUsRUFBRSxNQUFNLENBQUMsVUFBVTthQUM5QixDQUFDLENBQUM7UUFDTCxDQUFDO1FBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQztZQUNiLE1BQU0sT0FBTyxHQUFHLEdBQUcsWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLGlCQUFpQixDQUFDO1lBQ3ZFLE1BQU0sQ0FBQyxLQUFLLENBQUMsMEJBQTBCLE9BQU8sRUFBRSxDQUFDLENBQUM7WUFDbEQsSUFBSSxHQUFHLFlBQVksU0FBUyxFQUFFLENBQUM7Z0JBQzdCLGNBQWMsQ0FBQyxHQUFHLEVBQUUsdUJBQXVCLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLEdBQUcsQ0FBQyxJQUFJLEVBQUUsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1lBQ2hGLENBQUM7aUJBQU0sQ0FBQztnQkFDTixjQUFjLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxVQUFVLEVBQUUsT0FBTyxDQUFDLENBQUM7WUFDaEQsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDLENBQUMsQ0FBQztJQUVILE9BQU8sTUFBTSxDQUFDO0FBQ2hCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIENvbnNvbGUgdG9rZW4gbWFuYWdlbWVudCBIVFRQIHJvdXRlcyDigJQgIzE3OTUuXG4gKlxuICogUHJvdmlkZXM6XG4gKiAtIFBPU1QgL2FwaS9jb25zb2xlL3Rva2VuL3JvdGF0ZSDigJQgcm90YXRlIHRoZSBwcmltYXJ5IHRva2VuIHdpdGggVE9UUCBjb25maXJtYXRpb25cbiAqXG4gKiBTZWN1cml0eSBtb2RlbDpcbiAqIC0gQWxsIGVuZHBvaW50cyByZXF1aXJlIGEgdmFsaWQgZXhpc3RpbmcgY29uc29sZSB0b2tlbi4gRW5mb3JjZW1lbnRcbiAqICAgaGFwcGVucyB2aWEgYW4gYWx3YXlzLW9uIGBjcmVhdGVBdXRoTWlkZGxld2FyZWAgaW5zdGFuY2UgbW91bnRlZCBhdCB0aGVcbiAqICAgdG9wIG9mIHRoaXMgcm91dGVyLCBpbmRlcGVuZGVudCBvZiBgRE9MTEhPVVNFX1dFQl9BVVRIX0VOQUJMRURgLlxuICogLSBSb3RhdGlvbiBhZGRpdGlvbmFsbHkgcmVxdWlyZXMgVE9UUCBjb25maXJtYXRpb24gKFBhdHRlcm4gQikuIFBhdHRlcm4gQVxuICogICAoT1MgZGlhbG9nIGZhbGxiYWNrKSBpcyBkZWZlcnJlZCB0byBhIGZvbGxvdy11cCBpc3N1ZS5cbiAqIC0gQSBzbGlkaW5nLXdpbmRvdyByYXRlIGxpbWl0IHRocm90dGxlcyByb3RhdGlvbiBhdHRlbXB0cyBzbyBhIGJhZCBhY3RvclxuICogICB3aXRoIGEgbGl2ZSBzZXNzaW9uIGNhbid0IGJydXRlLWZvcmNlIFRPVFAgY29kZXMgYnkgZmxvb2Rpbmcgcm90YXRpb25zLlxuICpcbiAqIEBzaW5jZSB2Mi4xLjAg4oCUIElzc3VlICMxNzk1XG4gKi9cblxuaW1wb3J0IGV4cHJlc3MsIHsgUm91dGVyIH0gZnJvbSAnZXhwcmVzcyc7XG5pbXBvcnQgdHlwZSB7IFJlcXVlc3QsIFJlc3BvbnNlIH0gZnJvbSAnZXhwcmVzcyc7XG5pbXBvcnQgeyBUb3RwRXJyb3IsIHR5cGUgQ29uc29sZVRva2VuU3RvcmUgfSBmcm9tICcuLi9jb25zb2xlL2NvbnNvbGVUb2tlbi5qcyc7XG5pbXBvcnQgeyBjcmVhdGVBdXRoTWlkZGxld2FyZSB9IGZyb20gJy4uL21pZGRsZXdhcmUvYXV0aE1pZGRsZXdhcmUuanMnO1xuaW1wb3J0IHsgU2xpZGluZ1dpbmRvd1JhdGVMaW1pdGVyIH0gZnJvbSAnLi4vLi4vdXRpbHMvU2xpZGluZ1dpbmRvd1JhdGVMaW1pdGVyLmpzJztcbmltcG9ydCB7IGh0dHBTdGF0dXNGb3JTdG9yZUVycm9yLCBzZW5kU3RvcmVFcnJvciwgZ2V0Tm9ybWFsaXplZFN0cmluZ0ZpZWxkIH0gZnJvbSAnLi9jb25zb2xlUm91dGVIZWxwZXJzLmpzJztcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gJy4uLy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5cbi8qKiBKU09OIGJvZHkgc2l6ZSBsaW1pdCDigJQgcm90YXRpb24gcmVxdWVzdHMgYXJlIHRpbnkuICovXG5jb25zdCBCT0RZX0xJTUlUID0gJzFrYic7XG5cbi8qKlxuICogUmF0ZSBsaW1pdCBmb3IgdGhlIHJvdGF0aW9uIGVuZHBvaW50OiAxMCBhdHRlbXB0cyBwZXIgbWludXRlLlxuICogU2FtZSByYXRpb25hbGUgYXMgdGhlIFRPVFAgZW5yb2xsbWVudCBjb25maXJtIGxpbWl0ZXIg4oCUIGJydXRlLWZvcmNlXG4gKiBzdWNjZXNzIHByb2JhYmlsaXR5IHN0YXlzIHdlbGwgYmVsb3cgNWUtNS9taW4uXG4gKi9cbmNvbnN0IERFRkFVTFRfUkFURV9MSU1JVF9NQVggPSAxMDtcbmNvbnN0IERFRkFVTFRfUkFURV9MSU1JVF9XSU5ET1dfTVMgPSA2MF8wMDA7XG5cbi8qKlxuICogT3B0aW9ucyBmb3IgdGhlIHRva2VuIHJvdXRlcyBmYWN0b3J5LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFRva2VuUm91dGVzT3B0aW9ucyB7XG4gIHN0b3JlOiBDb25zb2xlVG9rZW5TdG9yZTtcbiAgLyoqIE1heGltdW0gcm90YXRpb24gYXR0ZW1wdHMgcGVyIHdpbmRvdy4gRGVmYXVsdDogMTAuICovXG4gIHJhdGVMaW1pdE1heD86IG51bWJlcjtcbiAgLyoqIFJhdGUgbGltaXQgd2luZG93IGluIG1pbGxpc2Vjb25kcy4gRGVmYXVsdDogNjBfMDAwICgxIG1pbnV0ZSkuICovXG4gIHJhdGVMaW1pdFdpbmRvd01zPzogbnVtYmVyO1xufVxuXG4vKipcbiAqIEJ1aWxkIHRoZSBFeHByZXNzIHJvdXRlciBleHBvc2luZyB0b2tlbiBtYW5hZ2VtZW50IGVuZHBvaW50cy4gVGhlIHJldHVybmVkXG4gKiByb3V0ZXIgc2hvdWxkIGJlIG1vdW50ZWQgYXQgYC9hcGkvY29uc29sZS90b2tlbmA7IHRoZSBjYWxsZXIgZG9lcyBub3QgbmVlZFxuICogdG8gYWRkIGFkZGl0aW9uYWwgYXV0aCBtaWRkbGV3YXJlIOKAlCB0aGlzIHJvdXRlciBlbmZvcmNlcyBpdHMgb3duIGF1dGhcbiAqIHJlZ2FyZGxlc3Mgb2YgdGhlIGdsb2JhbCBmZWF0dXJlIGZsYWcuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVUb2tlblJvdXRlcyhvcHRpb25zOiBUb2tlblJvdXRlc09wdGlvbnMpOiBSb3V0ZXIge1xuICBjb25zdCB7IHN0b3JlIH0gPSBvcHRpb25zO1xuICBjb25zdCByb3V0ZXIgPSBSb3V0ZXIoKTtcbiAgY29uc3QganNvblBhcnNlciA9IGV4cHJlc3MuanNvbih7IGxpbWl0OiBCT0RZX0xJTUlULCB0eXBlOiAnYXBwbGljYXRpb24vanNvbicgfSk7XG4gIGNvbnN0IHJhdGVMaW1pdE1heCA9IG9wdGlvbnMucmF0ZUxpbWl0TWF4ID8/IERFRkFVTFRfUkFURV9MSU1JVF9NQVg7XG4gIGNvbnN0IHJhdGVMaW1pdFdpbmRvd01zID0gb3B0aW9ucy5yYXRlTGltaXRXaW5kb3dNcyA/PyBERUZBVUxUX1JBVEVfTElNSVRfV0lORE9XX01TO1xuICBjb25zdCByb3RhdGVMaW1pdGVyID0gbmV3IFNsaWRpbmdXaW5kb3dSYXRlTGltaXRlcihyYXRlTGltaXRNYXgsIHJhdGVMaW1pdFdpbmRvd01zKTtcblxuICAvLyBBbHdheXMtb24gYXV0aCDigJQgc2FtZSBwYXR0ZXJuIGFzIHRoZSBUT1RQIHJvdXRlci5cbiAgY29uc3QgYXV0aCA9IGNyZWF0ZUF1dGhNaWRkbGV3YXJlKHtcbiAgICBzdG9yZSxcbiAgICBlbmFibGVkOiB0cnVlLFxuICAgIGxhYmVsOiAndG9rZW4nLFxuICB9KTtcbiAgcm91dGVyLnVzZShhdXRoKTtcblxuICAvKiogR0VUIC9pbmZvIOKAlCB0b2tlbiBtZXRhZGF0YSArIFRPVFAgc3RhdHVzIGZvciB0aGUgU2VjdXJpdHkgdGFiIFVJICgjMTc5MSkuICovXG4gIHJvdXRlci5nZXQoJy9pbmZvJywgKF9yZXE6IFJlcXVlc3QsIHJlczogUmVzcG9uc2UpID0+IHtcbiAgICBjb25zdCBtYXNrZWQgPSBzdG9yZS5saXN0TWFza2VkKCk7XG4gICAgY29uc3QgdG90cFN0YXR1cyA9IHN0b3JlLmdldFRvdHBTdGF0dXMoKTtcbiAgICByZXMuanNvbih7XG4gICAgICB0b2tlbnM6IG1hc2tlZCxcbiAgICAgIHRvdHA6IHRvdHBTdGF0dXMsXG4gICAgICBmaWxlUGF0aDogc3RvcmUuZ2V0RmlsZVBhdGgoKSxcbiAgICB9KTtcbiAgfSk7XG5cbiAgLyoqIFBPU1QgL3JvdGF0ZSDigJQgcm90YXRlIHRoZSBwcmltYXJ5IGNvbnNvbGUgdG9rZW4gd2l0aCBUT1RQIGNvbmZpcm1hdGlvbi4gKi9cbiAgcm91dGVyLnBvc3QoJy9yb3RhdGUnLCBqc29uUGFyc2VyLCBhc3luYyAocmVxOiBSZXF1ZXN0LCByZXM6IFJlc3BvbnNlKSA9PiB7XG4gICAgaWYgKCFyb3RhdGVMaW1pdGVyLnRyeUFjcXVpcmUoKSkge1xuICAgICAgc2VuZFN0b3JlRXJyb3IocmVzLCA0MjksICdSQVRFX0xJTUlURUQnLCAnVG9vIG1hbnkgcm90YXRpb24gYXR0ZW1wdHMg4oCUIHNsb3cgZG93bicpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBjb25maXJtYXRpb25Db2RlID0gZ2V0Tm9ybWFsaXplZFN0cmluZ0ZpZWxkKHJlcS5ib2R5LCAnY29uZmlybWF0aW9uQ29kZScpO1xuICAgIGlmICghY29uZmlybWF0aW9uQ29kZSkge1xuICAgICAgc2VuZFN0b3JlRXJyb3IocmVzLCA0MDAsICdNSVNTSU5HX0ZJRUxEUycsICdjb25maXJtYXRpb25Db2RlIGlzIHJlcXVpcmVkJyk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHRyeSB7XG4gICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCBzdG9yZS5yb3RhdGVQcmltYXJ5KGNvbmZpcm1hdGlvbkNvZGUpO1xuICAgICAgcmVzLmpzb24oe1xuICAgICAgICB0b2tlbjogcmVzdWx0LnRva2VuLFxuICAgICAgICByb3RhdGVkQXQ6IHJlc3VsdC5yb3RhdGVkQXQsXG4gICAgICAgIGdyYWNlVW50aWw6IHJlc3VsdC5ncmFjZVVudGlsLFxuICAgICAgfSk7XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICBjb25zdCBtZXNzYWdlID0gZXJyIGluc3RhbmNlb2YgRXJyb3IgPyBlcnIubWVzc2FnZSA6ICdSb3RhdGlvbiBmYWlsZWQnO1xuICAgICAgbG9nZ2VyLmRlYnVnKGBbVG9rZW5dIHJvdGF0ZSBmYWlsZWQ6ICR7bWVzc2FnZX1gKTtcbiAgICAgIGlmIChlcnIgaW5zdGFuY2VvZiBUb3RwRXJyb3IpIHtcbiAgICAgICAgc2VuZFN0b3JlRXJyb3IocmVzLCBodHRwU3RhdHVzRm9yU3RvcmVFcnJvcihlcnIuY29kZSksIGVyci5jb2RlLCBlcnIubWVzc2FnZSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBzZW5kU3RvcmVFcnJvcihyZXMsIDUwMCwgJ0lOVEVSTkFMJywgbWVzc2FnZSk7XG4gICAgICB9XG4gICAgfVxuICB9KTtcblxuICByZXR1cm4gcm91dGVyO1xufVxuIl19