@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.
484 lines • 62.8 kB
JavaScript
/**
* Secure GitHub token management and validation
*/
import { logger } from '../utils/logger.js';
import { RateLimiter } from '../update/RateLimiter.js';
import { SecurityError } from './errors.js';
import * as crypto from 'crypto';
import * as fs from 'fs/promises';
import * as path from 'path';
import { homedir } from 'os';
import { SecurityMonitor } from './securityMonitor.js';
import { UnicodeValidator } from './validators/unicodeValidator.js';
/**
* Secure GitHub token manager with validation and protection
*/
export class TokenManager {
static GITHUB_TOKEN_PATTERNS = {
PERSONAL_ACCESS_TOKEN: /^ghp_[A-Za-z0-9_]{36,}$/,
INSTALLATION_TOKEN: /^ghs_[A-Za-z0-9_]{36,}$/,
USER_ACCESS_TOKEN: /^ghu_[A-Za-z0-9_]{36,}$/,
REFRESH_TOKEN: /^ghr_[A-Za-z0-9_]{36,}$/
};
// Secure storage configuration
static TOKEN_DIR = path.join(homedir(), '.dollhouse', '.auth');
static TOKEN_FILE = 'github_token.enc';
static ALGORITHM = 'aes-256-gcm';
static KEY_LENGTH = 32;
static IV_LENGTH = 16;
static TAG_LENGTH = 16;
static SALT_LENGTH = 32;
static ITERATIONS = 100000;
// Rate limiter for token validation operations - prevents brute force attacks
static tokenValidationLimiter = null;
/**
* Get or create the token validation rate limiter
* Prevents brute force token validation attacks
*/
static getTokenValidationLimiter() {
if (!this.tokenValidationLimiter) {
this.tokenValidationLimiter = this.createTokenValidationLimiter();
}
return this.tokenValidationLimiter;
}
/**
* Create a rate limiter specifically for token validation
* Conservative limits to prevent abuse while allowing legitimate usage
*/
static createTokenValidationLimiter() {
return new RateLimiter({
maxRequests: 10, // 10 validation attempts
windowMs: 60 * 60 * 1000, // per hour
minDelayMs: 5 * 1000 // 5 seconds minimum between attempts
});
}
/**
* Reset the token validation rate limiter
* Useful for testing or manual intervention
*/
static resetTokenValidationLimiter() {
this.tokenValidationLimiter?.reset();
}
/**
* Validate GitHub token format
*/
static validateTokenFormat(token) {
if (!token || typeof token !== 'string') {
return false;
}
// Check against all known GitHub token patterns
return Object.values(this.GITHUB_TOKEN_PATTERNS).some(pattern => pattern.test(token));
}
/**
* Get GitHub token from environment with validation
*/
static getGitHubToken() {
const token = process.env.GITHUB_TOKEN;
if (!token) {
logger.debug('No GitHub token found in environment');
return null;
}
if (!this.validateTokenFormat(token)) {
logger.warn('Invalid GitHub token format detected', {
tokenPrefix: this.getTokenPrefix(token),
length: token.length
});
return null;
}
logger.debug('Valid GitHub token found', {
tokenType: this.getTokenType(token),
tokenPrefix: this.getTokenPrefix(token)
});
return token;
}
/**
* Redact token for safe logging
*/
static redactToken(token) {
if (!token || token.length < 8) {
return '[REDACTED]';
}
return token.substring(0, 4) + '...' + token.substring(token.length - 4);
}
/**
* Get token type from format
*/
static getTokenType(token) {
if (this.GITHUB_TOKEN_PATTERNS.PERSONAL_ACCESS_TOKEN.test(token)) {
return 'Personal Access Token';
}
if (this.GITHUB_TOKEN_PATTERNS.INSTALLATION_TOKEN.test(token)) {
return 'Installation Token';
}
if (this.GITHUB_TOKEN_PATTERNS.USER_ACCESS_TOKEN.test(token)) {
return 'User Access Token';
}
if (this.GITHUB_TOKEN_PATTERNS.REFRESH_TOKEN.test(token)) {
return 'Refresh Token';
}
return 'Unknown';
}
/**
* Get safe token prefix for logging
*/
static getTokenPrefix(token) {
if (!token || token.length < 4) {
return '[INVALID]';
}
return token.substring(0, 4) + '...';
}
/**
* Validate token scopes via GitHub API
*/
static async validateTokenScopes(token, requiredScopes) {
// Validate token format before consuming rate limit
if (!this.validateTokenFormat(token)) {
return {
isValid: false,
error: 'Invalid token format'
};
}
// Check rate limit before making API call
const rateLimiter = this.getTokenValidationLimiter();
const rateLimitStatus = rateLimiter.checkLimit();
if (!rateLimitStatus.allowed) {
logger.warn('Token validation rate limit exceeded', {
tokenPrefix: this.getTokenPrefix(token),
retryAfterMs: rateLimitStatus.retryAfterMs,
remainingTokens: rateLimitStatus.remainingTokens
});
throw new SecurityError(`Token validation rate limit exceeded. Please retry in ${Math.ceil((rateLimitStatus.retryAfterMs || 0) / 1000)} seconds.`, 'RATE_LIMIT_EXCEEDED');
}
try {
// Consume rate limit token for this validation attempt
rateLimiter.consumeToken();
// Make a test API call to check token validity and scopes
const response = await fetch('https://api.github.com/user', {
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'DollhouseMCP/1.0'
}
});
const rateLimitRemaining = parseInt(response.headers.get('x-ratelimit-remaining') || '0');
const rateLimitReset = parseInt(response.headers.get('x-ratelimit-reset') || '0');
if (!response.ok) {
const error = `GitHub API error: ${response.status} ${response.statusText}`;
logger.warn('Token validation failed', {
status: response.status,
tokenPrefix: this.getTokenPrefix(token)
});
return {
isValid: false,
error: error
};
}
// Extract scopes from response headers
const scopesHeader = response.headers.get('x-oauth-scopes') || '';
const tokenScopes = scopesHeader.split(',').map(s => s.trim()).filter(s => s);
// Check if required scopes are present
const hasRequiredScopes = requiredScopes.required.every(scope => tokenScopes.includes(scope));
if (!hasRequiredScopes) {
const missingScopes = requiredScopes.required.filter(scope => !tokenScopes.includes(scope));
logger.warn('Token missing required scopes', {
tokenPrefix: this.getTokenPrefix(token),
missingScopes: missingScopes,
currentScopes: tokenScopes
});
return {
isValid: false,
scopes: tokenScopes,
error: `Missing required scopes: ${missingScopes.join(', ')}`
};
}
logger.info('Token validation successful', {
tokenType: this.getTokenType(token),
tokenPrefix: this.getTokenPrefix(token),
scopes: tokenScopes,
rateLimitRemaining: rateLimitRemaining
});
return {
isValid: true,
scopes: tokenScopes,
rateLimit: {
remaining: rateLimitRemaining,
resetTime: new Date(rateLimitReset * 1000)
}
};
}
catch (error) {
// Handle SecurityError (including rate limit errors) separately
if (error instanceof SecurityError && error.code === 'RATE_LIMIT_EXCEEDED') {
const currentStatus = rateLimiter.checkLimit();
return {
isValid: false,
rateLimitExceeded: true,
retryAfterMs: currentStatus.retryAfterMs,
error: error.message
};
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
logger.error('Token validation error', {
error: errorMessage,
tokenPrefix: this.getTokenPrefix(token)
});
return {
isValid: false,
error: `Validation error: ${errorMessage}`
};
}
}
/**
* Create safe error message without token exposure
*/
static createSafeErrorMessage(error, token) {
// Remove any potential token data from error messages
let safeMessage = error
.replace(/ghp_[A-Za-z0-9_]{36,}/g, '[REDACTED_PAT]')
.replace(/ghs_[A-Za-z0-9_]{36,}/g, '[REDACTED_INSTALL]')
.replace(/ghu_[A-Za-z0-9_]{36,}/g, '[REDACTED_USER]')
.replace(/ghr_[A-Za-z0-9_]{36,}/g, '[REDACTED_REFRESH]');
if (token) {
const tokenPrefix = this.getTokenPrefix(token);
safeMessage += ` (Token: ${tokenPrefix})`;
}
return safeMessage;
}
/**
* Get minimum required scopes for different operations
*
* NOTE: The 'marketplace' scope identifier is kept for backward compatibility
* with existing token validations. This is an internal scope name and does not
* affect user-facing functionality. (PR #280)
*/
static getRequiredScopes(operation) {
switch (operation) {
case 'read':
return {
required: ['repo'],
optional: ['user:email']
};
case 'write':
return {
required: ['repo'],
optional: ['user:email']
};
case 'marketplace': // Internal scope name kept for compatibility (PR #280)
case 'collection': // New preferred name
return {
required: ['repo'],
optional: ['user:email']
};
case 'gist':
return {
required: ['gist'],
optional: ['user:email']
};
default:
return {
required: ['repo']
};
}
}
/**
* Check if token has sufficient permissions for operation
*
* NOTE: The 'marketplace' operation type is kept for backward compatibility.
* This is called internally when accessing collection features. (PR #280)
*/
static async ensureTokenPermissions(operation) {
const token = this.getGitHubToken();
if (!token) {
return {
isValid: false,
error: 'No GitHub token available'
};
}
const requiredScopes = this.getRequiredScopes(operation);
return this.validateTokenScopes(token, requiredScopes);
}
/**
* Derive encryption key from a passphrase
*/
static deriveKey(passphrase, salt) {
return crypto.pbkdf2Sync(passphrase, salt, this.ITERATIONS, this.KEY_LENGTH, 'sha256');
}
/**
* Get machine-specific passphrase for encryption
* Uses a combination of machine ID and user info for uniqueness
*/
static getMachinePassphrase() {
// Use a combination of hostname, username, and a fixed app identifier
const hostname = crypto.createHash('sha256').update(homedir()).digest('hex').substring(0, 16);
const username = crypto.createHash('sha256').update(process.env.USER || 'default').digest('hex').substring(0, 16);
const appId = 'DollhouseMCP-TokenStore-v1';
return `${appId}-${hostname}-${username}`;
}
/**
* Store GitHub token securely to file
*/
static async storeGitHubToken(token) {
try {
// Validate token format first
if (!this.validateTokenFormat(token)) {
throw new SecurityError('Invalid token format');
}
// Normalize and validate token
const validation = UnicodeValidator.normalize(token);
if (!validation.isValid) {
throw new SecurityError('Token contains invalid characters');
}
// Ensure directory exists
await fs.mkdir(this.TOKEN_DIR, { recursive: true, mode: 0o700 });
// Generate encryption components
const salt = crypto.randomBytes(this.SALT_LENGTH);
const iv = crypto.randomBytes(this.IV_LENGTH);
const passphrase = this.getMachinePassphrase();
const key = this.deriveKey(passphrase, salt);
// Encrypt token
const cipher = crypto.createCipheriv(this.ALGORITHM, key, iv);
const encrypted = Buffer.concat([
cipher.update(validation.normalizedContent, 'utf8'),
cipher.final()
]);
const tag = cipher.getAuthTag();
// Create storage format: salt + iv + tag + encrypted
const stored = Buffer.concat([salt, iv, tag, encrypted]);
// Write to file with restricted permissions
const tokenPath = path.join(this.TOKEN_DIR, this.TOKEN_FILE);
await fs.writeFile(tokenPath, stored, { mode: 0o600 });
// Log security event
SecurityMonitor.logSecurityEvent({
type: 'TOKEN_VALIDATION_SUCCESS',
severity: 'LOW',
source: 'TokenManager.storeGitHubToken',
details: 'GitHub token stored securely',
metadata: {
tokenType: this.getTokenType(token),
tokenPrefix: this.getTokenPrefix(token)
}
});
logger.info('GitHub token stored securely');
}
catch (error) {
SecurityMonitor.logSecurityEvent({
type: 'TOKEN_VALIDATION_FAILURE',
severity: 'MEDIUM',
source: 'TokenManager.storeGitHubToken',
details: `Failed to store GitHub token: ${error instanceof Error ? error.message : 'Unknown error'}`
});
throw new SecurityError(`Failed to store token: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Retrieve GitHub token from secure storage
*/
static async retrieveGitHubToken() {
try {
const tokenPath = path.join(this.TOKEN_DIR, this.TOKEN_FILE);
// Check if file exists
try {
await fs.access(tokenPath);
}
catch {
// No stored token
return null;
}
// Read encrypted data
const stored = await fs.readFile(tokenPath);
// Extract components
const salt = stored.subarray(0, this.SALT_LENGTH);
const iv = stored.subarray(this.SALT_LENGTH, this.SALT_LENGTH + this.IV_LENGTH);
const tag = stored.subarray(this.SALT_LENGTH + this.IV_LENGTH, this.SALT_LENGTH + this.IV_LENGTH + this.TAG_LENGTH);
const encrypted = stored.subarray(this.SALT_LENGTH + this.IV_LENGTH + this.TAG_LENGTH);
// Derive decryption key
const passphrase = this.getMachinePassphrase();
const key = this.deriveKey(passphrase, salt);
// Decrypt token
const decipher = crypto.createDecipheriv(this.ALGORITHM, key, iv);
decipher.setAuthTag(tag);
const decrypted = Buffer.concat([
decipher.update(encrypted),
decipher.final()
]).toString('utf8');
// Validate decrypted token
if (!this.validateTokenFormat(decrypted)) {
SecurityMonitor.logSecurityEvent({
type: 'TOKEN_VALIDATION_FAILURE',
severity: 'HIGH',
source: 'TokenManager.retrieveGitHubToken',
details: 'Decrypted token has invalid format'
});
return null;
}
SecurityMonitor.logSecurityEvent({
type: 'TOKEN_VALIDATION_SUCCESS',
severity: 'LOW',
source: 'TokenManager.retrieveGitHubToken',
details: 'GitHub token retrieved from secure storage',
metadata: {
tokenType: this.getTokenType(decrypted),
tokenPrefix: this.getTokenPrefix(decrypted)
}
});
return decrypted;
}
catch (error) {
SecurityMonitor.logSecurityEvent({
type: 'TOKEN_VALIDATION_FAILURE',
severity: 'MEDIUM',
source: 'TokenManager.retrieveGitHubToken',
details: `Failed to retrieve GitHub token: ${error instanceof Error ? error.message : 'Unknown error'}`
});
logger.debug('Failed to retrieve stored token', { error });
return null;
}
}
/**
* Remove stored GitHub token
*/
static async removeStoredToken() {
try {
const tokenPath = path.join(this.TOKEN_DIR, this.TOKEN_FILE);
// Check if file exists before attempting deletion
try {
await fs.access(tokenPath);
await fs.unlink(tokenPath);
SecurityMonitor.logSecurityEvent({
type: 'TOKEN_CACHE_CLEARED',
severity: 'LOW',
source: 'TokenManager.removeStoredToken',
details: 'GitHub token removed from secure storage'
});
logger.info('Stored GitHub token removed');
}
catch (error) {
// File doesn't exist or couldn't be deleted
logger.debug('No stored token to remove');
}
}
catch (error) {
SecurityMonitor.logSecurityEvent({
type: 'TOKEN_CACHE_CLEARED',
severity: 'LOW',
source: 'TokenManager.removeStoredToken',
details: `Failed to remove stored token: ${error instanceof Error ? error.message : 'Unknown error'}`
});
logger.warn('Failed to remove stored token', { error });
}
}
/**
* Get GitHub token from environment or secure storage
* Updated to check secure storage if environment variable not set
*/
static async getGitHubTokenAsync() {
// First check environment variable
const envToken = this.getGitHubToken();
if (envToken) {
return envToken;
}
// Fall back to secure storage
return this.retrieveGitHubToken();
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9rZW5NYW5hZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlY3VyaXR5L3Rva2VuTWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUVILE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUM1QyxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDdkQsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUM1QyxPQUFPLEtBQUssTUFBTSxNQUFNLFFBQVEsQ0FBQztBQUNqQyxPQUFPLEtBQUssRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUNsQyxPQUFPLEtBQUssSUFBSSxNQUFNLE1BQU0sQ0FBQztBQUM3QixPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQzdCLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUN2RCxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxrQ0FBa0MsQ0FBQztBQW1CcEU7O0dBRUc7QUFDSCxNQUFNLE9BQU8sWUFBWTtJQUNmLE1BQU0sQ0FBVSxxQkFBcUIsR0FBRztRQUM5QyxxQkFBcUIsRUFBRSx5QkFBeUI7UUFDaEQsa0JBQWtCLEVBQUUseUJBQXlCO1FBQzdDLGlCQUFpQixFQUFFLHlCQUF5QjtRQUM1QyxhQUFhLEVBQUUseUJBQXlCO0tBQ3pDLENBQUM7SUFFRiwrQkFBK0I7SUFDdkIsTUFBTSxDQUFVLFNBQVMsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxFQUFFLFlBQVksRUFBRSxPQUFPLENBQUMsQ0FBQztJQUN4RSxNQUFNLENBQVUsVUFBVSxHQUFHLGtCQUFrQixDQUFDO0lBQ2hELE1BQU0sQ0FBVSxTQUFTLEdBQUcsYUFBYSxDQUFDO0lBQzFDLE1BQU0sQ0FBVSxVQUFVLEdBQUcsRUFBRSxDQUFDO0lBQ2hDLE1BQU0sQ0FBVSxTQUFTLEdB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SyxFQUFFLENBQUMsQ0FBQztRQUMxRCxDQUFDO0lBQ0gsQ0FBQztJQUVEOzs7T0FHRztJQUNILE1BQU0sQ0FBQyxLQUFLLENBQUMsbUJBQW1CO1FBQzlCLG1DQUFtQztRQUNuQyxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7UUFDdkMsSUFBSSxRQUFRLEVBQUUsQ0FBQztZQUNiLE9BQU8sUUFBUSxDQUFDO1FBQ2xCLENBQUM7UUFFRCw4QkFBOEI7UUFDOUIsT0FBTyxJQUFJLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztJQUNwQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBTZWN1cmUgR2l0SHViIHRva2VuIG1hbmFnZW1lbnQgYW5kIHZhbGlkYXRpb25cbiAqL1xuXG5pbXBvcnQgeyBsb2dnZXIgfSBmcm9tICcuLi91dGlscy9sb2dnZXIuanMnO1xuaW1wb3J0IHsgUmF0ZUxpbWl0ZXIgfSBmcm9tICcuLi91cGRhdGUvUmF0ZUxpbWl0ZXIuanMnO1xuaW1wb3J0IHsgU2VjdXJpdHlFcnJvciB9IGZyb20gJy4vZXJyb3JzLmpzJztcbmltcG9ydCAqIGFzIGNyeXB0byBmcm9tICdjcnlwdG8nO1xuaW1wb3J0ICogYXMgZnMgZnJvbSAnZnMvcHJvbWlzZXMnO1xuaW1wb3J0ICogYXMgcGF0aCBmcm9tICdwYXRoJztcbmltcG9ydCB7IGhvbWVkaXIgfSBmcm9tICdvcyc7XG5pbXBvcnQgeyBTZWN1cml0eU1vbml0b3IgfSBmcm9tICcuL3NlY3VyaXR5TW9uaXRvci5qcyc7XG5pbXBvcnQgeyBVbmljb2RlVmFsaWRhdG9yIH0gZnJvbSAnLi92YWxpZGF0b3JzL3VuaWNvZGVWYWxpZGF0b3IuanMnO1xuXG5leHBvcnQgaW50ZXJmYWNlIFRva2VuU2NvcGVzIHtcbiAgcmVxdWlyZWQ6IHN0cmluZ1tdO1xuICBvcHRpb25hbD86IHN0cmluZ1tdO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIFRva2VuVmFsaWRhdGlvblJlc3VsdCB7XG4gIGlzVmFsaWQ6IGJvb2xlYW47XG4gIHNjb3Blcz86IHN0cmluZ1tdO1xuICByYXRlTGltaXQ/OiB7XG4gICAgcmVtYWluaW5nOiBudW1iZXI7XG4gICAgcmVzZXRUaW1lOiBEYXRlO1xuICB9O1xuICByYXRlTGltaXRFeGNlZWRlZD86IGJvb2xlYW47XG4gIHJldHJ5QWZ0ZXJNcz86IG51bWJlcjtcbiAgZXJyb3I/OiBzdHJpbmc7XG59XG5cbi8qKlxuICogU2VjdXJlIEdpdEh1YiB0b2tlbiBtYW5hZ2VyIHdpdGggdmFsaWRhdGlvbiBhbmQgcHJvdGVjdGlvblxuICovXG5leHBvcnQgY2xhc3MgVG9rZW5NYW5hZ2VyIHtcbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgR0lUSFVCX1RPS0VOX1BBVFRFUk5TID0ge1xuICAgIFBFUlNPTkFMX0FDQ0VTU19UT0tFTjogL15naHBfW0EtWmEtejAtOV9dezM2LH0kLyxcbiAgICBJTlNUQUxMQVRJT05fVE9LRU46IC9eZ2hzX1tBLVphLXowLTlfXXszNix9JC8sXG4gICAgVVNFUl9BQ0NFU1NfVE9LRU46IC9eZ2h1X1tBLVphLXowLTlfXXszNix9JC8sXG4gICAgUkVGUkVTSF9UT0tFTjogL15naHJfW0EtWmEtejAtOV9dezM2LH0kL1xuICB9O1xuXG4gIC8vIFNlY3VyZSBzdG9yYWdlIGNvbmZpZ3VyYXRpb25cbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgVE9LRU5fRElSID0gcGF0aC5qb2luKGhvbWVkaXIoKSwgJy5kb2xsaG91c2UnLCAnLmF1dGgnKTtcbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgVE9LRU5fRklMRSA9ICdnaXRodWJfdG9rZW4uZW5jJztcbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgQUxHT1JJVEhNID0gJ2Flcy0yNTYtZ2NtJztcbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgS0VZX0xFTkdUSCA9IDMyO1xuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBJVl9MRU5HVEggPSAxNjtcbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgVEFHX0xFTkdUSCA9IDE2O1xuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBTQUxUX0xFTkdUSCA9IDMyO1xuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBJVEVSQVRJT05TID0gMTAwMDAwO1xuXG4gIC8vIFJhdGUgbGltaXRlciBmb3IgdG9rZW4gdmFsaWRhdGlvbiBvcGVyYXRpb25zIC0gcHJldmVudHMgYnJ1dGUgZm9yY2UgYXR0YWNrc1xuICBwcml2YXRlIHN0YXRpYyB0b2tlblZhbGlkYXRpb25MaW1pdGVyOiBSYXRlTGltaXRlciB8IG51bGwgPSBudWxsO1xuXG4gIC8qKlxuICAgKiBHZXQgb3IgY3JlYXRlIHRoZSB0b2tlbiB2YWxpZGF0aW9uIHJhdGUgbGltaXRlclxuICAgKiBQcmV2ZW50cyBicnV0ZSBmb3JjZSB0b2tlbiB2YWxpZGF0aW9uIGF0dGFja3NcbiAgICovXG4gIHByaXZhdGUgc3RhdGljIGdldFRva2VuVmFsaWRhdGlvbkxpbWl0ZXIoKTogUmF0ZUxpbWl0ZXIge1xuICAgIGlmICghdGhpcy50b2tlblZhbGlkYXRpb25MaW1pdGVyKSB7XG4gICAgICB0aGlzLnRva2VuVmFsaWRhdGlvbkxpbWl0ZXIgPSB0aGlzLmNyZWF0ZVRva2VuVmFsaWRhdGlvbkxpbWl0ZXIoKTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXMudG9rZW5WYWxpZGF0aW9uTGltaXRlcjtcbiAgfVxuXG4gIC8qKlxuICAgKiBDcmVhdGUgYSByYXRlIGxpbWl0ZXIgc3BlY2lmaWNhbGx5IGZvciB0b2tlbiB2YWxpZGF0aW9uXG4gICAqIENvbnNlcnZhdGl2ZSBsaW1pdHMgdG8gcHJldmVudCBhYnVzZSB3aGlsZSBhbGxvd2luZyBsZWdpdGltYXRlIHVzYWdlXG4gICAqL1xuICBzdGF0aWMgY3JlYXRlVG9rZW5WYWxpZGF0aW9uTGltaXRlcigpOiBSYXRlTGltaXRlciB7XG4gICAgcmV0dXJuIG5ldyBSYXRlTGltaXRlcih7XG4gICAgICBtYXhSZXF1ZXN0czogMTAsICAgICAgICAgIC8vIDEwIHZhbGlkYXRpb24gYXR0ZW1wdHNcbiAgICAgIHdpbmRvd01zOiA2MCAqIDYwICogMTAwMCwgLy8gcGVyIGhvdXJcbiAgICAgIG1pbkRlbGF5TXM6IDUgKiAxMDAwICAgICAgLy8gNSBzZWNvbmRzIG1pbmltdW0gYmV0d2VlbiBhdHRlbXB0c1xuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlc2V0IHRoZSB0b2tlbiB2YWxpZGF0aW9uIHJhdGUgbGltaXRlclxuICAgKiBVc2VmdWwgZm9yIHRlc3Rpbmcgb3IgbWFudWFsIGludGVydmVudGlvblxuICAgKi9cbiAgc3RhdGljIHJlc2V0VG9rZW5WYWxpZGF0aW9uTGltaXRlcigpOiB2b2lkIHtcbiAgICB0aGlzLnRva2VuVmFsaWRhdGlvbkxpbWl0ZXI/LnJlc2V0KCk7XG4gIH1cblxuICAvKipcbiAgICogVmFsaWRhdGUgR2l0SHViIHRva2VuIGZvcm1hdFxuICAgKi9cbiAgc3RhdGljIHZhbGlkYXRlVG9rZW5Gb3JtYXQodG9rZW46IHN0cmluZyk6IGJvb2xlYW4ge1xuICAgIGlmICghdG9rZW4gfHwgdHlwZW9mIHRva2VuICE9PSAnc3RyaW5nJykge1xuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cblxuICAgIC8vIENoZWNrIGFnYWluc3QgYWxsIGtub3duIEdpdEh1YiB0b2tlbiBwYXR0ZXJuc1xuICAgIHJldHVybiBPYmplY3QudmFsdWVzKHRoaXMuR0lUSFVCX1RPS0VOX1BBVFRFUk5TKS5zb21lKHBhdHRlcm4gPT4gXG4gICAgICBwYXR0ZXJuLnRlc3QodG9rZW4pXG4gICAgKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgR2l0SHViIHRva2VuIGZyb20gZW52aXJvbm1lbnQgd2l0aCB2YWxpZGF0aW9uXG4gICAqL1xuICBzdGF0aWMgZ2V0R2l0SHViVG9rZW4oKTogc3RyaW5nIHwgbnVsbCB7XG4gICAgY29uc3QgdG9rZW4gPSBwcm9jZXNzLmVudi5HSVRIVUJfVE9LRU47XG4gICAgXG4gICAgaWYgKCF0b2tlbikge1xuICAgICAgbG9nZ2VyLmRlYnVnKCdObyBHaXRIdWIgdG9rZW4gZm91bmQgaW4gZW52aXJvbm1lbnQnKTtcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cblxuICAgIGlmICghdGhpcy52YWxpZGF0ZVRva2VuRm9ybWF0KHRva2VuKSkge1xuICAgICAgbG9nZ2VyLndhcm4oJ0ludmFsaWQgR2l0SHViIHRva2VuIGZvcm1hdCBkZXRlY3RlZCcsIHtcbiAgICAgICAgdG9rZW5QcmVmaXg6IHRoaXMuZ2V0VG9rZW5QcmVmaXgodG9rZW4pLFxuICAgICAgICBsZW5ndGg6IHRva2VuLmxlbmd0aFxuICAgICAgfSk7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG5cbiAgICBsb2dnZXIuZGVidWcoJ1ZhbGlkIEdpdEh1YiB0b2tlbiBmb3VuZCcsIHtcbiAgICAgIHRva2VuVHlwZTogdGhpcy5nZXRUb2tlblR5cGUodG9rZW4pLFxuICAgICAgdG9rZW5QcmVmaXg6IHRoaXMuZ2V0VG9rZW5QcmVmaXgodG9rZW4pXG4gICAgfSk7XG5cbiAgICByZXR1cm4gdG9rZW47XG4gIH1cblxuICAvKipcbiAgICogUmVkYWN0IHRva2VuIGZvciBzYWZlIGxvZ2dpbmdcbiAgICovXG4gIHN0YXRpYyByZWRhY3RUb2tlbih0b2tlbjogc3RyaW5nKTogc3RyaW5nIHtcbiAgICBpZiAoIXRva2VuIHx8IHRva2VuLmxlbmd0aCA8IDgpIHtcbiAgICAgIHJldHVybiAnW1JFREFDVEVEXSc7XG4gICAgfVxuICAgIFxuICAgIHJldHVybiB0b2tlbi5zdWJzdHJpbmcoMCwgNCkgKyAnLi4uJyArIHRva2VuLnN1YnN0cmluZyh0b2tlbi5sZW5ndGggLSA0KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgdG9rZW4gdHlwZSBmcm9tIGZvcm1hdFxuICAgKi9cbiAgc3RhdGljIGdldFRva2VuVHlwZSh0b2tlbjogc3RyaW5nKTogc3RyaW5nIHtcbiAgICBpZiAodGhpcy5HSVRIVUJfVE9LRU5fUEFUVEVSTlMuUEVSU09OQUxfQUNDRVNTX1RPS0VOLnRlc3QodG9rZW4pKSB7XG4gICAgICByZXR1cm4gJ1BlcnNvbmFsIEFjY2VzcyBUb2tlbic7XG4gICAgfVxuICAgIGlmICh0aGlzLkdJVEhVQl9UT0tFTl9QQVRURVJOUy5JTlNUQUxMQVRJT05fVE9LRU4udGVzdCh0b2tlbikpIHtcbiAgICAgIHJldHVybiAnSW5zdGFsbGF0aW9uIFRva2VuJztcbiAgICB9XG4gICAgaWYgKHRoaXMuR0lUSFVCX1RPS0VOX1BBVFRFUk5TLlVTRVJfQUNDRVNTX1RPS0VOLnRlc3QodG9rZW4pKSB7XG4gICAgICByZXR1cm4gJ1VzZXIgQWNjZXNzIFRva2VuJztcbiAgICB9XG4gICAgaWYgKHRoaXMuR0lUSFVCX1RPS0VOX1BBVFRFUk5TLlJFRlJFU0hfVE9LRU4udGVzdCh0b2tlbikpIHtcbiAgICAgIHJldHVybiAnUmVmcmVzaCBUb2tlbic7XG4gICAgfVxuICAgIHJldHVybiAnVW5rbm93bic7XG4gIH1cblxuICAvKipcbiAgICogR2V0IHNhZmUgdG9rZW4gcHJlZml4IGZvciBsb2dnaW5nXG4gICAqL1xuICBzdGF0aWMgZ2V0VG9rZW5QcmVmaXgodG9rZW46IHN0cmluZyk6IHN0cmluZyB7XG4gICAgaWYgKCF0b2tlbiB8fCB0b2tlbi5sZW5ndGggPCA0KSB7XG4gICAgICByZXR1cm4gJ1tJTlZBTElEXSc7XG4gICAgfVxuICAgIHJldHVybiB0b2tlbi5zdWJzdHJpbmcoMCwgNCkgKyAnLi4uJztcbiAgfVxuXG4gIC8qKlxuICAgKiBWYWxpZGF0ZSB0b2tlbiBzY29wZXMgdmlhIEdpdEh1YiBBUElcbiAgICovXG4gIHN0YXRpYyBhc3luYyB2YWxpZGF0ZVRva2VuU2NvcGVzKFxuICAgIHRva2VuOiBzdHJpbmcsIFxuICAgIHJlcXVpcmVkU2NvcGVzOiBUb2tlblNjb3Blc1xuICApOiBQcm9taXNlPFRva2VuVmFsaWRhdGlvblJlc3VsdD4ge1xuICAgIC8vIFZhbGlkYXRlIHRva2VuIGZvcm1hdCBiZWZvcmUgY29uc3VtaW5nIHJhdGUgbGltaXRcbiAgICBpZiAoIXRoaXMudmFsaWRhdGVUb2tlbkZvcm1hdCh0b2tlbikpIHtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIGlzVmFsaWQ6IGZhbHNlLFxuICAgICAgICBlcnJvcjogJ0ludmFsaWQgdG9rZW4gZm9ybWF0J1xuICAgICAgfTtcbiAgICB9XG5cbiAgICAvLyBDaGVjayByYXRlIGxpbWl0IGJlZm9yZSBtYWtpbmcgQVBJIGNhbGxcbiAgICBjb25zdCByYXRlTGltaXRlciA9IHRoaXMuZ2V0VG9rZW5WYWxpZGF0aW9uTGltaXRlcigpO1xuICAgIGNvbnN0IHJhdGVMaW1pdFN0YXR1cyA9IHJhdGVMaW1pdGVyLmNoZWNrTGltaXQoKTtcblxuICAgIGlmICghcmF0ZUxpbWl0U3RhdHVzLmFsbG93ZWQpIHtcbiAgICAgIGxvZ2dlci53YXJuKCdUb2tlbiB2YWxpZGF0aW9uIHJhdGUgbGltaXQgZXhjZWVkZWQnLCB7XG4gICAgICAgIHRva2VuUHJlZml4OiB0aGlzLmdldFRva2VuUHJlZml4KHRva2VuKSxcbiAgICAgICAgcmV0cnlBZnRlck1zOiByYXRlTGltaXRTdGF0dXMucmV0cnlBZnRlck1zLFxuICAgICAgICByZW1haW5pbmdUb2tlbnM6IHJhdGVMaW1pdFN0YXR1cy5yZW1haW5pbmdUb2tlbnNcbiAgICAgIH0pO1xuXG4gICAgICB0aHJvdyBuZXcgU2VjdXJpdHlFcnJvcihcbiAgICAgICAgYFRva2VuIHZhbGlkYXRpb24gcmF0ZSBsaW1pdCBleGNlZWRlZC4gUGxlYXNlIHJldHJ5IGluICR7TWF0aC5jZWlsKChyYXRlTGltaXRTdGF0dXMucmV0cnlBZnRlck1zIHx8IDApIC8gMTAwMCl9IHNlY29uZHMuYCxcbiAgICAgICAgJ1JBVEVfTElNSVRfRVhDRUVERUQnXG4gICAgICApO1xuICAgIH1cblxuICAgIHRyeSB7XG4gICAgICAvLyBDb25zdW1lIHJhdGUgbGltaXQgdG9rZW4gZm9yIHRoaXMgdmFsaWRhdGlvbiBhdHRlbXB0XG4gICAgICByYXRlTGltaXRlci5jb25zdW1lVG9rZW4oKTtcbiAgICAgIC8vIE1ha2UgYSB0ZXN0IEFQSSBjYWxsIHRvIGNoZWNrIHRva2VuIHZhbGlkaXR5IGFuZCBzY29wZXNcbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2goJ2h0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcicsIHtcbiAgICAgICAgaGVhZGVyczoge1xuICAgICAgICAgICdBdXRob3JpemF0aW9uJzogYEJlYXJlciAke3Rva2VufWAsXG4gICAgICAgICAgJ0FjY2VwdCc6ICdhcHBsaWNhdGlvbi92bmQuZ2l0aHViLnYzK2pzb24nLFxuICAgICAgICAgICdVc2VyLUFnZW50JzogJ0RvbGxob3VzZU1DUC8xLjAnXG4gICAgICAgIH1cbiAgICAgIH0pO1xuXG4gICAgICBjb25zdCByYXRlTGltaXRSZW1haW5pbmcgPSBwYXJzZUludChyZXNwb25zZS5oZWFkZXJzLmdldCgneC1yYXRlbGltaXQtcmVtYWluaW5nJykgfHwgJzAnKTtcbiAgICAgIGNvbnN0IHJhdGVMaW1pdFJlc2V0ID0gcGFyc2VJbnQocmVzcG9uc2UuaGVhZGVycy5nZXQoJ3gtcmF0ZWxpbWl0LXJlc2V0JykgfHwgJzAnKTtcblxuICAgICAgaWYgKCFyZXNwb25zZS5vaykge1xuICAgICAgICBjb25zdCBlcnJvciA9IGBHaXRIdWIgQVBJIGVycm9yOiAke3Jlc3BvbnNlLnN0YXR1c30gJHtyZXNwb25zZS5zdGF0dXNUZXh0fWA7XG4gICAgICAgIGxvZ2dlci53YXJuKCdUb2tlbiB2YWxpZGF0aW9uIGZhaWxlZCcsIHtcbiAgICAgICAgICBzdGF0dXM6IHJlc3BvbnNlLnN0YXR1cyxcbiAgICAgICAgICB0b2tlblByZWZpeDogdGhpcy5nZXRUb2tlblByZWZpeCh0b2tlbilcbiAgICAgICAgfSk7XG4gICAgICAgIFxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIGlzVmFsaWQ6IGZhbHNlLFxuICAgICAgICAgIGVycm9yOiBlcnJvclxuICAgICAgICB9O1xuICAgICAgfVxuXG4gICAgICAvLyBFeHRyYWN0IHNjb3BlcyBmcm9tIHJlc3BvbnNlIGhlYWRlcnNcbiAgICAgIGNvbnN0IHNjb3Blc0hlYWRlciA9IHJlc3BvbnNlLmhlYWRlcnMuZ2V0KCd4LW9hdXRoLXNjb3BlcycpIHx8ICcnO1xuICAgICAgY29uc3QgdG9rZW5TY29wZXMgPSBzY29wZXNIZWFkZXIuc3BsaXQoJywnKS5tYXAocyA9PiBzLnRyaW0oKSkuZmlsdGVyKHMgPT4gcyk7XG5cbiAgICAgIC8vIENoZWNrIGlmIHJlcXVpcmVkIHNjb3BlcyBhcmUgcHJlc2VudFxuICAgICAgY29uc3QgaGFzUmVxdWlyZWRTY29wZXMgPSByZXF1aXJlZFNjb3Blcy5yZXF1aXJlZC5ldmVyeShzY29wZSA9PiBcbiAgICAgICAgdG9rZW5TY29wZXMuaW5jbHVkZXMoc2NvcGUpXG4gICAgICApO1xuXG4gICAgICBpZiAoIWhhc1JlcXVpcmVkU2NvcGVzKSB7XG4gICAgICAgIGNvbnN0IG1pc3NpbmdTY29wZXMgPSByZXF1aXJlZFNjb3Blcy5yZXF1aXJlZC5maWx0ZXIoc2NvcGUgPT4gXG4gICAgICAgICAgIXRva2VuU2NvcGVzLmluY2x1ZGVzKHNjb3BlKVxuICAgICAgICApO1xuICAgICAgICBcbiAgICAgICAgbG9nZ2VyLndhcm4oJ1Rva2VuIG1pc3NpbmcgcmVxdWlyZWQgc2NvcGVzJywge1xuICAgICAgICAgIHRva2VuUHJlZml4OiB0aGlzLmdldFRva2VuUHJlZml4KHRva2VuKSxcbiAgICAgICAgICBtaXNzaW5nU2NvcGVzOiBtaXNzaW5nU2NvcGVzLFxuICAgICAgICAgIGN1cnJlbnRTY29wZXM6IHRva2VuU2NvcGVzXG4gICAgICAgIH0pO1xuXG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgaXNWYWxpZDogZmFsc2UsXG4gICAgICAgICAgc2NvcGVzOiB0b2tlblNjb3BlcyxcbiAgICAgICAgICBlcnJvcjogYE1pc3NpbmcgcmVxdWlyZWQgc2NvcGVzOiAke21pc3NpbmdTY29wZXMuam9pbignLCAnKX1gXG4gICAgICAgIH07XG4gICAgICB9XG5cbiAgICAgIGxvZ2dlci5pbmZvKCdUb2tlbiB2YWxpZGF0aW9uIHN1Y2Nlc3NmdWwnLCB7XG4gICAgICAgIHRva2VuVHlwZTogdGhpcy5nZXRUb2tlblR5cGUodG9rZW4pLFxuICAgICAgICB0b2tlblByZWZpeDogdGhpcy5nZXRUb2tlblByZWZpeCh0b2tlbiksXG4gICAgICAgIHNjb3BlczogdG9rZW5TY29wZXMsXG4gICAgICAgIHJhdGVMaW1pdFJlbWFpbmluZzogcmF0ZUxpbWl0UmVtYWluaW5nXG4gICAgICB9KTtcblxuICAgICAgcmV0dXJuIHtcbiAgICAgICAgaXNWYWxpZDogdHJ1ZSxcbiAgICAgICAgc2NvcGVzOiB0b2tlblNjb3BlcyxcbiAgICAgICAgcmF0ZUxpbWl0OiB7XG4gICAgICAgICAgcmVtYWluaW5nOiByYXRlTGltaXRSZW1haW5pbmcsXG4gICAgICAgICAgcmVzZXRUaW1lOiBuZXcgRGF0ZShyYXRlTGltaXRSZXNldCAqIDEwMDApXG4gICAgICAgIH1cbiAgICAgIH07XG5cbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgLy8gSGFuZGxlIFNlY3VyaXR5RXJyb3IgKGluY2x1ZGluZyByYXRlIGxpbWl0IGVycm9ycykgc2VwYXJhdGVseVxuICAgICAgaWYgKGVycm9yIGluc3RhbmNlb2YgU2VjdXJpdHlFcnJvciAmJiBlcnJvci5jb2RlID09PSAnUkFURV9MSU1JVF9FWENFRURFRCcpIHtcbiAgICAgICAgY29uc3QgY3VycmVudFN0YXR1cyA9IHJhdGVMaW1pdGVyLmNoZWNrTGltaXQoKTtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBpc1ZhbGlkOiBmYWxzZSxcbiAgICAgICAgICByYXRlTGltaXRFeGNlZWRlZDogdHJ1ZSxcbiAgICAgICAgICByZXRyeUFmdGVyTXM6IGN1cnJlbnRTdGF0dXMucmV0cnlBZnRlck1zLFxuICAgICAgICAgIGVycm9yOiBlcnJvci5tZXNzYWdlXG4gICAgICAgIH07XG4gICAgICB9XG5cbiAgICAgIGNvbnN0IGVycm9yTWVzc2FnZSA9IGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogJ1Vua25vd24gZXJyb3InO1xuICAgICAgbG9nZ2VyLmVycm9yKCdUb2tlbiB2YWxpZGF0aW9uIGVycm9yJywge1xuICAgICAgICBlcnJvcjogZXJyb3JNZXNzYWdlLFxuICAgICAgICB0b2tlblByZWZpeDogdGhpcy5nZXRUb2tlblByZWZpeCh0b2tlbilcbiAgICAgIH0pO1xuXG4gICAgICByZXR1cm4ge1xuICAgICAgICBpc1ZhbGlkOiBmYWxzZSxcbiAgICAgICAgZXJyb3I6IGBWYWxpZGF0aW9uIGVycm9yOiAke2Vycm9yTWVzc2FnZX1gXG4gICAgICB9O1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBDcmVhdGUgc2FmZSBlcnJvciBtZXNzYWdlIHdpdGhvdXQgdG9rZW4gZXhwb3N1cmVcbiAgICovXG4gIHN0YXRpYyBjcmVhdGVTYWZlRXJyb3JNZXNzYWdlKGVycm9yOiBzdHJpbmcsIHRva2VuPzogc