@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.
174 lines • 24.3 kB
JavaScript
/**
* Express middleware for console Bearer token authentication (#1780).
*
* Checks the `Authorization: Bearer <token>` header on requests to protected
* endpoints, with a `?token=<token>` query parameter fallback for SSE streams
* (EventSource cannot set custom headers).
*
* Behavior is gated on the `DOLLHOUSE_WEB_AUTH_ENABLED` env var. When the flag
* is false (the default during Phase 1 rollout) the middleware is a no-op —
* requests pass through unconditionally. When true, every protected request
* must carry a valid token or receive a 401.
*
* Phase 1 design notes:
* - Every valid token is treated as admin-scoped. Scope enforcement is a
* stubbed hook (`authorizeScope`) that always returns true. Phase 2 swaps
* in real scope checks without touching any route handler.
* - Element boundaries and tenant filtering are similarly stubbed for Phase 3.
* - The middleware attaches the matched token entry to `res.locals.tokenEntry`
* so downstream handlers can inspect it (audit logs, scope decisions, etc.).
*
* @since v2.1.0 — Issue #1780
*/
import { UnicodeValidator } from '../../security/validators/unicodeValidator.js';
import { logger } from '../../utils/logger.js';
/** Query parameter name used as a fallback for SSE streams. */
const TOKEN_QUERY_PARAM = 'token';
/** Header name we look at for Bearer tokens. */
const AUTH_HEADER = 'authorization';
/** Prefix expected on the Authorization header value. */
const BEARER_PREFIX = 'Bearer ';
/**
* Strict format for console tokens — 64 lowercase hex characters (256 bits).
* Any presented token that does not match this pattern is rejected before it
* reaches the constant-time comparison. This blocks any non-ASCII Unicode
* payload (homographs, zero-width, bidi overrides, etc.) from ever touching
* the verify path. DMCP-SEC-004 mitigation.
*/
const TOKEN_FORMAT = /^[0-9a-f]{64}$/;
/**
* Sanitize a raw token string pulled from a request.
*
* - Normalizes to NFC via UnicodeValidator (DMCP-SEC-004)
* - Validates against the strict hex format
* - Returns the cleaned value, or null if anything looks wrong
*
* Any failure here results in a 401 at the call site. Legitimate tokens are
* 64-char lowercase hex, so normalization and format validation are effectively
* no-ops for valid input and hard rejections for anything malicious.
*/
function sanitizePresentedToken(raw) {
if (!raw)
return null;
const normalized = UnicodeValidator.normalize(raw).normalizedContent;
if (!TOKEN_FORMAT.test(normalized))
return null;
return normalized;
}
/**
* Extract a Bearer token from a request.
* Checks Authorization header first, then query parameter.
* Applies Unicode normalization and strict format validation before returning.
* Returns the sanitized token string, or null if none was found or the value
* failed validation.
*/
function extractToken(req) {
// Preferred: Authorization: Bearer <token>
const header = req.headers[AUTH_HEADER];
if (typeof header === 'string' && header.startsWith(BEARER_PREFIX)) {
const value = header.slice(BEARER_PREFIX.length).trim();
if (value) {
const sanitized = sanitizePresentedToken(value);
if (sanitized)
return sanitized;
}
}
// Fallback for EventSource: ?token=<token>
const q = req.query[TOKEN_QUERY_PARAM];
if (typeof q === 'string' && q.length > 0) {
return sanitizePresentedToken(q);
}
if (Array.isArray(q) && q.length > 0 && typeof q[0] === 'string') {
return sanitizePresentedToken(q[0]);
}
return null;
}
/**
* Create the core authentication middleware.
*
* The returned handler enforces Bearer token auth on every request it sees.
* Mount it with `app.use(createAuthMiddleware(...))` before protected routers,
* or attach it to individual routes that need protection.
*
* When `enabled: false`, the handler immediately calls `next()` — allowing
* the infrastructure to land with the default-off feature flag without
* breaking existing traffic.
*
* Phase 3 hardening (tracked in #1789): Add 401 rate limiting to prevent
* DoS from floods of bad-token requests. Brute-forcing a 256-bit token is
* infeasible, but an attacker flooding /api with wrong tokens could saturate
* the verify path. A sliding-window limiter keyed on the requesting IP is
* the right shape.
*/
export function createAuthMiddleware(options) {
const { store, enabled, label = 'console' } = options;
const publicPaths = options.publicPathPrefixes ?? [];
return (req, res, next) => {
if (!enabled) {
return next();
}
// Public path allowlist — skip auth for whitelisted prefixes.
// Use originalUrl.pathname so we match regardless of mount point.
const pathToCheck = req.originalUrl.split('?')[0];
for (const prefix of publicPaths) {
if (pathToCheck === prefix || pathToCheck.startsWith(prefix + '/')) {
return next();
}
}
const presented = extractToken(req);
if (!presented) {
return respondUnauthorized(res, 'missing_token', label, store, 0);
}
const entry = store.verify(presented);
if (!entry) {
// Log presented-length only — never the presented value itself.
// Distinguishes "token missing" from "token wrong length" from
// "length matches but content differs" when troubleshooting.
return respondUnauthorized(res, 'invalid_token', label, store, presented.length);
}
// Stubbed authorization hook — Phase 2 flips real scope checks on here.
if (!authorizeScope(entry, req)) {
return respondForbidden(res, 'scope_denied', label, entry);
}
// Stash the matched entry for downstream handlers and log success at debug.
res.locals.tokenEntry = entry;
logger.debug(`[Auth:${label}] verified token id=${entry.id} name="${entry.name}" route=${req.method} ${req.originalUrl.split('?')[0]}`);
return next();
};
}
/**
* Scope authorization hook.
*
* Phase 1: every valid token is treated as admin — returns true unconditionally.
* Phase 2: this function will check `entry.scopes` against the route's required
* scopes (which can be attached via `res.locals.requiredScope` or a route
* metadata system).
*/
function authorizeScope(_entry, _req) {
return true;
}
/**
* Respond with 401 Unauthorized and a helpful hint about where to find the token.
* Includes presented-token length at debug level for troubleshooting — never the value.
*/
function respondUnauthorized(res, reason, label, store, presentedLength) {
logger.debug(`[Auth:${label}] 401 ${reason} presentedLength=${presentedLength}`);
res.status(401).json({
error: 'Authentication required',
reason,
hint: `Token file: ${store.getFilePath()}. Send 'Authorization: Bearer <token>' header, or append ?token=<token> for SSE streams.`,
});
}
/**
* Respond with 403 Forbidden — token was valid but scope did not permit this route.
* Phase 1 never reaches here because `authorizeScope` always returns true, but
* the code path exists so Phase 2 can wire it up without changing the middleware shape.
*/
function respondForbidden(res, reason, label, entry) {
logger.debug(`[Auth:${label}] 403 ${reason}`, { tokenId: entry.id, scopes: entry.scopes });
res.status(403).json({
error: 'Token scope does not permit this action',
reason,
});
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aE1pZGRsZXdhcmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvd2ViL21pZGRsZXdhcmUvYXV0aE1pZGRsZXdhcmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXFCRztBQUlILE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLCtDQUErQyxDQUFDO0FBQ2pGLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUUvQywrREFBK0Q7QUFDL0QsTUFBTSxpQkFBaUIsR0FBRyxPQUFPLENBQUM7QUFFbEMsZ0RBQWdEO0FBQ2hELE1BQU0sV0FBVyxHQUFHLGVBQWUsQ0FBQztBQUVwQyx5REFBeUQ7QUFDekQsTUFBTSxhQUFhLEdBQUcsU0FBUyxDQUFDO0FBRWhDOzs7Ozs7R0FNRztBQUNILE1BQU0sWUFBWSxHQUFHLGdCQUFnQixDQUFDO0FBRXRDOzs7Ozs7Ozs7O0dBVUc7QUFDSCxTQUFTLHNCQUFzQixDQUFDLEdBQVc7SUFDekMsSUFBSSxDQUFDLEdBQUc7UUFBRSxPQUFPLElBQUksQ0FBQztJQUN0QixNQUFNLFVBQVUsR0FBRyxnQkFBZ0IsQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLENBQUMsaUJBQWlCLENBQUM7SUFDckUsSUFBSSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDaEQsT0FBTyxVQUFVLENBQUM7QUFDcEIsQ0FBQztBQUVEOzs7Ozs7R0FNRztBQUNILFNBQVMsWUFBWSxDQUFDLEdBQVk7SUFDaEMsMkNBQTJDO0lBQzNDLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDLENBQUM7SUFDeEMsSUFBSSxPQUFPLE1BQU0sS0FBSyxRQUFRLElBQUksTUFBTSxDQUFDLFVBQVUsQ0FBQyxhQUFhLENBQUMsRUFBRSxDQUFDO1FBQ25FLE1BQU0sS0FBSyxHQUFH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QUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBFeHByZXNzIG1pZGRsZXdhcmUgZm9yIGNvbnNvbGUgQmVhcmVyIHRva2VuIGF1dGhlbnRpY2F0aW9uICgjMTc4MCkuXG4gKlxuICogQ2hlY2tzIHRoZSBgQXV0aG9yaXphdGlvbjogQmVhcmVyIDx0b2tlbj5gIGhlYWRlciBvbiByZXF1ZXN0cyB0byBwcm90ZWN0ZWRcbiAqIGVuZHBvaW50cywgd2l0aCBhIGA/dG9rZW49PHRva2VuPmAgcXVlcnkgcGFyYW1ldGVyIGZhbGxiYWNrIGZvciBTU0Ugc3RyZWFtc1xuICogKEV2ZW50U291cmNlIGNhbm5vdCBzZXQgY3VzdG9tIGhlYWRlcnMpLlxuICpcbiAqIEJlaGF2aW9yIGlzIGdhdGVkIG9uIHRoZSBgRE9MTEhPVVNFX1dFQl9BVVRIX0VOQUJMRURgIGVudiB2YXIuIFdoZW4gdGhlIGZsYWdcbiAqIGlzIGZhbHNlICh0aGUgZGVmYXVsdCBkdXJpbmcgUGhhc2UgMSByb2xsb3V0KSB0aGUgbWlkZGxld2FyZSBpcyBhIG5vLW9wIOKAlFxuICogcmVxdWVzdHMgcGFzcyB0aHJvdWdoIHVuY29uZGl0aW9uYWxseS4gV2hlbiB0cnVlLCBldmVyeSBwcm90ZWN0ZWQgcmVxdWVzdFxuICogbXVzdCBjYXJyeSBhIHZhbGlkIHRva2VuIG9yIHJlY2VpdmUgYSA0MDEuXG4gKlxuICogUGhhc2UgMSBkZXNpZ24gbm90ZXM6XG4gKiAtIEV2ZXJ5IHZhbGlkIHRva2VuIGlzIHRyZWF0ZWQgYXMgYWRtaW4tc2NvcGVkLiBTY29wZSBlbmZvcmNlbWVudCBpcyBhXG4gKiAgIHN0dWJiZWQgaG9vayAoYGF1dGhvcml6ZVNjb3BlYCkgdGhhdCBhbHdheXMgcmV0dXJucyB0cnVlLiBQaGFzZSAyIHN3YXBzXG4gKiAgIGluIHJlYWwgc2NvcGUgY2hlY2tzIHdpdGhvdXQgdG91Y2hpbmcgYW55IHJvdXRlIGhhbmRsZXIuXG4gKiAtIEVsZW1lbnQgYm91bmRhcmllcyBhbmQgdGVuYW50IGZpbHRlcmluZyBhcmUgc2ltaWxhcmx5IHN0dWJiZWQgZm9yIFBoYXNlIDMuXG4gKiAtIFRoZSBtaWRkbGV3YXJlIGF0dGFjaGVzIHRoZSBtYXRjaGVkIHRva2VuIGVudHJ5IHRvIGByZXMubG9jYWxzLnRva2VuRW50cnlgXG4gKiAgIHNvIGRvd25zdHJlYW0gaGFuZGxlcnMgY2FuIGluc3BlY3QgaXQgKGF1ZGl0IGxvZ3MsIHNjb3BlIGRlY2lzaW9ucywgZXRjLikuXG4gKlxuICogQHNpbmNlIHYyLjEuMCDigJQgSXNzdWUgIzE3ODBcbiAqL1xuXG5pbXBvcnQgdHlwZSB7IFJlcXVlc3QsIFJlc3BvbnNlLCBOZXh0RnVuY3Rpb24sIFJlcXVlc3RIYW5kbGVyIH0gZnJvbSAnZXhwcmVzcyc7XG5pbXBvcnQgdHlwZSB7IENvbnNvbGVUb2tlblN0b3JlLCBDb25zb2xlVG9rZW5FbnRyeSB9IGZyb20gJy4uL2NvbnNvbGUvY29uc29sZVRva2VuLmpzJztcbmltcG9ydCB7IFVuaWNvZGVWYWxpZGF0b3IgfSBmcm9tICcuLi8uLi9zZWN1cml0eS92YWxpZGF0b3JzL3VuaWNvZGVWYWxpZGF0b3IuanMnO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSAnLi4vLi4vdXRpbHMvbG9nZ2VyLmpzJztcblxuLyoqIFF1ZXJ5IHBhcmFtZXRlciBuYW1lIHVzZWQgYXMgYSBmYWxsYmFjayBmb3IgU1NFIHN0cmVhbXMuICovXG5jb25zdCBUT0tFTl9RVUVSWV9QQVJBTSA9ICd0b2tlbic7XG5cbi8qKiBIZWFkZXIgbmFtZSB3ZSBsb29rIGF0IGZvciBCZWFyZXIgdG9rZW5zLiAqL1xuY29uc3QgQVVUSF9IRUFERVIgPSAnYXV0aG9yaXphdGlvbic7XG5cbi8qKiBQcmVmaXggZXhwZWN0ZWQgb24gdGhlIEF1dGhvcml6YXRpb24gaGVhZGVyIHZhbHVlLiAqL1xuY29uc3QgQkVBUkVSX1BSRUZJWCA9ICdCZWFyZXIgJztcblxuLyoqXG4gKiBTdHJpY3QgZm9ybWF0IGZvciBjb25zb2xlIHRva2VucyDigJQgNjQgbG93ZXJjYXNlIGhleCBjaGFyYWN0ZXJzICgyNTYgYml0cykuXG4gKiBBbnkgcHJlc2VudGVkIHRva2VuIHRoYXQgZG9lcyBub3QgbWF0Y2ggdGhpcyBwYXR0ZXJuIGlzIHJlamVjdGVkIGJlZm9yZSBpdFxuICogcmVhY2hlcyB0aGUgY29uc3RhbnQtdGltZSBjb21wYXJpc29uLiBUaGlzIGJsb2NrcyBhbnkgbm9uLUFTQ0lJIFVuaWNvZGVcbiAqIHBheWxvYWQgKGhvbW9ncmFwaHMsIHplcm8td2lkdGgsIGJpZGkgb3ZlcnJpZGVzLCBldGMuKSBmcm9tIGV2ZXIgdG91Y2hpbmdcbiAqIHRoZSB2ZXJpZnkgcGF0aC4gRE1DUC1TRUMtMDA0IG1pdGlnYXRpb24uXG4gKi9cbmNvbnN0IFRPS0VOX0ZPUk1BVCA9IC9eWzAtOWEtZl17NjR9JC87XG5cbi8qKlxuICogU2FuaXRpemUgYSByYXcgdG9rZW4gc3RyaW5nIHB1bGxlZCBmcm9tIGEgcmVxdWVzdC5cbiAqXG4gKiAtIE5vcm1hbGl6ZXMgdG8gTkZDIHZpYSBVbmljb2RlVmFsaWRhdG9yIChETUNQLVNFQy0wMDQpXG4gKiAtIFZhbGlkYXRlcyBhZ2FpbnN0IHRoZSBzdHJpY3QgaGV4IGZvcm1hdFxuICogLSBSZXR1cm5zIHRoZSBjbGVhbmVkIHZhbHVlLCBvciBudWxsIGlmIGFueXRoaW5nIGxvb2tzIHdyb25nXG4gKlxuICogQW55IGZhaWx1cmUgaGVyZSByZXN1bHRzIGluIGEgNDAxIGF0IHRoZSBjYWxsIHNpdGUuIExlZ2l0aW1hdGUgdG9rZW5zIGFyZVxuICogNjQtY2hhciBsb3dlcmNhc2UgaGV4LCBzbyBub3JtYWxpemF0aW9uIGFuZCBmb3JtYXQgdmFsaWRhdGlvbiBhcmUgZWZmZWN0aXZlbHlcbiAqIG5vLW9wcyBmb3IgdmFsaWQgaW5wdXQgYW5kIGhhcmQgcmVqZWN0aW9ucyBmb3IgYW55dGhpbmcgbWFsaWNpb3VzLlxuICovXG5mdW5jdGlvbiBzYW5pdGl6ZVByZXNlbnRlZFRva2VuKHJhdzogc3RyaW5nKTogc3RyaW5nIHwgbnVsbCB7XG4gIGlmICghcmF3KSByZXR1cm4gbnVsbDtcbiAgY29uc3Qgbm9ybWFsaXplZCA9IFVuaWNvZGVWYWxpZGF0b3Iubm9ybWFsaXplKHJhdykubm9ybWFsaXplZENvbnRlbnQ7XG4gIGlmICghVE9LRU5fRk9STUFULnRlc3Qobm9ybWFsaXplZCkpIHJldHVybiBudWxsO1xuICByZXR1cm4gbm9ybWFsaXplZDtcbn1cblxuLyoqXG4gKiBFeHRyYWN0IGEgQmVhcmVyIHRva2VuIGZyb20gYSByZXF1ZXN0LlxuICogQ2hlY2tzIEF1dGhvcml6YXRpb24gaGVhZGVyIGZpcnN0LCB0aGVuIHF1ZXJ5IHBhcmFtZXRlci5cbiAqIEFwcGxpZXMgVW5pY29kZSBub3JtYWxpemF0aW9uIGFuZCBzdHJpY3QgZm9ybWF0IHZhbGlkYXRpb24gYmVmb3JlIHJldHVybmluZy5cbiAqIFJldHVybnMgdGhlIHNhbml0aXplZCB0b2tlbiBzdHJpbmcsIG9yIG51bGwgaWYgbm9uZSB3YXMgZm91bmQgb3IgdGhlIHZhbHVlXG4gKiBmYWlsZWQgdmFsaWRhdGlvbi5cbiAqL1xuZnVuY3Rpb24gZXh0cmFjdFRva2VuKHJlcTogUmVxdWVzdCk6IHN0cmluZyB8IG51bGwge1xuICAvLyBQcmVmZXJyZWQ6IEF1dGhvcml6YXRpb246IEJlYXJlciA8dG9rZW4+XG4gIGNvbnN0IGhlYWRlciA9IHJlcS5oZWFkZXJzW0FVVEhfSEVBREVSXTtcbiAgaWYgKHR5cGVvZiBoZWFkZXIgPT09ICdzdHJpbmcnICYmIGhlYWRlci5zdGFydHNXaXRoKEJFQVJFUl9QUkVGSVgpKSB7XG4gICAgY29uc3QgdmFsdWUgPSBoZWFkZXIuc2xpY2UoQkVBUkVSX1BSRUZJWC5sZW5ndGgpLnRyaW0oKTtcbiAgICBpZiAodmFsdWUpIHtcbiAgICAgIGNvbnN0IHNhbml0aXplZCA9IHNhbml0aXplUHJlc2VudGVkVG9rZW4odmFsdWUpO1xuICAgICAgaWYgKHNhbml0aXplZCkgcmV0dXJuIHNhbml0aXplZDtcbiAgICB9XG4gIH1cblxuICAvLyBGYWxsYmFjayBmb3IgRXZlbnRTb3VyY2U6ID90b2tlbj08dG9rZW4+XG4gIGNvbnN0IHEgPSByZXEucXVlcnlbVE9LRU5fUVVFUllfUEFSQU1dO1xuICBpZiAodHlwZW9mIHEgPT09ICdzdHJpbmcnICYmIHEubGVuZ3RoID4gMCkge1xuICAgIHJldHVybiBzYW5pdGl6ZVByZXNlbnRlZFRva2VuKHEpO1xuICB9XG4gIGlmIChBcnJheS5pc0FycmF5KHEpICYmIHEubGVuZ3RoID4gMCAmJiB0eXBlb2YgcVswXSA9PT0gJ3N0cmluZycpIHtcbiAgICByZXR1cm4gc2FuaXRpemVQcmVzZW50ZWRUb2tlbihxWzBdKTtcbiAgfVxuXG4gIHJldHVybiBudWxsO1xufVxuXG4vKipcbiAqIE9wdGlvbnMgZm9yIHRoZSBhdXRoIG1pZGRsZXdhcmUgZmFjdG9yeS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBBdXRoTWlkZGxld2FyZU9wdGlvbnMge1xuICAvKiogVGhlIHRva2VuIHN0b3JlIGhvbGRpbmcgdmFsaWQgdG9rZW5zLiAqL1xuICBzdG9yZTogQ29uc29sZVRva2VuU3RvcmU7XG4gIC8qKiBXaGV0aGVyIGF1dGggaXMgZW5mb3JjZWQuIFdoZW4gZmFsc2UsIG1pZGRsZXdhcmUgaXMgYSBuby1vcC4gKi9cbiAgZW5hYmxlZDogYm9vbGVhbjtcbiAgLyoqXG4gICAqIFBhdGggcHJlZml4ZXMgdGhhdCBhcmUgbmV2ZXIgcHJvdGVjdGVkLiBBIHJlcXVlc3Qgd2hvc2UgVVJMIHBhdGggc3RhcnRzXG4gICAqIHdpdGggYW55IG9mIHRoZXNlIHN0cmluZ3Mgd2lsbCBza2lwIGF1dGggYW5kIGJlIHBhc3NlZCB0aHJvdWdoIHRvIHRoZVxuICAgKiBuZXh0IGhhbmRsZXIuIFVzZWQgdG8gZXhlbXB0IGhlYWx0aCBjaGVja3MsIHZlcnNpb24gaW5mbywgY2xpZW50IGRldGVjdGlvbixcbiAgICogYW5kIHNpbWlsYXIgcHVibGljIG1ldGFkYXRhIGVuZHBvaW50cy5cbiAgICpcbiAgICogUGF0aHMgYXJlIGNvbXBhcmVkIGFnYWluc3QgYHJlcS5wYXRoYCAodGhlIHJvdXRlLXJlbGF0aXZlIHBhdGgpLCBzbyBpbmNsdWRlXG4gICAqIHRoZSBmdWxsIHBhdGhuYW1lIHN0YXJ0aW5nIHdpdGggYC9gIOKAlCBlLmcuIGAvYXBpL2hlYWx0aGAsIGAvYXBpL3NldHVwL3ZlcnNpb25gLlxuICAgKi9cbiAgcHVibGljUGF0aFByZWZpeGVzPzogc3RyaW5nW107XG4gIC8qKiBPcHRpb25hbCBsYWJlbCBmb3IgbG9nIG1lc3NhZ2VzIChlLmcuIFwiYXBpXCIgb3IgXCJzc2VcIikuICovXG4gIGxhYmVsPzogc3RyaW5nO1xufVxuXG4vKipcbiAqIENyZWF0ZSB0aGUgY29yZSBhdXRoZW50aWNhdGlvbiBtaWRkbGV3YXJlLlxuICpcbiAqIFRoZSByZXR1cm5lZCBoYW5kbGVyIGVuZm9yY2VzIEJlYXJlciB0b2tlbiBhdXRoIG9uIGV2ZXJ5IHJlcXVlc3QgaXQgc2Vlcy5cbiAqIE1vdW50IGl0IHdpdGggYGFwcC51c2UoY3JlYXRlQXV0aE1pZGRsZXdhcmUoLi4uKSlgIGJlZm9yZSBwcm90ZWN0ZWQgcm91dGVycyxcbiAqIG9yIGF0dGFjaCBpdCB0byBpbmRpdmlkdWFsIHJvdXRlcyB0aGF0IG5lZWQgcHJvdGVjdGlvbi5cbiAqXG4gKiBXaGVuIGBlbmFibGVkOiBmYWxzZWAsIHRoZSBoYW5kbGVyIGltbWVkaWF0ZWx5IGNhbGxzIGBuZXh0KClgIOKAlCBhbGxvd2luZ1xuICogdGhlIGluZnJhc3RydWN0dXJlIHRvIGxhbmQgd2l0aCB0aGUgZGVmYXVsdC1vZmYgZmVhdHVyZSBmbGFnIHdpdGhvdXRcbiAqIGJyZWFraW5nIGV4aXN0aW5nIHRyYWZmaWMuXG4gKlxuICogUGhhc2UgMyBoYXJkZW5pbmcgKHRyYWNrZWQgaW4gIzE3ODkpOiBBZGQgNDAxIHJhdGUgbGltaXRpbmcgdG8gcHJldmVudFxuICogRG9TIGZyb20gZmxvb2RzIG9mIGJhZC10b2tlbiByZXF1ZXN0cy4gQnJ1dGUtZm9yY2luZyBhIDI1Ni1iaXQgdG9rZW4gaXNcbiAqIGluZmVhc2libGUsIGJ1dCBhbiBhdHRhY2tlciBmbG9vZGluZyAvYXBpIHdpdGggd3JvbmcgdG9rZW5zIGNvdWxkIHNhdHVyYXRlXG4gKiB0aGUgdmVyaWZ5IHBhdGguIEEgc2xpZGluZy13aW5kb3cgbGltaXRlciBrZXllZCBvbiB0aGUgcmVxdWVzdGluZyBJUCBpc1xuICogdGhlIHJpZ2h0IHNoYXBlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlQXV0aE1pZGRsZXdhcmUob3B0aW9uczogQXV0aE1pZGRsZXdhcmVPcHRpb25zKTogUmVxdWVzdEhhbmRsZXIge1xuICBjb25zdCB7IHN0b3JlLCBlbmFibGVkLCBsYWJlbCA9ICdjb25zb2xlJyB9ID0gb3B0aW9ucztcbiAgY29uc3QgcHVibGljUGF0aHMgPSBvcHRpb25zLnB1YmxpY1BhdGhQcmVmaXhlcyA/PyBbXTtcblxuICByZXR1cm4gKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSwgbmV4dDogTmV4dEZ1bmN0aW9uKSA9PiB7XG4gICAgaWYgKCFlbmFibGVkKSB7XG4gICAgICByZXR1cm4gbmV4dCgpO1xuICAgIH1cblxuICAgIC8vIFB1YmxpYyBwYXRoIGFsbG93bGlzdCDigJQgc2tpcCBhdXRoIGZvciB3aGl0ZWxpc3RlZCBwcmVmaXhlcy5cbiAgICAvLyBVc2Ugb3JpZ2luYWxVcmwucGF0aG5hbWUgc28gd2UgbWF0Y2ggcmVnYXJkbGVzcyBvZiBtb3VudCBwb2ludC5cbiAgICBjb25zdCBwYXRoVG9DaGVjayA9IHJlcS5vcmlnaW5hbFVybC5zcGxpdCgnPycpWzBdO1xuICAgIGZvciAoY29uc3QgcHJlZml4IG9mIHB1YmxpY1BhdGhzKSB7XG4gICAgICBpZiAocGF0aFRvQ2hlY2sgPT09IHByZWZpeCB8fCBwYXRoVG9DaGVjay5zdGFydHNXaXRoKHByZWZpeCArICcvJykpIHtcbiAgICAgICAgcmV0dXJuIG5leHQoKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBjb25zdCBwcmVzZW50ZWQgPSBleHRyYWN0VG9rZW4ocmVxKTtcbiAgICBpZiAoIXByZXNlbnRlZCkge1xuICAgICAgcmV0dXJuIHJlc3BvbmRVbmF1dGhvcml6ZWQocmVzLCAnbWlzc2luZ190b2tlbicsIGxhYmVsLCBzdG9yZSwgMCk7XG4gICAgfVxuXG4gICAgY29uc3QgZW50cnkgPSBzdG9yZS52ZXJpZnkocHJlc2VudGVkKTtcbiAgICBpZiAoIWVudHJ5KSB7XG4gICAgICAvLyBMb2cgcHJlc2VudGVkLWxlbmd0aCBvbmx5IOKAlCBuZXZlciB0aGUgcHJlc2VudGVkIHZhbHVlIGl0c2VsZi5cbiAgICAgIC8vIERpc3Rpbmd1aXNoZXMgXCJ0b2tlbiBtaXNzaW5nXCIgZnJvbSBcInRva2VuIHdyb25nIGxlbmd0aFwiIGZyb21cbiAgICAgIC8vIFwibGVuZ3RoIG1hdGNoZXMgYnV0IGNvbnRlbnQgZGlmZmVyc1wiIHdoZW4gdHJvdWJsZXNob290aW5nLlxuICAgICAgcmV0dXJuIHJlc3BvbmRVbmF1dGhvcml6ZWQocmVzLCAnaW52YWxpZF90b2tlbicsIGxhYmVsLCBzdG9yZSwgcHJlc2VudGVkLmxlbmd0aCk7XG4gICAgfVxuXG4gICAgLy8gU3R1YmJlZCBhdXRob3JpemF0aW9uIGhvb2sg4oCUIFBoYXNlIDIgZmxpcHMgcmVhbCBzY29wZSBjaGVja3Mgb24gaGVyZS5cbiAgICBpZiAoIWF1dGhvcml6ZVNjb3BlKGVudHJ5LCByZXEpKSB7XG4gICAgICByZXR1cm4gcmVzcG9uZEZvcmJpZGRlbihyZXMsICdzY29wZV9kZW5pZWQnLCBsYWJlbCwgZW50cnkpO1xuICAgIH1cblxuICAgIC8vIFN0YXNoIHRoZSBtYXRjaGVkIGVudHJ5IGZvciBkb3duc3RyZWFtIGhhbmRsZXJzIGFuZCBsb2cgc3VjY2VzcyBhdCBkZWJ1Zy5cbiAgICByZXMubG9jYWxzLnRva2VuRW50cnkgPSBlbnRyeTtcbiAgICBsb2dnZXIuZGVidWcoYFtBdXRoOiR7bGFiZWx9XSB2ZXJpZmllZCB0b2tlbiBpZD0ke2VudHJ5LmlkfSBuYW1lPVwiJHtlbnRyeS5uYW1lfVwiIHJvdXRlPSR7cmVxLm1ldGhvZH0gJHtyZXEub3JpZ2luYWxVcmwuc3BsaXQoJz8nKVswXX1gKTtcbiAgICByZXR1cm4gbmV4dCgpO1xuICB9O1xufVxuXG4vKipcbiAqIFNjb3BlIGF1dGhvcml6YXRpb24gaG9vay5cbiAqXG4gKiBQaGFzZSAxOiBldmVyeSB2YWxpZCB0b2tlbiBpcyB0cmVhdGVkIGFzIGFkbWluIOKAlCByZXR1cm5zIHRydWUgdW5jb25kaXRpb25hbGx5LlxuICogUGhhc2UgMjogdGhpcyBmdW5jdGlvbiB3aWxsIGNoZWNrIGBlbnRyeS5zY29wZXNgIGFnYWluc3QgdGhlIHJvdXRlJ3MgcmVxdWlyZWRcbiAqIHNjb3BlcyAod2hpY2ggY2FuIGJlIGF0dGFjaGVkIHZpYSBgcmVzLmxvY2Fscy5yZXF1aXJlZFNjb3BlYCBvciBhIHJvdXRlXG4gKiBtZXRhZGF0YSBzeXN0ZW0pLlxuICovXG5mdW5jdGlvbiBhdXRob3JpemVTY29wZShfZW50cnk6IENvbnNvbGVUb2tlbkVudHJ5LCBfcmVxOiBSZXF1ZXN0KTogYm9vbGVhbiB7XG4gIHJldHVybiB0cnVlO1xufVxuXG4vKipcbiAqIFJlc3BvbmQgd2l0aCA0MDEgVW5hdXRob3JpemVkIGFuZCBhIGhlbHBmdWwgaGludCBhYm91dCB3aGVyZSB0byBmaW5kIHRoZSB0b2tlbi5cbiAqIEluY2x1ZGVzIHByZXNlbnRlZC10b2tlbiBsZW5ndGggYXQgZGVidWcgbGV2ZWwgZm9yIHRyb3VibGVzaG9vdGluZyDigJQgbmV2ZXIgdGhlIHZhbHVlLlxuICovXG5mdW5jdGlvbiByZXNwb25kVW5hdXRob3JpemVkKFxuICByZXM6IFJlc3BvbnNlLFxuICByZWFzb246ICdtaXNzaW5nX3Rva2VuJyB8ICdpbnZhbGlkX3Rva2VuJyxcbiAgbGFiZWw6IHN0cmluZyxcbiAgc3RvcmU6IENvbnNvbGVUb2tlblN0b3JlLFxuICBwcmVzZW50ZWRMZW5ndGg6IG51bWJlcixcbik6IHZvaWQge1xuICBsb2dnZXIuZGVidWcoYFtBdXRoOiR7bGFiZWx9XSA0MDEgJHtyZWFzb259IHByZXNlbnRlZExlbmd0aD0ke3ByZXNlbnRlZExlbmd0aH1gKTtcbiAgcmVzLnN0YXR1cyg0MDEpLmpzb24oe1xuICAgIGVycm9yOiAnQXV0aGVudGljYXRpb24gcmVxdWlyZWQnLFxuICAgIHJlYXNvbixcbiAgICBoaW50OiBgVG9rZW4gZmlsZTogJHtzdG9yZS5nZXRGaWxlUGF0aCgpfS4gU2VuZCAnQXV0aG9yaXphdGlvbjogQmVhcmVyIDx0b2tlbj4nIGhlYWRlciwgb3IgYXBwZW5kID90b2tlbj08dG9rZW4+IGZvciBTU0Ugc3RyZWFtcy5gLFxuICB9KTtcbn1cblxuLyoqXG4gKiBSZXNwb25kIHdpdGggNDAzIEZvcmJpZGRlbiDigJQgdG9rZW4gd2FzIHZhbGlkIGJ1dCBzY29wZSBkaWQgbm90IHBlcm1pdCB0aGlzIHJvdXRlLlxuICogUGhhc2UgMSBuZXZlciByZWFjaGVzIGhlcmUgYmVjYXVzZSBgYXV0aG9yaXplU2NvcGVgIGFsd2F5cyByZXR1cm5zIHRydWUsIGJ1dFxuICogdGhlIGNvZGUgcGF0aCBleGlzdHMgc28gUGhhc2UgMiBjYW4gd2lyZSBpdCB1cCB3aXRob3V0IGNoYW5naW5nIHRoZSBtaWRkbGV3YXJlIHNoYXBlLlxuICovXG5mdW5jdGlvbiByZXNwb25kRm9yYmlkZGVuKFxuICByZXM6IFJlc3BvbnNlLFxuICByZWFzb246IHN0cmluZyxcbiAgbGFiZWw6IHN0cmluZyxcbiAgZW50cnk6IENvbnNvbGVUb2tlbkVudHJ5LFxuKTogdm9pZCB7XG4gIGxvZ2dlci5kZWJ1ZyhgW0F1dGg6JHtsYWJlbH1dIDQwMyAke3JlYXNvbn1gLCB7IHRva2VuSWQ6IGVudHJ5LmlkLCBzY29wZXM6IGVudHJ5LnNjb3BlcyB9KTtcbiAgcmVzLnN0YXR1cyg0MDMpLmpzb24oe1xuICAgIGVycm9yOiAnVG9rZW4gc2NvcGUgZG9lcyBub3QgcGVybWl0IHRoaXMgYWN0aW9uJyxcbiAgICByZWFzb24sXG4gIH0pO1xufVxuIl19