123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- import express from 'express'
- import path from 'path'
- import cors from 'cors'
- import { Express, NextFunction } from 'express-serve-static-core'
- import * as OpenApiValidator from 'express-openapi-validator'
- import { HttpError, OpenAPIV3 } from 'express-openapi-validator/dist/framework/types'
- import { KeyringPair } from '@polkadot/keyring/types'
- import { ApiPromise } from '@polkadot/api'
- import { RequestData, verifyTokenSignature, parseUploadToken, UploadToken } from '../helpers/auth'
- import { checkRemoveNonce } from '../../services/helpers/tokenNonceKeeper'
- import { httpLogger, errorLogger } from '../../services/logger'
- /**
- * Creates Express web application. Uses the OAS spec file for the API.
- *
- * @param api - runtime API promise
- * @param account - KeyringPair instance
- * @param workerId - storage provider ID (worker ID)
- * @param uploadsDir - directory for the file uploading
- * @param maxFileSize - max allowed file size
- * @returns Express promise.
- */
- export async function createApp(
- api: ApiPromise,
- account: KeyringPair,
- workerId: number,
- uploadsDir: string,
- maxFileSize: number
- ): Promise<Express> {
- const spec = path.join(__dirname, './../../api-spec/openapi.yaml')
- const app = express()
- app.use(cors())
- app.use(express.json())
- app.use(httpLogger())
- app.use(
- // Set parameters for each request.
- (req: express.Request, res: express.Response, next: NextFunction) => {
- res.locals.uploadsDir = uploadsDir
- res.locals.storageProviderAccount = account
- res.locals.workerId = workerId
- res.locals.api = api
- next()
- },
- // Setup OpenAPiValidator
- OpenApiValidator.middleware({
- apiSpec: spec,
- validateApiSpec: true,
- validateResponses: true,
- validateRequests: true,
- operationHandlers: {
- basePath: path.join(__dirname, './controllers'),
- resolver: OpenApiValidator.resolvers.modulePathResolver,
- },
- fileUploader: {
- dest: uploadsDir,
- // Busboy library settings
- limits: {
- // For multipart forms, the max number of file fields (Default: Infinity)
- files: 1,
- // For multipart forms, the max file size (in bytes) (Default: Infinity)
- fileSize: maxFileSize,
- },
- },
- validateSecurity: {
- handlers: {
- UploadAuth: validateUpload(api, account),
- },
- },
- })
- ) // Required signature.
- app.use(errorLogger())
- /* eslint-disable @typescript-eslint/no-unused-vars */
- app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
- // Express error handling recommendation:
- // https://expressjs.com/en/guide/error-handling.html
- if (res.headersSent) {
- return next(err)
- }
- // Request error handler.
- if (err instanceof HttpError) {
- res.status(err.status || 500).json({
- type: 'request_exception',
- message: err.message,
- })
- } else {
- res.status(500).json({
- type: 'unknown_error',
- message: err.message,
- })
- }
- next()
- })
- return app
- }
- // Defines a signature for a upload validation function.
- type ValidateUploadFunction = (
- req: express.Request,
- scopes: string[],
- schema: OpenAPIV3.SecuritySchemeObject
- ) => boolean | Promise<boolean>
- /**
- * Creates upload validation function with captured parameters from the request.
- *
- * @param api - runtime API promise
- * @param account - KeyringPair instance
- * @returns ValidateUploadFunction.
- */
- function validateUpload(api: ApiPromise, account: KeyringPair): ValidateUploadFunction {
- // We don't use these variables yet.
- /* eslint-disable @typescript-eslint/no-unused-vars */
- return (req: express.Request, scopes: string[], schema: OpenAPIV3.SecuritySchemeObject) => {
- const tokenString = req.headers['x-api-key'] as string
- const token = parseUploadToken(tokenString)
- const sourceTokenRequest: RequestData = {
- dataObjectId: parseInt(req.body.dataObjectId),
- storageBucketId: parseInt(req.body.storageBucketId),
- bagId: req.body.bagId,
- }
- verifyUploadTokenData(account.address, token, sourceTokenRequest)
- return true
- }
- }
- /**
- * Verifies upload request token. Throws exceptions on errors.
- *
- * @param accountAddress - account address (public key)
- * @param token - token object
- * @param request - data from the request to validate token
- */
- 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')
- }
- }
|