diff --git a/.gitattributes b/.gitattributes index 2e3916624..ab68c6d84 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,5 @@ *.js linguist-vendored css/font-awesome.css linguist-vendored +js/jsqrcode/index.d.ts linguist-vendored +_locales/* linguist-generated=true +_locales/en/messages.json linguist-generated=false diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index dd6efb364..033860369 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,3 +1,4 @@ + diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 5a845760c..9525d876a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,10 +6,11 @@ about: Report a bug in Authenticator + **Describe the bug:** **Platform:** - - Browser [e.g. chrome, safari] - - Version [e.g. 22] + - Browser: [Chrome, Firefox, Edge] + - Browser Version: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 5134fe5a2..91a81d4d8 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -3,6 +3,7 @@ name: Feature request about: Suggest an idea for this project --- + **Describe the feature you want:** diff --git a/.gitignore b/.gitignore index 06abdbcfb..66c00d5eb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ firefox edge .vscode .atom-build.yml -ci/authenticator-build-key.enc +scripts/authenticator-build-key.enc +cert.pfx \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index d18a6b5ef..b07ccbbee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,9 +16,9 @@ jobs: script: "npm run chrome" - stage: test # new i18n strings - script: bash ci/i18n.sh + script: bash scripts/i18n.sh if: branch = dev AND type != pull_request - stage: deploy # release tagging - script: bash ci/tag.sh + script: bash scripts/tag.sh if: branch = release AND type != pull_request diff --git a/README.md b/README.md index 18c04a358..9bb9a25c0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ - - -# Authenticator [![Build Status](https://travis-ci.org/Authenticator-Extension/Authenticator.svg?branch=dev)](https://travis-ci.org/Authenticator-Extension/Authenticator) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/authenticator-firefox/localized.svg)](https://crowdin.com/project/authenticator-firefox) +# Authenticator [![Build Status](https://travis-ci.org/Authenticator-Extension/Authenticator.svg?branch=dev)](https://travis-ci.org/Authenticator-Extension/Authenticator) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/authenticator-firefox/localized.svg)](https://crowdin.com/project/authenticator-firefox) > Authenticator generates 2-Step Verification codes in your browser. @@ -8,8 +6,6 @@ [](https://chrome.google.com/webstore/detail/authenticator/bhghoamapcdpbohphigoooaddinpkbai) [](https://addons.mozilla.org/en-US/firefox/addon/auth-helper?src=external-github) [](https://www.microsoft.com/store/apps/9P0FD39WFFMK?ocid=badge) -Authenticator is one of the [featured extensions on Firefox Add-ons this month](https://blog.mozilla.org/addons/2018/07/02/julys-featured-extensions-2/)! - ## Build Setup ``` bash @@ -23,4 +19,4 @@ gts fix npm run [chrome, firefox, edge] ``` -Note that Windows users should download [Cygwin](http://cygwin.com/) to build. Building for Edge requires the [Windows App Certification Kit](https://developer.microsoft.com/en-us/windows/develop/app-certification-kit) +Note that Windows users should download a tool like [Git Bash](https://git-scm.com/download/win) or [Cygwin](http://cygwin.com/) to build. Building for Edge requires the [Windows App Certification Kit](https://developer.microsoft.com/en-us/windows/develop/app-certification-kit) diff --git a/_locales/cs/messages.json b/_locales/cs/messages.json index 1443f7b87..583f13139 100644 --- a/_locales/cs/messages.json +++ b/_locales/cs/messages.json @@ -203,29 +203,13 @@ "message": "Importovat textovou zálohu", "description": "Import backup code." }, - "dropbox_backup": { - "message": "Automatické zálohování na Dropbox", - "description": "Auto backup to Dropbox." - }, - "dropbox_code": { - "message": "Dropbox kód", - "description": "Dropbox code." - }, - "dropbox_token": { - "message": "Dropbox Token", - "description": "Dropbox token." - }, - "dropbox_authorization": { - "message": "Přihlaste se k Dropboxu", - "description": "Dropbox authorization." - }, "show_all_entries": { "message": "Ukaž všechny záznamy", "description": "Show all entries." }, "dropbox_risk": { - "message": "Upozornění: zálohy uložené v Dropboxu jsou nezašifrované. Používáte na vlastní riziko.", - "description": "Dropbox backup risk warning." + "message": "Warning: backups are unencrypted. Use at your own risk.", + "description": "Backup risk warning." }, "import_error_password": { "message": "Je nutné zadat správné heslo pro import zálohy.", @@ -235,10 +219,6 @@ "message": "Vaše heslo je lokálně uloženo, prosím změňte to ihned v menu zabezpečení.", "description": "localStorage password warning." }, - "dropbox_logout": { - "message": "Odhlášení ze služby Dropbox", - "description": "Dropbox logout." - }, "remove": { "message": "Odstranit", "description": "Remove password." @@ -259,20 +239,40 @@ "message": "Zámek", "description": "Lock accounts" }, - "dropbox_tooltip": { - "message": "Dropbox synchronizace je zapnuta", - "description": "Dropbox sync enabled" - }, "edit": { "message": "Upravit", "description": "Edit" }, "manual_dropbox": { "message": "Manuální synchronizace", - "description": "Manual Dropbox sync" + "description": "Manual sync" }, "use_autofill": { "message": "Použití automatického vyplňování", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Logout", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 2a56ed656..ca5e18c98 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -203,29 +203,13 @@ "message": "Text-Sicherungsdatei importieren", "description": "Import backup code." }, - "dropbox_backup": { - "message": "Automatische Sicherung in Dropbox", - "description": "Auto backup to Dropbox." - }, - "dropbox_code": { - "message": "Dropbox-Code", - "description": "Dropbox code." - }, - "dropbox_token": { - "message": "Dropbox-Token", - "description": "Dropbox token." - }, - "dropbox_authorization": { - "message": "Einloggen in Dropbox", - "description": "Dropbox authorization." - }, "show_all_entries": { "message": "Alle Einträge anzeigen", "description": "Show all entries." }, "dropbox_risk": { - "message": "Vorsicht: Sicherungen, die in Dropbox gespeichert werden, sind nicht verschlüsselt. Benutzung auf eigene Gefahr.", - "description": "Dropbox backup risk warning." + "message": "Warning: backups are unencrypted. Use at your own risk.", + "description": "Backup risk warning." }, "import_error_password": { "message": "Sie müssen das richtige Passwort angeben um Sicherungsdateien zu importieren.", @@ -235,10 +219,6 @@ "message": "Ihr Passwort wird lokal gespeichert, bitte ändern Sie es sofort im Sicherheitsmenü.", "description": "localStorage password warning." }, - "dropbox_logout": { - "message": "Aus Dropbox ausloggen", - "description": "Dropbox logout." - }, "remove": { "message": "Löschen", "description": "Remove password." @@ -259,20 +239,40 @@ "message": "Sperren", "description": "Lock accounts" }, - "dropbox_tooltip": { - "message": "Dropbox-Synchronisation aktiviert", - "description": "Dropbox sync enabled" - }, "edit": { "message": "Ändern", "description": "Edit" }, "manual_dropbox": { "message": "Manuelle Synchronisation", - "description": "Manual Dropbox sync" + "description": "Manual sync" }, "use_autofill": { "message": "Use Autofill", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Speicher & Sicherung", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Wählen Sie, wo ihre Daten gespeichert werden sollen. Mit \"local\" werden ihre Daten auf ihrem PC gespeichert. Mit \"sync\" synchonisiert ihr Browser ihre Daten mit der Cloud, sofern Sie sich mit einem Sync-Account angemeldet haben.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatische Sicherung auf Drittanbieter-Speicher.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Speicherort", + "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Logout", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/el/messages.json b/_locales/el/messages.json new file mode 100644 index 000000000..e67cb7e7f --- /dev/null +++ b/_locales/el/messages.json @@ -0,0 +1,278 @@ +{ + "extName": { + "message": "Authenticator", + "description": "Extension Name." + }, + "extShortName": { + "message": "Authenticator", + "description": "Extension Short Name." + }, + "extDesc": { + "message": "Ο Επαληθευτής δημιουργεί κωδικούς επαλήθευσης σε 2 βήματα στο πρόγραμμα περιήγησης.", + "description": "Extension Description." + }, + "added": { + "message": "έχει προστεθεί.", + "description": "Added Account." + }, + "errorqr": { + "message": "Μη αναγνωρισμένος κώδικας QR.", + "description": "QR Error." + }, + "errorsecret": { + "message": "Μυστικό σφάλμα. Υποστηρίζονται μόνο τα Base32 (A-Z, 2-7 και =) και HEX (0-9 και A-F). Ωστόσο, το μυστικό σας είναι:", + "description": "Secret Error." + }, + "add_qr": { + "message": "Σάρωση QR κωδικού", + "description": "Scan QR Code." + }, + "add_secret": { + "message": "Χειροκίνητη εισαγωγή", + "description": "Manual Entry." + }, + "close": { + "message": "Κλείσιμο", + "description": "Close." + }, + "ok": { + "message": "Εντάξει", + "description": "OK." + }, + "yes": { + "message": "Ναι", + "description": "Yes." + }, + "no": { + "message": "Όχι", + "description": "No." + }, + "account": { + "message": "Λογαριασμός", + "description": "Account." + }, + "accountName": { + "message": "Όνομα Λογαριασμού", + "description": "Account Name." + }, + "issuer": { + "message": "Εκδότης", + "description": "Issuer." + }, + "secret": { + "message": "Μυστικό", + "description": "Secret." + }, + "updateSuccess": { + "message": "Επιτυχία", + "description": "Update Success." + }, + "updateFailure": { + "message": "Αποτυχία", + "description": "Update Failure." + }, + "about": { + "message": "Σχετικά", + "description": "About." + }, + "export_import": { + "message": "Εξαγωγή\/εισαγωγή", + "description": "Export and Import." + }, + "settings": { + "message": "Ρυθμίσεις", + "description": "Settings." + }, + "security": { + "message": "Ασφάλεια", + "description": "Security." + }, + "current_phrase": { + "message": "Τρέχον Κωδικός", + "description": "Current Passphrase." + }, + "new_phrase": { + "message": "Νέος κωδικός πρόσβασης", + "description": "New Passphrase." + }, + "phrase": { + "message": "Κωδικός πρόσβασης", + "description": "Passphrase." + }, + "confirm_phrase": { + "message": "Επιβεβαίωση Κωδικού Πρόσβασης", + "description": "Confirm Passphrase." + }, + "confirm_delete": { + "message": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το αρχείο; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", + "description": "Remove entry confirmation" + }, + "security_warning": { + "message": "Αυτός ο κωδικός πρόσβασης θα χρησιμοποιηθεί για να κρυπτογραφήσετε τα μυστικά σας. Κανείς δεν μπορεί να σας βοηθήσει εάν ξεχάσετε τον κωδικό πρόσβασης.", + "description": "Passphrase Warning." + }, + "update": { + "message": "Ενημέρωση", + "description": "Update." + }, + "phrase_incorrect": { + "message": "Δεν μπορείτε να προσθέσετε νέο λογαριασμό ή να εξαγάγετε δεδομένα μέχρι να αποκρυπτογραφηθούν όλοι οι λογαριασμοί. Εισαγάγετε τον σωστό κωδικό πρόσβασης πριν συνεχίσετε.", + "description": "Passphrase Incorrect." + }, + "phrase_not_match": { + "message": "Το Συνθηματικό δεν ταιριάζει", + "description": "Passphrase Not Match." + }, + "encrypted": { + "message": "Κρυπτογραφημένο", + "description": "Encrypted." + }, + "copied": { + "message": "Αντιγράφηκε", + "description": "Copied." + }, + "feedback": { + "message": "Αξιολόγηση", + "description": "Feedback." + }, + "translate": { + "message": "Μετάφραση", + "description": "Translate." + }, + "source": { + "message": "Πηγαίος Κώδικας", + "description": "Source Code." + }, + "passphrase_info": { + "message": "Εισάγετε κωδικό πρόσβασης για να αποκρυπτογραφήσει τα δεδομένα του λογαριασμού.", + "description": "Passphrase Info" + }, + "sync_clock": { + "message": "Συγχρονισμός ρολογιού με την Google", + "description": "Sync Clock" + }, + "remember_phrase": { + "message": "Απομνημόνευση κωδικού πρόσβασης", + "description": "Remember Passphrase" + }, + "clock_too_far_off": { + "message": "ΠΡΟΣΟΧΗ! Τοπικό ρολόι σας είναι πάρα πολύ μακριά, παρακαλώ διορθώστε το πριν να συνεχίσετε.", + "description": "Local Time is Too Far Off" + }, + "remind_backup": { + "message": "Έχετε ένα αντίγραφο ασφαλείας για τα μυστικά σας; Μην περιμένετε έως ότου είναι πολύ αργά!", + "description": "Remind Backup" + }, + "capture_failed": { + "message": "Η καταγραφή απέτυχε, επαναλάβετε τη φόρτωση της σελίδας και προσπαθήστε ξανά.", + "description": "Capture Failed" + }, + "based_on_time": { + "message": "Βάσει χρόνου", + "description": "Time Based" + }, + "based_on_counter": { + "message": "Με μετρητή", + "description": "Counter Based" + }, + "resize_popup_page": { + "message": "Αναδυόμενο σελίδα", + "description": "Popup Page Settings" + }, + "scale": { + "message": "Κλίμακα", + "description": "Scale" + }, + "export_info": { + "message": "Προειδοποίηση: όλα τα αντίγραφα ασφαλείας είναι κρυπτογραφημένη. Θέλετε να προσθέσετε ένα λογαριασμό σε μια άλλη εφαρμογή; Καταδείξτε επάνω δεξιά μέρος οποιουδήποτε λογαριασμού και πάτα το κουμπί κρυφό.", + "description": "Export menu info text" + }, + "download_backup": { + "message": "Λήψη αντιγράφου ασφαλείας", + "description": "Download backup file." + }, + "import_backup": { + "message": "Εισαγωγή αντιγράφου ασφαλείας", + "description": "Import backup." + }, + "import_backup_file": { + "message": "Εισαγωγή αντιγράφου ασφαλείας", + "description": "Import backup file." + }, + "import_backup_code": { + "message": "Εισαγωγή αντιγράφων κειμένου", + "description": "Import backup code." + }, + "show_all_entries": { + "message": "Δείτε όλες τις καταχωρήσεις", + "description": "Show all entries." + }, + "dropbox_risk": { + "message": "Warning: backups are unencrypted. Use at your own risk.", + "description": "Backup risk warning." + }, + "import_error_password": { + "message": "Πρέπει να παρέχετε το σωστό κωδικό πρόσβασης για να εισαγάγετε τα αντίγραφα ασφαλείας.", + "description": "Error password warning when import backups." + }, + "local_passphrase_warning": { + "message": "Τον κωδικό πρόσβασής σας είναι αποθηκευμένη τοπικά, παρακαλούμε αμέσως να το αλλάξετε από το μενού ασφάλεια.", + "description": "localStorage password warning." + }, + "remove": { + "message": "Αφαίρεση", + "description": "Remove password." + }, + "download_enc_backup": { + "message": "Λήψη αντιγράφων ασφαλείας που προστατεύεται με κωδικό πρόσβασης", + "description": "Download Encrypted Backup" + }, + "search": { + "message": "Αναζήτηση", + "description": "Search" + }, + "popout": { + "message": "Αναδυόμενο παράθυρο λειτουργίας", + "description": "Make window turn into persistent popup" + }, + "lock": { + "message": "Κλείδωμα", + "description": "Lock accounts" + }, + "edit": { + "message": "Επεξεργασία", + "description": "Edit" + }, + "manual_dropbox": { + "message": "Χειροκίνητος συγχρονισμός", + "description": "Manual sync" + }, + "use_autofill": { + "message": "Χρήση της αυτόματης συμπλήρωσης", + "description": "Use Autofill" + }, + "storage_menu": { + "message": "Αποθήκευση & δημιουργία αντιγράφων ασφαλείας", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Επιλέξτε όπου είναι αποθηκευμένα τα δεδομένα σας. Χρησιμοποιώντας «τοπικό» αποθηκεύει τα δεδομένα σας στον υπολογιστή σας. Χρησιμοποιώντας «συγχρονισμού» σας επιτρέπει να συγχρονίσετε τα δεδομένα σας στο cloud, αν είστε συνδεδεμένοι σε ένα λογαριασμό συγχρονισμού το πρόγραμμα περιήγησής σας.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Δημιουργήστε αυτόματα αντίγραφα ασφαλείας των δεδομένων σας σε υπηρεσίες αποθήκευσης τρίτων.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Θέση αποθήκευσης", + "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Logout", + "description": "Sign out of 3rd party storage services" + } +} \ No newline at end of file diff --git a/_locales/en/messages.json b/_locales/en/messages.json index b0508bb4f..3671bd902 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -203,29 +203,13 @@ "message": "Import Text Backup", "description": "Import backup code." }, - "dropbox_backup": { - "message": "Auto Backup to Dropbox", - "description": "Auto backup to Dropbox." - }, - "dropbox_code": { - "message": "Dropbox Code", - "description": "Dropbox code." - }, - "dropbox_token": { - "message": "Dropbox Token", - "description": "Dropbox token." - }, - "dropbox_authorization": { - "message": "Sign in to Dropbox", - "description": "Dropbox authorization." - }, "show_all_entries": { "message": "Show all entries", "description": "Show all entries." }, "dropbox_risk": { - "message": "Warning: backups saved in Dropbox are unencrypted. Use at your own risk.", - "description": "Dropbox backup risk warning." + "message": "Warning: backups are unencrypted. Use at your own risk.", + "description": "Backup risk warning." }, "import_error_password": { "message": "You must provide correct password to import backups.", @@ -235,10 +219,6 @@ "message": "Your password is stored locally, please change it in the security menu immediately.", "description": "localStorage password warning." }, - "dropbox_logout": { - "message": "Logout of Dropbox", - "description": "Dropbox logout." - }, "remove": { "message": "Remove", "description": "Remove password." @@ -259,20 +239,40 @@ "message": "Lock", "description": "Lock accounts" }, - "dropbox_tooltip": { - "message": "Dropbox sync enabled", - "description": "Dropbox sync enabled" - }, "edit": { "message": "Edit", "description": "Edit" }, "manual_dropbox": { "message": "Manual Sync", - "description": "Manual Dropbox sync" + "description": "Manual sync" }, "use_autofill": { "message": "Use Autofill", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Logout", + "description": "Sign out of 3rd party storage services" } } diff --git a/_locales/es/messages.json b/_locales/es/messages.json index 45e91247b..bd4996216 100644 --- a/_locales/es/messages.json +++ b/_locales/es/messages.json @@ -203,29 +203,13 @@ "message": "Copia de seguridad de importación", "description": "Import backup code." }, - "dropbox_backup": { - "message": "Copia de seguridad automática a Dropbox", - "description": "Auto backup to Dropbox." - }, - "dropbox_code": { - "message": "Código de Dropbox", - "description": "Dropbox code." - }, - "dropbox_token": { - "message": "Token de Dropbox", - "description": "Dropbox token." - }, - "dropbox_authorization": { - "message": "Sign in to Dropbox", - "description": "Dropbox authorization." - }, "show_all_entries": { "message": "Mostrar todas las entradas", "description": "Show all entries." }, "dropbox_risk": { - "message": "ADVERTENCIA: backups guardados en Dropbox están sin cifrar. Usar bajo su propio riesgo.", - "description": "Dropbox backup risk warning." + "message": "Warning: backups are unencrypted. Use at your own risk.", + "description": "Backup risk warning." }, "import_error_password": { "message": "Debe proporcionar la contraseña correcta para la importación de copias de seguridad.", @@ -235,10 +219,6 @@ "message": "La contraseña se almacena localmente, por favor cambiarlo en el menú de seguridad inmediatamente.", "description": "localStorage password warning." }, - "dropbox_logout": { - "message": "Logout of Dropbox", - "description": "Dropbox logout." - }, "remove": { "message": "Remove", "description": "Remove password." @@ -259,20 +239,40 @@ "message": "Lock", "description": "Lock accounts" }, - "dropbox_tooltip": { - "message": "Dropbox sync enabled", - "description": "Dropbox sync enabled" - }, "edit": { "message": "Edit", "description": "Edit" }, "manual_dropbox": { "message": "Manual Sync", - "description": "Manual Dropbox sync" + "description": "Manual sync" }, "use_autofill": { "message": "Use Autofill", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Logout", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index f6ab6fa9c..ad738207e 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -203,29 +203,13 @@ "message": "Importer sauvegarde en texte", "description": "Import backup code." }, - "dropbox_backup": { - "message": "Sauvegarde automatique vers Dropbox", - "description": "Auto backup to Dropbox." - }, - "dropbox_code": { - "message": "Code Dropbox", - "description": "Dropbox code." - }, - "dropbox_token": { - "message": "Jeton Dropbox", - "description": "Dropbox token." - }, - "dropbox_authorization": { - "message": "Se connecter à Dropbox", - "description": "Dropbox authorization." - }, "show_all_entries": { "message": "Afficher Toutes les Entrées", "description": "Show all entries." }, "dropbox_risk": { - "message": "Attention: les sauvegardes Dropbox ne sont pas protégées par chiffrement. Utilisez les à vos risques et périls.", - "description": "Dropbox backup risk warning." + "message": "Warning: backups are unencrypted. Use at your own risk.", + "description": "Backup risk warning." }, "import_error_password": { "message": "Vous devez fournir le mot de passe pour importer une sauvegarde.", @@ -235,10 +219,6 @@ "message": "Votre mot de passe est stocké localement, veuillez le changer immédiatement par l'intermédiaire du menu sécurité.", "description": "localStorage password warning." }, - "dropbox_logout": { - "message": "Se déconnecter de Dropbox", - "description": "Dropbox logout." - }, "remove": { "message": "Supprimer", "description": "Remove password." @@ -259,20 +239,40 @@ "message": "Vérouiller", "description": "Lock accounts" }, - "dropbox_tooltip": { - "message": "Synchronisation avec Dropbox activée", - "description": "Dropbox sync enabled" - }, "edit": { "message": "Modifier", "description": "Edit" }, "manual_dropbox": { "message": "Synchronisation manuelle", - "description": "Manual Dropbox sync" + "description": "Manual sync" }, "use_autofill": { "message": "Utiliser Autofill", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Logout", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/id/messages.json b/_locales/id/messages.json index 2f5a96f0e..098b92a86 100644 --- a/_locales/id/messages.json +++ b/_locales/id/messages.json @@ -12,7 +12,7 @@ "description": "Extension Description." }, "added": { - "message": " has been added.", + "message": " telah ditambahkan.", "description": "Added Account." }, "errorqr": { @@ -176,7 +176,7 @@ "description": "Counter Based" }, "resize_popup_page": { - "message": "Popup Page", + "message": "Halaman popup", "description": "Popup Page Settings" }, "scale": { @@ -203,29 +203,13 @@ "message": "Impor Text Cadangan", "description": "Import backup code." }, - "dropbox_backup": { - "message": "Cadangkan otomatis ke Dropbox", - "description": "Auto backup to Dropbox." - }, - "dropbox_code": { - "message": "Kode Dropbox", - "description": "Dropbox code." - }, - "dropbox_token": { - "message": "Token Dropbox", - "description": "Dropbox token." - }, - "dropbox_authorization": { - "message": "Masuk ke Dropbox", - "description": "Dropbox authorization." - }, "show_all_entries": { "message": "Tampilkan semua entri", "description": "Show all entries." }, "dropbox_risk": { - "message": "Peringatan: Cadangan yang disimpan di Dropbox tidak dienkripsi. Perhatikan resikonya sebelum digunakan.", - "description": "Dropbox backup risk warning." + "message": "Warning: backups are unencrypted. Use at your own risk.", + "description": "Backup risk warning." }, "import_error_password": { "message": "Masukkan kata sandi yang tepat untuk mengimpor cadangan.", @@ -235,44 +219,60 @@ "message": "Kata sandi anda disimpan secara lokal, tolong ubah kedalam menu keamanan segera.", "description": "localStorage password warning." }, - "dropbox_logout": { - "message": "Keluar dari Dropbox", - "description": "Dropbox logout." - }, "remove": { "message": "Hapus", "description": "Remove password." }, "download_enc_backup": { - "message": "Download Password-Protected Backup", + "message": "Download cadangan dilindungi sandi", "description": "Download Encrypted Backup" }, "search": { - "message": "Search", + "message": "Cari", "description": "Search" }, "popout": { - "message": "Popup mode", + "message": "Mode popup", "description": "Make window turn into persistent popup" }, "lock": { - "message": "Lock", + "message": "Kunci", "description": "Lock accounts" }, - "dropbox_tooltip": { - "message": "Dropbox sync enabled", - "description": "Dropbox sync enabled" - }, "edit": { - "message": "Edit", + "message": "Ubah", "description": "Edit" }, "manual_dropbox": { "message": "Manual Sync", - "description": "Manual Dropbox sync" + "description": "Manual sync" }, "use_autofill": { - "message": "Use Autofill", + "message": "IsiOtomatis", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Logout", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/it/messages.json b/_locales/it/messages.json index 1cf205359..b00498742 100644 --- a/_locales/it/messages.json +++ b/_locales/it/messages.json @@ -203,29 +203,13 @@ "message": "Importa un backup testuale", "description": "Import backup code." }, - "dropbox_backup": { - "message": "Backup automatico su Dropbox", - "description": "Auto backup to Dropbox." - }, - "dropbox_code": { - "message": "Codice di Dropbox", - "description": "Dropbox code." - }, - "dropbox_token": { - "message": "Token di Dropbox", - "description": "Dropbox token." - }, - "dropbox_authorization": { - "message": "Accedere a Dropbox", - "description": "Dropbox authorization." - }, "show_all_entries": { "message": "Visualizza tutte le voci", "description": "Show all entries." }, "dropbox_risk": { - "message": "Avviso: i backup salvati in Dropbox sono non crittografati. Utilizzare a proprio rischio.", - "description": "Dropbox backup risk warning." + "message": "Warning: backups are unencrypted. Use at your own risk.", + "description": "Backup risk warning." }, "import_error_password": { "message": "È necessario fornire la password corretta per importare il backup.", @@ -235,16 +219,12 @@ "message": "La password è memorizzata localmente, cambiala immediatamente nel menu sicurezza.", "description": "localStorage password warning." }, - "dropbox_logout": { - "message": "Logout da Dropbox", - "description": "Dropbox logout." - }, "remove": { "message": "Rimuovi", "description": "Remove password." }, "download_enc_backup": { - "message": "Download Password-Protected Backup", + "message": "Scarica Backup protetti da Password", "description": "Download Encrypted Backup" }, "search": { @@ -259,20 +239,40 @@ "message": "Lock", "description": "Lock accounts" }, - "dropbox_tooltip": { - "message": "Dropbox sync enabled", - "description": "Dropbox sync enabled" - }, "edit": { "message": "Edit", "description": "Edit" }, "manual_dropbox": { "message": "Manual Sync", - "description": "Manual Dropbox sync" + "description": "Manual sync" }, "use_autofill": { "message": "Use Autofill", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Logout", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/ja/messages.json b/_locales/ja/messages.json index eb599c289..465013167 100644 --- a/_locales/ja/messages.json +++ b/_locales/ja/messages.json @@ -176,7 +176,7 @@ "description": "Counter Based" }, "resize_popup_page": { - "message": "Popup Page", + "message": "ポップアップ表示", "description": "Popup Page Settings" }, "scale": { @@ -203,29 +203,13 @@ "message": "テキストのバックアップのインポート", "description": "Import backup code." }, - "dropbox_backup": { - "message": "Dropbox へ自動バックアップ", - "description": "Auto backup to Dropbox." - }, - "dropbox_code": { - "message": "Dropboxのコード", - "description": "Dropbox code." - }, - "dropbox_token": { - "message": "Dropboxのトークン", - "description": "Dropbox token." - }, - "dropbox_authorization": { - "message": "Sign in to Dropbox", - "description": "Dropbox authorization." - }, "show_all_entries": { "message": "すべての登録を表示", "description": "Show all entries." }, "dropbox_risk": { - "message": "警告: Dropbox に保存したバックアップは暗号化されていません。お気をつけください。", - "description": "Dropbox backup risk warning." + "message": "Warning: backups are unencrypted. Use at your own risk.", + "description": "Backup risk warning." }, "import_error_password": { "message": "バックアップをインポートするために正しいパスワードを入力してください。", @@ -235,44 +219,60 @@ "message": "パスワードをローカル環境に保存しました。すぐにメニューのセキュリティから変更してください。", "description": "localStorage password warning." }, - "dropbox_logout": { - "message": "Logout of Dropbox", - "description": "Dropbox logout." - }, "remove": { - "message": "Remove", + "message": "削除", "description": "Remove password." }, "download_enc_backup": { - "message": "Download Password-Protected Backup", + "message": "パスワードで保護されたバックアップをダウンロード", "description": "Download Encrypted Backup" }, "search": { - "message": "Search", + "message": "検索", "description": "Search" }, "popout": { - "message": "Popup mode", + "message": "ポップアップ表示", "description": "Make window turn into persistent popup" }, "lock": { - "message": "Lock", + "message": "ロック", "description": "Lock accounts" }, - "dropbox_tooltip": { - "message": "Dropbox sync enabled", - "description": "Dropbox sync enabled" - }, "edit": { - "message": "Edit", + "message": "編集", "description": "Edit" }, "manual_dropbox": { - "message": "Manual Sync", - "description": "Manual Dropbox sync" + "message": "手動で同期", + "description": "Manual sync" }, "use_autofill": { - "message": "Use Autofill", + "message": "自動入力を使用する", "description": "Use Autofill" + }, + "storage_menu": { + "message": "ストレージとバックアップ", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "データの保存場所を選択します。 「ローカル」はPCにデータを保存します。「同期」は、そのアカウントにログインいればブラウザーを通じてクラウドとデータを同期することができます。", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "サードパーティのストレージサービスへ自動的にデータをバックアップします。", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "ストレージの場所", + "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Logout", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/pl/messages.json b/_locales/pl/messages.json index b3f5af9d3..d849c918f 100644 --- a/_locales/pl/messages.json +++ b/_locales/pl/messages.json @@ -172,11 +172,11 @@ "description": "Time Based" }, "based_on_counter": { - "message": "Licznik oparty", + "message": "Oparte na liczniku", "description": "Counter Based" }, "resize_popup_page": { - "message": "Popup Page", + "message": "Wyskakująca strona", "description": "Popup Page Settings" }, "scale": { @@ -203,29 +203,13 @@ "message": "Import tekstowej kopii zapasowej", "description": "Import backup code." }, - "dropbox_backup": { - "message": "Automatyczna kopia zapasowa na Dropbox'ie", - "description": "Auto backup to Dropbox." - }, - "dropbox_code": { - "message": "Kod Dropbox", - "description": "Dropbox code." - }, - "dropbox_token": { - "message": "Token Dropbox", - "description": "Dropbox token." - }, - "dropbox_authorization": { - "message": "Zaloguj się do Dropbox'a", - "description": "Dropbox authorization." - }, "show_all_entries": { "message": "Pokaż wszystkie wpisy", "description": "Show all entries." }, "dropbox_risk": { - "message": "Ostrzeżenie: kopie zapasowe zapisane w Dropbox'ie są nieszyfrowane. Używasz na własne ryzyko.", - "description": "Dropbox backup risk warning." + "message": "Ostrzeżenie: kopie zapasowe nie są szyfrowane. Używaj na własne ryzyko.", + "description": "Backup risk warning." }, "import_error_password": { "message": "Należy podać prawidłowe hasło, żeby importować kopię zapasową.", @@ -235,44 +219,60 @@ "message": "Hasła są przechowywane lokalnie, należy to natychmiast zmienić w menu Zabezpieczenia.", "description": "localStorage password warning." }, - "dropbox_logout": { - "message": "Wyloguj się z Dropbox'a", - "description": "Dropbox logout." - }, "remove": { "message": "Usuń", "description": "Remove password." }, "download_enc_backup": { - "message": "Pobrania kopii zapasowej hasłem", + "message": "Pobierz kopię zapasową chronioną hasłem", "description": "Download Encrypted Backup" }, "search": { - "message": "Search", + "message": "Szukaj", "description": "Search" }, "popout": { - "message": "Popup mode", + "message": "Tryb wyskakujący", "description": "Make window turn into persistent popup" }, "lock": { - "message": "Lock", + "message": "Zablokuj", "description": "Lock accounts" }, - "dropbox_tooltip": { - "message": "Dropbox sync enabled", - "description": "Dropbox sync enabled" - }, "edit": { - "message": "Edit", + "message": "Edytuj", "description": "Edit" }, "manual_dropbox": { - "message": "Manual Sync", - "description": "Manual Dropbox sync" + "message": "Ręczna synchronizacja", + "description": "Manual sync" }, "use_autofill": { - "message": "Use Autofill", + "message": "Użyj autowypełniania", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Przechowywanie i kopie zapasowe", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Wybierz miejsce przechowywania danych. Używanie \"local\" zapisuje twoje dane na twoim komputerze. Korzystanie z opcji \"sync\" pozwala przeglądarce synchronizować dane z chmurą, jeśli jesteś zalogowany na koncie synchronizacji.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatycznie twórz kopie zapasowe danych w usługach przechowywania innych firm.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Miejsce przechowywania", + "description": "Storage location" + }, + "sign_in": { + "message": "Zaloguj", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Wyloguj", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/ru/messages.json b/_locales/ru/messages.json index bd193cd6a..d6be7798c 100644 --- a/_locales/ru/messages.json +++ b/_locales/ru/messages.json @@ -203,29 +203,13 @@ "message": "Импорт текста резервной копии", "description": "Import backup code." }, - "dropbox_backup": { - "message": "Автосохранение резервной копии в Dropbox", - "description": "Auto backup to Dropbox." - }, - "dropbox_code": { - "message": "Dropbox код", - "description": "Dropbox code." - }, - "dropbox_token": { - "message": "Dropbox маркер", - "description": "Dropbox token." - }, - "dropbox_authorization": { - "message": "Sign in to Dropbox", - "description": "Dropbox authorization." - }, "show_all_entries": { "message": "Показать все записи", "description": "Show all entries." }, "dropbox_risk": { - "message": "Внимание: резервные копии, сохраненные в Dropbox, не зашифрованы. Используйте на свой страх и риск.", - "description": "Dropbox backup risk warning." + "message": "Warning: backups are unencrypted. Use at your own risk.", + "description": "Backup risk warning." }, "import_error_password": { "message": "Для импорта резервных копий необходимо указать правильный пароль.", @@ -235,10 +219,6 @@ "message": "Ваш пароль хранится локально, пожалуйста немедленно изменить его в меню безопасность.", "description": "localStorage password warning." }, - "dropbox_logout": { - "message": "Logout Dropbox", - "description": "Dropbox logout." - }, "remove": { "message": "Удалить", "description": "Remove password." @@ -259,20 +239,40 @@ "message": "Заблокировать", "description": "Lock accounts" }, - "dropbox_tooltip": { - "message": "Dropbox Синхронизация включена", - "description": "Dropbox sync enabled" - }, "edit": { "message": "Редактировать", "description": "Edit" }, "manual_dropbox": { "message": "Ручная синхронизация", - "description": "Manual Dropbox sync" + "description": "Manual sync" }, "use_autofill": { "message": "Использование функции автозаполнения", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Logout", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/sv/messages.json b/_locales/sv/messages.json index db1796b9b..23e34d1ab 100644 --- a/_locales/sv/messages.json +++ b/_locales/sv/messages.json @@ -203,29 +203,13 @@ "message": "Importera okrypterad säkerhetskopia", "description": "Import backup code." }, - "dropbox_backup": { - "message": "Automatisk säkerhetskopiering till Dropbox", - "description": "Auto backup to Dropbox." - }, - "dropbox_code": { - "message": "Dropbox-kod", - "description": "Dropbox code." - }, - "dropbox_token": { - "message": "Dropbox-token", - "description": "Dropbox token." - }, - "dropbox_authorization": { - "message": "Logga in på Dropbox", - "description": "Dropbox authorization." - }, "show_all_entries": { "message": "Visa alla poster", "description": "Show all entries." }, "dropbox_risk": { - "message": "Varning: säkerhetskopior som sparas i Dropbox är okrypterade. Använd på egen risk.", - "description": "Dropbox backup risk warning." + "message": "Warning: backups are unencrypted. Use at your own risk.", + "description": "Backup risk warning." }, "import_error_password": { "message": "Du måste ange rätt lösenord för att importera säkerhetskopior.", @@ -235,10 +219,6 @@ "message": "Ditt lösenord lagras lokalt, var vänlig ändra det på säkerhetsmenyn omedelbart.", "description": "localStorage password warning." }, - "dropbox_logout": { - "message": "Logga ut från Dropbox", - "description": "Dropbox logout." - }, "remove": { "message": "Radera", "description": "Remove password." @@ -259,20 +239,40 @@ "message": "Lock", "description": "Lock accounts" }, - "dropbox_tooltip": { - "message": "Dropbox sync enabled", - "description": "Dropbox sync enabled" - }, "edit": { "message": "Edit", "description": "Edit" }, "manual_dropbox": { "message": "Manual Sync", - "description": "Manual Dropbox sync" + "description": "Manual sync" }, "use_autofill": { "message": "Use Autofill", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Logout", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/tr/messages.json b/_locales/tr/messages.json index 63dd00346..471b62bb2 100644 --- a/_locales/tr/messages.json +++ b/_locales/tr/messages.json @@ -203,29 +203,13 @@ "message": "Metin dosyasından içeri aktar", "description": "Import backup code." }, - "dropbox_backup": { - "message": "Dropbox için otomatik yedekleme", - "description": "Auto backup to Dropbox." - }, - "dropbox_code": { - "message": "Dropbox kodu", - "description": "Dropbox code." - }, - "dropbox_token": { - "message": "Dropbox jetonu", - "description": "Dropbox token." - }, - "dropbox_authorization": { - "message": "Dropbox'a giriş yapın", - "description": "Dropbox authorization." - }, "show_all_entries": { "message": "Tüm girdileri göster", "description": "Show all entries." }, "dropbox_risk": { - "message": "Uyarı: Dropbox'a kaydedilen yedekler şifrelenmemiştir. Bu riski göz önüne alarak kullanınız.", - "description": "Dropbox backup risk warning." + "message": "Warning: backups are unencrypted. Use at your own risk.", + "description": "Backup risk warning." }, "import_error_password": { "message": "Yedekleri içeri aktarmak için doğru şifreyi sağlamalısınız.", @@ -235,10 +219,6 @@ "message": "Parolanız yerel olarak depolanır, lütfen bunu güvenlik menüsünden hemen değiştirin.", "description": "localStorage password warning." }, - "dropbox_logout": { - "message": "Dropbox'tan çıkış yapın", - "description": "Dropbox logout." - }, "remove": { "message": "Sil", "description": "Remove password." @@ -259,20 +239,40 @@ "message": "Lock", "description": "Lock accounts" }, - "dropbox_tooltip": { - "message": "Dropbox sync enabled", - "description": "Dropbox sync enabled" - }, "edit": { "message": "Edit", "description": "Edit" }, "manual_dropbox": { "message": "Manual Sync", - "description": "Manual Dropbox sync" + "description": "Manual sync" }, "use_autofill": { "message": "Use Autofill", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Logout", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/vi/messages.json b/_locales/vi/messages.json index 2db402bec..8e659e21c 100644 --- a/_locales/vi/messages.json +++ b/_locales/vi/messages.json @@ -203,29 +203,13 @@ "message": "Nhập mã sao lưu", "description": "Import backup code." }, - "dropbox_backup": { - "message": "Tự động sao lưu vào Dropbox", - "description": "Auto backup to Dropbox." - }, - "dropbox_code": { - "message": "Mã Dropbox", - "description": "Dropbox code." - }, - "dropbox_token": { - "message": "Dropbox Token", - "description": "Dropbox token." - }, - "dropbox_authorization": { - "message": "Đăng nhập vào Dropbox", - "description": "Dropbox authorization." - }, "show_all_entries": { "message": "Xem tất cả", "description": "Show all entries." }, "dropbox_risk": { - "message": "Cảnh báo: các bản sao lưu được lưu trên Dropbox đều chưa được mã hóa. Hãy cẩn thận.", - "description": "Dropbox backup risk warning." + "message": "Warning: backups are unencrypted. Use at your own risk.", + "description": "Backup risk warning." }, "import_error_password": { "message": "Bạn phải nhập mật khẩu đúng để có thể nhập sao lưu.", @@ -235,10 +219,6 @@ "message": "Mật khẩu của bạn được lưu ngay trên trình duyệt, xin hãy đổi mật khẩu trong Menu > Bảo mật ngay.", "description": "localStorage password warning." }, - "dropbox_logout": { - "message": "Đăng xuất khỏi Dropbox", - "description": "Dropbox logout." - }, "remove": { "message": "Xóa", "description": "Remove password." @@ -259,20 +239,40 @@ "message": "Khóa", "description": "Lock accounts" }, - "dropbox_tooltip": { - "message": "Đã kích hoạt tự động đồng bộ với Dropbox", - "description": "Dropbox sync enabled" - }, "edit": { "message": "Điều chỉnh", "description": "Edit" }, "manual_dropbox": { "message": "Đồng bộ thủ công", - "description": "Manual Dropbox sync" + "description": "Manual sync" }, "use_autofill": { "message": "Chế độ Điền tự động", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Logout", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index a1f3851c6..8c7a2948d 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -272,7 +272,31 @@ "description": "Use Autofill" }, "search": { - "message": "Search", + "message": "搜索", "description": "Search" + }, + "storage_menu": { + "message": "存储及备份", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "选择数据存储位置。local为仅在本地存储,sync为云端同步存储。", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "自动备份您的数据至第三方服务。", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "存储位置", + "description": "Storage location" + }, + "sign_in": { + "message": "登录", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "注销", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/_locales/zh_TW/messages.json b/_locales/zh_TW/messages.json index 5f700c34a..aad820a43 100644 --- a/_locales/zh_TW/messages.json +++ b/_locales/zh_TW/messages.json @@ -203,29 +203,13 @@ "message": "以文字匯入", "description": "Import backup code." }, - "dropbox_backup": { - "message": "自動備份至Dropbox", - "description": "Auto backup to Dropbox." - }, - "dropbox_code": { - "message": "Dropbox授權碼", - "description": "Dropbox code." - }, - "dropbox_token": { - "message": "Dropbox Token", - "description": "Dropbox token." - }, - "dropbox_authorization": { - "message": "登入 Dropbox", - "description": "Dropbox authorization." - }, "show_all_entries": { "message": "顯示全部條目", "description": "Show all entries." }, "dropbox_risk": { - "message": "警告:儲存至Dropbox的備份檔案均未加密,需自行承擔風險。", - "description": "Dropbox backup risk warning." + "message": "Warning: backups are unencrypted. Use at your own risk.", + "description": "Backup risk warning." }, "import_error_password": { "message": "您必須提供正確的密碼才能匯入備份。", @@ -235,10 +219,6 @@ "message": "您的密碼儲存在了本地,請立即通過安全選單更改密碼。", "description": "localStorage password warning." }, - "dropbox_logout": { - "message": "登出 Dropbox", - "description": "Dropbox logout." - }, "remove": { "message": "移除", "description": "Remove password." @@ -259,20 +239,40 @@ "message": "鎖定", "description": "Lock accounts" }, - "dropbox_tooltip": { - "message": "Dropbox同步已啟用", - "description": "Dropbox sync enabled" - }, "edit": { "message": "編輯", "description": "Edit" }, "manual_dropbox": { "message": "手動同步", - "description": "Manual Dropbox sync" + "description": "Manual sync" }, "use_autofill": { "message": "啟用自動填充", "description": "Use Autofill" + }, + "storage_menu": { + "message": "Storage & Backup", + "description": "Storage and sync menu title" + }, + "storage_location_info": { + "message": "Choose where your data is stored. Using 'local' stores your data on your PC. Using 'sync' lets your browser sync your data to the cloud if you are signed into a sync account.", + "description": "Message explaning the diffrences between sync and local storage spaces." + }, + "storage_sync_info": { + "message": "Automatically backup your data to 3rd party storgae services.", + "description": "3rd party backup info" + }, + "storage_location": { + "message": "Storage Location", + "description": "Storage location" + }, + "sign_in": { + "message": "Sign in", + "description": "Sign in to 3rd party storage services" + }, + "log_out": { + "message": "Logout", + "description": "Sign out of 3rd party storage services" } } \ No newline at end of file diff --git a/css/popup.css b/css/popup.css index 65d51099f..9765b0bc5 100644 --- a/css/popup.css +++ b/css/popup.css @@ -401,6 +401,7 @@ body { #add_button, #dropbox_get_code, #dropbox_logout, +#dropbox_menu, #download_backup, #import_backup, #upload_backup, @@ -746,6 +747,7 @@ body { #security_remove:hover, #dropbox_get_code:hover, #dropbox_logout:hover, +#dropbox_menu:hover, #export:hover, #confirm_cancel:hover, #confirm_ok:hover, @@ -854,12 +856,13 @@ body { padding: 5px; } -#dropbox_box label, #secret_box label, #security label, #passphrase label, #security_warning, #dropbox_risk, +#storage_location_info, +#storage_sync_info, #export_info, #passphrase_info { display: block; @@ -869,17 +872,27 @@ body { #security_warning, #export_info, #dropbox_risk, +#storage_location_info, +#storage_sync_info, #passphrase_info { color: gray; } #resize_list_label, +#storage_location_label, +#dropbox_encryption_label, +#dropbox_encryption, #use_autofill_label, -#resize_list { +#resize_list, +#storage_location { margin: 20px; font-size: 16px; } +#storage_location { + margin: 20px 20px 20px 0; +} + #message, #confirm { position: absolute; diff --git a/edge-files/AppXManifest.xml b/edge-files/AppXManifest.xml index beede6866..c9e3f866d 100644 --- a/edge-files/AppXManifest.xml +++ b/edge-files/AppXManifest.xml @@ -8,7 +8,7 @@ + Version="5.2.0.0"/> Authenticator Extension @@ -27,6 +27,7 @@ + diff --git a/edge-files/Assets/icon_150.png b/edge-files/Assets/icon_150.png new file mode 100644 index 000000000..7487ea57f Binary files /dev/null and b/edge-files/Assets/icon_150.png differ diff --git a/edge-files/Assets/icon_44.png b/edge-files/Assets/icon_44.png new file mode 100644 index 000000000..ceb40b862 Binary files /dev/null and b/edge-files/Assets/icon_44.png differ diff --git a/edge-files/Assets/icon_50.png b/edge-files/Assets/icon_50.png new file mode 100644 index 000000000..a83fc650f Binary files /dev/null and b/edge-files/Assets/icon_50.png differ diff --git a/edge-files/icon_150.png b/edge-files/icon_150.png deleted file mode 100644 index 177f3ea80..000000000 Binary files a/edge-files/icon_150.png and /dev/null differ diff --git a/edge-files/icon_44.png b/edge-files/icon_44.png deleted file mode 100644 index 50e53f41e..000000000 Binary files a/edge-files/icon_44.png and /dev/null differ diff --git a/edge-files/icon_50.png b/edge-files/icon_50.png deleted file mode 100644 index c23c7d0ea..000000000 Binary files a/edge-files/icon_50.png and /dev/null differ diff --git a/edge-files/images/icon128.png b/edge-files/images/icon128.png new file mode 100644 index 000000000..b96b52c57 Binary files /dev/null and b/edge-files/images/icon128.png differ diff --git a/edge-files/images/icon176.png b/edge-files/images/icon176.png new file mode 100644 index 000000000..bba0f98c9 Binary files /dev/null and b/edge-files/images/icon176.png differ diff --git a/edge-files/images/icon20.png b/edge-files/images/icon20.png new file mode 100644 index 000000000..6147ccd0a Binary files /dev/null and b/edge-files/images/icon20.png differ diff --git a/edge-files/images/icon25.png b/edge-files/images/icon25.png new file mode 100644 index 000000000..7e59f8e12 Binary files /dev/null and b/edge-files/images/icon25.png differ diff --git a/edge-files/images/icon30.png b/edge-files/images/icon30.png new file mode 100644 index 000000000..2a1907f48 Binary files /dev/null and b/edge-files/images/icon30.png differ diff --git a/edge-files/images/icon40.png b/edge-files/images/icon40.png new file mode 100644 index 000000000..8f3d0795f Binary files /dev/null and b/edge-files/images/icon40.png differ diff --git a/edge-files/images/icon48.png b/edge-files/images/icon48.png new file mode 100644 index 000000000..d6fedbad6 Binary files /dev/null and b/edge-files/images/icon48.png differ diff --git a/edge-files/images/scan.gif b/edge-files/images/scan.gif new file mode 100644 index 000000000..ae5698b01 Binary files /dev/null and b/edge-files/images/scan.gif differ diff --git a/edge-files/store-icon.png b/edge-files/store-icon.png new file mode 100644 index 000000000..35c6bd6b6 Binary files /dev/null and b/edge-files/store-icon.png differ diff --git a/images/icon.svg b/images/icon.svg new file mode 100644 index 000000000..6678b02b0 --- /dev/null +++ b/images/icon.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/icon128.png b/images/icon128.png index 6a4a18e36..b96b52c57 100644 Binary files a/images/icon128.png and b/images/icon128.png differ diff --git a/images/icon16.png b/images/icon16.png index fe84e4249..2e5104503 100644 Binary files a/images/icon16.png and b/images/icon16.png differ diff --git a/images/icon19.png b/images/icon19.png index e3335f57a..1e1823a5b 100644 Binary files a/images/icon19.png and b/images/icon19.png differ diff --git a/images/icon38.png b/images/icon38.png index 7d5174b4b..079ee2936 100644 Binary files a/images/icon38.png and b/images/icon38.png differ diff --git a/images/icon48.png b/images/icon48.png index 059a7ccc2..d6fedbad6 100644 Binary files a/images/icon48.png and b/images/icon48.png differ diff --git a/manifest-chrome.json b/manifest-chrome.json index 4a84374bc..a38a3082e 100644 --- a/manifest-chrome.json +++ b/manifest-chrome.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "__MSG_extShortName__", - "version": "5.1.1", + "version": "5.2.0", "default_locale": "en", "description": "__MSG_extDesc__", "icons": { @@ -18,6 +18,12 @@ "default_title": "__MSG_extShortName__", "default_popup": "view/popup.html" }, + "oauth2": { + "client_id": "292457304165-u8ve4j79gag5o231n5u2pdtdrbfdo1hh.apps.googleusercontent.com", + "scopes": [ + "https://www.googleapis.com/auth/drive.file" + ] + }, "background": { "scripts": [ "js/jsqrcode/grid.js", @@ -45,6 +51,7 @@ "build/models/interface.js", "build/models/otp.js", "build/models/storage.js", + "build/models/credentials.js", "build/background.js" ], "persistent": false @@ -58,12 +65,14 @@ "optional_permissions": [ "clipboardWrite", "https://www.google.com/", - "https://*.dropboxapi.com/*" + "https://*.dropboxapi.com/*", + "https://www.googleapis.com/*", + "https://accounts.google.com/o/oauth2/revoke" ], "offline_enabled": true, "web_accessible_resources": [ "view/qr.html", "images/scan.gif" ], - "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com; default-src 'none'" + "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com https://www.googleapis.com/ https://accounts.google.com/o/oauth2/revoke ; default-src 'none'" } diff --git a/manifest-edge.json b/manifest-edge.json index b7f6b445f..abcf12dd2 100644 --- a/manifest-edge.json +++ b/manifest-edge.json @@ -33,19 +33,21 @@ }, "browser_action": { "default_icon": { - "19": "images/icon19.png", - "38": "images/icon38.png" + "20": "images/icon20.png", + "25": "images/icon25.png", + "30": "images/icon30.png", + "40": "images/icon40.png" }, "default_title": "__MSG_extShortName__", "default_popup": "view/popup.html" }, - "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com; default-src 'none'", + "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/; default-src 'none'", "default_locale": "en", "description": "__MSG_extDesc__", "icons": { - "16": "images/icon16.png", "48": "images/icon48.png", - "128": "images/icon128.png" + "128": "images/icon128.png", + "176": "images/icon176.png" }, "manifest_version": 2, "name": "__MSG_extName__", @@ -54,7 +56,7 @@ "storage" ], "short_name": "__MSG_extShortName__", - "version": "5.1.1", + "version": "5.2.0", "web_accessible_resources": [ "view/qr.html", "images/scan.gif" diff --git a/manifest-firefox.json b/manifest-firefox.json index 5c3f500d5..367ed36c2 100644 --- a/manifest-firefox.json +++ b/manifest-firefox.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "__MSG_extShortName__", - "version": "5.1.1", + "version": "5.2.0", "default_locale": "en", "description": "__MSG_extDesc__", "applications": { @@ -17,6 +17,7 @@ "128": "images/icon128.png" }, "browser_action": { + "browser_style": false, "default_icon": { "19": "images/icon19.png", "38": "images/icon38.png" @@ -51,6 +52,7 @@ "build/models/interface.js", "build/models/otp.js", "build/models/storage.js", + "build/models/credentials.js", "build/background.js" ] }, @@ -64,11 +66,13 @@ "optional_permissions": [ "clipboardWrite", "https://www.google.com/", - "https://*.dropboxapi.com/*" + "https://*.dropboxapi.com/*", + "https://www.googleapis.com/*", + "https://accounts.google.com/o/oauth2/revoke" ], "web_accessible_resources": [ "view/qr.html", "images/scan.gif" ], - "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com; default-src 'none'" + "content_security_policy": "script-src 'self'; font-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; connect-src https://www.google.com/ https://*.dropboxapi.com https://www.googleapis.com/ https://accounts.google.com/o/oauth2/revoke ; default-src 'none'" } diff --git a/package.json b/package.json index b7041f046..a12be47fa 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,6 @@ "test": "echo \"Error: no test specified\" && exit 1", "check": "gts check", "clean": "gts clean", - "copyChrome": "cp -r src build css images js _locales LICENSE view chrome", - "copyFirefox": "cp -r src build css images js _locales LICENSE view firefox", - "copyEdge": "cp -r src build css images js _locales LICENSE view edge-files edge/Extension && mv edge/Extension/edge-files/AppXManifest.xml edge && mv edge/Extension/edge-files/icon*.png edge/Assets", "packageEdge": "cmd /C \"\"C:\\Program Files (x86)\\Windows Kits\\10\\App Certification Kit\\makeappx.exe\" pack /h SHA256 /d edge /p edge/Authenticator.appx\"", "installEdge": "rm -rf edge && npm run edge && powershell -Command \"Add-AppxPackage -Path edge\\AppxManifest.xml -Register\"", "compile": "gts clean && tsc -p .", @@ -16,9 +13,9 @@ "prepare": "npm run compile", "pretest": "npm run compile", "posttest": "npm run check", - "chrome": "node ensureDir.js chrome && npm run compile && npm run copyChrome && cp manifest-chrome.json chrome/manifest.json", - "firefox": "node ensureDir.js firefox && npm run compile && npm run copyFirefox && cp manifest-firefox.json firefox/manifest.json", - "edge": "node ensureDir.js edge && node ensureDir.js edge/Extension && node ensureDir.js edge/Assets && npm run compile && npm run copyEdge && cp manifest-edge.json edge/Extension/manifest.json" + "chrome": "bash scripts/build.sh chrome", + "firefox": "bash scripts/build.sh firefox", + "edge": "bash scripts/build.sh edge" }, "repository": { "type": "git", diff --git a/ci/authenticator-build-key.enc b/scripts/authenticator-build-key.enc similarity index 100% rename from ci/authenticator-build-key.enc rename to scripts/authenticator-build-key.enc diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100644 index 000000000..a610e225a --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# Extension build script +# Syntax: +# build.sh +# Platforms: +# 'chrome', 'firefox', and 'edge' + +PLATFORM=$1 +REMOTE=$(git config --get remote.origin.url) +CREDS=$(cat ./src/models/credentials.ts | tr -d '\n') +CREDREGEX="^.*'.+'.*'.+'.*'.+'.*$" +set -e + +if [[ $PLATFORM != "chrome" ]] && [[ $PLATFORM != "firefox" ]] && [[ $PLATFORM != "edge" ]]; then + echo "Invalid platform type. Supported platforms are 'chrome', 'firefox', and 'edge'" + exit 1 +fi + +echo "Removing old build files..." +rm -rf build +rm -rf $PLATFORM +echo "Checking code style..." +if gts check 1> /dev/null ; then + true +else + echo "Fixing code style..." + gts fix +fi + +if ! [[ $CREDS =~ $CREDREGEX ]] ; then + echo -e "\e[7m\033[33mWarning: Missing info in credentials.ts\033[0m" +fi + +if ! [[ $REMOTE = *"https://github.com/Authenticator-Extension/Authenticator.git"* ]] ; then + echo + echo -e "\e[7m\033[33mNotice\033[0m" + echo + echo -e "Thanks for forking Authenticator! If you plan on redistributing your own version of Authenticator please generate your own API keys and put them in ./src/models/credentials.ts" + echo "Clear this warning by commenting it out in ./scripts/build.sh" + echo + read -rsp $'Press any key to continue...\n' -n1 key + echo +fi + +echo "Compiling..." +npm run compile 1> /dev/null +mkdir $PLATFORM +if [[ $PLATFORM = "edge" ]]; then + mkdir $PLATFORM/Extension + mkdir $PLATFORM/Assets + cp -r build css js _locales LICENSE view edge-files $PLATFORM/Extension + mv $PLATFORM/Extension/edge-files/AppXManifest.xml $PLATFORM + mv $PLATFORM/Extension/edge-files/images $PLATFORM/Extension + mv $PLATFORM/Extension/edge-files/Assets/icon*.png $PLATFORM/Assets + cp manifest-$PLATFORM.json $PLATFORM/Extension/manifest.json +else + cp -r build css images js _locales LICENSE view $PLATFORM + cp manifest-$PLATFORM.json $PLATFORM/manifest.json +fi + +echo -e "\033[0;32mDone!\033[0m" \ No newline at end of file diff --git a/ensureDir.js b/scripts/ensureDir.js similarity index 100% rename from ensureDir.js rename to scripts/ensureDir.js diff --git a/ci/i18n.js b/scripts/i18n.js similarity index 100% rename from ci/i18n.js rename to scripts/i18n.js diff --git a/ci/i18n.sh b/scripts/i18n.sh similarity index 56% rename from ci/i18n.sh rename to scripts/i18n.sh index bb68171d7..1fe109c29 100644 --- a/ci/i18n.sh +++ b/scripts/i18n.sh @@ -4,31 +4,35 @@ # Define colors RED='\033[0;31m' GREEN='\033[0;32m' -BOLD='\033[1m' NC='\033[0m' +if [[ -n $TRAVIS_PULL_REQUEST_BRANCH ]]; then + BRANCH=$TRAVIS_PULL_REQUEST_BRANCH +else + BRANCH=$TRAVIS_BRANCH +fi + # Configure git git config --global user.email "deploy@travis-ci.org" git config --global user.name "Travis CI" git remote set-url origin git@github.com:Authenticator-Extension/Authenticator.git -openssl aes-256-cbc -K $encrypted_2b3e3bd93233_key -iv $encrypted_2b3e3bd93233_iv -in $TRAVIS_BUILD_DIR/ci/authenticator-build-key.enc -out $TRAVIS_BUILD_DIR/ci/authenticator-build-key -d -chmod 600 $TRAVIS_BUILD_DIR/ci/authenticator-build-key +openssl aes-256-cbc -K $encrypted_2b3e3bd93233_key -iv $encrypted_2b3e3bd93233_iv -in $TRAVIS_BUILD_DIR/scripts/authenticator-build-key.enc -out $TRAVIS_BUILD_DIR/scripts/authenticator-build-key -d +chmod 600 $TRAVIS_BUILD_DIR/scripts/authenticator-build-key eval `ssh-agent -s` &> /dev/null -ssh-add $TRAVIS_BUILD_DIR/ci/authenticator-build-key &> /dev/null +ssh-add $TRAVIS_BUILD_DIR/scripts/authenticator-build-key &> /dev/null # Fix i18n issues cd $TRAVIS_BUILD_DIR -node ./ci/i18n.js +node ./scripts/i18n.js # Branch changes and error with details on how to fix i18n if branched if [[ `git diff _locales` ]]; then - git checkout -b i18n-$TRAVIS_BUILD_NUMBER &> /dev/null + git checkout $BRANCH &> /dev/null git add ./_locales/*/messages.json git commit -m "Add new strings" -m "This commit was automatically made by TravisCI build $TRAVIS_JOB_NUMBER" --quiet - git push -u origin i18n-$TRAVIS_BUILD_NUMBER --quiet - git checkout $TRAVIS_BRANCH --quiet - printf "${RED}You added new strings to _locales/en/messages.json, but not some of the other translation files. A branch has been created at ${BOLD}i18n-$TRAVIS_BUILD_NUMBER ${NC}${RED}with the required changes already made. \n\n${RED}Please ${BOLD}merge i18n-$TRAVIS_BUILD_NUMBER into $TRAVIS_BRANCH ${NC}${RED}to resolve this issue.${NC}\n" - exit 1 + git push --quiet + printf "${RED}You added new strings to _locales/en/messages.json, but not some of the other translation files. A commit has been created on the current branch with the required changes already made. ${NC}\n" + exit 0 else printf "${GREEN}No new translation strings detected.${NC}" fi diff --git a/ci/tag.sh b/scripts/tag.sh similarity index 71% rename from ci/tag.sh rename to scripts/tag.sh index f4476924c..aef6d5bec 100644 --- a/ci/tag.sh +++ b/scripts/tag.sh @@ -5,10 +5,10 @@ git config --global user.email "deploy@travis-ci.org" git config --global user.name "Travis CI" git remote set-url origin git@github.com:Authenticator-Extension/Authenticator.git -openssl aes-256-cbc -K $encrypted_2b3e3bd93233_key -iv $encrypted_2b3e3bd93233_iv -in $TRAVIS_BUILD_DIR/ci/authenticator-build-key.enc -out $TRAVIS_BUILD_DIR/ci/authenticator-build-key -d -chmod 600 $TRAVIS_BUILD_DIR/ci/authenticator-build-key +openssl aes-256-cbc -K $encrypted_2b3e3bd93233_key -iv $encrypted_2b3e3bd93233_iv -in $TRAVIS_BUILD_DIR/scripts/authenticator-build-key.enc -out $TRAVIS_BUILD_DIR/scripts/authenticator-build-key -d +chmod 600 $TRAVIS_BUILD_DIR/scripts/authenticator-build-key eval `ssh-agent -s` -ssh-add $TRAVIS_BUILD_DIR/ci/authenticator-build-key +ssh-add $TRAVIS_BUILD_DIR/scripts/authenticator-build-key # Create and push tag export GIT_TAG=v$(grep -m 1 "\"version\"" $TRAVIS_BUILD_DIR/manifest-chrome.json | sed -r 's/^ *//;s/.*: *"//;s/",?//') diff --git a/src/background.ts b/src/background.ts index c1116a4d1..d1470ba2e 100644 --- a/src/background.ts +++ b/src/background.ts @@ -3,6 +3,7 @@ /// /// /// +/// let cachedPassphrase = ''; @@ -18,8 +19,8 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { cachedPassphrase = message.value; } else if (message.action === 'passphrase') { sendResponse(cachedPassphrase); - } else if (message.action === 'dropbox') { - getDropboxToken(); + } else if (['dropbox', 'drive'].indexOf(message.action) > -1) { + getBackupToken(message.action); } else if (message.action === 'lock') { cachedPassphrase = ''; } @@ -154,44 +155,113 @@ async function getTotp(text: string, passphrase: string) { return; } -function getDropboxToken() { - chrome.identity.launchWebAuthFlow( - { - url: - 'https://www.dropbox.com/oauth2/authorize?response_type=token&client_id=mmx38seexw3tvps&redirect_uri=' + - encodeURIComponent(chrome.identity.getRedirectURL()), - interactive: true - }, - (url) => { - if (!url) { - return; - } - const hashMatches = url.split('#'); - if (hashMatches.length < 2) { - return; - } +function getBackupToken(service: string) { + if (navigator.userAgent.indexOf('Chrome') !== -1 && service === 'drive') { + chrome.identity.getAuthToken( + { + 'interactive': true, + 'scopes': ['https://www.googleapis.com/auth/drive.file'] + }, + (token) => { + localStorage.driveToken = token; + chrome.runtime.sendMessage({action: 'drivetoken', token}); + return true; + }); + } else { + let authUrl = ''; + if (service === 'dropbox') { + authUrl = + 'https://www.dropbox.com/oauth2/authorize?response_type=token&client_id=' + + getCredentials().dropbox.client_id + '&redirect_uri=' + + encodeURIComponent(chrome.identity.getRedirectURL()); + } else if (service === 'drive') { + authUrl = + 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&access_type=offline&client_id=' + + getCredentials().drive.client_id + + '&scope=https%3A//www.googleapis.com/auth/drive.file&prompt=consent&redirect_uri=' + + encodeURIComponent('https://authenticator.cc/oauth'); + } + chrome.identity.launchWebAuthFlow( + {url: authUrl, interactive: true}, async (url) => { + if (!url) { + return; + } + let hashMatches = url.split('#'); + if (service === 'drive') { + hashMatches = url.slice(0, -1).split('?'); + } + if (hashMatches.length < 2) { + return; + } - const hash = hashMatches[1]; + const hash = hashMatches[1]; - const resData = hash.split('&'); - for (let i = 0; i < resData.length; i++) { - const kv = resData[i]; - if (/^(.*?)=(.*?)$/.test(kv)) { - const kvMatches = kv.match(/^(.*?)=(.*?)$/); - if (!kvMatches) { - continue; - } - const key = kvMatches[1]; - const value = kvMatches[2]; - if (key === 'access_token') { - localStorage.dropboxToken = value; - chrome.runtime.sendMessage({action: 'dropboxtoken', value}); - return; + const resData = hash.split('&'); + for (let i = 0; i < resData.length; i++) { + const kv = resData[i]; + if (/^(.*?)=(.*?)$/.test(kv)) { + const kvMatches = kv.match(/^(.*?)=(.*?)$/); + if (!kvMatches) { + continue; + } + const key = kvMatches[1]; + const value = kvMatches[2]; + if (key === 'access_token') { + if (service === 'dropbox') { + localStorage.dropboxToken = value; + chrome.runtime.sendMessage({action: 'dropboxtoken', value}); + return; + } + } else if (key === 'code') { + if (service === 'drive') { + const xhr = new XMLHttpRequest(); + // Need to trade code we got from launchWebAuthFlow for a + // token & refresh token + await new Promise( + (resolve: (value: boolean) => void, + reject: (reason: Error) => void) => { + xhr.open( + 'POST', + 'https://www.googleapis.com/oauth2/v4/token?client_id=' + + getCredentials().drive.client_id + + '&client_secret=' + + getCredentials().drive.client_secret + + '&code=' + value + + '&redirect_uri=https://authenticator.cc/oauth&grant_type=authorization_code'); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.setRequestHeader( + 'Content-Type', + 'application/x-www-form-urlencoded'); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + try { + const res = JSON.parse(xhr.responseText); + if (res.error) { + console.error(res.error_description); + resolve(false); + } else { + localStorage.driveToken = res.access_token; + localStorage.driveRefreshToken = + res.refresh_token; + resolve(true); + } + } catch (error) { + console.error(error); + reject(error); + } + } + return; + }; + xhr.send(); + }); + chrome.runtime.sendMessage({action: 'drivetoken', value}); + } + } } } - } - return; - }); + return; + }); + } } // Show issue page after first install diff --git a/src/content.ts b/src/content.ts index 356c50edb..35561cc52 100644 --- a/src/content.ts +++ b/src/content.ts @@ -27,8 +27,8 @@ if (!document.getElementById('__ga_grayLayout__')) { }); } -sessionStorage.captureBoxPositionLeft = 0; -sessionStorage.captureBoxPositionTop = 0; +sessionStorage.setItem('captureBoxPositionLeft', '0'); +sessionStorage.setItem('captureBoxPositionTop', '0'); function showGrayLayout(passphrase: string) { let grayLayout = document.getElementById('__ga_grayLayout__'); @@ -67,8 +67,8 @@ function grayLayoutDown(event: MouseEvent) { return; } - sessionStorage.captureBoxPositionLeft = event.clientX; - sessionStorage.captureBoxPositionTop = event.clientY; + sessionStorage.setItem('captureBoxPositionLeft', event.clientX.toString()); + sessionStorage.setItem('captureBoxPositionTop', event.clientY.toString()); captureBox.style.left = event.clientX + 'px'; captureBox.style.top = event.clientY + 'px'; captureBox.style.width = '1px'; @@ -87,14 +87,20 @@ function grayLayoutMove(event: MouseEvent) { return; } - const captureBoxLeft = - Math.min(sessionStorage.captureBoxPositionLeft, event.clientX); - const captureBoxTop = - Math.min(sessionStorage.captureBoxPositionTop, event.clientY); + const captureBoxLeft = Math.min( + Number(sessionStorage.getItem('captureBoxPositionLeft')), event.clientX); + const captureBoxTop = Math.min( + Number(sessionStorage.getItem('captureBoxPositionTop')), event.clientY); const captureBoxWidth = - Math.abs(sessionStorage.captureBoxPositionLeft - event.clientX) - 1; + Math.abs( + Number(sessionStorage.getItem('captureBoxPositionLeft')) - + event.clientX) - + 1; const captureBoxHeight = - Math.abs(sessionStorage.captureBoxPositionTop - event.clientY) - 1; + Math.abs( + Number(sessionStorage.getItem('captureBoxPositionTop')) - + event.clientY) - + 1; captureBox.style.left = captureBoxLeft + 'px'; captureBox.style.top = captureBoxTop + 'px'; captureBox.style.width = captureBoxWidth + 'px'; @@ -120,13 +126,25 @@ function grayLayoutUp(event: MouseEvent, passphrase: string) { } const captureBoxLeft = - Math.min(sessionStorage.captureBoxPositionLeft, event.clientX) + 1; + Math.min( + Number(sessionStorage.getItem('captureBoxPositionLeft')), + event.clientX) + + 1; const captureBoxTop = - Math.min(sessionStorage.captureBoxPositionTop, event.clientY) + 1; + Math.min( + Number(sessionStorage.getItem('captureBoxPositionTop')), + event.clientY) + + 1; const captureBoxWidth = - Math.abs(sessionStorage.captureBoxPositionLeft - event.clientX) - 1; + Math.abs( + Number(sessionStorage.getItem('captureBoxPositionLeft')) - + event.clientX) - + 1; const captureBoxHeight = - Math.abs(sessionStorage.captureBoxPositionTop - event.clientY) - 1; + Math.abs( + Number(sessionStorage.getItem('captureBoxPositionTop')) - + event.clientY) - + 1; // make sure captureBox and grayLayout is hidden setTimeout(() => { @@ -168,7 +186,8 @@ function pasteCode(code: string) { const _inputBoxes = document.getElementsByTagName('input'); const inputBoxes: HTMLInputElement[] = []; for (let i = 0; i < _inputBoxes.length; i++) { - if (_inputBoxes[i].type === 'text' || _inputBoxes[i].type === 'number') { + if (_inputBoxes[i].type === 'text' || _inputBoxes[i].type === 'number' || + _inputBoxes[i].type === 'tel') { inputBoxes.push(_inputBoxes[i]); } } diff --git a/src/models/backup.ts b/src/models/backup.ts new file mode 100644 index 000000000..60d35dd86 --- /dev/null +++ b/src/models/backup.ts @@ -0,0 +1,308 @@ +/* tslint:disable:no-reference */ +/// +/// +/// +/// + +class Dropbox { + private async getToken() { + return localStorage.dropboxToken || ''; + } + + async upload(encryption: Encryption) { + if (localStorage.dropboxEncrypted === undefined) { + // Encrypt by default if user hasn't set yet + localStorage.dropboxEncrypted = 'true'; + } + const exportData = await EntryStorage.getExport( + encryption, (localStorage.dropboxEncrypted === 'true')); + const backup = JSON.stringify(exportData, null, 2); + + const url = 'https://content.dropboxapi.com/2/files/upload'; + const token = await this.getToken(); + return new Promise( + (resolve: (value: boolean) => void, + reject: (reason: Error) => void) => { + if (!token) { + resolve(false); + } + try { + const xhr = new XMLHttpRequest(); + const now = + (new Date()).toISOString().slice(0, 10).replace(/-/g, ''); + const apiArg = { + path: `/${now}.json`, + mode: 'add', + autorename: true + }; + xhr.open('POST', url); + xhr.setRequestHeader('Authorization', 'Bearer ' + token); + xhr.setRequestHeader('Content-type', 'application/octet-stream'); + xhr.setRequestHeader('Dropbox-API-Arg', JSON.stringify(apiArg)); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 401) { + localStorage.removeItem('dropboxToken'); + resolve(false); + } + try { + const res = JSON.parse(xhr.responseText); + if (res.name) { + resolve(true); + } else { + resolve(false); + } + } catch (error) { + reject(error); + } + } + return; + }; + xhr.send(backup); + } catch (error) { + return reject(error); + } + }); + } +} + +class Drive { + private async getToken() { + if (!localStorage.driveToken || + await new Promise( + (resolve: (value: boolean) => void, + reject: (reason: Error) => void) => { + const xhr = new XMLHttpRequest(); + xhr.open('GET', 'https://www.googleapis.com/drive/v3/files'); + xhr.setRequestHeader( + 'Authorization', 'Bearer ' + localStorage.driveToken); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + try { + const res = JSON.parse(xhr.responseText); + if (res.error) { + if (res.error.code === 401) { + localStorage.driveToken = ''; + resolve(true); + } + } else { + resolve(false); + } + } catch (error) { + console.error(error); + reject(error); + } + } + return; + }; + xhr.send(); + })) { + await this.refreshToken(); + } + return localStorage.driveToken; + } + + async refreshToken() { + if (navigator.userAgent.indexOf('Chrome') !== -1) { + return new Promise((resolve: (value: boolean) => void) => { + return chrome.identity.getAuthToken( + { + 'interactive': false, + 'scopes': ['https://www.googleapis.com/auth/drive.file'] + }, + (token) => { + localStorage.driveToken = token; + resolve(Boolean(token)); + }); + }); + } else { + return new Promise( + (resolve: (value: boolean) => void, + reject: (reason: Error) => void) => { + const xhr = new XMLHttpRequest(); + xhr.open( + 'POST', + 'https://www.googleapis.com/oauth2/v4/token?client_id=' + + getCredentials().drive.client_id + + '&client_secret=' + getCredentials().drive.client_secret + + '&refresh_token=' + localStorage.driveRefreshToken + + '&grant_type=refresh_token'); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 401) { + localStorage.removeItem('driveRefreshToken'); + resolve(false); + } + try { + const res = JSON.parse(xhr.responseText); + if (res.error) { + console.error(res.error_description); + resolve(false); + } else { + localStorage.driveToken = res.access_token; + resolve(true); + } + } catch (error) { + console.error(error); + reject(error); + } + } + return; + }; + xhr.send(); + }); + } + } + + private async getFolder() { + const token = await this.getToken(); + if (localStorage.driveFolder) { + await new Promise( + (resolve: (value: boolean) => void, + reject: (reason: Error) => void) => { + const xhr = new XMLHttpRequest(); + xhr.open( + 'GET', + 'https://www.googleapis.com/drive/v3/files/' + + localStorage.driveFolder + '?fields=trashed'); + xhr.setRequestHeader('Authorization', 'Bearer ' + token); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 401) { + localStorage.removeItem('driveToken'); + resolve(false); + } + try { + const res = JSON.parse(xhr.responseText); + if (res.error) { + if (res.error.code === 404) { + localStorage.driveFolder = ''; + resolve(true); + } + } else if (res.trashed) { + localStorage.driveFolder = ''; + resolve(true); + } else if (res.error) { + console.error(res.error.message); + resolve(false); + } else { + resolve(true); + } + } catch (error) { + console.error(error); + reject(error); + } + } + return; + }; + xhr.send(); + }); + } + if (!localStorage.driveFolder) { + await new Promise( + (resolve: (value: boolean) => void, + reject: (reason: Error) => void) => { + // create folder + const xhr = new XMLHttpRequest(); + xhr.open('POST', 'https://www.googleapis.com/drive/v3/files/'); + xhr.setRequestHeader('Authorization', 'Bearer ' + token); + xhr.setRequestHeader('Accept', 'application/json'); + xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 401) { + localStorage.removeItem('driveToken'); + resolve(false); + } + try { + const res = JSON.parse(xhr.responseText); + if (!res.error) { + localStorage.driveFolder = res.id; + resolve(true); + } else { + console.error(res.error.message); + resolve(false); + } + } catch (error) { + console.error(error); + reject(error); + } + } + return; + }; + xhr.send(JSON.stringify({ + 'name': 'Authenticator Backups', + 'mimeType': 'application/vnd.google-apps.folder' + })); + }); + } + return localStorage.driveFolder; + } + + async upload(encryption: Encryption) { + if (localStorage.driveEncrypted === undefined) { + localStorage.driveEncrypted = 'true'; + } + const exportData = await EntryStorage.getExport( + encryption, (localStorage.driveEncrypted === 'true')); + const backup = JSON.stringify(exportData, null, 2); + + const token = await this.getToken(); + const folderId = await this.getFolder(); + return new Promise( + (resolve: (value: boolean) => void, + reject: (reason: Error) => void) => { + if (!token || !folderId) { + resolve(false); + } + try { + const xhr = new XMLHttpRequest(); + const now = + (new Date()).toISOString().slice(0, 10).replace(/-/g, ''); + xhr.open( + 'POST', + 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart'); + xhr.setRequestHeader('Authorization', 'Bearer ' + token); + xhr.setRequestHeader( + 'Content-type', 'multipart/related; boundary=segment_marker'); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status === 401) { + localStorage.removeItem('driveToken'); + resolve(false); + } + try { + const res = JSON.parse(xhr.responseText); + if (!res.error) { + resolve(true); + } else { + console.error(res.error.message); + resolve(false); + } + } catch (error) { + reject(error); + } + } + return; + }; + const requestDataPrototype = [ + '--segment_marker', + 'Content-Type: application/json; charset=UTF-8', '', + JSON.stringify( + {name: `${now}.json`, parents: [localStorage.driveFolder]}), + '', '--segment_marker', 'Content-Type: application/octet-stream', + '', backup, '--segment_marker--' + ]; + let requestData = ''; + requestDataPrototype.forEach((line) => { + requestData = requestData + line + '\n'; + }); + xhr.send(requestData); + } catch (error) { + return reject(error); + } + }); + } +} \ No newline at end of file diff --git a/src/models/credentials.ts b/src/models/credentials.ts new file mode 100644 index 000000000..b5a1134f4 --- /dev/null +++ b/src/models/credentials.ts @@ -0,0 +1,12 @@ +function getCredentials() { + return { + drive: { + client_id: + '292457304165-ria4acohb2i875o1kmda5a31vkan7rj7.apps.googleusercontent.com', // Google client ID + client_secret: 'Bs41C5BTQ40o4jVYsutNXGFt' // Google client secret + }, + dropbox: { + client_id: 'mmx38seexw3tvps' // Dropbox client ID + } + }; +} \ No newline at end of file diff --git a/src/models/dropbox.ts b/src/models/dropbox.ts deleted file mode 100644 index c240106d9..000000000 --- a/src/models/dropbox.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* tslint:disable:no-reference */ -/// -/// -/// - -class Dropbox { - async getToken() { - return localStorage.dropboxToken || ''; - } - - async upload(encryption: Encryption) { - const exportData = await EntryStorage.getExport(encryption); - for (const hash of Object.keys(exportData)) { - if (exportData[hash].encrypted) { - throw new Error('Error passphrase.'); - } - } - const backup = JSON.stringify(exportData, null, 2); - - const url = 'https://content.dropboxapi.com/2/files/upload'; - const token = await this.getToken(); - return new Promise( - (resolve: (value: boolean) => void, - reject: (reason: Error) => void) => { - if (!token) { - resolve(false); - } - try { - const xhr = new XMLHttpRequest(); - const now = - (new Date()).toISOString().slice(0, 10).replace(/-/g, ''); - const apiArg = { - path: `/${now}.json`, - mode: 'add', - autorename: true - }; - xhr.open('POST', url); - xhr.setRequestHeader('Authorization', 'Bearer ' + token); - xhr.setRequestHeader('Content-type', 'application/octet-stream'); - xhr.setRequestHeader('Dropbox-API-Arg', JSON.stringify(apiArg)); - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if (xhr.status === 401) { - localStorage.removeItem('dropboxToken'); - resolve(false); - } - try { - const res = JSON.parse(xhr.responseText); - if (res.name) { - resolve(true); - } else { - resolve(false); - } - } catch (error) { - reject(error); - } - } - return; - }; - xhr.send(backup); - } catch (error) { - return reject(error); - } - }); - } -} diff --git a/src/models/storage.ts b/src/models/storage.ts index 346824670..5f82966e4 100644 --- a/src/models/storage.ts +++ b/src/models/storage.ts @@ -3,6 +3,82 @@ /// /// +class BrowserStorage { + private static getStorageLocation() { + if (localStorage.storageLocation !== 'sync' && + localStorage.storageLocation !== 'local') { + return new Promise((resolve, reject) => { + let amountSync: number; + let amountLocal: number; + chrome.storage.local.get((local) => { + amountLocal = Object.keys(local).length; + try { + chrome.storage.sync.get((sync) => { + amountSync = Object.keys(sync).length; + // If storage location can't be found try to auto-detect storage + // location + if (amountLocal > amountSync && amountSync === 0) { + localStorage.storageLocation = 'local'; + } else if (amountLocal < amountSync && amountLocal === 0) { + localStorage.storageLocation = 'sync'; + } else { + // Use default + if (navigator.userAgent.indexOf('Edge') !== -1) { + localStorage.storageLocation = 'local'; + } else { + localStorage.storageLocation = 'sync'; + } + } + resolve(localStorage.storageLocation); + return; + }); + } catch (error) { + reject(error); + return; + } + }); + }); + } else { + return new Promise((resolve) => { + resolve(localStorage.storageLocation); + return; + }); + } + } + + /* tslint:disable-next-line:no-any */ + static async get(callback: (items: {[key: string]: any;}) => void) { + const storageLocation = await this.getStorageLocation(); + if (storageLocation === 'local') { + chrome.storage.local.get(callback); + } else if (storageLocation === 'sync') { + chrome.storage.sync.get(callback); + } + return; + } + + static async set(data: object, callback?: (() => void)|undefined) { + const storageLocation = await this.getStorageLocation(); + if (storageLocation === 'local') { + chrome.storage.local.set(data, callback); + } else if (storageLocation === 'sync') { + chrome.storage.sync.set(data, callback); + } + return; + } + + static async remove( + data: string|string[], callback?: (() => void)|undefined) { + const storageLocation = await this.getStorageLocation(); + if (storageLocation === 'local') { + chrome.storage.local.remove(data, callback); + } else if (storageLocation === 'sync') { + chrome.storage.sync.remove(data, callback); + } + return; + } +} + class EntryStorage { private static getOTPStorageFromEntry( encryption: Encryption, entry: OTPEntry): OTPStorage { @@ -69,7 +145,7 @@ class EntryStorage { return new Promise( (resolve: (value: boolean) => void, reject: (reason: Error) => void) => { - chrome.storage.sync.get((_data: {[hash: string]: OTPStorage}) => { + BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { for (const hash of Object.keys(_data)) { if (!this.isValidEntry(_data, hash)) { continue; @@ -89,7 +165,7 @@ class EntryStorage { (resolve: (value: {[hash: string]: OTPStorage}) => void, reject: (reason: Error) => void) => { try { - chrome.storage.sync.get((_data: {[hash: string]: OTPStorage}) => { + BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { for (const hash of Object.keys(_data)) { if (!this.isValidEntry(_data, hash)) { delete _data[hash]; @@ -127,7 +203,7 @@ class EntryStorage { return new Promise( (resolve: () => void, reject: (reason: Error) => void) => { try { - chrome.storage.sync.get((_data: {[hash: string]: OTPStorage}) => { + BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { for (const hash of Object.keys(data)) { // never trust data import from user // we do not support encrypted data import any longer @@ -197,7 +273,7 @@ class EntryStorage { } } _data = this.ensureUniqueIndex(_data); - chrome.storage.sync.set(_data, resolve); + BrowserStorage.set(_data, resolve); }); return; } catch (error) { @@ -210,7 +286,7 @@ class EntryStorage { return new Promise( (resolve: () => void, reject: (reason: Error) => void) => { try { - chrome.storage.sync.get((_data: {[hash: string]: OTPStorage}) => { + BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { if (_data.hasOwnProperty(entry.hash)) { throw new Error('The specific entry has already existed.'); } @@ -218,7 +294,7 @@ class EntryStorage { this.getOTPStorageFromEntry(encryption, entry); _data[entry.hash] = storageItem; _data = this.ensureUniqueIndex(_data); - chrome.storage.sync.set(_data, resolve); + BrowserStorage.set(_data, resolve); }); return; } catch (error) { @@ -231,7 +307,7 @@ class EntryStorage { return new Promise( (resolve: () => void, reject: (reason: Error) => void) => { try { - chrome.storage.sync.get((_data: {[hash: string]: OTPStorage}) => { + BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { if (!_data.hasOwnProperty(entry.hash)) { throw new Error('The specific entry is not existing.'); } @@ -239,7 +315,7 @@ class EntryStorage { this.getOTPStorageFromEntry(encryption, entry); _data[entry.hash] = storageItem; _data = this.ensureUniqueIndex(_data); - chrome.storage.sync.set(_data, resolve); + BrowserStorage.set(_data, resolve); }); return; } catch (error) { @@ -252,14 +328,14 @@ class EntryStorage { return new Promise( (resolve: () => void, reject: (reason: Error) => void) => { try { - chrome.storage.sync.get((_data: {[hash: string]: OTPStorage}) => { + BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { entries.forEach(entry => { const storageItem = this.getOTPStorageFromEntry(encryption, entry); _data[entry.hash] = storageItem; }); _data = this.ensureUniqueIndex(_data); - chrome.storage.sync.set(_data, resolve); + BrowserStorage.set(_data, resolve); }); return; } catch (error) { @@ -273,143 +349,138 @@ class EntryStorage { (resolve: (value: OTPEntry[]) => void, reject: (reason: Error) => void) => { try { - chrome.storage.sync.get( - async (_data: {[hash: string]: OTPStorage}) => { - const data: OTPEntry[] = []; - for (const hash of Object.keys(_data)) { - if (!this.isValidEntry(_data, hash)) { - continue; - } - const entryData = _data[hash]; - let needMigrate = false; + BrowserStorage.get(async (_data: {[hash: string]: OTPStorage}) => { + const data: OTPEntry[] = []; + for (const hash of Object.keys(_data)) { + if (!this.isValidEntry(_data, hash)) { + continue; + } + const entryData = _data[hash]; + let needMigrate = false; - if (!entryData.hash) { - entryData.hash = hash; - needMigrate = true; - } + if (!entryData.hash) { + entryData.hash = hash; + needMigrate = true; + } - if (!entryData.type) { - entryData.type = OTPType[OTPType.totp]; - needMigrate = true; - } + if (!entryData.type) { + entryData.type = OTPType[OTPType.totp]; + needMigrate = true; + } - let type: OTPType; - switch (entryData.type) { - case 'totp': - case 'hotp': - case 'battle': - case 'steam': - case 'hex': - case 'hhex': - type = OTPType[entryData.type]; - break; - default: - // we need correct the type here - // and save it - type = OTPType.totp; - entryData.type = OTPType[OTPType.totp]; - needMigrate = true; - } + let type: OTPType; + switch (entryData.type) { + case 'totp': + case 'hotp': + case 'battle': + case 'steam': + case 'hex': + case 'hhex': + type = OTPType[entryData.type]; + break; + default: + // we need correct the type here + // and save it + type = OTPType.totp; + entryData.type = OTPType[OTPType.totp]; + needMigrate = true; + } - let period = 30; - if (entryData.type === OTPType[OTPType.totp] && - entryData.period && entryData.period > 0) { - period = entryData.period; - } + let period = 30; + if (entryData.type === OTPType[OTPType.totp] && + entryData.period && entryData.period > 0) { + period = entryData.period; + } - entryData.secret = entryData.encrypted ? - encryption.getDecryptedSecret(entryData.secret, hash) : - entryData.secret; - - // we need migrate secret in old format here - if (/^(blz\-|bliz\-)/.test(entryData.secret)) { - const secretMatches = - entryData.secret.match(/^(blz\-|bliz\-)(.*)/); - if (secretMatches && secretMatches.length >= 3) { - entryData.secret = secretMatches[2]; - entryData.type = OTPType[OTPType.battle]; - entryData.hash = - CryptoJS.MD5(entryData.secret).toString(); - await this.remove(hash); - needMigrate = true; - } - } + entryData.secret = entryData.encrypted ? + encryption.getDecryptedSecret(entryData.secret, hash) : + entryData.secret; - if (/^stm\-/.test(entryData.secret)) { - const secretMatches = - entryData.secret.match(/^stm\-(.*)/); - if (secretMatches && secretMatches.length >= 2) { - entryData.secret = secretMatches[1]; - entryData.type = OTPType[OTPType.steam]; - entryData.hash = - CryptoJS.MD5(entryData.secret).toString(); - await this.remove(hash); - needMigrate = true; - } - } + // we need migrate secret in old format here + if (/^(blz\-|bliz\-)/.test(entryData.secret)) { + const secretMatches = + entryData.secret.match(/^(blz\-|bliz\-)(.*)/); + if (secretMatches && secretMatches.length >= 3) { + entryData.secret = secretMatches[2]; + entryData.type = OTPType[OTPType.battle]; + entryData.hash = CryptoJS.MD5(entryData.secret).toString(); + await this.remove(hash); + needMigrate = true; + } + } - if (!/^[a-z2-7]+=*$/i.test(entryData.secret) && - /^[0-9a-f]+$/i.test(entryData.secret) && - entryData.type === OTPType[OTPType.totp]) { - entryData.type = OTPType[OTPType.hex]; - needMigrate = true; - } + if (/^stm\-/.test(entryData.secret)) { + const secretMatches = entryData.secret.match(/^stm\-(.*)/); + if (secretMatches && secretMatches.length >= 2) { + entryData.secret = secretMatches[1]; + entryData.type = OTPType[OTPType.steam]; + entryData.hash = CryptoJS.MD5(entryData.secret).toString(); + await this.remove(hash); + needMigrate = true; + } + } - if (!/^[a-z2-7]+=*$/i.test(entryData.secret) && - /^[0-9a-f]+$/i.test(entryData.secret) && - entryData.type === OTPType[OTPType.hotp]) { - entryData.type = OTPType[OTPType.hhex]; - needMigrate = true; - } + if (!/^[a-z2-7]+=*$/i.test(entryData.secret) && + /^[0-9a-f]+$/i.test(entryData.secret) && + entryData.type === OTPType[OTPType.totp]) { + entryData.type = OTPType[OTPType.hex]; + needMigrate = true; + } - const entry = new OTPEntry( - type, entryData.issuer, entryData.secret, - entryData.account, entryData.index, entryData.counter, - period, entryData.hash); - data.push(entry); - - // we need correct the hash - - // Do not correct hash, wrong password - // may not only 'Encrypted', but also - // other wrong secret. We cannot know - // if the hash doesn't match the correct - // secret - - // Only correct invalid hash here - - if (entry.secret !== 'Encrypted' && - !/^[0-9a-f]{32}$/.test(hash)) { - const _hash = CryptoJS.MD5(entryData.secret).toString(); - if (hash !== _hash) { - await this.remove(hash); - entryData.hash = _hash; - needMigrate = true; - } - } + if (!/^[a-z2-7]+=*$/i.test(entryData.secret) && + /^[0-9a-f]+$/i.test(entryData.secret) && + entryData.type === OTPType[OTPType.hotp]) { + entryData.type = OTPType[OTPType.hhex]; + needMigrate = true; + } - if (needMigrate) { - const _entry: {[hash: string]: OTPStorage} = {}; - _entry[entryData.hash] = entryData; - _entry[entryData.hash].encrypted = false; - this.import(encryption, _entry); - } - } + const entry = new OTPEntry( + type, entryData.issuer, entryData.secret, entryData.account, + entryData.index, entryData.counter, period, entryData.hash); + data.push(entry); - data.sort((a, b) => { - return a.index - b.index; - }); + // we need correct the hash - for (let i = 0; i < data.length; i++) { - if (data[i].index !== i) { - const exportData = await this.getExport(encryption); - await this.import(encryption, exportData); - break; - } + // Do not correct hash, wrong password + // may not only 'Encrypted', but also + // other wrong secret. We cannot know + // if the hash doesn't match the correct + // secret + + // Only correct invalid hash here + + if (entry.secret !== 'Encrypted' && + !/^[0-9a-f]{32}$/.test(hash)) { + const _hash = CryptoJS.MD5(entryData.secret).toString(); + if (hash !== _hash) { + await this.remove(hash); + entryData.hash = _hash; + needMigrate = true; } + } - return resolve(data); - }); + if (needMigrate) { + const _entry: {[hash: string]: OTPStorage} = {}; + _entry[entryData.hash] = entryData; + _entry[entryData.hash].encrypted = false; + this.import(encryption, _entry); + } + } + + data.sort((a, b) => { + return a.index - b.index; + }); + + for (let i = 0; i < data.length; i++) { + if (data[i].index !== i) { + const exportData = await this.getExport(encryption); + await this.import(encryption, exportData); + break; + } + } + + return resolve(data); + }); return; } catch (error) { return reject(error); @@ -420,7 +491,7 @@ class EntryStorage { static async remove(hash: string) { return new Promise( (resolve: () => void, reject: (reason: Error) => void) => { - return chrome.storage.sync.remove(hash, resolve); + return BrowserStorage.remove(hash, resolve); }); } @@ -428,13 +499,13 @@ class EntryStorage { return new Promise( (resolve: () => void, reject: (reason: Error) => void) => { try { - chrome.storage.sync.get((_data: {[hash: string]: OTPStorage}) => { + BrowserStorage.get((_data: {[hash: string]: OTPStorage}) => { if (_data.hasOwnProperty(entry.hash)) { delete _data[entry.hash]; } _data = this.ensureUniqueIndex(_data); - chrome.storage.sync.remove(entry.hash, () => { - chrome.storage.sync.set(_data, resolve); + BrowserStorage.remove(entry.hash, () => { + BrowserStorage.set(_data, resolve); }); return; }); diff --git a/src/popup.ts b/src/popup.ts index a3fadf618..17b35fd00 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -7,9 +7,10 @@ /// /// /// +/// /// /// -/// +/// async function init() { const ui = new UI({el: '#authenticator'}); @@ -23,6 +24,7 @@ async function init() { .load(qr) .load(message) .load(addAccount) + .load(backup) .render(); try { @@ -60,7 +62,7 @@ async function init() { } else if ( clientTime - localStorage.lastRemindingBackupTime >= 30 || clientTime - localStorage.lastRemindingBackupTime < 0) { - // backup to Dropbox + // backup to cloud if (authenticator.dropboxToken) { chrome.permissions.contains( {origins: ['https://*.dropboxapi.com/*']}, @@ -82,6 +84,30 @@ async function init() { authenticator.alert(authenticator.i18n.remind_backup); localStorage.lastRemindingBackupTime = clientTime; }); + } else if (authenticator.driveToken) { + chrome.permissions.contains( + { + origins: [ + 'https://www.googleapis.com/*', + 'https://accounts.google.com/o/oauth2/revoke' + ] + }, + async (hasPermission) => { + if (hasPermission) { + try { + const drive = new Drive(); + const res = await drive.upload(authenticator.encryption); + if (res) { + localStorage.lastRemindingBackupTime = clientTime; + return; + } + } catch (error) { + // ignore + } + } + authenticator.alert(authenticator.i18n.remind_backup); + localStorage.lastRemindingBackupTime = clientTime; + }); } else { authenticator.alert(authenticator.i18n.remind_backup); localStorage.lastRemindingBackupTime = clientTime; @@ -100,16 +126,22 @@ async function init() { } chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { - if (message.action === 'dropboxtoken') { - authenticator.dropboxToken = message.value; - authenticator.dropboxUpload(); - if (authenticator.info === 'dropbox') { + if (['dropboxtoken', 'drivetoken'].indexOf(message.action) > -1) { + if (message.action === 'dropboxtoken') { + authenticator.dropboxToken = message.value; + } else if (message.account === 'drivetoken') { + authenticator.driveToken = message.value; + } + authenticator.backupUpload( + String(message.action) + .substring(0, String(message.action).indexOf('token'))); + if (['dropbox', 'drive'].indexOf(authenticator.info) > -1) { setTimeout(authenticator.closeInfo, 500); } } }); - if (ui.instance.isPopup()) { + if (ui.instance.isPopup() && !ui.instance.isEdge()) { ui.instance.fixPopupSize(); } diff --git a/src/ui/backup.ts b/src/ui/backup.ts new file mode 100644 index 000000000..f483087a1 --- /dev/null +++ b/src/ui/backup.ts @@ -0,0 +1,83 @@ +/* tslint:disable:no-reference */ +/// +/// + +async function backup(_ui: UI) { + const ui: UIConfig = { + data: { + dropboxEncrypted: localStorage.dropboxEncrypted, + driveEncrypted: localStorage.driveEncrypted, + dropboxToken: localStorage.dropboxToken || '', + driveToken: localStorage.driveToken || '' + }, + methods: { + backupUpload: async (service: string) => { + if (service === 'dropbox') { + const dbox = new Dropbox(); + const response = await dbox.upload(_ui.instance.encryption); + if (response === true) { + _ui.instance.alert(_ui.instance.i18n.updateSuccess); + } else { + _ui.instance.alert(_ui.instance.i18n.updateFailure); + } + } else if (service === 'drive') { + const drive = new Drive(); + const response = await drive.upload(_ui.instance.encryption); + if (response === true) { + _ui.instance.alert(_ui.instance.i18n.updateSuccess); + } else { + _ui.instance.alert(_ui.instance.i18n.updateFailure); + } + } + }, + backupUpdateEncryption: (service: string) => { + if (service === 'dropbox') { + localStorage.dropboxEncrypted = _ui.instance.dropboxEncrypted; + } else if (service === 'drive') { + localStorage.driveEncrypted = _ui.instance.driveEncrypted; + } + }, + backupLogout: async (service: string) => { + if (service === 'dropbox') { + _ui.instance.dropboxToken = ''; + await new Promise((resolve: (value: boolean) => void) => { + const xhr = new XMLHttpRequest(); + xhr.open('POST', 'https://api.dropboxapi.com/2/auth/token/revoke'); + xhr.setRequestHeader( + 'Authorization', 'Bearer ' + localStorage.dropboxToken); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + resolve(true); + return; + } + }; + xhr.send(); + }); + } else if (service === 'drive') { + _ui.instance.driveToken = ''; + await new Promise((resolve: (value: boolean) => void) => { + const xhr = new XMLHttpRequest(); + xhr.open( + 'POST', + 'https://accounts.google.com/o/oauth2/revoke?token=' + + localStorage.driveRefreshToken); + xhr.onreadystatechange = () => { + if (xhr.readyState === 4) { + resolve(true); + return; + } + }; + xhr.send(); + }); + } + localStorage.removeItem(service + 'Token'); + setTimeout(_ui.instance.closeInfo, 500); + }, + getBackupToken: (service: string) => { + chrome.runtime.sendMessage({action: service}); + }, + } + }; + + _ui.update(ui); +} diff --git a/src/ui/info.ts b/src/ui/info.ts index 232183d10..cff20d728 100644 --- a/src/ui/info.ts +++ b/src/ui/info.ts @@ -4,16 +4,8 @@ async function info(_ui: UI) { const ui: UIConfig = { - data: {info: '', dropboxToken: localStorage.dropboxToken || ''}, + data: {info: ''}, methods: { - getDropboxToken: () => { - chrome.runtime.sendMessage({action: 'dropbox'}); - }, - logoutDropbox: async () => { - localStorage.removeItem('dropboxToken'); - _ui.instance.dropboxToken = ''; - _ui.instance.openLink('https://www.dropbox.com/account/connected_apps'); - }, showInfo: (tab: string) => { if (tab === 'export' || tab === 'security') { const entries = _ui.instance.entries as OTPEntry[]; @@ -28,6 +20,12 @@ async function info(_ui: UI) { } } } else if (tab === 'dropbox') { + if (localStorage.dropboxEncrypted !== 'true' && + localStorage.dropboxEncrypted !== 'false') { + localStorage.dropboxEncrypted = 'true'; + _ui.instance.dropboxEncrypted = localStorage.dropboxEncrypted; + } + chrome.permissions.request( {origins: ['https://*.dropboxapi.com/*']}, async (granted) => { if (granted) { @@ -38,6 +36,33 @@ async function info(_ui: UI) { return; }); return; + } else if (tab === 'drive') { + if (localStorage.driveEncrypted !== 'true' && + localStorage.driveEncrypted !== 'false') { + localStorage.driveEncrypted = 'true'; + _ui.instance.driveEncrypted = localStorage.driveEncrypted; + } + chrome.permissions.request( + { + origins: [ + 'https://www.googleapis.com/*', + 'https://accounts.google.com/o/oauth2/revoke' + ] + }, + async (granted) => { + if (granted) { + _ui.instance.class.fadein = true; + _ui.instance.class.fadeout = false; + _ui.instance.info = tab; + } + return; + }); + return; + } else if (tab === 'storage') { + if (_ui.instance.newStorageLocation !== 'sync' && + _ui.instance.newStorageLocation !== 'local') { + _ui.instance.newStorageLocation = localStorage.storageLocation; + } } _ui.instance.class.fadein = true; diff --git a/src/ui/menu.ts b/src/ui/menu.ts index c98200ef1..e2746e9a3 100644 --- a/src/ui/menu.ts +++ b/src/ui/menu.ts @@ -1,6 +1,6 @@ /* tslint:disable:no-reference */ /// -/// +/// /// function getVersion() { @@ -11,7 +11,8 @@ async function syncTimeWithGoogle() { return new Promise( (resolve: (value: string) => void, reject: (reason: Error) => void) => { try { - const xhr = new XMLHttpRequest(); + // @ts-ignore + const xhr = new XMLHttpRequest({'mozAnon': true}); xhr.open('HEAD', 'https://www.google.com/generate_204'); const xhrAbort = setTimeout(() => { xhr.abort(); @@ -74,7 +75,12 @@ async function menu(_ui: UI) { let useAutofill = (localStorage.autofill === 'true'); const ui: UIConfig = { - data: {version, zoom, useAutofill}, + data: { + version, + zoom, + useAutofill, + newStorageLocation: localStorage.storageLocation + }, methods: { openLink: (url: string) => { window.open(url, '_blank'); @@ -173,19 +179,74 @@ async function menu(_ui: UI) { const correctWidth = 320 * zoom; if (window.innerHeight !== correctHeight || window.innerWidth !== correctWidth) { - chrome.windows.getCurrent((currentWindow) => { - chrome.windows.update( - currentWindow.id, {height: correctHeight, width: correctWidth}); - }); + // window update to correct size + const adjustedHeight = + correctHeight + (window.outerHeight - window.innerHeight); + const adjustedWidth = + correctWidth + (window.outerWidth - window.innerWidth); + chrome.windows.update( + chrome.windows.WINDOW_ID_CURRENT, + {height: adjustedHeight, width: adjustedWidth}); } }, - dropboxUpload: async () => { - const dbox = new Dropbox(); - const response = await dbox.upload(_ui.instance.encryption); - if (response === true) { - _ui.instance.alert(_ui.instance.i18n.updateSuccess); + migrateStorage: async () => { + // sync => local + if (localStorage.storageLocation === 'sync' && + _ui.instance.newStorageLocation === 'local') { + return new Promise((resolve, reject) => { + chrome.storage.sync.get(syncData => { + chrome.storage.local.set(syncData, () => { + chrome.storage.local.get((localData) => { + // Double check if data was set + if (Object.keys(syncData).every( + (value) => + Object.keys(localData).indexOf(value) >= 0)) { + localStorage.storageLocation = 'local'; + chrome.storage.sync.clear(); + _ui.instance.alert(_ui.instance.i18n.updateSuccess); + resolve(); + return; + } else { + _ui.instance.alert( + _ui.instance.i18n.updateFailure + + ' All data not transferred successfully.'); + reject('Transfer failure'); + return; + } + }); + }); + }); + }); + // local => sync + } else if ( + localStorage.storageLocation === 'local' && + _ui.instance.newStorageLocation === 'sync') { + return new Promise((resolve, reject) => { + chrome.storage.local.get(localData => { + chrome.storage.sync.set(localData, () => { + chrome.storage.sync.get((syncData) => { + // Double check if data was set + if (Object.keys(localData).every( + (value) => + Object.keys(syncData).indexOf(value) >= 0)) { + localStorage.storageLocation = 'sync'; + chrome.storage.local.clear(); + _ui.instance.alert(_ui.instance.i18n.updateSuccess); + resolve(); + return; + } else { + _ui.instance.alert( + _ui.instance.i18n.updateFailure + + ' All data not transferred successfully.'); + reject('Transfer failure'); + return; + } + }); + }); + }); + }); } else { - _ui.instance.alert(_ui.instance.i18n.updateFailure); + return; } } } diff --git a/view/import.html b/view/import.html index e359738bd..0c1937334 100644 --- a/view/import.html +++ b/view/import.html @@ -16,7 +16,7 @@ - +
diff --git a/view/popup.html b/view/popup.html index 314ee1215..6ac69112a 100644 --- a/view/popup.html +++ b/view/popup.html @@ -15,7 +15,8 @@ - + +
@@ -24,7 +25,7 @@
-
+
@@ -77,7 +78,7 @@