BrowserGuiasAutomação de MFA do GitHub

Automação MFA do GitHub

Introdução

O maior desafio na automação do login do GitHub é a Autenticação de Dois Fatores (2FA). Seja um aplicativo autenticador (TOTP) ou um OTP por e-mail, os fluxos de automação tradicionais geralmente ficam travados nesta etapa devido a:

  • Incapacidade de recuperar códigos de verificação automaticamente
  • Incapacidade de sincronizar códigos em tempo real
  • Incapacidade de inserir códigos automaticamente via automação
  • Ambientes de navegador insuficientemente realistas, acionando as verificações de segurança do GitHub

Este artigo demonstra como construir um fluxo de trabalho 2FA do GitHub totalmente automatizado usando Scrapeless Browser + Signal CDP, incluindo:

  • Caso 1: GitHub 2FA (Autenticador / geração automática de TOTP)
  • Caso 2: GitHub 2FA (escuta automática de OTP por e-mail)

Explicaremos o fluxo de trabalho completo para cada caso e mostraremos como coordenar o script de login com o ouvinte de código de verificação em um sistema automatizado.


Caso 1: GitHub 2FA (Modo OTP do Autenticador)

O mecanismo TOTP (Time-based One-Time Password) do GitHub é ideal para cenários de automação. Usando Scrapeless Browser + Signal CDP, você pode fazer com que o navegador automaticamente:

  • Acione um evento ao atingir a página 2FA
  • Gere o código OTP
  • Preencha o código automaticamente
  • Conclua o login

Comparado ao OTP tradicional por e-mail/SMS, o TOTP oferece:

  • Códigos gerados localmente sem dependências externas
  • Geração de código rápida e estável
  • Nenhuma API adicional necessária
  • Totalmente automatizável sem intervenção humana

Cenários aplicáveis:

  • Contas GitHub usando Google Authenticator / Authy / 1Password
  • Caminhos da página 2FA: /sessions/two-factor/app ou /sessions/two-factor/webauthn

Vídeo

Caso 1: GitHub 2FA (Modo OTP do Autenticador)

Etapa 1: Conectar ao Scrapeless Browser

Nesta etapa, estabelecemos uma conexão WebSocket full-duplex com o Scrapeless Browser:

import puppeteer from 'puppeteer-core';
 
const query = new URLSearchParams({
  token: "",
  proxyCountry: "ANY",
  sessionRecording: true,
  sessionTTL: 900,
  sessionName: "Data Scraping",
});
 
const connectionURL = `wss://browser.scrapeless.com/api/v2/browser?${query.toString()}`;
const browserWSEndpoint = connectionURL;
 
const browser = await puppeteer.connect({ browserWSEndpoint });
console.log("✅ Connected to Scrapeless Browser");

Vantagens:

  • Chrome real na nuvem com forte anti-detecção
  • Não consome recursos locais
  • Proxy automático, persistência e gravação de sessão
  • Execução confiável de fluxos de trabalho de login automatizados em larga escala

Etapa 2: Inicializar o Gerenciador MFA + Provedor TOTP

O MFA é tratado por meio da comunicação bidirecional do Signal CDP, que:

  • Envia automaticamente mfa_code_request quando uma página 2FA é detectada
  • Gera códigos TOTP
  • Retorna códigos via mfa_code_response
  • Envia eventos de resultado de login
import { authenticator } from 'otplib';
 
const TOTP_SECRETS = {
    'github': 'secret-code',
    'default': 'secret-code'
};
 
authenticator.options = {
    digits: 6,
    step: 30,
    window: 1
};

Etapa 3: Criar Gerenciador MFA

Aqui, criamos um gerenciador MFA centralizado para lidar com a comunicação com o Signal CDP:

  • Acionar mfa_code_request
  • Aguardar e receber o código de verificação (mfa_code_response)
  • Retornar o código para o processo de login do GitHub
  • Enviar eventos de resultado de login final
class MFAManager {
    constructor() {
        this.client = null;
    }
 
    setClient(client) {
        this.client = client;
    }
 
    async sendMFARequest(username, provider = 'github') {
        const requestData = {
            type: 'mfa_required',
            provider,
            username,
            timestamp: new Date().toISOString(),
            service: 'github_2fa'
        };
 
        return await this.client.send('Signal.send', {
            event: 'mfa_code_request',
            data: JSON.stringify(requestData)
        });
    }
 
    async waitForMFACode(timeout = 120000) {
        const result = await this.client.send('Signal.wait', {
            event: 'mfa_code_response',
            timeout
        });
 
        if (result.status === 200 && result.data) {
            return JSON.parse(result.data).code;
        } else if (result.status === 408) {
            throw new Error('Waiting for MFA code timed out');
        } else {
            throw new Error(`Signal wait failed, status: ${result.status}`);
        }
    }
 
    async sendMFAResponse(code, username) {
        const responseData = {
            code,
            username,
            timestamp: new Date().toISOString(),
            status: 'code_provided'
        };
 
        return await this.client.send('Signal.send', {
            event: 'mfa_code_response',
            data: JSON.stringify(responseData)
        });
    }
 
    async sendLoginResult(success, username, url, error = null) {
        const resultData = {
            success,
            username,
            url,
            timestamp: new Date().toISOString(),
            error
        };
 
        await this.client.send('Signal.send', {
            event: 'github_login_result',
            data: JSON.stringify(resultData)
        });
    }
}

Vantagens:

  • Fluxo de trabalho 2FA totalmente automatizado (solicitar → aguardar → recuperar → preencher)
  • Gerenciamento unificado de vários métodos de autenticação (TOTP/Email/Push)
  • Elimina a lógica 2FA repetida em scripts

Etapa 4: Iniciar o Provedor de Código TOTP (Lida com a Escuta e o Preenchimento Automático de Códigos)

Nesta etapa, lançamos o ouvinte de código automático TOTP, que:

  • Escuta continuamente os eventos mfa_code_request
  • Gera automaticamente códigos TOTP (usando otplib)
  • Envia automaticamente o código de volta via Signal (mfa_code_response)
  • Substitui totalmente a entrada manual de código
class TOTPCodeProvider {
    constructor(mfaManager) {
        this.mfaManager = mfaManager;
        this.isListening = false;
        this.secrets = TOTP_SECRETS;
    }
 
    async startListening() {
        if (this.isListening) return;
        this.isListening = true;
        this.listenLoop();
    }
 
    async listenLoop() {
        while (this.isListening) {
            try {
                const result = await this.mfaManager.client.send('Signal.wait', {
                    event: 'mfa_code_request',
                    timeout: 5000
                });
 
                if (result.status === 200 && result.data) {
                    const requestData = JSON.parse(result.data);
                    await this.handleMFARequest(requestData);
                }
            } catch (err) {}
 
            await new Promise(r => setTimeout(r, 100));
        }
    }
 
    async handleMFARequest(requestData) {
        const code = this.generateTOTPCode(requestData.provider);
        await this.mfaManager.sendMFAResponse(code, requestData.username);
        console.log(`✅ TOTP code sent via Signal CDP: ${code}`);
    }
 
    generateTOTPCode(service = 'github') {
        const secret = this.secrets[service] || this.secrets.default;
        return authenticator.generate(secret);
    }
}

Vantagens:

  • Automatiza totalmente o 2FA sem entrada manual
  • Não é necessário e-mail ou aplicativo móvel
  • A geração de TOTP é precisa, estável e escalável
  • Suporta GitHub, AWS, Azure, Google e qualquer provedor baseado em TOTP

Etapa 5: Visitar a Página de Login do GitHub e Inserir Credenciais

Esta etapa lida com a primeira parte do fluxo de login do GitHub:

Abrir página de login → Inserir nome de usuário e senha → Prosseguir para a próxima etapa (2FA ou login direto)

await page.goto('https://github.com/login', { waitUntil: 'networkidle2' });
 
await page.waitForSelector('#login_field', { timeout: 10000 });
 
await page.type('#login_field', githubCredentials.username);
await page.type('#password', githubCredentials.password);
 
await page.click('input[type="submit"][value="Sign in"]');

Vantagens:

  • Usar o Scrapeless Browser garante um ambiente de navegador realista e estável
  • A digitação automatizada simula o comportamento real do usuário, evitando sinalizadores de segurança do GitHub
  • Garante a navegação para a página 2FA correta

Etapa 6: Detectar Página 2FA e Enviar TOTP Automaticamente

if (currentUrl.includes('/sessions/two-factor')) {
    console.log('🔐 2FA necessário detectado');
 
    await mfaManager.sendMFARequest(githubCredentials.username, 'github');
 
    const mfaCode = await mfaManager.waitForMFACode(120000);
 
    await page.waitForSelector('#app_totp');
    await page.type('#app_totp', mfaCode);
 
    await new Promise(resolve => setTimeout(resolve, 5000));
}

Vantagens:

  • Conclui o 2FA do GitHub sem qualquer intervenção humana
  • O TOTP é gerado automaticamente, evitando atrasos dos códigos de e-mail
  • A comunicação bidirecional do Signal CDP garante a entrega precisa do código
  • Estabilidade e confiabilidade de nível empresarial

Etapa 7: Enviar Resultado de Login via Signal CDP

Após o login do GitHub + 2FA ser concluído, o resultado final do login é enviado através do CDP (Signal.send) para o seu fluxo de trabalho de automação. Isso permite que seu backend, bot ou sistema CI/CD saiba em tempo real se o login foi bem-sucedido.

const isLoggedIn = !finalUrl.includes('/sessions/two-factor')
                && !finalUrl.includes('/login');
 
await mfaManager.sendLoginResult(isLoggedIn, githubCredentials.username, finalUrl);

Código completo

import puppeteer from 'puppeteer-core';
import {authenticator} from 'otplib';
 
const query = new URLSearchParams({
    token: "api-key",
    proxyCountry: "ANY",
    sessionRecording: true,
    sessionTTL: 900,
    sessionName: "GithubLogin",
});
 
const browserWSEndpoint = `wss://browser.scrapeless.com/api/v2/browser?${query.toString()}`;
 
// TOTP secrets
const TOTP_SECRETS = {
    'github': 'secret-code',
    'default': 'secret-code'
};
 
// Configure TOTP
authenticator.options = {digits: 6, step: 30, window: 1};
 
// MFA Manager
class MFAManager {
    constructor() {
        this.client = null;
    }
 
    setClient(client) {
        this.client = client;
    }
 
    async sendMFARequest(username, provider = 'github') {
        return await this.client.send('Signal.send', {
            event: 'mfa_code_request',
            data: JSON.stringify({
                type: 'mfa_required',
                provider,
                username,
                timestamp: new Date().toISOString(),
                service: 'github_2fa'
            })
        });
    }
 
    async waitForMFACode(timeout = 120000) {
        const result = await this.client.send('Signal.wait', {
            event: 'mfa_code_response',
            timeout
        });
 
        if (result.status === 200 && result.data) {
            return JSON.parse(result.data).code;
        } else if (result.status === 408) {
            throw new Error('Timeout waiting for MFA code');
        } else {
            throw new Error(`Signal wait failed, status: ${result.status}`);
        }
    }
 
    async sendMFAResponse(code, username) {
        return await this.client.send('Signal.send', {
            event: 'mfa_code_response',
            data: JSON.stringify({code, username, timestamp: new Date().toISOString(), status: 'code_provided'})
        });
    }
 
    async sendLoginResult(success, username, url, error = null) {
        await this.client.send('Signal.send', {
            event: 'github_login_result',
            data: JSON.stringify({success, username, url, timestamp: new Date().toISOString(), error})
        });
    }
}
 
// TOTP provider
class TOTPCodeProvider {
    constructor(mfaManager) {
        this.mfaManager = mfaManager;
        this.isListening = false;
        this.secrets = TOTP_SECRETS;
    }
 
    async startListening() {
        if (this.isListening) return;
        this.isListening = true;
        this.listenLoop();
    }
 
    async listenLoop() {
        while (this.isListening) {
            try {
                const result = await this.mfaManager.client.send('Signal.wait', {
                    event: 'mfa_code_request',
                    timeout: 5000
                });
 
                if (result.status === 200 && result.data) {
                    const requestData = JSON.parse(result.data);
                    await this.handleMFARequest(requestData);
                }
            } catch (error) {
            }
 
            await new Promise(resolve => setTimeout(resolve, 100));
        }
    }
 
    async handleMFARequest(requestData) {
        try {
            const code = this.generateTOTPCode(requestData.provider);
            if (code) {
                await this.mfaManager.sendMFAResponse(code, requestData.username);
                console.log(`✅ TOTP code sent via Signal CDP: ${code}`);
            } else {
                await this.sendErrorResponse(requestData.username, 'Unable to generate TOTP code');
            }
        } catch (error) {
            await this.sendErrorResponse(requestData.username, error.message);
        }
    }
 
    generateTOTPCode(service = 'github') {
        const secret = this.getSecretForService(service);
        if (!secret) throw new Error(`No TOTP secret found for ${service}`);
 
        const token = authenticator.generate(secret);
        if (!token || token.length !== 6 || isNaN(token)) {
            throw new Error(`Invalid TOTP token generated: ${token}`);
        }
 
        return token;
    }
 
    getSecretForService(service) {
        if (this.secrets[service]) return this.secrets[service];
        const lower = service.toLowerCase();
        if (this.secrets[lower]) return this.secrets[lower];
        if (this.secrets.default) return this.secrets.default;
        return null;
    }
 
    async sendErrorResponse(username, errorMessage) {
        await this.mfaManager.client.send('Signal.send', {
            event: 'mfa_code_error',
            data: JSON.stringify({username, error: errorMessage, timestamp: new Date().toISOString()})
        });
    }
 
    stopListening() {
        this.isListening = false;
    }
}
 
// GitHub credentials
const githubCredentials = {
    username: "***@gmail.com",
    password: "****"
};
 
// Main login flow
async function githubLoginWithAutoMFA() {
    let browser, page, codeProvider;
 
    try {
        console.log("🚀 Starting GitHub login flow...");
        browser = await puppeteer.connect({browserWSEndpoint});
 
        const pages = await browser.pages();
        page = pages.length > 0 ? pages[0] : await browser.newPage();
 
        const client = await page.target().createCDPSession();
        const mfaManager = new MFAManager();
        mfaManager.setClient(client);
 
        codeProvider = new TOTPCodeProvider(mfaManager);
        await codeProvider.startListening();
 
        page.setDefaultTimeout(30000);
        page.setDefaultNavigationTimeout(30000);
 
        await page.goto('https://github.com/login', {waitUntil: 'networkidle2'});
        await page.waitForSelector('#login_field, input[name="login"]', {timeout: 10000});
 
        await page.type('#login_field', githubCredentials.username);
        await page.type('#password', githubCredentials.password);
        await page.click('input[type="submit"][value="Sign in"]');
 
        await new Promise(resolve => setTimeout(resolve, 3000));
 
        const currentUrl = page.url();
 
        if (currentUrl.includes('/sessions/two-factor')) {
            console.log('🔐 2FA required detected');
            await mfaManager.sendMFARequest(githubCredentials.username, 'github');
 
            if (currentUrl.includes('/sessions/two-factor/webauthn')) {
                const moreOptionsButton = await page.$('.more-options-two-factor');
                if (moreOptionsButton) {
                    await moreOptionsButton.click();
                    await new Promise(resolve => setTimeout(resolve, 1000));
                }
 
                const authAppLink = await page.$('a[href="/sessions/two-factor/app"]');
                if (authAppLink) {
                    await authAppLink.click();
                    await page.waitForNavigation({waitUntil: 'networkidle2'});
                }
            }
 
            if (page.url().includes('/sessions/two-factor/app')) {
                const mfaCode = await mfaManager.waitForMFACode(120000);
 
                await page.waitForSelector('#app_totp', {timeout: 10000});
                await page.click('#app_totp', {clickCount: 3});
                await page.type('#app_totp', mfaCode);
 
                await new Promise(resolve => setTimeout(resolve, 5000));
 
                const finalUrl = page.url();
                const isLoggedIn = !finalUrl.includes('/sessions/two-factor') &&
                    !finalUrl.includes('/login') &&
                    (finalUrl.includes('github.com') || finalUrl === 'https://github.com/');
 
                await mfaManager.sendLoginResult(isLoggedIn, githubCredentials.username, finalUrl);
 
                if (isLoggedIn) {
                    console.log('🎉 GitHub login successful!');
                    await page.goto('https://github.com/', {waitUntil: 'networkidle2', timeout: 10000});
                } else {
                    console.log('❌ Login failed');
                }
            }
        } else if (currentUrl.includes('github.com') && !currentUrl.includes('/login')) {
            console.log('✅ Login successful (no 2FA)');
            await mfaManager.sendLoginResult(true, githubCredentials.username, currentUrl);
        } else {
            console.log('❌ Login failed, still on login page');
            await mfaManager.sendLoginResult(false, githubCredentials.username, currentUrl, 'Login failed');
        }
 
    } catch (error) {
        console.error('❌ GitHub login flow failed:', error);
 
        try {
            if (page) {
                const client = await page.target().createCDPSession();
                const mfaManager = new MFAManager();
                mfaManager.setClient(client);
                await mfaManager.sendLoginResult(false, githubCredentials.username, page.url() || 'unknown', error.message);
            }
        } catch (signalError) {
            console.error('❌ Failed to send error signal:', signalError);
        }
 
    } finally {
        if (codeProvider) codeProvider.stopListening();
        if (browser) await browser.close();
        console.log('🔚 GitHub login script finished');
    }
}
 
githubLoginWithAutoMFA().catch(console.error);

Caso 2: GitHub 2FA (Modo OTP por E-mail)

O tipo mais comum de 2FA em ambientes empresariais é a autenticação multifator (MFA) via OTP por E-mail.

Aplicável a:

  • GitHub com MFA ativado e verificação de e-mail vinculada
  • Políticas de segurança do GitHub que exigem verificação por e-mail

Vídeo

Caso 2: GitHub 2FA (Modo OTP por E-mail)

Etapa 1: Conectar ao Scrapeless Browser

import puppeteer from 'puppeteer-core';
 
const query = new URLSearchParams({
  token: "",
  proxyCountry: "ANY",
  sessionRecording: true,
  sessionTTL: 900,
  sessionName: "Data Scraping",
});
 
const connectionURL = `wss://browser.scrapeless.com/api/v2/browser?${query.toString()}`;
const browserWSEndpoint = connectionURL;

Vantagens:

  • Execução remota do navegador, sem consumo de recursos locais
  • sessionRecording facilita a reprodução e auditoria
  • Suporta comunicação bidirecional em tempo real com o Signal

Etapa 2: Abrir GitHub e Inserir Credenciais

const githubCredentials = {
    username: "1040111453@qq.com",
    password: "shijee1218",
    twoFactorCode: null
};
 
const browser = await puppeteer.connect({ browserWSEndpoint });
const pages = await browser.pages();
const page = pages.length > 0 ? pages[0] : await browser.newPage();
 
page.setDefaultTimeout(30000);
page.setDefaultNavigationTimeout(30000);
 
await page.goto('https://github.com/login', { waitUntil: 'networkidle2' });
await page.waitForSelector('#login_field', { timeout: 10000 });

Vantagens:

  • Simula com precisão a entrada real do usuário para evitar acionar a segurança do GitHub
  • Aguarda automaticamente a renderização da página, melhorando a estabilidade do script
  • Suporta configurações de tempo limite longas para lidar com flutuações de rede

Etapa 3: Detectar se a Página 2FA por E-mail foi Carregada

const currentUrl = page.url();
 
if (currentUrl.includes('/sessions/verified-device')) {
    const client = await page.target().createCDPSession();
    
    // Enviar notificação Signal para o ouvinte de e-mail para preparar o OTP
    await client.send('Signal.send', {
        event: 'github_2fa_required',
        data: JSON.stringify({ status: '2fa_required', timestamp: new Date().toISOString() })
    });
 
    // Aguardar o ouvinte de e-mail retornar o OTP
    const twoFactorResult = await client.send('Signal.wait', {
        event: 'github_2fa_code',
        timeout: 120000
    });

Vantagens:

  • Identifica com precisão a página /sessions/verified-device
  • Notifica proativamente o ouvinte de e-mail para preparar o OTP
  • Suporta a espera por OTP de e-mail em tempo real, melhorando a taxa de sucesso da automação

Etapa 4: Ouvinte de E-mail Envia OTP

Exemplo de sinal:

{
  "event": "github_2fa_code",
  "data": { "code": "123456" }
}

Vantagens:

  • Lê e-mails automaticamente
  • Extrai automaticamente o OTP do GitHub (código de 6 dígitos)
  • Envia Signal para o Scrapeless Browser em tempo real, sem necessidade de operação manual

Etapa 5: Inserir OTP e Enviar

if (twoFactorResult.status === 200 && twoFactorResult.data) {
    const twoFactorData = JSON.parse(twoFactorResult.data);
    githubCredentials.twoFactorCode = twoFactorData.code;
 
    if (!page.url().includes('/sessions/verified-device')) return;
 
    await page.$eval('#otp', (input) => { input.value = ''; });
    await page.type('#otp', githubCredentials.twoFactorCode);
 
    await page.evaluate(() => {
        const button = document.querySelector('button[type="submit"]');
        if (button) button.click();
    });
    await new Promise(resolve => setTimeout(resolve, 5000));
}

Vantagens:

  • Preenche automaticamente o OTP, melhorando a eficiência da automação
  • Garante que a página ainda esteja no estado 2FA, evitando erros de navegação
  • Simula ações de clique reais, reduzindo o risco de acionar verificações de segurança

Etapa 6: Verificar o Resultado Final do Login e Enviar Signal

const finalUrl = page.url();
const isLoggedIn =
    !finalUrl.includes('/sessions/verified-device') &&
    !finalUrl.includes('/login') &&
    (finalUrl.includes('github.com') || finalUrl === 'https://github.com/');
 
await client.send('Signal.send', {
    event: 'github_login_result',
    data: JSON.stringify({
        success: isLoggedIn,
        username: githubCredentials.username,
        url: finalUrl,
        twoFactorCode: githubCredentials.twoFactorCode,
        timestamp: new Date().toISOString()
    })
});
 
if (isLoggedIn) await page.goto('https://github.com/', { waitUntil: 'networkidle2' });

Vantagens:

  • Evita julgamento errôneo, garante que a página foi logada com sucesso
  • Navega automaticamente para a página inicial do GitHub para verificação, melhorando a confiabilidade
  • O resultado do login pode ser relatado em tempo real para sistemas CI/CD ou de bot

Etapa 7: Manter a Sessão e Fechar o Navegador

await new Promise(resolve => setTimeout(resolve, 5000));
await browser.close();

Vantagens:

  • Garante que todos os eventos Signal foram enviados
  • Mantém a sessão ativa por tempo suficiente para operações subsequentes
  • Fecha o navegador para evitar vazamento de recursos

Código Completo

  1. Primeiro, você precisa usar este script para executar o preenchimento de nome de usuário e senha da página de login do GitHub e a lógica de login, e aguardar a entrada do OTP na janela de verificação de e-mail.

Você obterá um taskId quando a tarefa da sessão do navegador for criada, lembre-se disso porque você precisará dele na próxima etapa.

import puppeteer from 'puppeteer-core';
 
const token = "api-key";
 
const query = new URLSearchParams({
    token,
    proxyCountry: "ANY",
    sessionRecording: true,
    sessionTTL: 900,
    sessionName: "Data Scraping",
});
 
const createBrowserSessionURL = `https://browser.scrapeless.com/api/v2/browser?${query.toString()}`;
 
// Get session taskId via HTTP API
const sessionResponse = await fetch(createBrowserSessionURL);
const {taskId} = await sessionResponse.json();
console.log('Session created with task ID:', taskId);
 
const browserWSEndpoint = `wss://api.scrapeless.com/browser/${taskId}?x-api-key=${token}`;
 
async function githubLoginWith2FA() {
    const browser = await puppeteer.connect({browserWSEndpoint});
    let page;
    
    try {
```javascript
console.log("🚀 Iniciando processo de login no GitHub...");
        const githubCredentials = {
            username: "****@gmail.com",
            password: "******",
            twoFactorCode: null
        };
        
        const pages = await browser.pages();
        page = pages.length > 0 ? pages[0] : await browser.newPage();
        
        page.setDefaultTimeout(30000);
        page.setDefaultNavigationTimeout(30000);
        
        console.log('📱 Navegando para a página de login do GitHub...');
        await page.goto('https://github.com/login', {waitUntil: 'networkidle2'});
        
        // Wait for the login form to load
        await page.waitForSelector('#login_field', {timeout: 10000});
        
        console.log('🔑 Digitando nome de usuário e senha...');
        await page.type('#login_field', githubCredentials.username);
        await page.type('#password', githubCredentials.password);
        
        // Click the sign in button
        console.log('🖱️ Clicando no botão de login...');
        await page.click('input[type="submit"][value="Sign in"]');
        
        // use setTimeout instead of waitForTimeout
        console.log('⏳ Aguardando resposta da página...');
        await new Promise(resolve => setTimeout(resolve, 3000));
        
        // Check whether an email verification (2FA) is required
        const currentUrl = page.url();
        console.log(`🔍 URL atual: ${currentUrl}`);
        
        if (currentUrl.includes('/sessions/verified-device')) {
            console.log('🔐 Verificação de e-mail detectada, aguardando código de verificação...');
            
            const client = await page.target().createCDPSession();
            
            // send signal notifying that email verification code is required
            await client.send('Signal.send', {
                event: 'github_2fa_required',
                data: JSON.stringify({
                    status: '2fa_required',
                    timestamp: new Date().toISOString()
                })
            });
            
            // Wait to receive the email verification code
            console.log('⏳ Aguardando o código de verificação por e-mail...');
            const twoFactorResult = await client.send('Signal.wait', {
                event: 'github_2fa_code',
                timeout: 120000
            });
            
            if (twoFactorResult.status === 200 && twoFactorResult.data) {
                const twoFactorData = JSON.parse(twoFactorResult.data);
                githubCredentials.twoFactorCode = twoFactorData.code;
                
                console.log(`✅ Código de verificação por e-mail recebido: ${githubCredentials.twoFactorCode}, inserindo código...`);
                
                // Ensure we are still on the verification page
                if (!page.url().includes('/sessions/verified-device')) {
                    console.log('⚠️ A página navegou para outro lugar, a verificação pode não ser mais necessária');
                    return;
                }
                
                // Enter the verification code
                console.log('⌨️ Inserindo o código de verificação...');
                await page.$eval('#otp', (input, code) => {
                    input.value = '';
                }, githubCredentials.twoFactorCode);
                
                await page.type('#otp', githubCredentials.twoFactorCode);
                console.log(`✅ Código de verificação ${githubCredentials.twoFactorCode} foi inserido`);
                
                // Click the verify button
                console.log('🖱️ Clicando no botão de verificar...');
                try {
                    await page.evaluate(() => {
                        const button = document.querySelector('button[type="submit"]');
                        if (button) button.click();
                    });
                    
                    console.log('✅ Botão de verificar clicado, aguardando resposta da página...');
                    await new Promise(resolve => setTimeout(resolve, 5000));
                    
                } catch (clickError) {
                    console.log('⚠️ Problema ao clicar no botão:', clickError.message);
                }
                
                // Check login result
                await new Promise(resolve => setTimeout(resolve, 3000));
                const finalUrl = page.url();
                console.log(`🔍 URL final: ${finalUrl}`);
                
                const isLoggedIn = !finalUrl.includes('/sessions/verified-device') &&
                    !finalUrl.includes('/login') &&
                    (finalUrl.includes('github.com') || finalUrl === 'https://github.com/');
                
                // send login result signal
                if (client) {
                    await client.send('Signal.send', {
                        event: 'github_login_result',
                        data: JSON.stringify({
                            success: isLoggedIn,
                            username: githubCredentials.username,
                            url: finalUrl,
                            twoFactorCode: githubCredentials.twoFactorCode,
                            timestamp: new Date().toISOString()
                        })
                    });
                }
                
                if (isLoggedIn) {
                    console.log('🎉 Login no GitHub bem-sucedido!');
                    try {
                        await page.goto('https://github.com/', {
                            waitUntil: 'networkidle2',
                            timeout: 10000
                        });
                        console.log('✅ Acesso à página inicial do GitHub bem-sucedido');
                    } catch (profileError) {
                        console.log('⚠️ Problema ao acessar a página inicial:', profileError.message);
                    }
                } else {
                    console.log('❌ Verificação de e-mail falhou, login sem sucesso');
                    console.log('🔍 Título da página atual:', await page.title());
                }
                
            } else {
                console.log('❌ Tempo esgotado aguardando o código de verificação por e-mail');
            }
            
        } else if (currentUrl.includes('github.com') && !currentUrl.includes('/login')) {
            // No email verification required
            console.log('✅ Login bem-sucedido (sem verificação por e-mail necessária)');
            
            const client = await page.target().createCDPSession();
            await client.send('Signal.send', {
                event: 'github_login_result',
                data: JSON.stringify({
                    success: true,
                    username: githubCredentials.username,
                    url: currentUrl,
                    timestamp: new Date().toISOString()
                })
            });
        } else {
            console.log('❌ Login falhou, ainda na página de login');
            console.log('🔍 Título da página atual:', await page.title());
        }
        
        // Keep the session for a short time
        console.log('⏳ Mantendo conexão por 5 segundos...');
        await new Promise(resolve => setTimeout(resolve, 5000));
        
    } catch (error) {
        console.error('❌ Processo de login no GitHub falhou:', error);
        
        try {
            const pages = await browser.pages();
            const currentPage = pages.length > 0 ? pages[0] : page;
            if (currentPage) {
                const errorClient = await currentPage.target().createCDPSession();
                await errorClient.send('Signal.send', {
                    event: 'github_login_error',
                    data: JSON.stringify({
                        error: error.message,
                        timestamp: new Date().toISOString()
                    })
                });
            }
        } catch (signalError) {
            console.error('❌ Falha ao enviar sinal de erro também:', signalError);
        }
        
    } finally {
        if (browser) await browser.close();
        console.log('🔚 Script de login no GitHub finalizado');
    }
}
 
// Run the script
githubLoginWith2FA().catch(console.error);
 
  1. Quando o script acima atinge a página aguardando o código de verificação, execute imediatamente este script de ouvinte de e-mail, que enviará o código mais recente para o script principal para concluir a verificação.
import Imap from 'imap';
import {simpleParser} from 'mailparser';
 
const CONFIG = {
    imap: {
        user: "****@gmail.com",
        password: "****",
        host: "mail.privateemail.com",
        port: 993,
        tls: true,
        tlsOptions: {rejectUnauthorized: false}
    },
 
    signal: {
        baseUrl: "https://browser.scrapeless.com",
        apiKey: "api-key"
    },
 
    checkInterval: 5000,
    maxWaitTime: 120000
};
 
class EmailListener {
    constructor() {
        this.imap = null;
        this.isListening = false;
        this.sessionId = null;
    }
 
    async start(sessionId) {
        console.log('Iniciando ouvinte de e-mail...');
        this.sessionId = sessionId;
 
        try {
            await this.connectIMAP();
            const code = await this.listenForCode();
 
            if (code) {
                console.log(`Código encontrado: ${code}`);
                await this.sendSignal('github_2fa_code', {code});
                console.log('Código enviado para o navegador');
            } else {
                console.log('Nenhum código encontrado (tempo esgotado)');
                await this.sendSignal('email_listener_timeout', {status: 'timeout'});
            }
        } catch (error) {
            console.error('Erro do ouvinte:', error.message || error);
        } finally {
            await this.cleanup();
        }
    }
 
    connectIMAP() {
        return new Promise((resolve, reject) => {
            this.imap = new Imap(CONFIG.imap);
 
            this.imap.once('ready', () => {
                console.log('IMAP conectado');
                resolve();
            });
 
            this.imap.once('error', reject);
            this.imap.connect();
        });
    }
 
    async listenForCode() {
        console.log('Ouvindo código de verificação do GitHub...');
        this.isListening = true;
        const startTime = Date.now();
 
        while (this.isListening && (Date.now() - startTime) < CONFIG.maxWaitTime) {
            try {
                const code = await this.checkEmails();
                if (code) return code;
                await new Promise(resolve => setTimeout(resolve, CONFIG.checkInterval));
            } catch (error) {
                console.error('checkEmails falhou:', error.message || error);
                await new Promise(resolve => setTimeout(resolve, 10000));
            }
        }
        return null;
    }
 
    checkEmails() {
        return new Promise((resolve, reject) => {
            this.imap.openBox('INBOX', false, (err, box) => {
                if (err) return reject(err);
 
                const criteria = ['UNSEEN', ['FROM', '****@github.com']];
 
                this.imap.search(criteria, (err, results) => {
                    if (err) return reject(err);
                    if (!results || results.length === 0) return resolve(null);
 
                    this.processEmails(results, resolve, reject);
                });
            });
        });
    }
 
    processEmails(results, resolve, reject) {
        const fetcher = this.imap.fetch(results, {
            bodies: ['TEXT'],
            markSeen: true
        });
 
        let processed = 0;
        let foundCode = null;
 
        fetcher.on('message', (msg) => {
            let buffer = '';
 
            msg.on('body', (stream) => {
                stream.on('data', (chunk) => buffer += chunk.toString('utf8'));
            });
 
            msg.once('end', async () => {
                try {
                    const mail = await simpleParser(buffer);
                    const code = this.extractCode(mail.text || '');
                    if (code) foundCode = code;
                } catch (error) {
                    console.error('Falha ao analisar e-mail:', error);
                }
 
                processed++;
                if (processed === results.length) resolve(foundCode);
            });
        });
 
        fetcher.once('error', reject);
    }
 
    extractCode(text) {
        const patterns = [
            /código de verificação:?\s*(\d{6})/i,
            /código de verificação:?\s*(\d{6})/i,
            /código:?\s*(\d{6})/i,
            /código de verificação do GitHub:?\s*(\d{6})/i
        ];
 
        for (const pattern of patterns) {
            const match = text.match(pattern);
            if (match) return match[1];
        }
 
        const digitMatch = text.match(/\b\d{6}\b/);
        return digitMatch ? digitMatch[0] : null;
    }
 
    // Send signal via HTTP, sessionId as parameter
    async sendSignal(event, data, sessionId = this.sessionId) {
        if (!sessionId) throw new Error('ID da sessão não disponível');
 
        try {
            const url = `${CONFIG.signal.baseUrl}/browser/${sessionId}/signal/send`;
            const response = await fetch(url, {
                method: 'POST',
                headers: {
                    'content-type': 'application/json',
                    'token': CONFIG.signal.apiKey
                },
                body: JSON.stringify({event, data})
            });
 
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}: ${response.statusText}`);
            }
 
            const result = await response.json();
            console.log('Sinal enviado com sucesso:', result);
            return result;
        } catch (err) {
            console.error('Falha ao enviar sinal via HTTP:', err);
            throw err;
        }
    }
 
    async cleanup() {
        this.isListening = false;
        if (this.imap) {
            try {
                this.imap.end();
                console.log('Conexão IMAP fechada');
            } catch (e) {
                console.error('Erro ao fechar IMAP:', e);
            }
        }
        this.sessionId = null;
    }
}
 
const listener = new EmailListener();
listener.start({taskId}).then(); // Substitua por taskId da primeira etapa
 

Através dos dois exemplos de login no GitHub acima, demonstramos como alcançar logins automatizados eficientes e estáveis em ambientes corporativos, cobrindo os dois modos comuns de 2FA: TOTP e códigos de verificação por e-mail. Usando o Scrapeless Browser + Signal CDP, você pode realizar operações reais de navegador, simular com precisão o comportamento do usuário e interagir em tempo real com sistemas MFA e ouvintes de e-mail para recuperar e enviar automaticamente códigos de verificação. Seja para desenvolver fluxos de trabalho de login automatizados, integrar com sistemas CI/CD ou gerenciar contas corporativas internas, esta solução pode aumentar significativamente as taxas de sucesso de login, reduzir a intervenção manual e fornecer auditoria e monitoramento operacionais completos.