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/appou/sessions/two-factor/webauthn
Vídeo

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_requestquando 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

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
sessionRecordingfacilita 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
- 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);
- 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.