Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce google permissions requirement #1286

Merged
merged 2 commits into from
Mar 2, 2024
Merged
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
9 changes: 3 additions & 6 deletions source/main/services/googleDrive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { logInfo, logWarn } from "../library/log";
import {
GOOGLE_AUTH_REDIRECT,
GOOGLE_AUTH_TIMEOUT,
GOOGLE_DRIVE_SCOPES_PERMISSIVE,
GOOGLE_DRIVE_SCOPES_STANDARD,
GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET
Expand All @@ -25,11 +24,9 @@ export function addGoogleTokens(
__googleDriveTokens[sourceID] = tokens;
}

async function authenticateGoogleDrive(
openPermissions: boolean = false
): Promise<{ accessToken: string; refreshToken: string }> {
logInfo(`Authenticating Google Drive (permissive: ${openPermissions ? "yes" : "no"})`);
const scopes = openPermissions ? GOOGLE_DRIVE_SCOPES_PERMISSIVE : GOOGLE_DRIVE_SCOPES_STANDARD;
async function authenticateGoogleDrive(): Promise<{ accessToken: string; refreshToken: string }> {
logInfo("Authenticating Google Drive");
const scopes = GOOGLE_DRIVE_SCOPES_STANDARD;
const oauth2Client = getGoogleDriveOAuthClient();
const url = oauth2Client.generateAuthUrl({
access_type: "offline",
Expand Down
2 changes: 1 addition & 1 deletion source/renderer/actions/addVault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export async function addNewVaultTarget(
datasourceConfig: DatasourceConfig,
password: string,
createNew: boolean,
fileNameOverride: string = null
fileNameOverride: string | null = null
): Promise<VaultSourceID> {
setBusy(true);
const addNewVaultPromise = new Promise<VaultSourceID>((resolve, reject) => {
Expand Down
90 changes: 57 additions & 33 deletions source/renderer/components/AddVaultMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import { testWebDAV } from "../actions/webdav";
import { getFSInstance } from "../library/fsInterface";
import { FileChooser } from "./standalone/FileChooser";
import { addNewVaultTarget, getFileVaultParameters } from "../actions/addVault";
import { showError } from "../services/notifications";
import { authenticateGoogleDrive } from "../services/authGoogle";
import { showError, showSuccess } from "../services/notifications";
import { authenticateGoogleDrive, getGoogleDriveAuthURL, waitForGoogleAuthCode } from "../services/authGoogle";
import { createEmptyVault as createEmptyGoogleDriveVault } from "../services/googleDrive";
import { showWarning } from "../services/notifications";
import { getIconForProvider } from "../library/icons";
import { copyText } from "../actions/clipboard";
import { t } from "../../shared/i18n/trans";
import { DatasourceConfig, SourceType } from "../types";

Expand All @@ -25,9 +26,9 @@ interface WebDAVCredentialsState {
password: string;
}

const { useCallback, useEffect, useState } = React;
const { Fragment, useCallback, useEffect, useState } = React;

const EMPTY_DATASOURCE_CONFIG = { type: null };
const EMPTY_DATASOURCE_CONFIG: DatasourceConfig = { type: null };
const EMPTY_WEBDAV_CREDENTIALS: WebDAVCredentialsState = { url: "", username: "", password: "" };
const PAGE_TYPE = "type";
const PAGE_AUTH = "auth";
Expand Down Expand Up @@ -110,16 +111,15 @@ export function AddVaultMenu() {
const showAddVault = useHookState(SHOW_ADD_VAULT);
const [previousShowAddVault, setPreviousShowAddVault] = useState(false);
const [currentPage, setCurrentPage] = useState(PAGE_TYPE);
const [selectedType, setSelectedType] = useState<SourceType>(null);
const [selectedRemotePath, setSelectedRemotePath] = useState<string>(null);
const [selectedType, setSelectedType] = useState<SourceType | null>(null);
const [selectedRemotePath, setSelectedRemotePath] = useState<string | null>(null);
const [datasourcePayload, setDatasourcePayload] = useState<DatasourceConfig>({ ...EMPTY_DATASOURCE_CONFIG });
const [fsInstance, setFsInstance] = useState<FileSystemInterface>(null);
const [fsInstance, setFsInstance] = useState<FileSystemInterface | null>(null);
const [createNew, setCreateNew] = useState(false);
const [vaultPassword, setVaultPassword] = useState("");
const [webdavCredentials, setWebDAVCredentials] = useState<WebDAVCredentialsState>({ ...EMPTY_WEBDAV_CREDENTIALS });
const [authenticatingGoogleDrive, setAuthenticatingGoogleDrive] = useState(false);
const [googleDriveOpenPerms, setGoogleDriveOpenPerms] = useState(false);
const [vaultFilenameOverride, setVaultFilenameOverride] = useState(null);
const [vaultFilenameOverride, setVaultFilenameOverride] = useState<string | null>(null);
useEffect(() => {
const newValue = showAddVault.get();
if (previousShowAddVault !== newValue) {
Expand All @@ -136,7 +136,6 @@ export function AddVaultMenu() {
setDatasourcePayload({ ...EMPTY_DATASOURCE_CONFIG });
setWebDAVCredentials({ ...EMPTY_WEBDAV_CREDENTIALS });
setVaultPassword("");
setGoogleDriveOpenPerms(false);
setAuthenticatingGoogleDrive(false);
setVaultFilenameOverride(null);
}, []);
Expand Down Expand Up @@ -194,7 +193,7 @@ export function AddVaultMenu() {
const handleAuthSubmit = useCallback(async () => {
if (selectedType === SourceType.GoogleDrive) {
try {
const { accessToken, refreshToken } = await authenticateGoogleDrive(googleDriveOpenPerms);
const { accessToken, refreshToken } = await authenticateGoogleDrive();
setDatasourcePayload({
...datasourcePayload,
token: accessToken,
Expand Down Expand Up @@ -235,8 +234,35 @@ export function AddVaultMenu() {
setFsInstance(getFSInstance(SourceType.WebDAV, newPayload));
setCurrentPage(PAGE_CHOOSE);
}
}, [selectedType, datasourcePayload, webdavCredentials, googleDriveOpenPerms]);
const handleSelectedPathChange = useCallback((parentIdentifier: string | null, identifier: string, isNew: boolean, fileName: string | null) => {
}, [selectedType, datasourcePayload, webdavCredentials]);
const handleAuthURLCopy = useCallback(async () => {
if (selectedType === SourceType.GoogleDrive) {
const url = getGoogleDriveAuthURL();
try {
await copyText(url);
showSuccess(t("add-vault-menu.copy-auth-link.url-copied"));
} catch (err) {
showError(err.message);
}
try {
const { accessToken, refreshToken } = await waitForGoogleAuthCode();
setDatasourcePayload({
...datasourcePayload,
token: accessToken,
refreshToken
});
setFsInstance(getFSInstance(SourceType.GoogleDrive, {
token: accessToken
}));
setCurrentPage(PAGE_CHOOSE);
} catch (err) {
console.error(err);
showWarning(`${t("add-vault-menu.google-auth-error")}: ${err.message}`);
setAuthenticatingGoogleDrive(false);
}
}
}, [selectedType, datasourcePayload]);
const handleSelectedPathChange = useCallback((parentIdentifier: string | number | null, identifier: string, isNew: boolean, fileName: string | null) => {
if (selectedType === SourceType.GoogleDrive) {
setSelectedRemotePath(JSON.stringify([parentIdentifier, identifier]));
setVaultFilenameOverride(fileName);
Expand Down Expand Up @@ -271,7 +297,7 @@ export function AddVaultMenu() {
}, [selectedRemotePath, selectedType, datasourcePayload]);
const handleFinalConfirm = useCallback(async () => {
const datasource = { ...datasourcePayload };
if (selectedType === SourceType.GoogleDrive) {
if (selectedType === SourceType.GoogleDrive && selectedRemotePath && datasource.token) {
const [parentIdentifier, identifier] = JSON.parse(selectedRemotePath);
datasource.fileID = createNew
? await createEmptyGoogleDriveVault(datasource.token, parentIdentifier, identifier, vaultPassword)
Expand Down Expand Up @@ -311,17 +337,6 @@ export function AddVaultMenu() {
<p dangerouslySetInnerHTML={{ __html: t("add-vault-menu.loader.google-auth.instr-1") }} />
<p dangerouslySetInnerHTML={{ __html: t("add-vault-menu.loader.google-auth.instr-2") }} />
<p dangerouslySetInnerHTML={{ __html: t("add-vault-menu.loader.google-auth.instr-3") }} />
<WideFormGroup
inline
label={t("add-vault-menu.loader.google-auth.perm-label")}
>
<Switch
disabled={authenticatingGoogleDrive}
label={t("add-vault-menu.loader.google-auth.perm-switch")}
checked={googleDriveOpenPerms}
onChange={(evt: React.ChangeEvent<HTMLInputElement>) => setGoogleDriveOpenPerms(evt.target.checked)}
/>
</WideFormGroup>
</>
)}
{selectedType === SourceType.WebDAV && (
Expand Down Expand Up @@ -418,14 +433,23 @@ export function AddVaultMenu() {
</Button>
)}
{currentPage === PAGE_AUTH && selectedType === SourceType.GoogleDrive && (
<Button
disabled={authenticatingGoogleDrive}
intent={Intent.PRIMARY}
onClick={handleAuthSubmit}
title={t("add-vault-menu.google-auth-button-title")}
>
{t("add-vault-menu.google-auth-button")}
</Button>
<Fragment>
<Button
intent={Intent.NONE}
onClick={handleAuthURLCopy}
title={t("add-vault-menu.copy-auth-link.title")}
>
{t("add-vault-menu.copy-auth-link.button")}
</Button>
<Button
disabled={authenticatingGoogleDrive}
intent={Intent.PRIMARY}
onClick={handleAuthSubmit}
title={t("add-vault-menu.google-auth-button-title")}
>
{t("add-vault-menu.google-auth-button")}
</Button>
</Fragment>
)}
{currentPage === PAGE_AUTH && selectedType === SourceType.WebDAV && (
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function GoogleReAuthDialog() {
}, [googleReAuthState]);
const authenticate = useCallback(() => {
setBusy(true);
authenticateGoogleDrive(false)
authenticateGoogleDrive()
.then(tokens => updateGoogleTokensForSource(sourceID, tokens))
.then(() => {
setBusy(false);
Expand Down
55 changes: 33 additions & 22 deletions source/renderer/services/authGoogle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,21 @@ import type { VaultSourceID } from "buttercup";
import { logInfo } from "../library/log";
import {
GOOGLE_AUTH_REDIRECT,
GOOGLE_DRIVE_SCOPES_PERMISSIVE,
GOOGLE_DRIVE_SCOPES_STANDARD,
GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET
} from "../../shared/symbols";

let __googleDriveOAuthClient: OAuth2Client | null = null;

export async function authenticateGoogleDrive(
openPermissions: boolean = false
): Promise<{ accessToken: string; refreshToken: string }> {
logInfo(`Authenticating Google Drive (permissive: ${openPermissions ? "yes" : "no"})`);
const scopes = openPermissions ? GOOGLE_DRIVE_SCOPES_PERMISSIVE : GOOGLE_DRIVE_SCOPES_STANDARD;
const oauth2Client = getGoogleDriveOAuthClient();
const url = oauth2Client.generateAuthUrl({
access_type: "offline",
scope: [...scopes],
prompt: "consent select_account"
});
logInfo(`Google Drive: Opening authentication URL: ${url}`);
export async function authenticateGoogleDrive(): Promise<{
accessToken: string;
refreshToken: string;
}> {
logInfo("Authenticating Google Drive");
const url = getGoogleDriveAuthURL();
shell.openExternal(url);
const authCode = await listenForGoogleAuthCode();
logInfo("Google Drive: Received auth code - exchanging for tokens");
const response = await oauth2Client.exchangeAuthCodeForToken(authCode);
const { access_token: accessToken, refresh_token: refreshToken } = response.tokens;
logInfo("Google Drive: tokens received");
return {
accessToken,
refreshToken
};
return waitForGoogleAuthCode();
}

export async function authenticateGoogleDriveWithRefreshToken(
Expand All @@ -51,6 +36,16 @@ export async function authenticateGoogleDriveWithRefreshToken(
};
}

export function getGoogleDriveAuthURL(): string {
const scopes = GOOGLE_DRIVE_SCOPES_STANDARD;
const oauth2Client = getGoogleDriveOAuthClient();
return oauth2Client.generateAuthUrl({
access_type: "offline",
scope: [...scopes],
prompt: "consent select_account"
});
}

function getGoogleDriveOAuthClient(): OAuth2Client {
if (!__googleDriveOAuthClient) {
__googleDriveOAuthClient = new OAuth2Client(
Expand Down Expand Up @@ -86,3 +81,19 @@ export async function updateGoogleTokensForSource(
): Promise<void> {
await ipcRenderer.invoke("set-reauth-google-tokens", sourceID, tokens);
}

export async function waitForGoogleAuthCode(): Promise<{
accessToken: string;
refreshToken: string;
}> {
const authCode = await listenForGoogleAuthCode();
logInfo("Google Drive: Received auth code - exchanging for tokens");
const oauth2Client = getGoogleDriveOAuthClient();
const response = await oauth2Client.exchangeAuthCodeForToken(authCode);
const { access_token: accessToken, refresh_token: refreshToken } = response.tokens;
logInfo("Google Drive: tokens received");
return {
accessToken,
refreshToken
};
}
4 changes: 1 addition & 3 deletions source/shared/i18n/translations/ca_es.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@
"google-auth": {
"instr-1": "Pots seleccionar el nivell de permisos que utilitzarà Buttercup al accedir a la teva compte de <strong>Google Drive</strong>.",
"instr-2": "Seleccionar una configuració de permís <strong>oberta</strong> atorgarà a Buttercup accés a tots els arxius i carpetes del seu compte i els recursos compartits connectats.",
"instr-3": "Seleccionar una configuració <i>no</i> oberta atorgarà a Buttercup accés als arxius que ha creat / accedit prèviament.",
"perm-label": "Permisos",
"perm-switch": "Obert"
"instr-3": "Seleccionar una configuració <i>no</i> oberta atorgarà a Buttercup accés als arxius que ha creat / accedit prèviament."
},
"webdav-auth": {
"password-label": "Contrasenya",
Expand Down
4 changes: 1 addition & 3 deletions source/shared/i18n/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@
"google-auth": {
"instr-1": "Du kannst die Berechtigung auswählen, die Buttercup nutzen wird, während es auf deinen <strong>Google Drive</strong> Konto zugreift.",
"instr-2": "Wenn Du eine <strong>offene</strong> Berechtigung auswählst, erlaubst Du Buttercup auf alle deine Dateien und Ordner deines Accounts und verbundenen Freigaben zuzugreifen.",
"instr-3": "Wenn Du eine <i>nicht-</i>offene Berechtigung auswählst, erlaubst Du Buttercup auf Dateien zuzugreifen, die zuvor erstellt oder auf die zuvor zugegriffen wurde.",
"perm-label": "Berechtigungen",
"perm-switch": "Offen"
"instr-3": "Wenn Du eine <i>nicht-</i>offene Berechtigung auswählst, erlaubst Du Buttercup auf Dateien zuzugreifen, die zuvor erstellt oder auf die zuvor zugegriffen wurde."
},
"webdav-auth": {
"password-label": "Passwort",
Expand Down
9 changes: 6 additions & 3 deletions source/shared/i18n/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
"new-password": "Enter a new primary vault password:",
"password-placeholder": "Vault password..."
},
"copy-auth-link": {
"button": "Copy URL",
"title": "Copy authentication URL",
"url-copied": "Authentication URL copied to clipboard"
},
"google-auth-button": "Authenticate",
"google-auth-button-title": "Authenticate with Google Drive",
"google-auth-error": "Google authentication failed",
Expand All @@ -29,9 +34,7 @@
"google-auth": {
"instr-1": "You may select the level of permission that Buttercup will use while accessing your <strong>Google Drive</strong> account.",
"instr-2": "Selecting an <strong>open</strong> permission setting will grant Buttercup access to all files and folders in your account and connected shares.",
"instr-3": "Selecting a <i>non-</i>open setting will grant Buttercup access to files that it has created/accessed previously.",
"perm-label": "Permissions",
"perm-switch": "Open"
"instr-3": "Selecting a <i>non-</i>open setting will grant Buttercup access to files that it has created/accessed previously."
},
"webdav-auth": {
"password-label": "Password",
Expand Down
4 changes: 1 addition & 3 deletions source/shared/i18n/translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@
"google-auth": {
"instr-1": "Puede seleccionar el nivel de permisos que utilizará Buttercup al acceder a tu cuenta de <strong>Google Drive</strong>.",
"instr-2": "Seleccionar una configuración de permiso <strong>abierta</strong> otorgará a Buttercup acceso a todos los archivos y carpetas de su cuenta y los recursos compartidos conectados.",
"instr-3": "Seleccionar una configuración <i>no</i> abierta otorgará a Buttercup acceso a los archivos que ha creado / accedido previamente.",
"perm-label": "Permisos",
"perm-switch": "Abierto"
"instr-3": "Seleccionar una configuración <i>no</i> abierta otorgará a Buttercup acceso a los archivos que ha creado / accedido previamente."
},
"webdav-auth": {
"password-label": "Contraseña",
Expand Down
4 changes: 1 addition & 3 deletions source/shared/i18n/translations/fi.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@
"google-auth": {
"instr-1": "Voit valita käyttöoikeustason, jota Buttercup käyttää käyttäessään <strong>Google Drive</strong> tiliäsi.",
"instr-2": "Selecting an <strong>open</strong> Avoimen käyttöoikeusasetuksen valitseminen antaa Buttercupille pääsyn kaikkiin tilisi tiedostoihin ja kansioihin sekä yhdistettyihin jaettuihin jakoihin.",
"instr-3": "<i>ei-</i>avoin asetuksen valitseminen antaa Buttercupille pääsyn tiedostoihin, jotka se on luonut tai käyttänyt aiemmin.",
"perm-label": "Käyttöoikeudet",
"perm-switch": "Avaa"
"instr-3": "<i>ei-</i>avoin asetuksen valitseminen antaa Buttercupille pääsyn tiedostoihin, jotka se on luonut tai käyttänyt aiemmin."
},
"webdav-auth": {
"password-label": "Salasana",
Expand Down
4 changes: 1 addition & 3 deletions source/shared/i18n/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@
"google-auth": {
"instr-1": "Vous pouvez sélectionner le niveau de permission que Buttercup utilisera lors de l’accès à votre compte <strong>Google Drive</strong>.",
"instr-2": "En sélectionnant un paramètre de permission <strong>étendu</strong>, Buttercup aura accès à tous les fichiers et dossiers de votre compte et aux partages connectés.",
"instr-3": "La sélection d’un paramètre <i>non-</i>étendu permettra à Buttercup d'accéder aux fichiers qu’il a créés ou auxquels il a accédé précédemment.",
"perm-label": "Permissions",
"perm-switch": "Étendu"
"instr-3": "La sélection d’un paramètre <i>non-</i>étendu permettra à Buttercup d'accéder aux fichiers qu’il a créés ou auxquels il a accédé précédemment."
},
"webdav-auth": {
"password-label": "Mot de passe",
Expand Down
Loading
Loading