Logo 2FA Authenticator

Quét QR Code

Quét QR code hiện trên trang web

hoặc
hoặc tải ảnh
Chưa chọn file
hoặc nhập URL
Đã tìm thấy
-
-

Thêm tài khoản mới

Trang 1 / 1

Chưa có tài khoản nào

Đăng nhập

⚙️ Cài đặt Sync

Để trống để dùng API mặc định của ứng dụng.

Đây là mật khẩu bạn tự tạo và phải nhớ. App không lưu mật khẩu này, chỉ dùng để mã hóa dữ liệu local và cloud.

Xuất và khôi phục file JSON gồm cấu hình app, danh sách tài khoản và tombstone xóa. Master Password không được lưu vào file.

Nhập Master Password

Nhập mật khẩu đã tạo để mở dữ liệu local hoặc tải dữ liệu từ Cloud.

Hướng dẫn tạo Cloud Sheet

Dữ liệu lưu trên Google Sheet là chuỗi đã mã hóa. Không sửa trực tiếp cột data trong Sheet.

  1. Mở Google Sheets và tạo một file mới.
  2. Vào Extensions → Apps Script.
  3. Xóa code mẫu, dán đoạn script bên dưới rồi bấm Save.
  4. Bấm Deploy → New deployment → Web app.
  5. Chọn Execute as: Me. Chọn Who has access: Anyone with the link.
  6. Copy Web app URL và dán vào ô Google Apps Script URL trong app này.
  7. Tự tạo một Master Password mạnh, nhập vào ô Tạo Master Password rồi bấm Lưu.
  8. Khi Sync Cloud, nhập lại đúng Master Password đó để tải lên hoặc tải xuống dữ liệu.
Apps Script
const CLOUD_SHEET = 'Cloud';
const BACKUP_SHEET = 'Backups';
const MAX_BACKUPS = 50;
const MIN_DATA_LENGTH = 80;

function getSheet(name, header) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  let sheet = ss.getSheetByName(name);
  if (!sheet) sheet = ss.insertSheet(name);
  if (sheet.getLastRow() === 0) sheet.appendRow(header);
  return sheet;
}

function getCloudSheet() {
  return getSheet(CLOUD_SHEET, ['key', 'data', 'timestamp']);
}

function getBackupSheet() {
  return getSheet(BACKUP_SHEET, ['backup_at', 'data', 'timestamp']);
}

function jsonResponse(payload) {
  return ContentService
    .createTextOutput(JSON.stringify(payload))
    .setMimeType(ContentService.MimeType.JSON);
}

function backupCurrentCloud() {
  const cloud = getCloudSheet();
  if (cloud.getLastRow() < 2) return;

  const row = cloud.getRange(2, 1, 1, 3).getValues()[0];
  const currentData = row[1] || '';
  if (!currentData) return;

  const backups = getBackupSheet();
  backups.insertRowBefore(2);
  backups.getRange(2, 1, 1, 3).setValues([[
    new Date(),
    currentData,
    row[2] || Date.now()
  ]]);

  const extraRows = backups.getLastRow() - 1 - MAX_BACKUPS;
  if (extraRows > 0) {
    backups.deleteRows(MAX_BACKUPS + 2, extraRows);
  }
}

function doGet() {
  const sheet = getCloudSheet();
  const data = sheet.getRange(2, 2).getValue() || '';
  const timestamp = Number(sheet.getRange(2, 3).getValue() || 0);
  return jsonResponse({ success: true, data, timestamp });
}

function doPost(e) {
  try {
    const body = JSON.parse((e.postData && e.postData.contents) || '{}');
    if (!body.data) throw new Error('Missing data');
    if (String(body.data).length < MIN_DATA_LENGTH) {
      throw new Error('Refuse short payload to avoid overwriting backup with empty data');
    }

    const sheet = getCloudSheet();
    if (sheet.getLastRow() < 2) sheet.appendRow(['cloud', '', '']);

    backupCurrentCloud();

    sheet.getRange(2, 1, 1, 3).setValues([[
      'cloud',
      body.data,
      body.timestamp || Date.now()
    ]]);

    return jsonResponse({ success: true });
  } catch (error) {
    return jsonResponse({ success: false, error: error.message });
  }
}

☁️ Sync Cloud

Xác nhận

Bạn có chắc chắn muốn thực hiện hành động này?