Tài liệuTrình duyệt và CrawlAgent BrowserTự động hóa MFA trên GitHub

Tự động hóa GitHub MFA

Giới thiệu

Thách thức lớn nhất khi tự động hóa quá trình đăng nhập GitHub chính là Xác thực hai yếu tố (2FA). Dù là ứng dụng Authenticator (TOTP) hay Email OTP, các luồng tự động hóa truyền thống thường bị kẹt ở bước này vì:

  • Không thể tự động lấy mã xác thực
  • Không thể đồng bộ mã theo thời gian thực
  • Không thể tự động nhập mã thông qua tự động hóa
  • Môi trường trình duyệt chưa đủ chân thực, kích hoạt các kiểm tra bảo mật của GitHub

Bài viết này minh họa cách xây dựng một quy trình 2FA của GitHub hoàn toàn tự động bằng Scrapeless Browser + Signal CDP, bao gồm:

  • Trường hợp 1: GitHub 2FA (tự động tạo Authenticator / TOTP)
  • Trường hợp 2: GitHub 2FA (tự động lắng nghe Email OTP)

Chúng tôi sẽ giải thích toàn bộ quy trình cho từng trường hợp và trình bày cách phối hợp script đăng nhập với trình lắng nghe mã xác thực trong một hệ thống tự động.


Trường hợp 1: GitHub 2FA (Chế độ Authenticator OTP)

Cơ chế TOTP (Time-based One-Time Password) của GitHub rất phù hợp cho các kịch bản tự động hóa. Sử dụng Scrapeless Browser + Signal CDP, bạn có thể để trình duyệt tự động:

  • Kích hoạt một sự kiện khi đến trang 2FA
  • Tạo mã OTP
  • Tự động điền mã
  • Hoàn tất đăng nhập

So với Email/SMS OTP truyền thống, TOTP mang lại:

  • Mã được tạo cục bộ, không phụ thuộc vào bên ngoài
  • Tạo mã nhanh và ổn định
  • Không cần API bổ sung
  • Hoàn toàn có thể tự động hóa mà không cần can thiệp thủ công

Các kịch bản áp dụng:

  • Tài khoản GitHub sử dụng Google Authenticator / Authy / 1Password
  • Đường dẫn trang 2FA: /sessions/two-factor/app hoặc /sessions/two-factor/webauthn

Video

Trường hợp 1: GitHub 2FA (Chế độ Authenticator OTP)

Bước 1: Kết nối tới Scrapeless Browser

Ở bước này, chúng ta thiết lập một kết nối WebSocket song công (full-duplex) tới 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");

Ưu điểm:

  • Chrome thực trên cloud với khả năng chống phát hiện mạnh mẽ
  • Không tiêu tốn tài nguyên cục bộ
  • Tự động proxy, lưu trữ bền vững và ghi lại phiên
  • Thực thi đáng tin cậy các quy trình đăng nhập tự động quy mô lớn

Bước 2: Khởi tạo MFA Manager + TOTP Provider

MFA được xử lý thông qua giao tiếp hai chiều của Signal CDP, giúp:

  • Tự động gửi mfa_code_request khi phát hiện một trang 2FA
  • Tạo mã TOTP
  • Trả về mã qua mfa_code_response
  • Gửi các sự kiện kết quả đăng nhập
import { authenticator } from 'otplib';
 
const TOTP_SECRETS = {
    'github': 'secret-code',
    'default': 'secret-code'
};
 
authenticator.options = {
    digits: 6,
    step: 30,
    window: 1
};

Bước 3: Tạo MFA Manager

Ở đây, chúng ta tạo một trình quản lý MFA tập trung để xử lý giao tiếp với Signal CDP:

  • Kích hoạt mfa_code_request
  • Chờ và nhận mã xác thực (mfa_code_response)
  • Trả mã về cho quá trình đăng nhập GitHub
  • Gửi các sự kiện kết quả đăng nhập cuối cùng
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)
        });
    }
}

Ưu điểm:

  • Quy trình 2FA hoàn toàn tự động (yêu cầu → chờ → lấy → điền)
  • Quản lý thống nhất nhiều phương thức xác thực (TOTP/Email/Push)
  • Loại bỏ logic 2FA lặp lại trong các script

Bước 4: Khởi động TOTP Code Provider (Xử lý lắng nghe & tự động điền mã)

Ở bước này, chúng ta khởi chạy trình lắng nghe tự động tạo mã TOTP, giúp:

  • Liên tục lắng nghe các sự kiện mfa_code_request
  • Tự động tạo mã TOTP (bằng otplib)
  • Tự động gửi mã trở lại qua Signal (mfa_code_response)
  • Thay thế hoàn toàn việc nhập mã thủ công
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);
    }
}

Ưu điểm:

  • Tự động hóa hoàn toàn 2FA mà không cần nhập thủ công
  • Không cần email hay ứng dụng di động
  • Việc tạo TOTP chính xác, ổn định và có thể mở rộng
  • Hỗ trợ GitHub, AWS, Azure, Google và bất kỳ nhà cung cấp nào dựa trên TOTP

Bước 5: Truy cập trang đăng nhập GitHub và nhập thông tin đăng nhập

Bước này xử lý phần đầu tiên của luồng đăng nhập GitHub:

Mở trang đăng nhập → Nhập tên người dùng & mật khẩu → Tiến tới bước tiếp theo (2FA hoặc đăng nhập trực tiếp)

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"]');

Ưu điểm:

  • Sử dụng Scrapeless Browser đảm bảo môi trường trình duyệt chân thực, ổn định
  • Việc gõ phím tự động mô phỏng hành vi người dùng thực, tránh bị GitHub gắn cờ bảo mật
  • Đảm bảo điều hướng đến đúng trang 2FA

Bước 6: Phát hiện trang 2FA và tự động gửi TOTP

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));
}

Ưu điểm:

  • Hoàn tất 2FA của GitHub mà không cần bất kỳ can thiệp thủ công nào
  • TOTP được tạo tự động, tránh độ trễ từ mã email
  • Giao tiếp hai chiều của Signal CDP đảm bảo phân phối mã chính xác
  • Độ ổn định và tin cậy ở mức doanh nghiệp

Bước 7: Gửi kết quả đăng nhập qua Signal CDP

Sau khi hoàn tất đăng nhập GitHub + 2FA, kết quả đăng nhập cuối cùng được gửi qua CDP (Signal.send) đến quy trình tự động hóa của bạn. Điều này cho phép backend, bot hoặc hệ thống CI/CD của bạn biết theo thời gian thực liệu việc đăng nhập có thành công hay không.

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

Mã hoàn chỉnh

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

Trường hợp 2: GitHub 2FA (Chế độ Email OTP)

Loại 2FA phổ biến nhất trong môi trường doanh nghiệp là xác thực đa yếu tố (MFA) qua Email OTP.

Áp dụng cho:

  • GitHub đã bật MFA và liên kết xác thực qua email
  • Chính sách bảo mật của GitHub yêu cầu xác thực qua Email

Video

Trường hợp 2: GitHub 2FA (Chế độ Email OTP)

Bước 1: Kết nối tới 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;

Ưu điểm:

  • Thực thi trình duyệt từ xa, không tiêu tốn tài nguyên cục bộ
  • sessionRecording tạo điều kiện cho việc phát lại và kiểm toán
  • Hỗ trợ giao tiếp hai chiều theo thời gian thực với Signal

Bước 2: Mở GitHub và nhập thông tin đăng nhập

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 });

Ưu điểm:

  • Mô phỏng chính xác thao tác nhập liệu của người dùng thực để tránh kích hoạt bảo mật của GitHub
  • Tự động chờ trang được kết xuất, cải thiện độ ổn định của script
  • Hỗ trợ thiết lập thời gian chờ dài để xử lý các biến động mạng

Bước 3: Phát hiện xem trang Email 2FA đã được tải hay chưa

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
    });

Ưu điểm:

  • Nhận diện chính xác trang /sessions/verified-device
  • Chủ động thông báo cho trình lắng nghe email chuẩn bị nhận OTP
  • Hỗ trợ chờ OTP email theo thời gian thực, cải thiện tỷ lệ thành công của tự động hóa

Bước 4: Trình lắng nghe email gửi OTP

Ví dụ Signal:

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

Ưu điểm:

  • Tự động đọc email
  • Tự động trích xuất GitHub OTP (mã 6 chữ số)
  • Gửi Signal tới Scrapeless Browser theo thời gian thực, không cần thao tác thủ công

Bước 5: Nhập OTP và gửi đi

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));
}

Ưu điểm:

  • Tự động điền OTP, cải thiện hiệu quả tự động hóa
  • Đảm bảo trang vẫn đang ở trạng thái 2FA, ngăn ngừa lỗi điều hướng
  • Mô phỏng thao tác nhấp chuột thực, giảm nguy cơ kích hoạt các kiểm tra bảo mật

Bước 6: Kiểm tra kết quả đăng nhập cuối cùng và gửi 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' });

Ưu điểm:

  • Tránh nhận định sai, đảm bảo trang đã đăng nhập thành công
  • Tự động điều hướng tới trang chủ GitHub để xác minh, cải thiện độ tin cậy
  • Kết quả đăng nhập có thể được báo cáo theo thời gian thực tới hệ thống CI/CD hoặc bot

Bước 7: Duy trì phiên và đóng trình duyệt

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

Ưu điểm:

  • Đảm bảo tất cả các sự kiện Signal đã được gửi
  • Giữ phiên hoạt động đủ lâu cho các thao tác tiếp theo
  • Đóng trình duyệt để ngăn rò rỉ tài nguyên

Mã hoàn chỉnh

  1. Trước tiên, bạn cần dùng script này để thực hiện việc điền tên người dùng và mật khẩu ở trang đăng nhập GitHub cùng logic đăng nhập, và chờ nhập OTP trên cửa sổ xác thực Email.

Bạn sẽ nhận được một taskId khi tác vụ phiên trình duyệt được tạo, hãy ghi nhớ nó vì bạn sẽ cần đến nó ở bước tiếp theo.

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);
 
  1. Khi script trên đến trang chờ mã xác thực, hãy chạy ngay script lắng nghe email này, nó sẽ gửi mã mới nhất tới script chính để hoàn tất việc xác thực.
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
 

Qua hai ví dụ đăng nhập GitHub ở trên, chúng tôi đã minh họa cách đạt được các luồng đăng nhập tự động hiệu quả và ổn định trong môi trường doanh nghiệp, bao gồm hai chế độ 2FA phổ biến: TOTP và mã xác thực qua Email. Bằng cách sử dụng Scrapeless Browser + Signal CDP, bạn có thể thực hiện các thao tác trình duyệt thực, mô phỏng chính xác hành vi người dùng và tương tác theo thời gian thực với các hệ thống MFA cùng trình lắng nghe email để tự động lấy và gửi mã xác thực. Dù là để phát triển các quy trình đăng nhập tự động, tích hợp với hệ thống CI/CD, hay quản lý các tài khoản nội bộ của doanh nghiệp, giải pháp này có thể tăng đáng kể tỷ lệ đăng nhập thành công, giảm sự can thiệp thủ công, và cung cấp khả năng kiểm toán cùng giám sát vận hành đầy đủ.