Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ build
chrome*
firefox*
edge*
safari*
dist
.vscode
.atom-build.yml
Expand Down
76 changes: 76 additions & 0 deletions manifests/manifest-safari.json
Original file line number Diff line number Diff line change
@@ -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';"
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 5 additions & 5 deletions scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
10 changes: 9 additions & 1 deletion src/argon.ts
Original file line number Diff line number Diff line change
@@ -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;
}

Expand Down
92 changes: 91 additions & 1 deletion src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,32 @@ 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;

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;
Expand Down Expand Up @@ -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(
{
Expand Down
2 changes: 2 additions & 0 deletions src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
21 changes: 18 additions & 3 deletions src/components/Popup/BackupPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,24 @@
<a-button-link href="import.html">{{ i18n.import_backup }}</a-button-link>
<br />
<!-- 3rd Party Backup Services -->
<div v-show="!backupDisabled && isBackupServiceSupported">
<div v-show="!backupDisabled">
<div class="text">
{{ i18n.storage_sync_info }}
</div>
<p></p>
<a-button @click="showInfo('DrivePage')"> Google Drive </a-button>
<a-button @click="showInfo('OneDrivePage')"> OneDrive </a-button>
<a-button @click="showInfo('DropboxPage')"> Dropbox </a-button>
<a-button
v-show="isBackupServiceSupported"
@click="showInfo('OneDrivePage')"
>
OneDrive
</a-button>
<a-button
v-show="isBackupServiceSupported"
@click="showInfo('DropboxPage')"
>
Dropbox
</a-button>
</div>
</div>
</template>
Expand Down Expand Up @@ -119,6 +129,11 @@ export default Vue.extend({
);
return;
} else if (tab === "DrivePage") {
if (isSafari) {
this.$store.commit("style/showInfo");
this.$store.commit("currentView/changeView", tab);
return;
}
chrome.permissions.request(
{
origins: [
Expand Down
10 changes: 10 additions & 0 deletions src/components/Popup/DrivePage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export default Vue.extend({
xhr.send();
});
UserSettings.removeItem("driveToken");
UserSettings.removeItem("driveRefreshToken");
this.$store.commit("backup/setToken", { service, value: false });
this.$store.commit("style/hideInfo");
},
Expand Down Expand Up @@ -130,6 +131,15 @@ export default Vue.extend({
if (this.backupToken) {
this.email = await this.getUser();
}
chrome.runtime.onMessage.addListener((message) => {
if (message.action !== "drivetoken") {
return;
}
this.$store.commit("backup/setToken", { service, value: true });
this.getUser().then((email: string) => {
this.email = email;
});
});
},
});
</script>
33 changes: 33 additions & 0 deletions src/models/storage.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,43 @@
import { Encryption } from "./encryption";
import { OTPEntry, OTPType, OTPAlgorithm, CodeState } from "./otp";
import { isSafari } from "../browser";
import { StorageLocation, UserSettings } from "./settings";
import { DataType } from "./otp";
export class BrowserStorage {
private static async migrateSyncToLocal(): Promise<void> {
const flag = await chrome.storage.local.get("safariStorageMigrated");
if (flag.safariStorageMigrated === true) {
return;
}
const sync = await chrome.storage.sync.get();
const syncKeys = Object.keys(sync);
const local = await chrome.storage.local.get();
const localKeys = Object.keys(local).filter(
(key) => key !== "safariStorageMigrated"
);
if (syncKeys.length === 0 || localKeys.length > 0) {
await chrome.storage.local.set({ safariStorageMigrated: true });
return;
}
await chrome.storage.local.set(sync);
const copied = await chrome.storage.local.get(syncKeys);
if (Object.keys(copied).length !== syncKeys.length) {
return;
}
await chrome.storage.sync.remove(syncKeys);
await chrome.storage.local.set({ safariStorageMigrated: true });
}

private static async getStorageLocation(): Promise<StorageLocation> {
await UserSettings.updateItems();
if (isSafari) {
await this.migrateSyncToLocal();
if (UserSettings.items.storageLocation !== StorageLocation.Local) {
UserSettings.items.storageLocation = StorageLocation.Local;
await UserSettings.commitItems();
}
return StorageLocation.Local;
}
const managedLocation = await ManagedStorage.get<StorageLocation>(
"storageArea"
);
Expand Down
Loading