diff --git a/docs/securityHandlers.md b/docs/securityHandlers.md index 8873b442..5d200a5c 100644 --- a/docs/securityHandlers.md +++ b/docs/securityHandlers.md @@ -31,13 +31,17 @@ If you provide a securityHandler called `petstore_auth` then it will be called a If you want authentication to succeed you can simply return. If you want authentication to fail you can just throw an error. +If your error contains a `statusCode` property then the status code of the last failing handler will be passed to fastify. The default status code that is returned upon validation failure is `401`. + Any errors that result from `securityHandlers` are available to registered error handlers. E.g.: ```javascript fastify.setErrorHandler((err, req, reply) => { reply.code(err.statusCode).send(err); }); ``` -will return errors originating from the securityHandlers as well. +will return errors originating from the securityHandlers as well in `err.errors`. **Please make sure this does not expose sensitive information to the requestor!** +You can use `err.errors` also to trigger other behaviour in a registered error handler. + For a more elaborate example see the [examples/generatedProject](/examples/generatedProject) folder. diff --git a/examples/generatedProject/package.json b/examples/generatedProject/package.json index 8c907ba6..93206d0b 100644 --- a/examples/generatedProject/package.json +++ b/examples/generatedProject/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "fastify-plugin": "^3.0.1", - "fastify-openapi-glue": "^3.0.1" + "fastify-openapi-glue": "^3.1.0" }, "devDependencies": { "fastify": "^4.0.3", diff --git a/lib/securityHandlers.js b/lib/securityHandlers.js index 9bd22452..b282290f 100644 --- a/lib/securityHandlers.js +++ b/lib/securityHandlers.js @@ -50,6 +50,7 @@ export default class Security { return async (req, reply) => { const handlerErrors = []; const schemeList = []; + let statusCode = 401; for (const scheme of schemes) { try { await securityHandlers[scheme.name](req, reply, scheme.parameters); @@ -57,6 +58,9 @@ export default class Security { } catch (err) { req.log.debug(`Security handler '${scheme.name}' failed: '${err}'`); handlerErrors.push(err); + if (err.statusCode !== undefined){ + statusCode = err.statusCode; + } } schemeList.push(scheme.name); } @@ -66,7 +70,7 @@ export default class Security { ", " )}) successfully authenticated this request.` ); - err.statusCode = 401; + err.statusCode = statusCode; err.name = "Unauthorized"; err.errors = handlerErrors; throw err; diff --git a/test/security.js b/test/security.js index f11d4ab0..d5f6c588 100644 --- a/test/security.js +++ b/test/security.js @@ -9,6 +9,12 @@ class Security { throw 'API key was invalid or not found'; } + async failingAuthCheckCustomStatusCode(req, reply, params) { + const err = new Error('API key was invalid or not found'); + err.statusCode = 451; + throw err; + } + } export default new Security(); diff --git a/test/test-securityHandlers.v3.js b/test/test-securityHandlers.v3.js index 985b0c6f..c0e0ea34 100644 --- a/test/test-securityHandlers.v3.js +++ b/test/test-securityHandlers.v3.js @@ -234,3 +234,32 @@ test("security preHandler gets parameters passed", t => { } ); }); + +test("security preHandler throws error with custom StatusCode", t => { + const opts = { + specification: testSpec, + service, + securityHandlers: { + api_key: securityHandlers.failingAuthCheckCustomStatusCode + } + }; + + t.plan(4); + const fastify = Fastify(); + fastify.setErrorHandler((err, req, reply) => { + t.equal(err.errors.length, 3); + reply.code(err.statusCode).send(err); + }); + fastify.register(fastifyOpenapiGlue, opts); + fastify.inject( + { + method: "GET", + url: "/operationSecurity", + }, + (err, res) => { + t.error(err); + t.equal(res.statusCode, 451); + t.equal(res.statusMessage, 'Unavailable For Legal Reasons'); + } + ); +}); \ No newline at end of file