Automação de MFA do GitHub
Introdução
O maior desafio na automação do login do GitHub é a Autenticação de Dois Fatores (2FA). Seja por meio de um aplicativo autenticador (TOTP) ou de Email OTP, os fluxos de automação tradicionais geralmente ficam presos nesta etapa devido a:
- Incapacidade de obter automaticamente os códigos de verificação
- Incapacidade de sincronizar os códigos em tempo real
- Incapacidade de inserir os códigos automaticamente por meio de automação
- Ambientes de navegador insuficientemente realistas, que acionam as verificações de segurança do GitHub
Este artigo demonstra como construir um fluxo de trabalho totalmente automatizado de 2FA do GitHub usando Scrapeless Browser + Signal CDP, incluindo:
- Caso 1: 2FA do GitHub (geração automática de Authenticator / TOTP)
- Caso 2: 2FA do GitHub (escuta automática de Email OTP)
Vamos explicar o fluxo de trabalho completo de cada caso e mostrar como coordenar o script de login com o listener de códigos de verificação em um sistema automatizado.
Caso 1: 2FA do GitHub (Modo Authenticator OTP)
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 o navegador automaticamente:
- Acionar um evento quando chegar à página de 2FA
- Gerar o código OTP
- Preencher o código automaticamente
- Concluir o login
Em comparação com o Email/SMS OTP tradicional, o TOTP oferece:
- Códigos gerados localmente sem dependências externas
- Geração de códigos rápida e estável
- Nenhuma API adicional necessária
- Totalmente automatizável, sem intervenção humana
Cenários aplicáveis:
- Contas do GitHub que usam Google Authenticator / Authy / 1Password
- Caminhos da página de 2FA:
/sessions/two-factor/appou/sessions/two-factor/webauthn
Vídeo

Passo 1: Conectar-se 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 capacidade anti-detecção
- Não consome recursos locais
- Proxy, persistência e gravação de sessão automáticos
- Execução confiável de fluxos de trabalho de login automatizados em larga escala
Passo 2: Inicializar o MFA Manager + Provedor TOTP
O MFA é tratado por meio da comunicação bidirecional do Signal CDP, que:
- Envia automaticamente
mfa_code_requestquando uma página de 2FA é detectada - Gera códigos TOTP
- Retorna os códigos via
mfa_code_response - Envia eventos com o resultado do login
import { authenticator } from 'otplib';
const TOTP_SECRETS = {
'github': 'secret-code',
'default': 'secret-code'
};
authenticator.options = {
digits: 6,
step: 30,
window: 1
};Passo 3: Criar o MFA Manager
Aqui, criamos um gerenciador de 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 com o resultado final do login
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 de 2FA totalmente automatizado (solicitar → aguardar → obter → preencher)
- Gerenciamento unificado de múltiplos métodos de autenticação (TOTP/Email/Push)
- Elimina a lógica repetida de 2FA nos scripts
Passo 4: Iniciar o Provedor de Códigos TOTP (Trata a Escuta e o Preenchimento Automático dos Códigos)
Nesta etapa, iniciamos o listener de código TOTP automático, que:
- Escuta continuamente eventos
mfa_code_request - Gera automaticamente códigos TOTP (usando
otplib) - Envia automaticamente o código de volta via Signal (
mfa_code_response) - Substitui completamente a inserção manual dos códigos
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 inserção manual
- Não precisa de e-mail nem de 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
Passo 5: Acessar a Página de Login do GitHub e Inserir as Credenciais
Esta etapa trata da primeira parte do fluxo de login do GitHub:
Abrir a página de login → Inserir 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:
- O uso do Scrapeless Browser garante um ambiente de navegador realista e estável
- A digitação automatizada simula o comportamento real do usuário, evitando os alertas de segurança do GitHub
- Garante a navegação até a página correta de 2FA
Passo 6: Detectar a Página de 2FA e Enviar o TOTP Automaticamente
if (currentUrl.includes('/sessions/two-factor')) {
console.log('🔐 Detected 2FA required');
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 causados pelos códigos por e-mail
- A comunicação bidirecional do Signal CDP garante a entrega precisa do código
- Estabilidade e confiabilidade de nível empresarial
Passo 7: Enviar o Resultado do Login via Signal CDP
Após a conclusão do login do GitHub + 2FA, o resultado final do login é enviado por meio do CDP (Signal.send) para o seu fluxo de trabalho de automação. Isso permite que seu backend, bot ou sistema de CI/CD saibam 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: 2FA do GitHub (Modo Email OTP)
O tipo mais comum de 2FA em ambientes empresariais é a autenticação multifator (MFA) via Email OTP.
Aplicável a:
- GitHub com MFA habilitado e verificação por e-mail vinculada
- Políticas de segurança do GitHub que exigem verificação por Email
Vídeo

Passo 1: Conectar-se 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 consumir recursos locais
- O
sessionRecordingfacilita a reprodução e a auditoria - Suporta comunicação bidirecional em tempo real com o Signal
Passo 2: Abrir o GitHub e Inserir as 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, aumentando a estabilidade do script
- Suporta configurações de timeout longas para lidar com oscilações da rede
Passo 3: Detectar se a Página de 2FA por Email Foi Carregada
const currentUrl = page.url();
if (currentUrl.includes('/sessions/verified-device')) {
const client = await page.target().createCDPSession();
// Send Signal notification to email listener to prepare for OTP
await client.send('Signal.send', {
event: 'github_2fa_required',
data: JSON.stringify({ status: '2fa_required', timestamp: new Date().toISOString() })
});
// Wait for email listener to return 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 listener de e-mail para se preparar para o OTP
- Suporta a espera por OTP de e-mail em tempo real, aumentando a taxa de sucesso da automação
Passo 4: O Listener de E-mail Envia o OTP
Exemplo de Signal:
{
"event": "github_2fa_code",
"data": { "code": "123456" }
}Vantagens:
- Lê os e-mails automaticamente
- Extrai automaticamente o OTP do GitHub (código de 6 dígitos)
- Envia o Signal para o Scrapeless Browser em tempo real, sem operação manual
Passo 5: Inserir o 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, aumentando a eficiência da automação
- Garante que a página ainda está no estado de 2FA, evitando erros de navegação
- Simula ações de clique reais, reduzindo o risco de acionar verificações de segurança
Passo 6: Verificar o Resultado Final do Login e Enviar o 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 julgamentos incorretos, garantindo que a página tenha feito login com sucesso
- Navega automaticamente até a página inicial do GitHub para verificação, aumentando a confiabilidade
- O resultado do login pode ser reportado em tempo real para sistemas de CI/CD ou de bot
Passo 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 do 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 do usuário e da senha na página de login do GitHub e a lógica de login, aguardando a inserção do OTP na janela de verificação por Email.
Você receberá um taskId quando a tarefa de sessão do navegador for criada; lembre-se dele, pois será necessário 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 {
console.log("🚀 Starting GitHub login process...");
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('📱 Navigating to GitHub login page...');
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('🔑 Typing username and password...');
await page.type('#login_field', githubCredentials.username);
await page.type('#password', githubCredentials.password);
// Click the sign in button
console.log('🖱️ Clicking the sign in button...');
await page.click('input[type="submit"][value="Sign in"]');
// use setTimeout instead of waitForTimeout
console.log('⏳ Waiting for page response...');
await new Promise(resolve => setTimeout(resolve, 3000));
// Check whether an email verification (2FA) is required
const currentUrl = page.url();
console.log(`🔍 Current URL: ${currentUrl}`);
if (currentUrl.includes('/sessions/verified-device')) {
console.log('🔐 Detected email verification required, waiting for verification code...');
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('⏳ Waiting for the email verification code...');
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(`✅ Received email verification code: ${githubCredentials.twoFactorCode}, entering code...`);
// Ensure we are still on the verification page
if (!page.url().includes('/sessions/verified-device')) {
console.log('⚠️ The page has navigated away, verification may no longer be required');
return;
}
// Enter the verification code
console.log('⌨️ Entering the verification code...');
await page.$eval('#otp', (input, code) => {
input.value = '';
}, githubCredentials.twoFactorCode);
await page.type('#otp', githubCredentials.twoFactorCode);
console.log(`✅ Verification code ${githubCredentials.twoFactorCode} has been entered`);
// Click the verify button
console.log('🖱️ Clicking the verify button...');
try {
await page.evaluate(() => {
const button = document.querySelector('button[type="submit"]');
if (button) button.click();
});
console.log('✅ Verify button clicked, waiting for page response...');
await new Promise(resolve => setTimeout(resolve, 5000));
} catch (clickError) {
console.log('⚠️ Problem clicking the button:', clickError.message);
}
// Check login result
await new Promise(resolve => setTimeout(resolve, 3000));
const finalUrl = page.url();
console.log(`🔍 Final URL: ${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('🎉 GitHub login successful!');
try {
await page.goto('https://github.com/', {
waitUntil: 'networkidle2',
timeout: 10000
});
console.log('✅ Successfully accessed GitHub homepage');
} catch (profileError) {
console.log('⚠️ Problem accessing homepage:', profileError.message);
}
} else {
console.log('❌ Email verification failed, login unsuccessful');
console.log('🔍 Current page title:', await page.title());
}
} else {
console.log('❌ Timed out waiting for the email verification code');
}
} else if (currentUrl.includes('github.com') && !currentUrl.includes('/login')) {
// No email verification required
console.log('✅ Login successful (no email verification required)');
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 failed, still on the login page');
console.log('🔍 Current page title:', await page.title());
}
// Keep the session for a short time
console.log('⏳ Keeping connection for 5 seconds...');
await new Promise(resolve => setTimeout(resolve, 5000));
} catch (error) {
console.error('❌ GitHub login process failed:', 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('❌ Failed to send error signal as well:', signalError);
}
} finally {
if (browser) await browser.close();
console.log('🔚 GitHub login script finished');
}
}
// Run the script
githubLoginWith2FA().catch(console.error);
- Quando o script acima chegar à página que aguarda o código de verificação, execute imediatamente este script de listener de e-mail, que enviará o código mais recente ao 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('Starting email listener...');
this.sessionId = sessionId;
try {
await this.connectIMAP();
const code = await this.listenForCode();
if (code) {
console.log(`Found code: ${code}`);
await this.sendSignal('github_2fa_code', {code});
console.log('Code sent to browser');
} else {
console.log('No code found (timeout)');
await this.sendSignal('email_listener_timeout', {status: 'timeout'});
}
} catch (error) {
console.error('Listener error:', 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 connected');
resolve();
});
this.imap.once('error', reject);
this.imap.connect();
});
}
async listenForCode() {
console.log('Listening for GitHub verification code...');
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 failed:', 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('Failed to parse mail:', error);
}
processed++;
if (processed === results.length) resolve(foundCode);
});
});
fetcher.once('error', reject);
}
extractCode(text) {
const patterns = [
/verification code:?\s*(\d{6})/i,
/verification code:?\s*(\d{6})/i,
/code:?\s*(\d{6})/i,
/GitHub verification code:?\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('Session ID not available');
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('Signal sent successfully:', result);
return result;
} catch (err) {
console.error('Failed to send signal via HTTP:', err);
throw err;
}
}
async cleanup() {
this.isListening = false;
if (this.imap) {
try {
this.imap.end();
console.log('IMAP connection closed');
} catch (e) {
console.error('Error closing IMAP:', e);
}
}
this.sessionId = null;
}
}
const listener = new EmailListener();
listener.start({taskId}).then(); // Replace with taskId from the first step
Através dos dois exemplos de login do GitHub acima, demonstramos como alcançar logins automatizados eficientes e estáveis em ambientes empresariais, cobrindo os dois modos comuns de 2FA: TOTP e códigos de verificação por Email. Usando Scrapeless Browser + Signal CDP, você pode realizar operações reais no navegador, simular com precisão o comportamento do usuário e interagir em tempo real com sistemas de MFA e listeners de e-mail para obter e enviar automaticamente os códigos de verificação. Seja para desenvolver fluxos de trabalho de login automatizados, integrar com sistemas de CI/CD ou gerenciar contas empresariais internas, esta solução pode aumentar significativamente as taxas de sucesso de login, reduzir a intervenção manual e fornecer auditoria e monitoramento operacional completos.