Skip to content

Commit

Permalink
Merge pull request #1286 from buttercup/feat/google_auth_reduced_perms
Browse files Browse the repository at this point in the history
Reduce google permissions requirement
  • Loading branch information
perry-mitchell authored Mar 2, 2024
2 parents e586863 + 1ec556d commit 68301c6
Show file tree
Hide file tree
Showing 19 changed files with 454 additions and 442 deletions.
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

0 comments on commit 68301c6

Please sign in to comment.