diff --git a/components/device-sync.js b/components/device-sync.js deleted file mode 100644 index 99b590d8..00000000 --- a/components/device-sync.js +++ /dev/null @@ -1,238 +0,0 @@ -import { useCallback, useEffect, useState } from 'react' -import { useMe } from './me' -import { useShowModal } from './modal' -import { useVaultConfigurator } from './vault/use-vault-configurator' -import { Button, InputGroup } from 'react-bootstrap' -import { Form, Input, PasswordInput, SubmitButton } from './form' -import bip39Words from '@/lib/bip39-words' -import Info from './info' -import CancelButton from './cancel-button' -import * as yup from 'yup' -import { deviceSyncSchema } from '@/lib/validate' -import RefreshIcon from '@/svgs/refresh-line.svg' -import { useApolloClient } from '@apollo/client' - -export default function DeviceSync () { - const { me } = useMe() - const apollo = useApolloClient() - const { key, setVaultKey, clearVault } = useVaultConfigurator() - const showModal = useShowModal() - - const enabled = !!me?.privates?.vaultKeyHash - const connected = !!key - - const manage = useCallback(async () => { - if (enabled && connected) { - showModal((onClose) => ( -
-

Device sync is enabled!

-

- Sensitive data (like wallet credentials) are now securely synced between all connected devices. -

-

- Disconnect to prevent this device from syncing data or to reset your passphrase. -

-
-
- - -
-
-
- )) - } else { - showModal((onClose) => ( - - )) - } - }, [enabled, connected, key]) - - const reset = useCallback(async () => { - const schema = yup.object().shape({ - confirm: yup.string() - .oneOf(['yes'], 'you must confirm by typing "yes"') - .required('required') - }) - showModal((onClose) => ( -
-

Reset device sync

-

- This will delete all encrypted data on the server and disconnect all devices. -

-

- You will need to enter a new passphrase on this and all other devices to sync data again. -

-
{ - await clearVault() - onClose() - }} - > - -
-
- - - continue - -
-
-
-
- )) - }, []) - - const onConnect = useCallback(async (values, formik) => { - if (values.passphrase) { - try { - await setVaultKey(values.passphrase) - apollo.cache.evict({ fieldName: 'BestWallets' }) - apollo.cache.gc() - await apollo.refetchQueries({ include: ['BestWallets'] }) - } catch (e) { - formik?.setErrors({ passphrase: e.message }) - throw e - } - } - }, [setVaultKey]) - - return ( - <> -
device sync
-
-
- -
- -

- Device sync uses end-to-end encryption to securely synchronize your data across devices. -

-

- Your sensitive data remains private and inaccessible to our servers while being synced across all your connected devices using only a passphrase. -

-
-
- {enabled && !connected && ( -
-
- -
- -

- If you have lost your passphrase or wish to erase all encrypted data from the server, you can reset the device sync data and start over. -

-

- This action cannot be undone. -

-
-
- )} - - ) -} - -const generatePassphrase = (n = 12) => { - const rand = new Uint32Array(n) - window.crypto.getRandomValues(rand) - return Array.from(rand).map(i => bip39Words[i % bip39Words.length]).join(' ') -} - -function ConnectForm ({ onClose, onConnect, enabled }) { - const [passphrase, setPassphrase] = useState(!enabled ? generatePassphrase : '') - - useEffect(() => { - const scannedPassphrase = window.localStorage.getItem('qr:passphrase') - if (scannedPassphrase) { - setPassphrase(scannedPassphrase) - window.localStorage.removeItem('qr:passphrase') - } - }) - - const newPassphrase = useCallback(() => { - setPassphrase(() => generatePassphrase(12)) - }, []) - - return ( -
-

{!enabled ? 'Enable device sync' : 'Input your passphrase'}

-

- {!enabled - ? 'Enable secure sync of sensitive data (like wallet credentials) between your devices. You’ll need to enter this passphrase on each device you want to connect.' - : 'Enter the passphrase from device sync to access your encrypted sensitive data (like wallet credentials) on the server.'} -

-
{ - try { - await onConnect(values, formik) - onClose() - } catch {} - }} - > - - - - ) - } - /> -

- { - !enabled - ? 'This passphrase is stored only on your device and cannot be shown again.' - : 'If you have forgotten your passphrase, you can reset and start over.' - } -

-
-
-
- - {enabled ? 'connect' : 'enable'} -
-
-
- -
- ) -} diff --git a/pages/settings/index.js b/pages/settings/index.js index b1c9f5c1..6ed1dfae 100644 --- a/pages/settings/index.js +++ b/pages/settings/index.js @@ -31,7 +31,6 @@ import { OverlayTrigger, Tooltip } from 'react-bootstrap' import { useField } from 'formik' import styles from './settings.module.css' import { AuthBanner } from '@/components/banners' -import DeviceSync from '@/components/device-sync' export const getServerSideProps = getGetServerSideProps({ query: SETTINGS, authRequired: true }) @@ -612,7 +611,6 @@ export default function Settings ({ ssrData }) {
saturday newsletter
{settings?.authMethods && } - diff --git a/pages/settings/passphrase/index.js b/pages/settings/passphrase/index.js index d52e3a72..79cedac8 100644 --- a/pages/settings/passphrase/index.js +++ b/pages/settings/passphrase/index.js @@ -33,7 +33,14 @@ export default function DeviceSync ({ ssrData }) {
-
+ +

+ Device sync uses end-to-end encryption to securely synchronize your data across devices. + + Your sensitive data remains private and inaccessible to our servers while being synced across all your connected devices using only a passphrase. +

+
+
{ (connected && passphrase && ) || (connected && ) || diff --git a/pages/settings/wallets/[wallet].js b/pages/settings/wallets/[wallet].js index f1c1a026..b5469e9b 100644 --- a/pages/settings/wallets/[wallet].js +++ b/pages/settings/wallets/[wallet].js @@ -41,9 +41,11 @@ export default function WalletSettings () { [field.name]: wallet?.config?.[field.name] || '' } }, wallet?.config) - if (wallet?.def.clientOnly) { + + if (wallet?.def.fields.every(f => f.clientOnly)) { return initial } + return { ...initial, ...autowithdrawInitial({ me }) diff --git a/prisma/migrations/20241024175439_vault/migration.sql b/prisma/migrations/20241024175439_vault/migration.sql index ca5c3ea4..c8952fb1 100644 --- a/prisma/migrations/20241024175439_vault/migration.sql +++ b/prisma/migrations/20241024175439_vault/migration.sql @@ -43,17 +43,23 @@ ALTER TABLE "VaultEntry" ADD CONSTRAINT "VaultEntry_userId_fkey" FOREIGN KEY ("u -- AddForeignKey ALTER TABLE "VaultEntry" ADD CONSTRAINT "VaultEntry_walletId_fkey" FOREIGN KEY ("walletId") REFERENCES "Wallet"("id") ON DELETE CASCADE ON UPDATE CASCADE; -CREATE FUNCTION wallet_updated_at_trigger() RETURNS TRIGGER AS $$ +CREATE OR REPLACE FUNCTION wallet_updated_at_trigger() RETURNS TRIGGER AS $$ BEGIN - UPDATE "users" SET "walletsUpdatedAt" = NOW() WHERE "id" = NEW."userId"; - RETURN NEW; + UPDATE "users" + SET "walletsUpdatedAt" = NOW() + WHERE "id" = CASE + WHEN TG_OP = 'DELETE' + THEN OLD."userId" + ELSE NEW."userId" + END; + RETURN NULL; END; $$ LANGUAGE plpgsql; -CREATE TRIGGER wallet_updated_at_trigger -AFTER INSERT OR UPDATE ON "Wallet" +CREATE OR REPLACE TRIGGER wallet_updated_at_trigger +AFTER INSERT OR UPDATE OR DELETE ON "Wallet" FOR EACH ROW EXECUTE PROCEDURE wallet_updated_at_trigger(); -CREATE TRIGGER vault_entry_updated_at_trigger -AFTER INSERT OR UPDATE ON "VaultEntry" +CREATE OR REPLACE TRIGGER vault_entry_updated_at_trigger +AFTER INSERT OR UPDATE OR DELETE ON "VaultEntry" FOR EACH ROW EXECUTE PROCEDURE wallet_updated_at_trigger(); diff --git a/wallets/blink/index.js b/wallets/blink/index.js index b10d8205..3f4344aa 100644 --- a/wallets/blink/index.js +++ b/wallets/blink/index.js @@ -1,4 +1,3 @@ -import { blinkSchema } from '@/lib/validate' import { string } from '@/lib/yup' export const galoyBlinkUrl = 'https://api.blink.sv/graphql' @@ -7,7 +6,6 @@ export const galoyBlinkDashboardUrl = 'https://dashboard.blink.sv/' export const name = 'blink' export const walletType = 'BLINK' export const walletField = 'walletBlink' -export const fieldValidation = blinkSchema export const fields = [ { diff --git a/wallets/cln/index.js b/wallets/cln/index.js index 16c21cb7..3a915543 100644 --- a/wallets/cln/index.js +++ b/wallets/cln/index.js @@ -1,12 +1,10 @@ import { decodeRune } from '@/lib/cln' import { B64_URL_REGEX } from '@/lib/format' -import { CLNAutowithdrawSchema } from '@/lib/validate' import { string } from '@/lib/yup' export const name = 'cln' export const walletType = 'CLN' export const walletField = 'walletCLN' -export const fieldValidation = CLNAutowithdrawSchema export const fields = [ { diff --git a/wallets/index.js b/wallets/index.js index fa7d0c8a..3bff887b 100644 --- a/wallets/index.js +++ b/wallets/index.js @@ -127,7 +127,7 @@ export function WalletsProvider ({ children }) { } }, [me?.privates?.autoWithdrawMaxFeePercent, me?.privates?.autoWithdrawThreshold, me?.privates?.autoWithdrawMaxFeeTotal]) - // if the vault key is set, and we have local wallets, + // whenever the vault key is set, and we have local wallets, // we'll send any merged local wallets to the server, and delete them from local storage const syncLocalWallets = useCallback(async encrypt => { const walletsToSync = wallets.filter(w => diff --git a/wallets/lnc/index.js b/wallets/lnc/index.js index 03a795bd..3ce8cc51 100644 --- a/wallets/lnc/index.js +++ b/wallets/lnc/index.js @@ -4,7 +4,6 @@ import { string } from '@/lib/yup' export const name = 'lnc' export const walletType = 'LNC' export const walletField = 'walletLNC' -export const clientOnly = true export const fields = [ { diff --git a/wallets/validate.js b/wallets/validate.js index 7d7734d7..592e642a 100644 --- a/wallets/validate.js +++ b/wallets/validate.js @@ -76,7 +76,7 @@ function composeWalletSchema (walletDef, serverSide, skipGenerated) { acc[name] = createFieldSchema(name, validate) if (!optional) { - acc[name] = acc[name].required('Required') + acc[name] = acc[name].required('required') } else if (requiredWithout) { // if we are the server, the pairSetting will be in the vaultEntries array acc[name] = acc[name].when([serverSide ? 'vaultEntries' : requiredWithout], ([pairSetting], schema) => {