lukeautry / tsoa

Build OpenAPI-compliant REST APIs using TypeScript and Node
MIT License
3.56k stars 503 forks source link

Hapi template incorrectly passing error object to h.response #921

Open tristanradams opened 3 years ago

tristanradams commented 3 years ago

The hapi template, hapi.hbs, at https://github.com/lukeautry/tsoa/blob/master/packages/cli/src/routeGeneration/templates/hapi.hbs#L128 incorrectly passes an error object to h.response, resulting in a 500 error with text 'Cannot wrap an error' causing the loss of the original error. (see https://github.com/hapijs/hapi/blob/b8eb5f908d9cbab9769f9b79227bc9ce03c02a37/lib/toolkit.js#L191)

In particular, it looks like commit https://github.com/hapijs/hapi/commit/fc16996a067105e08e7f8abc484b2299e66dbd06#diff-cd88ea53d3825cdbd93d7c9c0e50[…]4fdc45474e5facfe2cb3251abfe45R150 on 10/17/17 is what broke the tsoa hapi.hbs template.

Sorting

Expected Behavior

Should render the thrown error correctly.

Current Behavior

The thrown error is swallowed and a 500 error is thrown instead with the message "cannot wrap an error"

Possible Solution

Fix the template to not invoke h.response with an error.

Here is our workaround: Starting at line 125 of hapi.hbs, we commented out the if block:

                responded++;
    // next three lines are commented out to work around a bug where you cannot respond with an error, introduced by commit fc16996a067105e08e7f8abc484b2299e66dbd06 in @hapi/hapi
                //if (responded == security.length && !success) {
                //    h.response(error).code(error.status || 401);
                // }
                return error;
            }; 

Steps to Reproduce

Throw any error from the hapiAuthentication function. We happen to be throwing Boom.forbidden() and Boom.unauthorized(). This is our hapiAuthentication function:

async authorize(
    request: hapi.Request,
    securityName: string,
    scopes: string[] = []
  ): Promise<any> {
    if (securityName !== 'jwt') {
      throw Boom.forbidden(`securityName !== "jwt"`, { securityName })
    }

    const authn =
      request.headers[
        Context.IT.opts.server.api.controllers.AuthApiController.headerKey
      ]
    if (!authn) {
      throw Boom.forbidden('no authorization bearer token')
    }
    const [, token] = authn?.split(' ').map((it) => it.trim())

    const decodedJwt: any = jwt.verify(
      token,
      Context.IT.opts.server.api.controllers.AuthApiController.key,
      Context.IT.signOptions
    )

    const user = await (
      this.service || Context.IT.collectorService!
    ).findUserById(decodedJwt.userId)

    if (!user) {
      throw Boom.unauthorized('user not found', '', {
        userId: decodedJwt.userId,
      })
    }

    for (const role of user.roles) {
      if (scopes.includes(role)) {
        return decodedJwt
      }
    }

    throw Boom.forbidden('user does not have required scope', {
      requiredScopes: scopes,
      userScopes: user.roles,
    })
  }

Context (Environment)

Version of the library: tsoa@3.5.1, @hapi/hapi@20.1.0 Version of NodeJS: v14.15.4

Breaking change?

Unknown.

jackey8616 commented 9 months ago

@WoH This issue should be close by #960