@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.
641 lines โข 100 kB
JavaScript
/**
* FIX: DMCP-SEC-006 - Security audit suppression
* This handler delegates authentication operations to GitHubAuthManager.
* Audit logging happens in GitHubAuthManager for auth operations.
* @security-audit-suppress DMCP-SEC-006
*/
import { ConfigManager } from '../config/ConfigManager.js';
import { logger } from '../utils/logger.js';
import { fileURLToPath } from 'url';
import * as path from 'path';
import { homedir } from 'os';
import * as child_process from 'child_process';
import { SecurityMonitor } from '../security/securityMonitor.js';
export class GitHubAuthHandler {
githubAuthManager;
configManager;
initService;
indicatorService;
fileOperations;
constructor(githubAuthManager, configManager, initService, indicatorService, fileOperations) {
this.githubAuthManager = githubAuthManager;
this.configManager = configManager;
this.initService = initService;
this.indicatorService = indicatorService;
this.fileOperations = fileOperations;
}
async ensureInitialized() {
await this.initService.ensureInitialized();
}
prefix(message) {
return `${this.indicatorService.getPersonaIndicator()}${message}`;
}
async setupGitHubAuth() {
await this.ensureInitialized();
try {
// Check current auth status first
const currentStatus = await this.githubAuthManager.getAuthStatus();
if (currentStatus.isAuthenticated) {
return {
content: [{
type: "text",
text: this.prefix(`โ
**Already Connected to GitHub**\n\n` +
`๐ค **Username:** ${currentStatus.username}\n` +
`๐ **Permissions:** ${currentStatus.scopes?.join(', ') || 'basic access'}\n\n` +
`You're all set! You can:\n` +
`โข Browse the collection\n` +
`โข Install content\n` +
`โข Submit your creations\n\n` +
`To disconnect, say "disconnect from GitHub"`)
}]
};
}
// FIX: DMCP-SEC-006 - Add security audit logging for authentication initiation
SecurityMonitor.logSecurityEvent({
type: 'AUTH_FLOW_INITIATED',
severity: 'LOW',
source: 'GitHubAuthHandler.setupGitHubAuth',
details: 'GitHub authentication flow initiated via device code',
additionalData: { authFlow: 'device-code' }
});
// Initiate device flow
let deviceResponse;
try {
deviceResponse = await this.githubAuthManager.initiateDeviceFlow();
}
catch (deviceFlowError) {
logger.error('OAUTH_INDEX_2681: Failed to initiate device flow', { error: deviceFlowError });
throw new Error(`OAUTH_INDEX_2681: Device flow initiation failed - ${deviceFlowError instanceof Error ? deviceFlowError.message : 'Unknown error'}`);
}
// CRITICAL FIX: Use helper process approach from PR #518
// MCP servers are stateless and terminate after returning response
// The helper process survives MCP termination and can complete OAuth polling
// Get the OAuth client ID - use the same method that has the default fallback
// This ensures we get the default client ID if no env/config is set
logger.debug('OAUTH_STEP_4: Getting client ID for helper process');
const clientId = await this.githubAuthManager.resolveClientId();
logger.debug('OAUTH_STEP_5: Client ID obtained', { clientId: clientId?.substring(0, 8) + '...' });
if (!clientId) {
return {
content: [{
type: "text",
text: this.prefix(`โ **GitHub OAuth Configuration Error**\n\n` +
`Unable to obtain GitHub OAuth client ID.\n\n` +
`This is unexpected - please report this issue.\n\n` +
`**Workaround:**\n` +
`โข Set environment variable: DOLLHOUSE_GITHUB_CLIENT_ID\n` +
`โข Or use GitHub CLI: gh auth login --web`)
}]
};
}
let helperPath = null;
try {
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const overrideHelper = process.env.DOLLHOUSE_OAUTH_HELPER;
const possiblePaths = [
path.join(__dirname, '..', 'oauth-helper.mjs'),
path.join(process.cwd(), 'oauth-helper.mjs'),
path.join(__dirname, 'oauth-helper.mjs'),
path.join(__dirname, '..', '..', 'oauth-helper.mjs')
];
if (overrideHelper) {
possiblePaths.unshift(overrideHelper);
}
helperPath = null;
for (const testPath of possiblePaths) {
if (await this.fileOperations.exists(testPath)) {
helperPath = testPath;
break;
}
}
if (!helperPath) {
logger.error('OAUTH_INDEX_2734: oauth-helper.mjs not found', {
searchedPaths: possiblePaths,
cwd: process.cwd(),
dirname: __dirname
});
throw new Error(`OAUTH_HELPER_NOT_FOUND: oauth-helper.mjs not found at line 2734. Searched: ${possiblePaths.join(', ')}`);
}
logger.debug('OAUTH_STEP_6: Spawning helper process', {
helperPath,
clientId: clientId?.substring(0, 8) + '...',
deviceCode: deviceResponse.device_code.substring(0, 8) + '...'
});
const helper = this.spawnHelperProcess(helperPath, deviceResponse, clientId);
helper.unref();
logger.debug('OAUTH_STEP_7: Helper process spawned successfully', { pid: helper.pid });
logger.info('OAuth helper process spawned', {
pid: helper.pid,
expiresIn: deviceResponse.expires_in,
userCode: deviceResponse.user_code
});
const stateFile = path.join(this.getDollhouseHomeDir(), '.dollhouse', '.auth', 'oauth-helper-state.json');
const stateDir = path.dirname(stateFile);
await this.fileOperations.createDirectory(stateDir);
const state = {
pid: helper.pid,
deviceCode: deviceResponse.device_code,
userCode: deviceResponse.user_code,
startTime: new Date().toISOString(),
expiresAt: new Date(Date.now() + deviceResponse.expires_in * 1000).toISOString()
};
await this.fileOperations.writeFile(stateFile, JSON.stringify(state, null, 2), {
source: 'GitHubAuthHandler.setupGitHubAuth'
});
}
catch (spawnError) {
logger.error('OAUTH_INDEX_2774: Failed to spawn OAuth helper process', {
error: spawnError,
helperPath,
clientId: clientId?.substring(0, 8) + '...',
errorCode: spawnError?.code,
syscall: spawnError?.syscall
});
let errorDetail = '';
if (spawnError instanceof Error) {
if (spawnError.message.includes('ENOENT')) {
errorDetail = `OAUTH_HELPER_SPAWN_ENOENT: Node.js executable not found or helper script missing at ${helperPath}`;
}
else if (spawnError.message.includes('EACCES')) {
errorDetail = `OAUTH_HELPER_SPAWN_EACCES: Permission denied when trying to execute ${helperPath}`;
}
else if (spawnError.message.includes('E2BIG')) {
errorDetail = `OAUTH_HELPER_SPAWN_E2BIG: Argument list too long for helper process`;
}
else {
errorDetail = `OAUTH_HELPER_SPAWN_FAILED: Could not start background authentication process at line 2774 - ${spawnError.message}`;
}
}
else {
errorDetail = `OAUTH_HELPER_SPAWN_UNKNOWN: Unknown spawn error at line 2774`;
}
return {
content: [{
type: "text",
text: this.prefix(`โ ๏ธ **OAuth Helper Launch Failed**\n\n` +
`${errorDetail}\n\n` +
`**Alternative Options:**\n` +
`1. Try again: Run setup_github_auth again\n` +
`2. Use GitHub CLI: gh auth login --web\n` +
`3. Set token manually: export GITHUB_TOKEN=your_token`)
}]
};
}
return {
content: [{
type: "text",
text: this.githubAuthManager.formatAuthInstructions(deviceResponse) +
'\n\n๐ **Note**: Authentication will complete automatically once you authorize. ' +
'Your token will be stored securely for future use!'
}]
};
}
catch (error) {
logger.error('OAUTH_INDEX_2806: Main catch block - authentication setup failed', {
error,
errorType: error?.constructor?.name,
errorMessage: error instanceof Error ? error.message : 'Unknown'
});
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
const hasErrorCode = errorMessage.includes('OAUTH_');
return {
content: [{
type: "text",
text: this.prefix(`โ **Authentication Setup Failed**\n\n` +
`${hasErrorCode ? errorMessage : `OAUTH_INDEX_2806: Unable to start GitHub authentication - ${errorMessage}`}\n\n` +
`${!errorMessage.includes('OAUTH_NETWORK') ? 'Please check your internet connection and try again.' : ''}`)
}]
};
}
}
async checkGitHubAuth() {
await this.ensureInitialized();
try {
const helperHealth = await this.checkOAuthHelperHealth();
const status = await this.githubAuthManager.getAuthStatus();
if (status.isAuthenticated) {
if (helperHealth.exists) {
const stateFile = path.join(this.getDollhouseHomeDir(), '.dollhouse', '.auth', 'oauth-helper-state.json');
// FileOperationsService.deleteFile already handles ENOENT gracefully
await this.fileOperations.deleteFile(stateFile).catch(() => { }); // Preserve error swallowing pattern
}
return {
content: [{
type: "text",
text: this.prefix(`โ
**GitHub Connected**\n\n` +
`๐ค **Username:** ${status.username}\n` +
`๐ **Permissions:** ${status.scopes?.join(', ') || 'basic access'}\n\n` +
`**Available Actions:**\n` +
`โ
Browse collection\n` +
`โ
Install content\n` +
`โ
Submit content\n\n` +
`Everything is working properly!`)
}]
};
}
else if (helperHealth.isActive) {
return {
content: [{
type: "text",
text: this.prefix(`โณ **GitHub Authentication In Progress**\n\n` +
`๐ **User Code:** ${helperHealth.userCode}\n` +
`โฑ๏ธ **Time Remaining:** ${Math.floor(helperHealth.timeRemaining / 60)}m ${helperHealth.timeRemaining % 60}s\n` +
`๐ **Process Status:** ${helperHealth.processAlive ? 'โ
Running' : 'โ ๏ธ May have stopped'}\n` +
`๐ **Log Available:** ${helperHealth.hasLog ? 'Yes' : 'No'}\n\n` +
`**Waiting for you to:**\n` +
`1. Go to: https://github.com/login/device\n` +
`2. Enter code: **${helperHealth.userCode}**\n` +
`3. Authorize the application\n\n` +
`The authentication will complete automatically once you authorize.\n` +
`Run this command again to check status.`)
}]
};
}
else if (helperHealth.exists && helperHealth.expired) {
const lines = [
'โฑ๏ธ **Authentication Expired**',
'',
'The GitHub authentication request has expired.',
`User code: ${helperHealth.userCode} (expired)`,
'',
'**To try again:**',
'Run: `setup_github_auth` to get a new code',
''
];
if (helperHealth.errorLog) {
lines.push('**Error Log:**', '```', helperHealth.errorLog, '```', '');
}
return {
content: [{
type: "text",
text: this.prefix(lines.join('\n'))
}]
};
}
else if (status.hasToken) {
return {
content: [{
type: "text",
text: this.prefix(`โ ๏ธ **GitHub Token Invalid**\n\n`) +
`A GitHub token was found but it appears to be invalid or expired.\n\n` +
`**To fix this:**\n` +
`1. Say "set up GitHub" to authenticate again\n` +
`2. Or check your GITHUB_TOKEN environment variable\n\n` +
`Note: Browse and install still work without authentication!`
}]
};
}
else {
return {
content: [{
type: "text",
text: this.prefix(`๐ **Not Connected to GitHub**\n\n`) +
`You're not currently authenticated with GitHub.\n\n` +
`**What works without auth:**\n` +
`โ
Browse the public collection\n` +
`โ
Install community content\n` +
`โ Submit your own content (requires auth)\n\n` +
`To connect, just say "set up GitHub" or "connect to GitHub"`
}]
};
}
}
catch (error) {
logger.error('Failed to check GitHub auth', { error });
return {
content: [{
type: "text",
text: this.prefix(`โ **Unable to Check Authentication**\n\n`) +
`Error: ${error instanceof Error ? error.message : 'Unknown error'}`
}]
};
}
}
async getOAuthHelperStatus(verbose = false) {
await this.ensureInitialized();
try {
const health = await this.checkOAuthHelperHealth();
const homeDir = this.getDollhouseHomeDir();
const stateFile = path.join(homeDir, '.dollhouse', '.auth', 'oauth-helper-state.json');
const logFile = path.join(homeDir, '.dollhouse', 'oauth-helper.log');
const pidFile = path.join(homeDir, '.dollhouse', '.auth', 'oauth-helper.pid');
let statusText = `๐ **OAuth Helper Process Diagnostics**\n\n`;
if (!health.exists) {
statusText += `**Status:** No OAuth process detected\n`;
statusText += `**State File:** Not found\n\n`;
statusText += `No active authentication process. Run \`setup_github_auth\` to start one.\n`;
}
else if (health.isActive) {
statusText += `**Status:** ๐ข ACTIVE - Authentication in progress\n`;
statusText += `**User Code:** ${health.userCode}\n`;
statusText += `**Process ID:** ${health.pid}\n`;
statusText += `**Process Alive:** ${health.processAlive ? 'โ
Yes' : 'โ No (may have crashed)'}\n`;
statusText += `**Started:** ${health.startTime?.toLocaleString()}\n`;
statusText += `**Expires:** ${health.expiresAt?.toLocaleString()}\n`;
statusText += `**Time Remaining:** ${Math.floor(health.timeRemaining / 60)}m ${health.timeRemaining % 60}s\n\n`;
if (!health.processAlive) {
statusText += `โ ๏ธ **WARNING:** Process appears to have stopped!\n`;
statusText += `The helper process (PID ${health.pid}) is not responding.\n`;
statusText += `You may need to run
setup_github_auth
again.\n\n`;
}
}
else if (health.expired) {
statusText += `**Status:** ๐ด EXPIRED\n`;
statusText += `**User Code:** ${health.userCode} (expired)\n`;
statusText += `**Process ID:** ${health.pid}\n`;
statusText += `**Started:** ${health.startTime?.toLocaleString()}\n`;
statusText += `**Expired:** ${health.expiresAt?.toLocaleString()}\n\n`;
statusText += `The authentication request has expired. Run
setup_github_auth
to try again.\n\n`;
}
statusText += `**๐ File Locations:**\n`;
statusText += `โข State: ${stateFile}\n`;
statusText += `โข Log: ${logFile} ${health.hasLog ? '(exists)' : '(not found)'}\n`;
statusText += `โข PID: ${pidFile}\n\n`;
if (health.errorLog) {
statusText += `**โ ๏ธ Recent Errors:**\n\`\`\`\n${health.errorLog}\n\`\`\`\n\n`;
}
if (verbose && health.hasLog) {
try {
const fullLog = await this.fileOperations.readFile(logFile, {
source: 'GitHubAuthHandler.getOAuthHelperStatus'
});
const lines = fullLog.split('\n').filter(line => line.trim());
const recentLines = lines.slice(-20);
statusText += `**๐ Recent Log Output (last 20 lines):**\n\`\`\`\n`;
statusText += recentLines.join('\n');
statusText += `\n\`\`\`\n\n`;
}
catch {
statusText += `**๐ Log:** Unable to read log file\n\n`;
}
}
if (health.exists && !health.processAlive && !health.expired) {
statusText += `**๐ง Troubleshooting Tips:**\n`;
statusText += `1. The helper process may have crashed\n`;
statusText += `2. Check the log file for errors: ${logFile}\n`;
statusText += `3. Try running
setup_github_auth
again\n`;
statusText += `4. Ensure DOLLHOUSE_GITHUB_CLIENT_ID is set\n`;
statusText += `5. Check your internet connection\n`;
}
if (health.exists && (health.expired || !health.processAlive)) {
statusText += `\n**๐งน Manual Cleanup (if needed):**\n`;
statusText += '```bash';
statusText += `rm "${stateFile}"\n`;
statusText += `rm "${logFile}"\n`;
statusText += `rm "${pidFile}"\n`;
statusText += '```';
}
return {
content: [{
type: "text",
text: this.prefix(statusText)
}]
};
}
catch (error) {
logger.error('Failed to get OAuth helper status', { error });
return {
content: [{
type: "text",
text: this.prefix(`โ **Failed to Get OAuth Helper Status**\n\n`) +
`Error: ${error instanceof Error ? error.message : 'Unknown error'}`
}]
};
}
}
async checkOAuthHelperHealth() {
const homeDir = this.getDollhouseHomeDir();
const stateFile = path.join(homeDir, '.dollhouse', '.auth', 'oauth-helper-state.json');
const logFile = path.join(homeDir, '.dollhouse', 'oauth-helper.log');
const health = {
exists: false,
isActive: false,
expired: false,
processAlive: false,
hasLog: false,
userCode: '',
timeRemaining: 0,
pid: 0,
startTime: null,
expiresAt: null,
errorLog: ''
};
try {
const stateData = await this.fileOperations.readFile(stateFile, {
source: 'GitHubAuthHandler.checkOAuthHelperHealth'
});
const state = JSON.parse(stateData);
health.exists = true;
health.pid = state.pid;
health.userCode = state.userCode;
health.startTime = new Date(state.startTime);
health.expiresAt = new Date(state.expiresAt);
const now = new Date();
if (health.expiresAt > now) {
health.isActive = true;
health.timeRemaining = Math.ceil((health.expiresAt.getTime() - now.getTime()) / 1000);
// Check if process is alive (only reliable on non-Windows platforms)
if (process.platform !== 'win32') {
try {
process.kill(health.pid, 0); // Signal 0 checks existence without killing
health.processAlive = true;
}
catch {
health.processAlive = false;
}
}
else {
// On Windows, process.kill(pid, 0) is not reliable for checking existence.
// We'll assume it's alive if the state file exists and hasn't expired.
// A more robust check would involve platform-specific process management.
health.processAlive = true;
}
}
else {
health.expired = true;
}
// Check if log file exists
health.hasLog = await this.fileOperations.exists(logFile);
// Read error log if process is dead or expired, or if verbose mode is on
if (health.hasLog && (!health.processAlive || health.expired)) {
try {
const logContent = await this.fileOperations.readFile(logFile, {
source: 'GitHubAuthHandler.checkOAuthHelperHealth'
});
const lines = logContent.split('\n');
const importantLines = lines.filter(line => line.toLowerCase().includes('error') ||
line.toLowerCase().includes('fail') ||
line.includes('โ') ||
line.includes('โ ๏ธ')).slice(-10); // Get last 10 relevant lines
if (importantLines.length > 0) {
health.errorLog = importantLines.join('\n');
}
}
catch {
// Intentionally empty - ignore if log file read fails
}
}
}
catch (error) {
// If state file is not found (ENOENT), it's expected, so don't log as debug
if (error instanceof Error && error.message.includes('ENOENT')) {
// Intentionally empty - ENOENT is expected when state file doesn't exist yet
}
else {
logger.debug('Error reading OAuth helper state', { error });
}
}
return health;
}
async clearGitHubAuth() {
await this.ensureInitialized();
try {
// FIX: DMCP-SEC-006 - Add security audit logging for authentication clearing
SecurityMonitor.logSecurityEvent({
type: 'TOKEN_CACHE_CLEARED',
severity: 'LOW',
source: 'GitHubAuthHandler.clearGitHubAuth',
details: 'GitHub authentication credentials cleared'
});
await this.githubAuthManager.clearAuthentication();
return {
content: [{
type: "text",
text: this.prefix(`โ
**GitHub Disconnected**\n\n`) +
`Your GitHub connection has been cleared.\n\n` +
`**What still works:**\n` +
`โ
Browse the public collection\n` +
`โ
Install community content\n` +
`โ Submit content (requires reconnection)\n\n` +
`To reconnect later, just say "connect to GitHub"\n\n` +
`โ ๏ธ **Note:** To fully remove authentication, also unset the GITHUB_TOKEN environment variable.`
}]
};
}
catch (error) {
logger.error('Failed to clear GitHub auth', { error });
return {
content: [{
type: "text",
text: this.prefix(`โ **Failed to Clear Authentication**\n\n`) +
`Error: ${error instanceof Error ? error.message : 'Unknown error'}`
}]
};
}
}
async configureOAuth(client_id) {
await this.ensureInitialized();
try {
const configManager = this.configManager;
await configManager.initialize();
if (!client_id) {
const currentClientId = await this.githubAuthManager.resolveClientId();
if (currentClientId) {
const configuredClientId = configManager.getGitHubClientId();
const isUsingDefault = !configuredClientId;
const maskedClientId = currentClientId.substring(0, 10) + '...';
return {
content: [{
type: "text",
text: this.prefix(`โ
**GitHub OAuth Configuration**\n\n`) +
`**Current Status:** ${isUsingDefault ? 'Using Default' : 'Configured'}\n` +
`**Client ID:** ${maskedClientId}\n` +
`**Source:** ${isUsingDefault ? 'Built-in DollhouseMCP OAuth App' : 'Custom Configuration'}\n\n` +
`Your GitHub OAuth is ready to use! You can now:\n` +
`โข Run setup_github_auth to connect\n` +
`โข Submit content to the collection\n` +
`โข Access authenticated features\n\n` +
(isUsingDefault ?
`**Note:** Using the default DollhouseMCP OAuth app.\n` +
`To use your own OAuth app, provide a client_id parameter.\n\n` :
`To update the configuration, provide a new client_id parameter.\n\n`)
}]
};
}
else {
return {
content: [{
type: "text",
text: this.prefix(`โ ๏ธ **GitHub OAuth Not Configured**\n\n`) +
`No GitHub OAuth client ID is currently configured.\n\n` +
`**To set up OAuth:**\n` +
`1. Create a GitHub OAuth app at: https://github.com/settings/applications/new\n` +
`2. Use these settings:\n` +
` โข Homepage URL: https://github.com/DollhouseMCP\n` +
` โข Authorization callback URL: http://localhost:3000/callback\n` +
`3. Copy your Client ID (starts with "Ov23li")\n` +
`4. Run: configure_oauth with your client_id parameter\n\n` +
`**Need help?** Check the documentation for detailed setup instructions.`
}]
};
}
}
if (!ConfigManager.validateClientId(client_id)) {
return {
content: [{
type: "text",
text: this.prefix(`โ **Invalid Client ID Format**\n\n`) +
`GitHub OAuth Client IDs must:\n` +
`โข Start with "Ov23li"\n` +
`โข Be followed by at least 14 alphanumeric characters\n\n` +
`**Example:** Ov23liABCDEFGHIJKLMN\n\n` +
`Please check your client ID and try again.`
}]
};
}
await configManager.setGitHubClientId(client_id);
const maskedClientId = client_id.substring(0, 10) + '...';
return {
content: [{
type: "text",
text: this.prefix(`โ
**GitHub OAuth Configured Successfully**\n\n`) +
`**Client ID:** ${maskedClientId}\n` +
`**Saved to:** ~/.dollhouse/config.json\n\n` +
`Your GitHub OAuth is now ready! Next steps:\n` +
`โข Run setup_github_auth to connect your account\n` +
`โข Start submitting content to the collection\n` +
`โข Access authenticated features\n\n` +
`**Note:** Your client ID is securely stored in your local config file.`
}]
};
}
catch (error) {
logger.error('Failed to configure OAuth', { error });
return {
content: [{
type: "text",
text: this.prefix(`โ **OAuth Configuration Failed**\n\n`) +
`Error: ${error instanceof Error ? error.message : 'Unknown error'}\n\n` +
`Please check:\n` +
`โข File permissions for ~/.dollhouse/config.json\n` +
`โข Valid client ID format (starts with "Ov23li")\n` +
`โข Available disk space`
}]
};
}
}
spawnHelperProcess(helperPath, deviceResponse, clientId) {
return child_process.spawn('node', [
helperPath,
deviceResponse.device_code,
(deviceResponse.interval || 5).toString(),
deviceResponse.expires_in.toString(),
clientId
], {
detached: true,
stdio: 'ignore',
windowsHide: true
});
}
getDollhouseHomeDir() {
return process.env.DOLLHOUSE_HOME_DIR || homedir();
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiR2l0SHViQXV0aEhhbmRsZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaGFuZGxlcnMvR2l0SHViQXV0aEhhbmRsZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0E7Ozs7O0dBS0c7QUFHSCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDM0QsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQzVDLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxLQUFLLENBQUM7QUFDcEMsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUM7QUFDN0IsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLElBQUksQ0FBQztBQUM3QixPQUFPLEtBQUssYUFBYSxNQUFNLGVBQWUsQ0FBQztBQUcvQyxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFHakUsTUFBTSxPQUFPLGlCQUFpQjtJQUVMO0lBQ0E7SUFDQTtJQUNBO0lBQ0E7SUFMckIsWUFDcUIsaUJBQW9DLEVBQ3BDLGFBQTRCLEVBQzVCLFdBQWtDLEVBQ2xDLGdCQUF5QyxFQUN6QyxjQUFxQztRQUpyQyxzQkFBaUIsR0FBakIsaUJBQWlCLENBQW1CO1FBQ3BDLGtCQUFhLEdBQWIsYUFBYSxDQUFlO1FBQzVCLGdCQUFXLEdBQVgsV0FBVyxDQUF1QjtRQUNsQyxxQkFBZ0IsR0FBaEIsZ0JBQWdCLENBQXlCO1FBQ3pDLG1CQUFjLEdBQWQsY0FBYyxDQUF1QjtJQUN2RCxDQUFDO0lBRUksS0FBSyxDQUFDLGlCQUFpQjtRQUMzQixNQUFNLElBQUksQ0FBQyxXQUFXLENBQUMsaUJBQWlCLEVBQUUsQ0FBQztJQUMvQyxDQUFDO0lBRU8sTUFBTSxDQUFDLE9BQWU7UUFDMUIsT0FBTyxHQUFHLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxtQkFBbUIsRUFBRSxHQUFHLE9BQU8sRUFBRSxDQUFDO0lBQ3RFLENBQUM7SUFFRCxLQUFLLENB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