Prechádzať zdrojové kódy

storage-node-v2: Add token nonce and nonce local cache.

Shamil Gadelshin 3 rokov pred
rodič
commit
45cd651b42

+ 1 - 0
storage-node-v2/.gitignore

@@ -6,3 +6,4 @@
 /package-lock.json
 /tmp
 node_modules
+/uploads

+ 2 - 0
storage-node-v2/package.json

@@ -16,6 +16,7 @@
     "@types/base64url": "^2.0.0",
     "@types/express": "4.17.1",
     "@types/multer": "^1.4.5",
+    "@types/node-cache": "^4.2.5",
     "@types/winston": "^2.4.4",
     "await-lock": "^2.1.0",
     "base64url": "^3.0.1",
@@ -24,6 +25,7 @@
     "express-openapi-validator": "^4.12.4",
     "express-winston": "^4.1.0",
     "multihashes": "^4.0.2",
+    "node-cache": "^5.1.2",
     "openapi-editor": "^0.3.0",
     "tslib": "^1",
     "winston": "^3.3.3"

+ 1 - 22
storage-node-v2/src/services/helpers/auth.ts

@@ -21,6 +21,7 @@ export interface RequestData {
 
 export interface UploadTokenBody extends RequestData {
   validUntil: number // timestamp
+  nonce: string
 }
 
 export interface UploadToken {
@@ -65,25 +66,3 @@ export function createUploadToken(
 
   return base64url.encode(JSON.stringify(token))
 }
-
-// Throws exceptions on errors.
-export function verifyUploadTokenData(
-  token: UploadToken,
-  request: RequestData
-): void {
-  if (token.data.dataObjectId !== request.dataObjectId) {
-    throw new Error('Unexpected dataObjectId')
-  }
-
-  if (token.data.storageBucketId !== request.storageBucketId) {
-    throw new Error('Unexpected storageBucketId')
-  }
-
-  if (token.data.bagId !== request.bagId) {
-    throw new Error('Unexpected bagId')
-  }
-
-  if (token.data.validUntil < Date.now()) {
-    throw new Error('Token expired')
-  }
-}

+ 25 - 0
storage-node-v2/src/services/helpers/tokenNonceKeeper.ts

@@ -0,0 +1,25 @@
+import NodeCache from 'node-cache'
+
+// TODO: move to config or set to 10 seconds
+export const TokenExpirationPeriod: number = 100 * 1000 // seconds
+const MaxNonces = 100000
+
+const nonceCache = new NodeCache({
+  stdTTL: TokenExpirationPeriod,
+  deleteOnExpire: true,
+  maxKeys: MaxNonces,
+})
+
+export function createNonce(): string {
+  const nonce = process.hrtime.bigint().toString()
+
+  nonceCache.set(nonce, null, TokenExpirationPeriod)
+
+  return nonce
+}
+
+export function checkRemoveNonce(nonce: string): boolean {
+  const deletedEntries = nonceCache.del(nonce)
+
+  return deletedEntries > 0
+}

+ 1 - 1
storage-node-v2/src/services/runtime/api.ts

@@ -9,7 +9,7 @@ import {
   DispatchError,
   DispatchResult,
 } from '@polkadot/types/interfaces/system'
-import { getNonce } from './nonceKeeper'
+import { getNonce } from './transactionNonceKeeper'
 import logger from '../../services/logger'
 import ExitCodes from '../../command-base/ExitCodes'
 import { CLIError } from '@oclif/errors'

+ 1 - 1
storage-node-v2/src/services/runtime/nonceKeeper.ts → storage-node-v2/src/services/runtime/transactionNonceKeeper.ts

@@ -3,7 +3,7 @@ import type { Index } from '@polkadot/types/interfaces/runtime'
 import BN from 'bn.js'
 import AwaitLock from 'await-lock'
 import { ApiPromise } from '@polkadot/api'
-import logger from '../../services/logger'
+import logger from '../logger'
 
 let nonce: Index | null = null
 const lock = new AwaitLock()

+ 35 - 3
storage-node-v2/src/services/webApi/app.ts

@@ -13,8 +13,9 @@ import {
   RequestData,
   verifyTokenSignature,
   parseUploadToken,
-  verifyUploadTokenData,
+  UploadToken,
 } from '../helpers/auth'
+import { checkRemoveNonce } from '../../services/helpers/tokenNonceKeeper'
 import { httpLogger } from '../../services/logger'
 
 // Creates web API application.
@@ -111,8 +112,39 @@ function validateUpload(
       bagId: req.body.bagId,
     }
 
-    verifyUploadTokenData(token, sourceTokenRequest)
+    verifyUploadTokenData(account.address, token, sourceTokenRequest)
 
-    return verifyTokenSignature(token, account.address)
+    return true
+  }
+}
+
+// Throws exceptions on errors.
+function verifyUploadTokenData(
+  accountAddress: string,
+  token: UploadToken,
+  request: RequestData
+): void {
+  if (!verifyTokenSignature(token, accountAddress)) {
+    throw new Error('Invalid signature')
+  }
+
+  if (token.data.dataObjectId !== request.dataObjectId) {
+    throw new Error('Unexpected dataObjectId')
+  }
+
+  if (token.data.storageBucketId !== request.storageBucketId) {
+    throw new Error('Unexpected storageBucketId')
+  }
+
+  if (token.data.bagId !== request.bagId) {
+    throw new Error('Unexpected bagId')
+  }
+
+  if (token.data.validUntil < Date.now()) {
+    throw new Error('Token expired')
+  }
+
+  if (!checkRemoveNonce(token.data.nonce)) {
+    throw new Error('Nonce not found')
   }
 }

+ 6 - 2
storage-node-v2/src/services/webApi/controllers/publicApi.ts

@@ -7,11 +7,16 @@ import {
   verifyTokenSignature,
 } from '../../helpers/auth'
 import { hashFile } from '../../../services/helpers/hashing'
+import {
+  createNonce,
+  TokenExpirationPeriod,
+} from '../../../services/helpers/tokenNonceKeeper'
 import { KeyringPair } from '@polkadot/keyring/types'
 import { ApiPromise } from '@polkadot/api'
 import { parseBagId } from '../../../services/helpers/bagIdParser'
 import fs from 'fs'
 import { Membership } from '@joystream/types/members'
+
 const fsPromises = fs.promises
 
 interface UploadRequest {
@@ -68,6 +73,7 @@ export async function authToken(
     await validateTokenRequest(api, tokenRequest)
 
     const tokenBody: UploadTokenBody = {
+      nonce: createNonce(),
       validUntil: getTokenExpirationTime(),
       ...tokenRequest.data,
     }
@@ -151,8 +157,6 @@ async function validateTokenRequest(
   }
 }
 
-// TODO: move to config or set to 10 seconds
-const TokenExpirationPeriod: number = 100 * 1000 // seconds
 function getTokenExpirationTime(): number {
   return Date.now() + TokenExpirationPeriod
 }

+ 19 - 5
yarn.lock

@@ -5559,6 +5559,13 @@
   resolved "https://registry.yarnpkg.com/@types/mustache/-/mustache-4.1.1.tgz#fcfa2db0cee6261e66f2437dc2fe71e26c7856b4"
   integrity sha512-Sm0NWeLhS2QL7NNGsXvO+Fgp7e3JLHCO6RS3RCnfjAnkw6Y1bsji/AGfISdQZDIR/AeOyzkrxRk9jBkl55zdJw==
 
+"@types/node-cache@^4.2.5":
+  version "4.2.5"
+  resolved "https://registry.yarnpkg.com/@types/node-cache/-/node-cache-4.2.5.tgz#dac6be8bf8f486db4eef66f52f7bb9e90b65d2c1"
+  integrity sha512-faK2Owokboz53g8ooq2dw3iDJ6/HMTCIa2RvMte5WMTiABy+wA558K+iuyRtlR67Un5q9gEKysSDtqZYbSa0Pg==
+  dependencies:
+    node-cache "*"
+
 "@types/node-emoji@^1.8.1":
   version "1.8.1"
   resolved "https://registry.yarnpkg.com/@types/node-emoji/-/node-emoji-1.8.1.tgz#689cb74fdf6e84309bcafce93a135dfecd01de3f"
@@ -9928,16 +9935,16 @@ clone-stats@^1.0.0:
   resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680"
   integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=
 
+clone@2.x, clone@^2.1.1:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
+  integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
+
 clone@^1.0.2:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
   integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
 
-clone@^2.1.1:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
-  integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
-
 cloneable-readable@^1.0.0:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.3.tgz#120a00cb053bfb63a222e709f9683ea2e11d8cec"
@@ -21606,6 +21613,13 @@ node-addon-api@^3.0.2:
   resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239"
   integrity sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw==
 
+node-cache@*, node-cache@^5.1.2:
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-5.1.2.tgz#f264dc2ccad0a780e76253a694e9fd0ed19c398d"
+  integrity sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==
+  dependencies:
+    clone "2.x"
+
 node-dir@^0.1.10:
   version "0.1.17"
   resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5"