diff --git a/package-lock.json b/package-lock.json index d90c4308c75..61ffd3928e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -142109,6 +142109,7 @@ "abi-decoder": "2.4.0", "ajv": "6.12.2", "assert": "2.0.0", + "async-mutex": "0.5.0", "async-retry": "1.3.1", "axios": "0.19.2", "bn.js": "5.2.1", @@ -143390,6 +143391,14 @@ "node": ">=12" } }, + "packages/sdk/node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "dependencies": { + "tslib": "^2.4.0" + } + }, "packages/sdk/node_modules/async-retry": { "version": "1.3.1", "license": "MIT", @@ -145014,7 +145023,6 @@ }, "packages/sdk/node_modules/tslib": { "version": "2.7.0", - "dev": true, "license": "0BSD" }, "packages/sdk/node_modules/tsx": { diff --git a/packages/libs/src/NativeAudiusLibs.ts b/packages/libs/src/NativeAudiusLibs.ts index 68e47f82c88..c5a62738aa9 100644 --- a/packages/libs/src/NativeAudiusLibs.ts +++ b/packages/libs/src/NativeAudiusLibs.ts @@ -434,6 +434,7 @@ export class AudiusLibs { await this.determineCreatorNodeEndpointForWallet(wallet) ) this.discoveryProvider?.setCurrentUser(userId) + this.EntityManager?.setCurrentUserId(userId) } getCurrentUser() { @@ -446,6 +447,7 @@ export class AudiusLibs { delete this.currentUserId this.creatorNode?.setEndpoint(this.creatorNodeConfig.fallbackUrl) this.discoveryProvider?.clearCurrentUser() + this.EntityManager?.clearCurrentUserId() } /** Init services based on presence of a relevant config. */ diff --git a/packages/libs/src/WebAudiusLibs.ts b/packages/libs/src/WebAudiusLibs.ts index 7bbded6128d..4e8e4a5b063 100644 --- a/packages/libs/src/WebAudiusLibs.ts +++ b/packages/libs/src/WebAudiusLibs.ts @@ -458,6 +458,7 @@ export class AudiusLibs { await this.determineCreatorNodeEndpointForWallet(wallet) ) this.discoveryProvider?.setCurrentUser(userId) + this.EntityManager?.setCurrentUserId(userId) } getCurrentUser() { @@ -470,6 +471,7 @@ export class AudiusLibs { delete this.currentUserId this.creatorNode?.setEndpoint(this.creatorNodeConfig.fallbackUrl) this.discoveryProvider?.clearCurrentUser() + this.EntityManager?.clearCurrentUserId() } /** Init services based on presence of a relevant config. */ diff --git a/packages/sdk/package.json b/packages/sdk/package.json index d60a1402187..4149625c72f 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -76,6 +76,7 @@ "abi-decoder": "2.4.0", "ajv": "6.12.2", "assert": "2.0.0", + "async-mutex": "0.5.0", "async-retry": "1.3.1", "axios": "0.19.2", "bn.js": "5.2.1", diff --git a/packages/sdk/src/sdk/middleware/addRequestSignatureMiddleware.ts b/packages/sdk/src/sdk/middleware/addRequestSignatureMiddleware.ts index 4477fb68d0e..19acc6ae828 100644 --- a/packages/sdk/src/sdk/middleware/addRequestSignatureMiddleware.ts +++ b/packages/sdk/src/sdk/middleware/addRequestSignatureMiddleware.ts @@ -1,3 +1,5 @@ +import { Mutex } from 'async-mutex' + import { type Middleware, type RequestContext, @@ -19,65 +21,58 @@ export const addRequestSignatureMiddleware = ({ }: { services: Pick }): Middleware => { + const mutex = new Mutex() let message: string | null = null let signatureAddress: string | null = null let signature: string | null = null let timestamp: number | null = null - let signaturePromise: Promise | null = null - - return { - pre: async (context: RequestContext): Promise => { + const getSignature = async () => { + // Run this exclusively to prevent multiple requests from updating the signature at the same time + // and reverting to an older signature + return mutex.runExclusive(async () => { const { auth, logger } = services + const currentAddress = await auth.getAddress() + const currentTimestamp = new Date().getTime() + const isExpired = + !timestamp || timestamp + SIGNATURE_EXPIRY_MS < currentTimestamp - // Using a promise so that only one request at a time is allowed to - // update the signature. - // Any requests that queue behind the one updating will wait for the promise - // to resolve and then read the result. - if (!signaturePromise) { - signaturePromise = (async () => { - const currentAddress = await auth.getAddress() - const currentTimestamp = new Date().getTime() - const isExpired = - !timestamp || timestamp + SIGNATURE_EXPIRY_MS < currentTimestamp - - const needsUpdate = - !message || - !signature || - isExpired || - signatureAddress !== currentAddress + const needsUpdate = + !message || + !signature || + isExpired || + signatureAddress !== currentAddress - if (needsUpdate) { - try { - signatureAddress = currentAddress + if (needsUpdate) { + try { + signatureAddress = currentAddress - const m = `signature:${currentTimestamp}` - const prefix = `\x19Ethereum Signed Message:\n${m.length}` - const prefixedMessage = prefix + m + const m = `signature:${currentTimestamp}` + const prefix = `\x19Ethereum Signed Message:\n${m.length}` + const prefixedMessage = prefix + m - const [sig, recid] = await auth.sign( - Buffer.from(prefixedMessage, 'utf-8') - ) - const r = Buffer.from(sig.slice(0, 32)).toString('hex') - const s = Buffer.from(sig.slice(32, 64)).toString('hex') - const v = (recid + 27).toString(16) + const [sig, recid] = await auth.sign( + Buffer.from(prefixedMessage, 'utf-8') + ) + const r = Buffer.from(sig.slice(0, 32)).toString('hex') + const s = Buffer.from(sig.slice(32, 64)).toString('hex') + const v = (recid + 27).toString(16) - // Cache the new signature and message - message = m - signature = `0x${r}${s}${v}` - timestamp = currentTimestamp - } catch (e) { - logger.warn(`Unable to add request signature: ${e}`) - } finally { - // Clear the promise after update is complete - signaturePromise = null - } - } - })() + // Cache the new signature and message + message = m + signature = `0x${r}${s}${v}` + timestamp = currentTimestamp + } catch (e) { + logger.warn(`Unable to add request signature: ${e}`) + } } + return { message, signature } + }) + } - // Wait for current check/update signature to complete - await signaturePromise + return { + pre: async (context: RequestContext): Promise => { + const { message, signature } = await getSignature() // Return the updated request with the signature in the headers return !!message && !!signature