From 4bb857fa530c5d98111d60a8e001da6f51594e52 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 31 Jul 2024 11:36:43 +1000 Subject: [PATCH] fix: update fetching desktop release endpoint --- package.json | 2 +- .../useFetchLatestReleaseFromFileServer.ts | 11 +++-- ts/ip2country.d.ts | 1 - .../apis/file_server_api/FileServerApi.ts | 22 +++++++++- ts/session/apis/snode_api/getNetworkTime.ts | 7 ++++ .../crypto/DecryptedAttachmentsManager.ts | 3 +- ts/session/fetch_latest_release/index.ts | 16 ++++---- .../browser/libsession_worker_functions.d.ts | 4 +- .../browser/libsession_worker_interface.ts | 12 ++++++ .../node/libsession/libsession.worker.ts | 40 ++++++++++++++----- yarn.lock | 18 ++++----- 11 files changed, 100 insertions(+), 36 deletions(-) delete mode 100644 ts/ip2country.d.ts diff --git a/package.json b/package.json index d9a439abd..66baf1792 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "fs-extra": "9.0.0", "glob": "7.1.2", "image-type": "^4.1.0", - "libsession_util_nodejs": "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.3.19/libsession_util_nodejs-v0.3.19.tar.gz", + "libsession_util_nodejs": "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.3.21/libsession_util_nodejs-v0.3.21.tar.gz", "libsodium-wrappers-sumo": "^0.7.9", "linkify-it": "^4.0.1", "lodash": "^4.17.21", diff --git a/ts/hooks/useFetchLatestReleaseFromFileServer.ts b/ts/hooks/useFetchLatestReleaseFromFileServer.ts index 4766954e5..d699db589 100644 --- a/ts/hooks/useFetchLatestReleaseFromFileServer.ts +++ b/ts/hooks/useFetchLatestReleaseFromFileServer.ts @@ -1,15 +1,20 @@ +import { isEmpty } from 'lodash'; import { useSelector } from 'react-redux'; import useInterval from 'react-use/lib/useInterval'; -import { getOurPrimaryConversation } from '../state/selectors/conversations'; import { fetchLatestRelease } from '../session/fetch_latest_release'; +import { UserUtils } from '../session/utils'; +import { getOurPrimaryConversation } from '../state/selectors/conversations'; export function useFetchLatestReleaseFromFileServer() { const ourPrimaryConversation = useSelector(getOurPrimaryConversation); - useInterval(() => { + useInterval(async () => { if (!ourPrimaryConversation) { return; } - void fetchLatestRelease.fetchReleaseFromFSAndUpdateMain(); + const userEd25519SecretKey = (await UserUtils.getUserED25519KeyPairBytes())?.privKeyBytes; + if (userEd25519SecretKey && !isEmpty(userEd25519SecretKey)) { + void fetchLatestRelease.fetchReleaseFromFSAndUpdateMain(userEd25519SecretKey); + } }, fetchLatestRelease.fetchReleaseFromFileServerInterval); } diff --git a/ts/ip2country.d.ts b/ts/ip2country.d.ts deleted file mode 100644 index 6c4567906..000000000 --- a/ts/ip2country.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'ip2country'; diff --git a/ts/session/apis/file_server_api/FileServerApi.ts b/ts/session/apis/file_server_api/FileServerApi.ts index eb47c164d..67b5e079a 100644 --- a/ts/session/apis/file_server_api/FileServerApi.ts +++ b/ts/session/apis/file_server_api/FileServerApi.ts @@ -1,9 +1,12 @@ import AbortController from 'abort-controller'; +import { BlindingActions } from '../../../webworker/workers/browser/libsession_worker_interface'; import { OnionSending, OnionV4JSONSnodeResponse } from '../../onions/onionSend'; import { batchGlobalIsSuccess, parseBatchGlobalStatusCode, } from '../open_group_api/sogsv3/sogsV3BatchPoll'; +import { GetNetworkTime } from '../snode_api/getNetworkTime'; +import { fromUInt8ArrayToBase64 } from '../../utils/String'; export const fileServerHost = 'filev2.getsession.org'; export const fileServerURL = `http://${fileServerHost}`; @@ -123,12 +126,27 @@ const parseStatusCodeFromOnionRequestV4 = ( * Fetch the latest desktop release available on github from the fileserver. * This call is onion routed and so do not expose our ip to github nor the file server. */ -export const getLatestReleaseFromFileServer = async (): Promise => { +export const getLatestReleaseFromFileServer = async ( + userEd25519SecretKey: Uint8Array +): Promise => { + const sigTimestampSeconds = GetNetworkTime.getNowWithNetworkOffsetSeconds(); + const blindedPkHex = await BlindingActions.blindVersionPubkey({ + ed25519SecretKey: userEd25519SecretKey, + }); + const signature = await BlindingActions.blindVersionSign({ + ed25519SecretKey: userEd25519SecretKey, + sigTimestampSeconds, + }); + const body = { + 'X-FS-Pubkey': blindedPkHex, + 'X-FS-Timestamp': `${sigTimestampSeconds}`, + 'X-FS-Signature': fromUInt8ArrayToBase64(signature), + }; const result = await OnionSending.sendJsonViaOnionV4ToFileServer({ abortSignal: new AbortController().signal, endpoint: RELEASE_VERSION_ENDPOINT, method: 'GET', - stringifiedBody: null, + stringifiedBody: JSON.stringify(body), }); if (!batchGlobalIsSuccess(result) || parseStatusCodeFromOnionRequestV4(result) !== 200) { diff --git a/ts/session/apis/snode_api/getNetworkTime.ts b/ts/session/apis/snode_api/getNetworkTime.ts index 086d849b6..8f5786f7b 100644 --- a/ts/session/apis/snode_api/getNetworkTime.ts +++ b/ts/session/apis/snode_api/getNetworkTime.ts @@ -70,9 +70,16 @@ function getNowWithNetworkOffset() { return Date.now() - GetNetworkTime.getLatestTimestampOffset(); } +function getNowWithNetworkOffsetSeconds() { + // make sure to call exports here, as we stub the exported one for testing. + + return Math.floor(GetNetworkTime.getNowWithNetworkOffset() / 1000); +} + export const GetNetworkTime = { getNetworkTime, handleTimestampOffsetFromNetwork, + getNowWithNetworkOffsetSeconds, getLatestTimestampOffset, getNowWithNetworkOffset, }; diff --git a/ts/session/crypto/DecryptedAttachmentsManager.ts b/ts/session/crypto/DecryptedAttachmentsManager.ts index 57b0ad57f..b8567a00e 100644 --- a/ts/session/crypto/DecryptedAttachmentsManager.ts +++ b/ts/session/crypto/DecryptedAttachmentsManager.ts @@ -93,6 +93,8 @@ export const getDecryptedMediaUrl = async ( // we consider the file is encrypted. // if it's not, the hook caller has to fallback to setting the img src as an url to the file instead and load it if (urlToDecryptedBlobMap.has(url)) { + // typescript does not realize that the `has()` above makes sure the `get()` is not undefined + // refresh the last access timestamp so we keep the one being currently in use const existing = urlToDecryptedBlobMap.get(url); const existingObjUrl = existing?.decrypted as string; @@ -102,7 +104,6 @@ export const getDecryptedMediaUrl = async ( lastAccessTimestamp: Date.now(), forceRetain: existing?.forceRetain || false, }); - // typescript does not realize that the has above makes sure the get is not undefined return existingObjUrl; } diff --git a/ts/session/fetch_latest_release/index.ts b/ts/session/fetch_latest_release/index.ts index 0ecbe7cb8..f11c53ea9 100644 --- a/ts/session/fetch_latest_release/index.ts +++ b/ts/session/fetch_latest_release/index.ts @@ -7,12 +7,6 @@ import { ipcRenderer } from 'electron'; import { DURATION } from '../constants'; import { getLatestReleaseFromFileServer } from '../apis/file_server_api/FileServerApi'; -/** - * Try to fetch the latest release from the fileserver every 1 minute. - * If we did fetch a release already in the last 30 minutes, we will skip the call. - */ -const fetchReleaseFromFileServerInterval = DURATION.MINUTES * 1; - /** * We don't want to hit the fileserver too often. Only often on start, and then every 30 minutes */ @@ -24,7 +18,7 @@ function resetForTesting() { lastFetchedTimestamp = Number.MIN_SAFE_INTEGER; } -async function fetchReleaseFromFSAndUpdateMain() { +async function fetchReleaseFromFSAndUpdateMain(userEd25519SecretKey: Uint8Array) { try { window.log.info('[updater] about to fetchReleaseFromFSAndUpdateMain'); const diff = Date.now() - lastFetchedTimestamp; @@ -35,7 +29,7 @@ async function fetchReleaseFromFSAndUpdateMain() { return; } - const justFetched = await getLatestReleaseFromFileServer(); + const justFetched = await getLatestReleaseFromFileServer(userEd25519SecretKey); window.log.info('[updater] fetched latest release from fileserver: ', justFetched); if (isString(justFetched) && !isEmpty(justFetched)) { @@ -49,7 +43,11 @@ async function fetchReleaseFromFSAndUpdateMain() { } export const fetchLatestRelease = { - fetchReleaseFromFileServerInterval, + /** + * Try to fetch the latest release from the fileserver every 1 minute. + * If we did fetch a release already in the last 30 minutes, we will skip the call. + */ + fetchReleaseFromFileServerInterval: DURATION.MINUTES * 1, fetchReleaseFromFSAndUpdateMain, resetForTesting, }; diff --git a/ts/webworker/workers/browser/libsession_worker_functions.d.ts b/ts/webworker/workers/browser/libsession_worker_functions.d.ts index 4cb004816..bdcd0e74b 100644 --- a/ts/webworker/workers/browser/libsession_worker_functions.d.ts +++ b/ts/webworker/workers/browser/libsession_worker_functions.d.ts @@ -30,9 +30,11 @@ type UserGroupsConfigFunctions = type ConvoInfoVolatileConfigFunctions = | [ConvoInfoVolatileConfig, ...BaseConfigActions] | [ConvoInfoVolatileConfig, ...ConvoInfoVolatileConfigActionsType]; +type BlindingFunctions = ['Blinding', ...BlindingFunctions]; export type LibSessionWorkerFunctions = | UserConfigFunctions | ContactsConfigFunctions | UserGroupsConfigFunctions - | ConvoInfoVolatileConfigFunctions; + | ConvoInfoVolatileConfigFunctions + | BlindingFunctions; diff --git a/ts/webworker/workers/browser/libsession_worker_interface.ts b/ts/webworker/workers/browser/libsession_worker_interface.ts index b9476dd4b..4cb078d70 100644 --- a/ts/webworker/workers/browser/libsession_worker_interface.ts +++ b/ts/webworker/workers/browser/libsession_worker_interface.ts @@ -2,6 +2,7 @@ /* eslint-disable import/no-unresolved */ import { BaseWrapperActionsCalls, + BlindingActionsCalls, ContactInfoSet, ContactsWrapperActionsCalls, ConvoInfoVolatileWrapperActionsCalls, @@ -343,6 +344,17 @@ export const ConvoInfoVolatileWrapperActions: ConvoInfoVolatileWrapperActionsCal ]) as Promise>, }; +export const BlindingActions: BlindingActionsCalls = { + blindVersionPubkey: async (opts: { ed25519SecretKey: Uint8Array }) => + callLibSessionWorker(['Blinding', 'blindVersionPubkey', opts]) as Promise< + ReturnType + >, + blindVersionSign: async (opts: { ed25519SecretKey: Uint8Array; sigTimestampSeconds: number }) => + callLibSessionWorker(['Blinding', 'blindVersionSign', opts]) as Promise< + ReturnType + >, +}; + export const callLibSessionWorker = async ( callToMake: LibSessionWorkerFunctions ): Promise => { diff --git a/ts/webworker/workers/node/libsession/libsession.worker.ts b/ts/webworker/workers/node/libsession/libsession.worker.ts index 75100a511..9d3b6146b 100644 --- a/ts/webworker/workers/node/libsession/libsession.worker.ts +++ b/ts/webworker/workers/node/libsession/libsession.worker.ts @@ -3,6 +3,7 @@ import { isEmpty, isNull } from 'lodash'; import { BaseConfigWrapperNode, + BlindingWrapperNode, ContactsConfigWrapperNode, ConvoInfoVolatileWrapperNode, UserConfigWrapperNode, @@ -55,6 +56,7 @@ function getCorrespondingWrapper(wrapperType: ConfigWrapperObjectTypes): BaseCon throw new Error(`${wrapperType} is not init yet`); } return wrapper; + default: assertUnreachable( wrapperType, @@ -119,24 +121,44 @@ function initUserWrapper(options: Array, wrapperType: ConfigWrapperObjectTy } } -onmessage = async (e: { data: [number, ConfigWrapperObjectTypes, string, ...any] }) => { +onmessage = async (e: { + data: [number, ConfigWrapperObjectTypes | 'Blinding', string, ...any]; +}) => { const [jobId, config, action, ...args] = e.data; try { if (action === 'init') { - initUserWrapper(args, config); + if (config === 'Blinding') { + // nothing to do for the blinding wrapper, all functions are static + } else { + initUserWrapper(args, config); + } postMessage([jobId, null, null]); return; } - const wrapper = getCorrespondingWrapper(config); - const fn = (wrapper as any)[action]; - if (!fn) { - throw new Error( - `Worker: job "${jobId}" did not find function "${action}" on config "${config}"` - ); + let result: any; + + if (config === 'Blinding') { + const fn = (BlindingWrapperNode as any)[action]; + + if (!fn) { + throw new Error( + `Worker: job "${jobId}" did not find function "${action}" on wrapper "${config}"` + ); + } + result = await (BlindingWrapperNode as any)[action](...args); + } else { + const wrapper = getCorrespondingWrapper(config); + const fn = (wrapper as any)[action]; + + if (!fn) { + throw new Error( + `Worker: job "${jobId}" did not find function "${action}" on config "${config}"` + ); + } + result = await (wrapper as any)[action](...args); } - const result = await (wrapper as any)[action](...args); postMessage([jobId, null, result]); } catch (error) { const errorForDisplay = prepareErrorForPostMessage(error); diff --git a/yarn.lock b/yarn.lock index f965aa8ae..005709bf0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3906,9 +3906,9 @@ debug@2.6.9, debug@^2.2.0, debug@^2.6.8, debug@^2.6.9: ms "2.0.0" debug@4, debug@^4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: - version "4.3.5" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" - integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + version "4.3.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" + integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== dependencies: ms "2.1.2" @@ -6533,9 +6533,9 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -"libsession_util_nodejs@https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.3.19/libsession_util_nodejs-v0.3.19.tar.gz": - version "0.3.19" - resolved "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.3.19/libsession_util_nodejs-v0.3.19.tar.gz#221c1fc34fcc18601aea4ce1b733ebfa55af66ea" +"libsession_util_nodejs@https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.3.21/libsession_util_nodejs-v0.3.21.tar.gz": + version "0.3.21" + resolved "https://github.com/oxen-io/libsession-util-nodejs/releases/download/v0.3.21/libsession_util_nodejs-v0.3.21.tar.gz#64705b1f7c934ca32f929ea8127370cc82bab97a" dependencies: cmake-js "^7.2.1" node-addon-api "^6.1.0" @@ -7401,9 +7401,9 @@ node-addon-api@^6.1.0: integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== node-api-headers@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/node-api-headers/-/node-api-headers-1.1.0.tgz#3f9dd7bb10b29e1c3e3db675979605a308b2373c" - integrity sha512-ucQW+SbYCUPfprvmzBsnjT034IGRB2XK8rRc78BgjNKhTdFKgAwAmgW704bKIBmcYW48it0Gkjpkd39Azrwquw== + version "1.2.0" + resolved "https://registry.yarnpkg.com/node-api-headers/-/node-api-headers-1.2.0.tgz#b717cd420aec79031f8dc83a50eb0a8bdf24c70d" + integrity sha512-L9AiEkBfgupC0D/LsudLPOhzy/EdObsp+FHyL1zSK0kKv5FDA9rJMoRz8xd+ojxzlqfg0tTZm2h8ot2nS7bgRA== node-dir@^0.1.17: version "0.1.17"