diff --git a/.gitignore b/.gitignore
index d2335a07e..9528693d0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ build
chrome*
firefox*
edge*
+safari*
dist
.vscode
.atom-build.yml
diff --git a/manifests/manifest-safari.json b/manifests/manifest-safari.json
new file mode 100644
index 000000000..e4e650533
--- /dev/null
+++ b/manifests/manifest-safari.json
@@ -0,0 +1,76 @@
+{
+ "manifest_version": 3,
+ "name": "__MSG_extName__",
+ "short_name": "__MSG_extShortName__",
+ "version": "8.0.2",
+ "default_locale": "en",
+ "description": "__MSG_extDesc__",
+ "icons": {
+ "16": "images/icon16.png",
+ "48": "images/icon48.png",
+ "128": "images/icon128.png"
+ },
+ "action": {
+ "default_icon": {
+ "19": "images/icon19.png",
+ "38": "images/icon38.png"
+ },
+ "default_title": "__MSG_extShortName__",
+ "default_popup": "view/popup.html"
+ },
+ "commands": {
+ "_execute_action": {},
+ "scan-qr": {
+ "description": "Scan a QR code"
+ },
+ "autofill": {
+ "description": "Autofill the matched code"
+ }
+ },
+ "options_ui": {
+ "page": "view/options.html",
+ "open_in_tab": false
+ },
+ "storage": {
+ "managed_schema": "schema.json"
+ },
+ "oauth2": {
+ "client_id": "292457304165-u8ve4j79gag5o231n5u2pdtdrbfdo1hh.apps.googleusercontent.com",
+ "scopes": [
+ "https://www.googleapis.com/auth/drive.file"
+ ]
+ },
+ "background": {
+ "service_worker": "dist/background.js"
+ },
+ "sandbox": {
+ "pages": [
+ "view/argon.html"
+ ]
+ },
+ "permissions": [
+ "activeTab",
+ "storage",
+ "identity",
+ "alarms",
+ "scripting"
+ ],
+ "host_permissions": [
+ "https://www.google.com/",
+ "https://www.googleapis.com/*",
+ "https://accounts.google.com/*"
+ ],
+ "optional_permissions": [
+ "clipboardWrite",
+ "contextMenus"
+ ],
+ "optional_host_permissions": [
+ "https://*.dropboxapi.com/*",
+ "https://graph.microsoft.com/me/*",
+ "https://login.microsoftonline.com/common/oauth2/v2.0/token"
+ ],
+ "content_security_policy": {
+ "extension_pages": "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/ https://oauth2.googleapis.com/ https://login.microsoftonline.com/common/oauth2/v2.0/token https://graph.microsoft.com/; default-src 'none'",
+ "sandbox": "sandbox allow-scripts; script-src 'self' 'unsafe-eval';"
+ }
+}
diff --git a/package.json b/package.json
index 4970fa5be..f1cdd4cc2 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,7 @@
"compile": "bash scripts/build.sh firefox",
"dev:chrome": "npm run pretest && webpack --config ./webpack.watch.js",
"chrome": "bash scripts/build.sh chrome",
+ "safari": "bash scripts/build.sh safari",
"firefox": "bash scripts/build.sh firefox",
"edge": "bash scripts/build.sh edge",
"prod": "bash scripts/build.sh prod",
diff --git a/scripts/build.sh b/scripts/build.sh
index 3da00c0e2..80a324976 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -13,14 +13,14 @@ CREDREGEX='^.*".+".*".+".*".+".*".+".*".+".*$'
STYLEFILES="./src/* ./src/**/* ./src/**/**/* ./src/**/**/**/* ./sass/*.scss"
set -e
-if [[ $PLATFORM != "chrome" ]] && [[ $PLATFORM != "firefox" ]] && [[ $PLATFORM != "edge" ]] && [[ $PLATFORM != "prod" ]] && [[ $PLATFORM != "test" ]]; then
- echo "Invalid platform type. Supported platforms are 'chrome', 'firefox', 'test', and 'prod'"
+if [[ $PLATFORM != "chrome" ]] && [[ $PLATFORM != "firefox" ]] && [[ $PLATFORM != "edge" ]] && [[ $PLATFORM != "safari" ]] && [[ $PLATFORM != "prod" ]] && [[ $PLATFORM != "test" ]]; then
+ echo "Invalid platform type. Supported platforms are 'chrome', 'firefox', 'safari', 'test', and 'prod'"
exit 1
fi
echo "Removing old build files..."
rm -rf build dist
-rm -rf firefox chrome edge release test
+rm -rf firefox chrome edge safari release test
echo "Checking style..."
if ./node_modules/.bin/prettier --check $STYLEFILES 1> /dev/null ; then
true
@@ -39,7 +39,7 @@ if ! [[ $CREDS =~ $CREDREGEX ]] ; then
fi
fi
-if ! [[ $REMOTE = *"https://github.com/Authenticator-Extension/Authenticator.git"* || $REMOTE = *"git@github.com:Authenticator-Extension/Authenticator.git"* || $CI ]] ; then
+if [[ $PLATFORM != "safari" ]] && ! [[ $REMOTE = *"https://github.com/Authenticator-Extension/Authenticator.git"* || $REMOTE = *"git@github.com:Authenticator-Extension/Authenticator.git"* || $CI ]] ; then
echo
echo -e "\e[7m\033[33mNotice\033[0m"
echo
@@ -80,7 +80,7 @@ postCompile () {
cp manifests/manifest-$1.json $1/manifest.json
fi
- if [[ $1 = "chrome" ]] || [[ $1 = "edge" ]]; then
+ if [[ $1 = "chrome" ]] || [[ $1 = "edge" ]] || [[ $1 = "safari" ]]; then
cp manifests/schema-chrome.json $1/schema.json
fi
diff --git a/src/argon.ts b/src/argon.ts
index ea27b3967..5693cabb2 100644
--- a/src/argon.ts
+++ b/src/argon.ts
@@ -1,10 +1,18 @@
import argon2 from "argon2-browser";
+function isExtensionOrigin(origin: string): boolean {
+ return (
+ origin.indexOf("chrome-extension://") === 0 ||
+ origin.indexOf("safari-web-extension://") === 0 ||
+ origin.indexOf("moz-extension://") === 0
+ );
+}
+
window.addEventListener("message", (event) => {
const message = event.data;
const source = event.source as Window;
- if (!source) {
+ if (!source || !isExtensionOrigin(event.origin)) {
return;
}
diff --git a/src/background.ts b/src/background.ts
index bb59a2f9d..fb78749eb 100644
--- a/src/background.ts
+++ b/src/background.ts
@@ -11,7 +11,13 @@ import {
import { CodeState } from "./models/otp";
import { getOTPAuthPerLineFromOPTAuthMigration } from "./models/migration";
-import { isChrome, isFirefox } from "./browser";
+import {
+ containingAppId,
+ driveRedirect,
+ isChrome,
+ isFirefox,
+ isSafari,
+} from "./browser";
import { UserSettings } from "./models/settings";
let contentTab: chrome.tabs.Tab | undefined;
@@ -19,6 +25,18 @@ let contentTab: chrome.tabs.Tab | undefined;
chrome.runtime.onMessage.addListener(async (message, sender) => {
await UserSettings.updateItems();
+ if (
+ message.action === "cachePassphrase" ||
+ message.action === "lock" ||
+ message.action === "dropbox" ||
+ message.action === "drive" ||
+ message.action === "onedrive"
+ ) {
+ if (sender.id !== chrome.runtime.id || sender.tab) {
+ return true;
+ }
+ }
+
if (message.action === "getCapture") {
if (!sender.tab) {
return;
@@ -240,7 +258,79 @@ async function getTotp(text: string, silent = false) {
}
}
+async function exchangeDriveCode(code: string, encodedRedirect: string) {
+ const response = await fetch(
+ "https://www.googleapis.com/oauth2/v4/token?client_id=" +
+ getCredentials().drive.client_id +
+ "&client_secret=" +
+ getCredentials().drive.client_secret +
+ "&code=" +
+ code +
+ "&redirect_uri=" +
+ encodedRedirect +
+ "&grant_type=authorization_code",
+ {
+ method: "POST",
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ }
+ );
+ const res = await response.json();
+ if (res.error) {
+ console.error(res.error_description);
+ return false;
+ }
+ UserSettings.items.driveToken = res.access_token;
+ UserSettings.items.driveRefreshToken = res.refresh_token;
+ await UserSettings.commitItems();
+ return true;
+}
+
+function signInDriveSafari() {
+ const encodedRedirect = encodeURIComponent(driveRedirect);
+ const 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=" +
+ encodedRedirect;
+ chrome.runtime.sendNativeMessage(
+ containingAppId,
+ { action: "drive-auth", url: authUrl },
+ async (response: { ok?: boolean; url?: string }) => {
+ if (!response || !response.ok || !response.url) {
+ return;
+ }
+ let callback: URL;
+ try {
+ callback = new URL(response.url);
+ } catch (error) {
+ console.error(error);
+ return;
+ }
+ if (callback.origin + callback.pathname !== driveRedirect) {
+ return;
+ }
+ const code = callback.searchParams.get("code");
+ if (!code) {
+ return;
+ }
+ const exchanged = await exchangeDriveCode(code, encodedRedirect);
+ if (!exchanged) {
+ return;
+ }
+ chrome.runtime.sendMessage({ action: "drivetoken" });
+ uploadBackup("drive");
+ }
+ );
+}
+
function getBackupToken(service: string) {
+ if (isSafari && service === "drive") {
+ signInDriveSafari();
+ return;
+ }
if (isChrome && service === "drive") {
chrome.identity.getAuthToken(
{
diff --git a/src/browser.ts b/src/browser.ts
index c8b9fc7a2..ab1772c36 100644
--- a/src/browser.ts
+++ b/src/browser.ts
@@ -4,6 +4,8 @@ export const isEdge = navigator.userAgent.indexOf("Edg") >= 0;
export const isChromium = navigator.userAgent.indexOf("Chrome") >= 0;
export const isSafari =
!isChromium && navigator.userAgent.indexOf("Safari") >= 0;
+export const containingAppId = "com.yourCompany.Authenticator";
+export const driveRedirect = "https://authenticator.cc/oauth";
export const isChrome =
navigator.userAgent.indexOf("Chrome") !== -1 &&
navigator.userAgent.indexOf("Edg") === -1;
diff --git a/src/components/Popup/BackupPage.vue b/src/components/Popup/BackupPage.vue
index 8e718b46b..358d8765d 100644
--- a/src/components/Popup/BackupPage.vue
+++ b/src/components/Popup/BackupPage.vue
@@ -57,14 +57,24 @@
-