Bläddra i källkod

storage-node-v2: Add token request authentication.

Shamil Gadelshin 3 år sedan
förälder
incheckning
60ced50c86

+ 31 - 0
storage-node-v2/scripts/create-auth-request-signature.ts

@@ -0,0 +1,31 @@
+#!/usr/bin/env ts-node
+
+import { createApi } from '../src/services/runtime/api'
+import { getAlicePair } from '../src/services/runtime/accounts'
+import { UploadTokenRequestBody, signTokenBody, UploadTokenRequest } from '../src/services/helpers/auth'
+import { exit } from 'process'
+
+//Wasm init
+createApi('ws://localhost:9944').then(() => {
+
+  const alice = getAlicePair()
+
+  const tokenRequestBody: UploadTokenRequestBody = {
+    memberId: 0,
+    accountId: alice.address,
+    dataObjectId: 55,
+    storageBucketId: 1,
+    bagId: 'static:council'
+  }
+  
+  const signature = signTokenBody(tokenRequestBody, alice)
+  const tokenRequest: UploadTokenRequest = {
+    data: tokenRequestBody,
+    signature
+  }
+
+  console.log(JSON.stringify(tokenRequest))
+
+  exit(0)
+})
+

+ 1 - 8
storage-node-v2/scripts/create-several-buckets.sh

@@ -36,11 +36,4 @@ curl http://localhost:3333/test &
 curl http://localhost:3333/test &
 curl http://localhost:3333/test &
 curl http://localhost:3333/test &
-curl http://localhost:3333/test &
-
-
-# yarn storage-node leader:create-bucket -i=0 -a --dev &
-# yarn storage-node leader:create-bucket -i=0 -a --dev &
-# yarn storage-node leader:create-bucket -i=0 -a --dev &
-# yarn storage-node leader:create-bucket -i=0 -a --dev &
-# yarn storage-node leader:create-bucket -i=0 -a --dev
+curl http://localhost:3333/test &

+ 31 - 10
storage-node-v2/src/api-spec/openapi.yaml

@@ -100,6 +100,12 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorResponse'
+        410:
+          description: AuthToken request problem
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorResponse'
 
 components:
   securitySchemes:
@@ -111,17 +117,32 @@ components:
     TokenRequest:
       type: object
       required:
-        - dataObjectId
-        - storageBucketId
-        - bagId
+        - data
+        - signature
       properties:
-        dataObjectId:
-          type: integer
-          format: int64
-        storageBucketId:
-          type: integer
-          format: int64
-        bagId:
+        data:
+          type: object
+          required:
+            - memberId
+            - accountId
+            - dataObjectId
+            - storageBucketId
+            - bagId
+          properties:
+            memberId:
+              type: integer
+              format: int64
+            accountId:
+              type: string
+            dataObjectId:
+              type: integer
+              format: int64
+            storageBucketId:
+              type: integer
+              format: int64
+            bagId:
+              type: string
+        signature:
           type: string
     ErrorResponse:
       type: object

+ 39 - 17
storage-node-v2/src/services/helpers/auth.ts

@@ -3,43 +3,62 @@ import { u8aToHex } from '@polkadot/util'
 import { signatureVerify } from '@polkadot/util-crypto'
 import base64url from 'base64url'
 
-export interface TokenRequest {
-  dataObjectId: number
-  storageBucketId: number
-  bagId: string
+export interface UploadTokenRequest {
+  data: UploadTokenRequestBody
+  signature: string
 }
 
-export interface TokenBody {
+export interface UploadTokenRequestBody extends RequestData {
+  memberId: number
+  accountId: string
+}
+
+export interface RequestData {
   dataObjectId: number
   storageBucketId: number
   bagId: string
+}
+
+export interface UploadTokenBody extends RequestData {
   timestamp: number
 }
 
-export interface Token {
-  data: TokenBody
+export interface UploadToken {
+  data: UploadTokenBody
   signature: string
 }
 
-export function parseToken(tokenString: string): Token {
+export function parseUploadToken(tokenString: string): UploadToken {
   return JSON.parse(base64url.decode(tokenString))
 }
 
 export function verifyTokenSignature(
-  token: Token,
-  account: KeyringPair
+  token: UploadToken | UploadTokenRequest,
+  address: string
 ): boolean {
   const message = JSON.stringify(token.data)
-  const { isValid } = signatureVerify(message, token.signature, account.address)
+  const { isValid } = signatureVerify(message, token.signature, address)
 
   return isValid
 }
 
-export function signToken(tokenBody: TokenBody, account: KeyringPair): string {
+export function signTokenBody(
+  tokenBody: UploadTokenBody | UploadTokenRequestBody,
+  account: KeyringPair
+): string {
   const message = JSON.stringify(tokenBody)
   const signature = u8aToHex(account.sign(message))
 
-  const token: Token = {
+  return signature
+}
+
+export function createUploadToken(
+  tokenBody: UploadTokenBody,
+  account: KeyringPair
+): string {
+  const signature = signTokenBody(tokenBody, account)
+
+  const token = {
     data: tokenBody,
     signature,
   }
@@ -48,16 +67,19 @@ export function signToken(tokenBody: TokenBody, account: KeyringPair): string {
 }
 
 // Throws exceptions on errors.
-export function verifyTokenData(token: Token, data: TokenRequest): void {
-  if (token.data.dataObjectId !== data.dataObjectId) {
+export function verifyUploadTokenData(
+  token: UploadToken,
+  request: RequestData
+): void {
+  if (token.data.dataObjectId !== request.dataObjectId) {
     throw new Error('Unexpected dataObjectId')
   }
 
-  if (token.data.storageBucketId !== data.storageBucketId) {
+  if (token.data.storageBucketId !== request.storageBucketId) {
     throw new Error('Unexpected storageBucketId')
   }
 
-  if (token.data.bagId !== data.bagId) {
+  if (token.data.bagId !== request.bagId) {
     throw new Error('Unexpected bagId')
   }
 }

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

@@ -10,10 +10,10 @@ import {
 import { KeyringPair } from '@polkadot/keyring/types'
 import { ApiPromise } from '@polkadot/api'
 import {
-  TokenRequest,
+  RequestData,
   verifyTokenSignature,
-  parseToken,
-  verifyTokenData,
+  parseUploadToken,
+  verifyUploadTokenData,
 } from '../helpers/auth'
 import { httpLogger } from '../../services/logger'
 
@@ -103,16 +103,16 @@ function validateUpload(
     schema: OpenAPIV3.SecuritySchemeObject
   ) => {
     const tokenString = req.headers['x-api-key'] as string
-    const token = parseToken(tokenString)
+    const token = parseUploadToken(tokenString)
 
-    const sourceTokenRequest: TokenRequest = {
+    const sourceTokenRequest: RequestData = {
       dataObjectId: parseInt(req.body.dataObjectId),
       storageBucketId: parseInt(req.body.storageBucketId),
       bagId: req.body.bagId,
     }
 
-    verifyTokenData(token, sourceTokenRequest)
+    verifyUploadTokenData(token, sourceTokenRequest)
 
-    return verifyTokenSignature(token, account)
+    return verifyTokenSignature(token, account.address)
   }
 }

+ 52 - 14
storage-node-v2/src/services/webApi/controllers/publicApi.ts

@@ -1,11 +1,17 @@
 import * as express from 'express'
 import { acceptPendingDataObjects } from '../../runtime/extrinsics'
-import { TokenRequest, TokenBody, signToken } from '../../helpers/auth'
+import {
+  UploadTokenRequest,
+  UploadTokenBody,
+  createUploadToken,
+  verifyTokenSignature,
+} from '../../helpers/auth'
 import { hashFile } from '../../../services/helpers/hashing'
 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 {
@@ -44,7 +50,7 @@ export async function upload(
     })
   } catch (err) {
     res.status(410).json({
-      type: 'Upload error',
+      type: 'upload',
       message: err.toString(),
     })
   }
@@ -54,17 +60,28 @@ export async function authToken(
   req: express.Request,
   res: express.Response
 ): Promise<void> {
-  const account = getAccount(res)
-  const tokenRequest = getTokenRequest(req)
-  const tokenBody: TokenBody = {
-    timestamp: Date.now(),
-    ...tokenRequest,
-  }
-  const signedToken = signToken(tokenBody, account)
+  try {
+    const account = getAccount(res)
+    const tokenRequest = getTokenRequest(req)
+    const api = getApi(res)
+
+    await validateTokenRequest(api, tokenRequest)
+
+    const tokenBody: UploadTokenBody = {
+      timestamp: Date.now(),
+      ...tokenRequest.data,
+    }
+    const signedToken = createUploadToken(tokenBody, account)
 
-  res.status(201).json({
-    token: signedToken,
-  })
+    res.status(201).json({
+      token: signedToken,
+    })
+  } catch (err) {
+    res.status(410).json({
+      type: 'authtoken',
+      message: err.toString(),
+    })
+  }
 }
 
 function getFileObject(req: express.Request): Express.Multer.File {
@@ -104,11 +121,32 @@ function getApi(res: express.Response): ApiPromise {
   throw new Error('No Joystream API loaded.')
 }
 
-function getTokenRequest(req: express.Request): TokenRequest {
-  const tokenRequest = req.body as TokenRequest
+function getTokenRequest(req: express.Request): UploadTokenRequest {
+  const tokenRequest = req.body as UploadTokenRequest
   if (tokenRequest) {
     return tokenRequest
   }
 
   throw new Error('No token request provided.')
 }
+
+async function validateTokenRequest(
+  api: ApiPromise,
+  tokenRequest: UploadTokenRequest
+): Promise<void> {
+  const result = verifyTokenSignature(tokenRequest, tokenRequest.data.accountId)
+
+  if (!result) {
+    throw new Error('Invalid upload token request signature.')
+  }
+
+  const membership = (await api.query.members.membershipById(
+    tokenRequest.data.memberId
+  )) as Membership
+
+  if (
+    membership.controller_account.toString() !== tokenRequest.data.accountId
+  ) {
+    throw new Error(`Provided controller account and member id don't match.`)
+  }
+}